Merge remote-tracking branch 'origin/1.9'

This commit is contained in:
Wojtek Figat
2024-09-23 14:11:41 +02:00
694 changed files with 40054 additions and 25220 deletions

View File

@@ -1,3 +1,5 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using System.IO;
using FlaxEditor.Scripting;
@@ -94,30 +96,8 @@ public class AssetPickerValidator : IContentItemOwner
/// </summary>
public string SelectedPath
{
get
{
string path = _selectedItem?.Path ?? _selected?.Path;
if (path != null)
{
// Convert into path relative to the project (cross-platform)
var projectFolder = Globals.ProjectFolder;
if (path.StartsWith(projectFolder))
path = path.Substring(projectFolder.Length + 1);
}
return path;
}
set
{
if (string.IsNullOrEmpty(value))
{
SelectedItem = null;
}
else
{
var path = StringUtils.IsRelative(value) ? Path.Combine(Globals.ProjectFolder, value) : value;
SelectedItem = Editor.Instance.ContentDatabase.Find(path);
}
}
get => Utilities.Utils.ToPathProject(_selectedItem?.Path ?? _selected?.Path);
set => SelectedItem = string.IsNullOrEmpty(value) ? null : Editor.Instance.ContentDatabase.Find(Utilities.Utils.ToPathAbsolute(value));
}
/// <summary>
@@ -242,7 +222,7 @@ public class AssetPickerValidator : IContentItemOwner
/// <summary>
/// Initializes a new instance of the <see cref="AssetPickerValidator"/> class.
/// </summary>
/// <param name="assetType">The assets types that this picker accepts.</param>
/// <param name="assetType">The asset types that this picker accepts.</param>
public AssetPickerValidator(ScriptType assetType)
{
_type = assetType;

View File

@@ -388,7 +388,7 @@ namespace FlaxEditor.Content
{
sb.Append("Type: ").Append(TypeDescription).AppendLine();
if (File.Exists(Path))
sb.Append("Size: ").Append(Utilities.Utils.FormatBytesCount((int)new FileInfo(Path).Length)).AppendLine();
sb.Append("Size: ").Append(Utilities.Utils.FormatBytesCount((ulong)new FileInfo(Path).Length)).AppendLine();
sb.Append("Path: ").Append(Utilities.Utils.GetAssetNamePathWithExt(Path)).AppendLine();
}

View File

@@ -0,0 +1,28 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using FlaxEngine;
namespace FlaxEditor.Content
{
/// <summary>
/// Content item that contains video media file.
/// </summary>
/// <seealso cref="FlaxEditor.Content.JsonAssetItem" />
public sealed class VideoItem : FileItem
{
/// <summary>
/// Initializes a new instance of the <see cref="VideoItem"/> class.
/// </summary>
/// <param name="path">The file path.</param>
public VideoItem(string path)
: base(path)
{
}
/// <inheritdoc />
public override string TypeDescription => "Video";
/// <inheritdoc />
public override SpriteHandle DefaultThumbnail => Editor.Instance.Icons.Document128;
}
}

View File

@@ -30,9 +30,7 @@ namespace FlaxEditor.Content
/// <summary>
/// Determines whether [is virtual proxy].
/// </summary>
/// <returns>
/// <c>true</c> if [is virtual proxy]; otherwise, <c>false</c>.
/// </returns>
/// <returns><c>true</c> if [is virtual proxy]; otherwise, <c>false</c>.</returns>
public bool IsVirtualProxy()
{
return IsVirtual && CanExport == false;

View File

@@ -31,6 +31,12 @@ namespace FlaxEditor.Content
/// <param name="path">The path to the template</param>
protected abstract void GetTemplatePath(out string path);
/// <inheritdoc />
public override ContentItem ConstructItem(string path)
{
return new CSharpScriptItem(path);
}
/// <inheritdoc />
public override void Create(string outputPath, object arg)
{

View File

@@ -39,6 +39,16 @@ namespace FlaxEditor.Content
return false;
}
/// <summary>
/// Constructs the item for the file.
/// </summary>
/// <param name="path">The file path.</param>
/// <returns>Created item or null.</returns>
public virtual ContentItem ConstructItem(string path)
{
return null;
}
/// <summary>
/// Gets a value indicating whether this proxy if for assets.
/// </summary>

View File

@@ -87,6 +87,12 @@ namespace FlaxEditor.Content
return item is CppScriptItem;
}
/// <inheritdoc />
public override ContentItem ConstructItem(string path)
{
return new CppScriptItem(path);
}
/// <inheritdoc />
protected override void GetTemplatePaths(out string headerTemplate, out string sourceTemplate)
{

View File

@@ -20,6 +20,12 @@ namespace FlaxEditor.Content
return item is FileItem;
}
/// <inheritdoc />
public override ContentItem ConstructItem(string path)
{
return new FileItem(path);
}
/// <inheritdoc />
public override string FileExtension => string.Empty;

View File

@@ -94,7 +94,10 @@ namespace FlaxEditor.Content
_preview.Model = (Model)request.Asset;
_preview.Parent = guiRoot;
_preview.SyncBackbufferSize();
_preview.ViewportCamera.SetArcBallView(_preview.Model.GetBox());
var bounds = _preview.Model.GetBox();
var maxSize = Math.Max(0.001f, (float)bounds.Size.MaxValue);
_preview.ViewportCamera.SetArcBallView(bounds);
_preview.FarPlane = Mathf.Max(1000.0f, maxSize * 2 + 100.0f);
_preview.Task.OnDraw();
}

View File

@@ -0,0 +1,48 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using FlaxEditor.Windows;
using FlaxEditor.Windows.Assets;
using FlaxEngine;
namespace FlaxEditor.Content
{
/// <summary>
/// A video media file proxy object.
/// </summary>
public class VideoProxy : ContentProxy
{
private readonly string _extension;
internal VideoProxy(string extension)
{
_extension = extension;
}
/// <inheritdoc />
public override string Name => "Video";
/// <inheritdoc />
public override string FileExtension => _extension;
/// <inheritdoc />
public override Color AccentColor => Color.FromRGB(0x11f7f1);
/// <inheritdoc />
public override bool IsProxyFor(ContentItem item)
{
return item is VideoItem;
}
/// <inheritdoc />
public override ContentItem ConstructItem(string path)
{
return new VideoItem(path);
}
/// <inheritdoc />
public override EditorWindow Open(Editor editor, ContentItem item)
{
return new VideoWindow(editor, (VideoItem)item);
}
}
}

View File

@@ -134,6 +134,12 @@ API_ENUM() enum class BuildPlatform
/// </summary>
API_ENUM(Attributes="EditorDisplay(null, \"iOS ARM64\")")
iOSARM64 = 14,
/// <summary>
/// Windows (ARM64)
/// </summary>
API_ENUM(Attributes = "EditorDisplay(null, \"Windows ARM64\")")
WindowsARM64 = 15,
};
/// <summary>
@@ -285,24 +291,22 @@ public:
/// <summary>
/// The total assets amount in the build.
/// </summary>
int32 TotalAssets;
int32 TotalAssets = 0;
/// <summary>
/// The cooked assets (TotalAssets - CookedAssets is amount of reused cached assets).
/// </summary>
int32 CookedAssets;
int32 CookedAssets = 0;
/// <summary>
/// The final output content size in MB.
/// The final output content size (in bytes).
/// </summary>
int32 ContentSizeMB;
uint64 ContentSize = 0;
/// <summary>
/// The asset type stats. Key is the asset typename, value is the stats container.
/// </summary>
Dictionary<String, AssetTypeStatistics> AssetStats;
Statistics();
};
/// <summary>
@@ -328,6 +332,11 @@ public:
/// </summary>
HashSet<Guid> Assets;
/// <summary>
/// The final files collection to include in build (valid only after CollectAssetsStep).
/// </summary>
HashSet<String> Files;
struct BinaryModuleInfo
{
String Name;

View File

@@ -148,6 +148,8 @@ const Char* ToString(const BuildPlatform platform)
return TEXT("Mac ARM64");
case BuildPlatform::iOSARM64:
return TEXT("iOS ARM64");
case BuildPlatform::WindowsARM64:
return TEXT("Windows ARM64");
default:
return TEXT("");
}
@@ -202,13 +204,6 @@ bool CookingData::AssetTypeStatistics::operator<(const AssetTypeStatistics& othe
return Count > other.Count;
}
CookingData::Statistics::Statistics()
{
TotalAssets = 0;
CookedAssets = 0;
ContentSizeMB = 0;
}
CookingData::CookingData(const SpawnParams& params)
: ScriptingObject(params)
{
@@ -307,6 +302,10 @@ void CookingData::GetBuildPlatformName(const Char*& platform, const Char*& archi
platform = TEXT("iOS");
architecture = TEXT("ARM64");
break;
case BuildPlatform::WindowsARM64:
platform = TEXT("Windows");
architecture = TEXT("ARM64");
break;
default:
LOG(Fatal, "Unknown or unsupported build platform.");
}
@@ -393,6 +392,9 @@ PlatformTools* GameCooker::GetTools(BuildPlatform platform)
case BuildPlatform::Windows64:
result = New<WindowsPlatformTools>(ArchitectureType::x64);
break;
case BuildPlatform::WindowsARM64:
result = New<WindowsPlatformTools>(ArchitectureType::ARM64);
break;
#endif
#if PLATFORM_TOOLS_UWP
case BuildPlatform::UWPx86:
@@ -554,7 +556,12 @@ void GameCooker::GetCurrentPlatform(PlatformType& platform, BuildPlatform& build
switch (PLATFORM_TYPE)
{
case PlatformType::Windows:
buildPlatform = PLATFORM_64BITS ? BuildPlatform::Windows64 : BuildPlatform::Windows32;
if (PLATFORM_ARCH == ArchitectureType::x64)
buildPlatform = BuildPlatform::Windows64;
else if (PLATFORM_ARCH == ArchitectureType::ARM64)
buildPlatform = BuildPlatform::WindowsARM64;
else
buildPlatform = BuildPlatform::Windows32;
break;
case PlatformType::XboxOne:
buildPlatform = BuildPlatform::XboxOne;

View File

@@ -325,9 +325,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
const auto buildSettings = BuildSettings::Get();
if (buildSettings->SkipPackaging)
{
return false;
}
GameCooker::PackageFiles();
// Validate environment variables

View File

@@ -146,7 +146,7 @@ bool GDKPlatformTools::OnPostProcess(CookingData& data, GDKPlatformSettings* pla
sb.Append(TEXT(" </ExecutableList>\n"));
sb.AppendFormat(TEXT(" <ShellVisuals DefaultDisplayName=\"{0}\"\n"), gameSettings->ProductName);
sb.AppendFormat(TEXT(" PublisherDisplayName=\"{0}\"\n"), platformSettings->PublisherDisplayName.HasChars() ? platformSettings->PublisherDisplayName : gameSettings->CompanyName);
sb.AppendFormat(TEXT(" BackgroundColor=\"#{0}\"\n"), platformSettings->BackgroundColor.ToHexString());
sb.AppendFormat(TEXT(" BackgroundColor=\"#{0}\"\n"), platformSettings->BackgroundColor.ToHexString().Left(6));
sb.AppendFormat(TEXT(" ForegroundText=\"{0}\"\n"), platformSettings->ForegroundText);
sb.Append(TEXT(" Square150x150Logo=\"Assets\\Square150x150Logo.png\"\n"));
sb.Append(TEXT(" Square480x480Logo=\"Assets\\Square480x480Logo.png\"\n"));

View File

@@ -10,47 +10,26 @@
#include "Engine/Content/Assets/Shader.h"
#include "Engine/Content/Cache/AssetsCache.h"
bool CollectAssetsStep::Process(CookingData& data, Asset* asset)
{
// Skip virtual/temporary assets
if (asset->IsVirtual())
return false;
// Keep reference to the asset
AssetReference<Asset> ref(asset);
// Asset should have loaded data
if (asset->WaitForLoaded())
return false;
// Gather asset references
_references.Clear();
asset->Locker.Lock();
asset->GetReferences(_references);
asset->Locker.Unlock();
_assetsQueue.Add(_references);
return false;
}
bool CollectAssetsStep::Perform(CookingData& data)
{
LOG(Info, "Searching for assets to include in a build. Using {0} root assets.", data.RootAssets.Count());
data.StepProgress(TEXT("Collecting assets"), 0);
// Initialize assets queue
_assetsQueue.Clear();
_assetsQueue.EnsureCapacity(1024);
Array<Guid> assetsQueue;
assetsQueue.Clear();
assetsQueue.EnsureCapacity(1024);
for (auto i = data.RootAssets.Begin(); i.IsNotEnd(); ++i)
_assetsQueue.Add(i->Item);
assetsQueue.Add(i->Item);
// Iterate through the assets graph
AssetInfo assetInfo;
while (_assetsQueue.HasItems())
Array<Guid> references;
Array<String> files;
while (assetsQueue.HasItems())
{
BUILD_STEP_CANCEL_CHECK;
const auto assetId = _assetsQueue.Dequeue();
const Guid assetId = assetsQueue.Dequeue();
// Skip already processed or invalid assets
if (!assetId.IsValid()
@@ -69,14 +48,31 @@ bool CollectAssetsStep::Perform(CookingData& data)
}
// Load asset
const auto asset = Content::LoadAsync<Asset>(assetId);
AssetReference<Asset> asset = Content::LoadAsync<Asset>(assetId);
if (asset == nullptr)
continue;
// Process that asset
LOG_STR(Info, asset->GetPath());
data.Assets.Add(assetId);
Process(data, asset);
// Skip virtual/temporary assets
if (asset->IsVirtual())
continue;
// Asset should have loaded data
if (asset->WaitForLoaded())
continue;
// Gather asset references
references.Clear();
asset->Locker.Lock();
asset->GetReferences(references, files);
asset->Locker.Unlock();
assetsQueue.Add(references);
for (String& file : files)
{
if (file.HasChars())
data.Files.Add(MoveTemp(file));
}
}
data.Stats.TotalAssets = data.Assets.Count();

View File

@@ -12,15 +12,7 @@ class Asset;
/// <seealso cref="GameCooker::BuildStep" />
class CollectAssetsStep : public GameCooker::BuildStep
{
private:
Array<Guid> _assetsQueue;
Array<Guid> _references;
bool Process(CookingData& data, Asset* asset);
public:
// [BuildStep]
bool Perform(CookingData& data) override;
};

View File

@@ -447,6 +447,7 @@ bool ProcessShaderBase(CookAssetsStep::AssetCookData& data, ShaderAssetBase* ass
#if PLATFORM_TOOLS_WINDOWS
case BuildPlatform::Windows32:
case BuildPlatform::Windows64:
case BuildPlatform::WindowsARM64:
{
const char* platformDefineName = "PLATFORM_WINDOWS";
const auto settings = WindowsPlatformSettings::Get();
@@ -891,7 +892,6 @@ bool CookAssetsStep::Process(CookingData& data, CacheData& cache, JsonAssetBase*
class PackageBuilder : public NonCopyable
{
private:
int32 _packageIndex;
int32 MaxAssetsPerPackage;
int32 MaxPackageSize;
@@ -904,7 +904,6 @@ private:
uint64 packagesSizeTotal;
public:
/// <summary>
/// Initializes a new instance of the <see cref="PackageBuilder" /> class.
/// </summary>
@@ -933,7 +932,6 @@ public:
}
public:
uint64 GetPackagesSizeTotal() const
{
return packagesSizeTotal;
@@ -1042,8 +1040,11 @@ bool CookAssetsStep::Perform(CookingData& data)
float Step1ProgressEnd = 0.6f;
String Step1Info = TEXT("Cooking assets");
float Step2ProgressStart = Step1ProgressEnd;
float Step2ProgressEnd = 0.9f;
String Step2Info = TEXT("Packaging assets");
float Step2ProgressEnd = 0.8f;
String Step2Info = TEXT("Cooking files");
float Step3ProgressStart = Step2ProgressStart;
float Step3ProgressEnd = 0.9f;
String Step3Info = TEXT("Packaging assets");
data.StepProgress(TEXT("Loading build cache"), 0);
@@ -1100,11 +1101,14 @@ bool CookAssetsStep::Perform(CookingData& data)
#endif
int32 subStepIndex = 0;
AssetReference<Asset> assetRef;
assetRef.Unload.Bind([]() { LOG(Error, "Asset gets unloaded while cooking it!"); Platform::Sleep(100); });
assetRef.Unload.Bind([]
{
LOG(Error, "Asset got unloaded while cooking it!");
Platform::Sleep(100);
});
for (auto i = data.Assets.Begin(); i.IsNotEnd(); ++i)
{
BUILD_STEP_CANCEL_CHECK;
data.StepProgress(Step1Info, Math::Lerp(Step1ProgressStart, Step1ProgressEnd, static_cast<float>(subStepIndex++) / data.Assets.Count()));
const Guid assetId = i->Item;
@@ -1184,6 +1188,35 @@ bool CookAssetsStep::Perform(CookingData& data)
// Save build cache header
cache.Save(data);
// Process all files
for (auto i = data.Files.Begin(); i.IsNotEnd(); ++i)
{
BUILD_STEP_CANCEL_CHECK;
data.StepProgress(Step2Info, Math::Lerp(Step2ProgressStart, Step2ProgressEnd, (float)subStepIndex++ / data.Files.Count()));
const String& filePath = i->Item;
// Calculate destination path
String cookedPath = data.DataOutputPath;
if (FileSystem::IsRelative(filePath))
cookedPath /= filePath;
else
cookedPath /= String(TEXT("Content")) / StringUtils::GetFileName(filePath);
// Copy file
if (!FileSystem::FileExists(cookedPath) || FileSystem::GetFileLastEditTime(cookedPath) >= FileSystem::GetFileLastEditTime(filePath))
{
if (FileSystem::CreateDirectory(StringUtils::GetDirectoryName(cookedPath)))
return true;
if (FileSystem::CopyFile(cookedPath, filePath))
return true;
}
// Count stats of file extension
auto& assetStats = data.Stats.AssetStats[FileSystem::GetExtension(cookedPath)];
assetStats.Count++;
assetStats.ContentSize += FileSystem::GetFileSize(cookedPath);
}
// Create build game header
{
GameHeaderFlags gameFlags = GameHeaderFlags::None;
@@ -1229,13 +1262,11 @@ bool CookAssetsStep::Perform(CookingData& data)
for (auto i = AssetsRegistry.Begin(); i.IsNotEnd(); ++i)
{
BUILD_STEP_CANCEL_CHECK;
data.StepProgress(Step2Info, Math::Lerp(Step2ProgressStart, Step2ProgressEnd, static_cast<float>(subStepIndex++) / AssetsRegistry.Count()));
data.StepProgress(Step3Info, Math::Lerp(Step3ProgressStart, Step3ProgressEnd, (float)subStepIndex++ / AssetsRegistry.Count()));
const auto assetId = i->Key;
String cookedFilePath;
cache.GetFilePath(assetId, cookedFilePath);
if (!FileSystem::FileExists(cookedFilePath))
{
LOG(Warning, "Missing cooked file for asset \'{0}\'", assetId);
@@ -1253,12 +1284,12 @@ bool CookAssetsStep::Perform(CookingData& data)
return true;
for (auto& e : data.Stats.AssetStats)
e.Value.TypeName = e.Key;
data.Stats.ContentSizeMB = static_cast<int32>(packageBuilder.GetPackagesSizeTotal() / (1024 * 1024));
data.Stats.ContentSize += packageBuilder.GetPackagesSizeTotal();
}
BUILD_STEP_CANCEL_CHECK;
data.StepProgress(TEXT("Creating assets cache"), Step2ProgressEnd);
data.StepProgress(TEXT("Creating assets cache"), Step3ProgressEnd);
// Create asset paths mapping for the assets.
// Assets mapping is use to convert paths used in Content::Load(path) into the asset id.
@@ -1291,7 +1322,7 @@ bool CookAssetsStep::Perform(CookingData& data)
}
// Print stats
LOG(Info, "Cooked {0} assets, total assets: {1}, total content packages size: {2} MB", data.Stats.CookedAssets, AssetsRegistry.Count(), data.Stats.ContentSizeMB);
LOG(Info, "Cooked {0} assets, total assets: {1}, total content packages size: {2} MB", data.Stats.CookedAssets, AssetsRegistry.Count(), (int32)(data.Stats.ContentSize / (1024 * 1024)));
{
Array<CookingData::AssetTypeStatistics> assetTypes;
data.Stats.AssetStats.GetValues(assetTypes);

View File

@@ -73,6 +73,7 @@ bool DeployDataStep::Perform(CookingData& data)
{
case BuildPlatform::Windows32:
case BuildPlatform::Windows64:
case BuildPlatform::WindowsARM64:
canUseSystemDotnet = PLATFORM_TYPE == PlatformType::Windows;
break;
case BuildPlatform::LinuxX64:
@@ -159,7 +160,24 @@ bool DeployDataStep::Perform(CookingData& data)
}
else
{
#if 1
failed |= EditorUtilities::CopyDirectoryIfNewer(dstDotnet / TEXT("host/fxr") / version, srcDotnet / TEXT("host/fxr") / version, true);
#else
// TODO: hostfxr for target platform should be copied from nuget package location: microsoft.netcore.app.runtime.<RID>/<VERSION>/runtimes/<RID>/native/hostfxr.dll
String dstHostfxr = dstDotnet / TEXT("host/fxr") / version;
if (!FileSystem::DirectoryExists(dstHostfxr))
FileSystem::CreateDirectory(dstHostfxr);
const Char *platformName, *archName;
data.GetBuildPlatformName(platformName, archName);
if (data.Platform == BuildPlatform::Windows64 || data.Platform == BuildPlatform::WindowsARM64 || data.Platform == BuildPlatform::Windows32)
failed |= FileSystem::CopyFile(dstHostfxr / TEXT("hostfxr.dll"), depsRoot / TEXT("ThirdParty") / archName / TEXT("hostfxr.dll"));
else if (data.Platform == BuildPlatform::LinuxX64)
failed |= FileSystem::CopyFile(dstHostfxr / TEXT("hostfxr.so"), depsRoot / TEXT("ThirdParty") / archName / TEXT("hostfxr.so"));
else if (data.Platform == BuildPlatform::MacOSx64 || data.Platform == BuildPlatform::MacOSARM64)
failed |= FileSystem::CopyFile(dstHostfxr / TEXT("hostfxr.dylib"), depsRoot / TEXT("ThirdParty") / archName / TEXT("hostfxr.dylib"));
else
failed |= true;
#endif
failed |= EditorUtilities::CopyDirectoryIfNewer(dstDotnet / TEXT("shared/Microsoft.NETCore.App") / version, srcDotnet / TEXT("shared/Microsoft.NETCore.App") / version, true);
}
if (failed)
@@ -355,6 +373,7 @@ bool DeployDataStep::Perform(CookingData& data)
data.AddRootEngineAsset(TEXT("Shaders/Sky"));
data.AddRootEngineAsset(TEXT("Shaders/SSAO"));
data.AddRootEngineAsset(TEXT("Shaders/SSR"));
data.AddRootEngineAsset(TEXT("Shaders/SDF"));
data.AddRootEngineAsset(TEXT("Shaders/VolumetricFog"));
data.AddRootEngineAsset(TEXT("Engine/DefaultMaterial"));
data.AddRootEngineAsset(TEXT("Engine/DefaultDeformableMaterial"));

View File

@@ -392,6 +392,10 @@ namespace FlaxEditor.CustomEditors
else if (Values.HasDefaultValue && CanRevertDefaultValue)
color = Color.Yellow * 0.8f;
LinkedLabel.HighlightStripColor = color;
// Grey out deprecated members
if (Values.IsObsolete)
LinkedLabel.TextColor = LinkedLabel.TextColorHighlighted = FlaxEngine.GUI.Style.Current.ForegroundGrey;
}
}

View File

@@ -24,7 +24,7 @@ namespace FlaxEditor.CustomEditors.Dedicated
// Show instanced parameters to view/edit at runtime
if (Values.IsSingleObject && Editor.Instance.StateMachine.IsPlayMode)
{
var group = layout.Group("Parameters");
var group = SurfaceUtils.InitGraphParametersGroup(layout);
group.Panel.Open(false);
group.Panel.IndexInParent -= 2;

View File

@@ -6,6 +6,7 @@ using FlaxEditor.Content;
using FlaxEditor.GUI;
using FlaxEditor.Scripting;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
namespace FlaxEditor.CustomEditors.Editors
@@ -50,7 +51,6 @@ namespace FlaxEditor.CustomEditors.Editors
if (HasDifferentTypes)
return;
Picker = layout.Custom<AssetPicker>().CustomControl;
var value = Values[0];
_valueType = Values.Type.Type != typeof(object) || value == null ? Values.Type : TypeUtils.GetObjectType(value);
var assetType = _valueType;
@@ -58,37 +58,8 @@ namespace FlaxEditor.CustomEditors.Editors
assetType = new ScriptType(typeof(Asset));
else if (_valueType.Type != null && _valueType.Type.Name == typeof(JsonAssetReference<>).Name)
assetType = new ScriptType(_valueType.Type.GenericTypeArguments[0]);
float height = 48;
var attributes = Values.GetAttributes();
var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute);
if (assetReference != null)
{
if (assetReference.UseSmallPicker)
height = 32;
if (string.IsNullOrEmpty(assetReference.TypeName))
{
}
else if (assetReference.TypeName.Length > 1 && assetReference.TypeName[0] == '.')
{
// Generic file picker
assetType = ScriptType.Null;
Picker.Validator.FileExtension = assetReference.TypeName;
}
else
{
var customType = TypeUtils.GetType(assetReference.TypeName);
if (customType != ScriptType.Null)
assetType = customType;
else if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(assetReference.TypeName))
Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for asset picker filter.", assetReference.TypeName));
else
assetType = ScriptType.Void;
}
}
Picker.Validator.AssetType = assetType;
ApplyAssetReferenceAttribute(Values, out var height, Picker.Validator);
Picker.Height = height;
Picker.SelectedItemChanged += OnSelectedItemChanged;
}
@@ -115,6 +86,37 @@ namespace FlaxEditor.CustomEditors.Editors
SetValue(Picker.Validator.SelectedAsset);
}
internal static void ApplyAssetReferenceAttribute(ValueContainer values, out float height, AssetPickerValidator validator)
{
height = 48;
var attributes = values.GetAttributes();
var assetReference = (AssetReferenceAttribute)attributes?.FirstOrDefault(x => x is AssetReferenceAttribute);
if (assetReference != null)
{
if (assetReference.UseSmallPicker)
height = 32;
if (string.IsNullOrEmpty(assetReference.TypeName))
{
}
else if (assetReference.TypeName.Length > 1 && assetReference.TypeName[0] == '.')
{
// Generic file picker
validator.AssetType = ScriptType.Null;
validator.FileExtension = assetReference.TypeName;
}
else
{
var customType = TypeUtils.GetType(assetReference.TypeName);
if (customType != ScriptType.Null)
validator.AssetType = customType;
else if (!Content.Settings.GameSettings.OptionalPlatformSettings.Contains(assetReference.TypeName))
Debug.LogWarning(string.Format("Unknown asset type '{0}' to use for asset picker filter.", assetReference.TypeName));
else
validator.AssetType = ScriptType.Void;
}
}
}
/// <inheritdoc />
public override void Refresh()
{
@@ -140,4 +142,155 @@ namespace FlaxEditor.CustomEditors.Editors
}
}
}
/// <summary>
/// Default implementation of the inspector used to edit reference to the files via path (absolute or relative to the project).
/// </summary>
/// <remarks>Supports editing reference to the asset via path using various containers: <see cref="Asset"/> or <see cref="AssetItem"/> or <see cref="System.String"/>.</remarks>
public class FilePathEditor : CustomEditor
{
private sealed class TextBoxWithPicker : TextBox
{
private const float DropdownIconMargin = 3.0f;
private const float DropdownIconSize = 12.0f;
private Rectangle DropdownRect => new Rectangle(Width - DropdownIconSize - DropdownIconMargin, DropdownIconMargin, DropdownIconSize, DropdownIconSize);
public Action ShowPicker;
public override void Draw()
{
base.Draw();
var style = FlaxEngine.GUI.Style.Current;
var dropdownRect = DropdownRect;
Render2D.DrawSprite(style.ArrowDown, dropdownRect, Enabled ? (DropdownRect.Contains(PointFromWindow(RootWindow.MousePosition)) ? style.BorderSelected : style.Foreground) : style.ForegroundDisabled);
}
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (DropdownRect.Contains(ref location))
{
Focus();
ShowPicker();
return true;
}
return base.OnMouseDown(location, button);
}
public override void OnMouseMove(Float2 location)
{
base.OnMouseMove(location);
if (DropdownRect.Contains(ref location))
Cursor = CursorType.Default;
else
Cursor = CursorType.IBeam;
}
protected override Rectangle TextRectangle
{
get
{
var result = base.TextRectangle;
result.Size.X -= DropdownIconSize + DropdownIconMargin * 2;
return result;
}
}
protected override Rectangle TextClipRectangle
{
get
{
var result = base.TextClipRectangle;
result.Size.X -= DropdownIconSize + DropdownIconMargin * 2;
return result;
}
}
}
private TextBoxWithPicker _textBox;
private AssetPickerValidator _validator;
private bool _isRefreshing;
/// <inheritdoc />
public override DisplayStyle Style => DisplayStyle.Inline;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
if (HasDifferentTypes)
return;
_textBox = layout.Custom<TextBoxWithPicker>().CustomControl;
_textBox.ShowPicker = OnShowPicker;
_textBox.EditEnd += OnEditEnd;
_validator = new AssetPickerValidator(ScriptType.Null);
AssetRefEditor.ApplyAssetReferenceAttribute(Values, out _, _validator);
}
private void OnShowPicker()
{
if (_validator.AssetType != ScriptType.Null)
AssetSearchPopup.Show(_textBox, _textBox.BottomLeft, _validator.IsValid, SetPickerPath);
else
ContentSearchPopup.Show(_textBox, _textBox.BottomLeft, _validator.IsValid, SetPickerPath);
}
private void SetPickerPath(ContentItem item)
{
var path = Utilities.Utils.ToPathProject(item.Path);
SetPath(path);
_isRefreshing = true;
_textBox.Defocus();
_textBox.Text = path;
_isRefreshing = false;
_textBox.RootWindow.Focus();
_textBox.Focus();
}
private void OnEditEnd()
{
SetPath(_textBox.Text);
}
private string GetPath()
{
var value = Values[0];
if (value is AssetItem assetItem)
return Utilities.Utils.ToPathProject(assetItem.Path);
if (value is Asset asset)
return Utilities.Utils.ToPathProject(asset.Path);
if (value is string str)
return str;
return null;
}
private void SetPath(string path)
{
if (_isRefreshing)
return;
var value = Values[0];
if (value is AssetItem)
SetValue(Editor.Instance.ContentDatabase.Find(Utilities.Utils.ToPathAbsolute(path)));
else if (value is Asset)
SetValue(FlaxEngine.Content.LoadAsync(path));
else if (value is string)
SetValue(path);
}
/// <inheritdoc />
public override void Refresh()
{
base.Refresh();
if (!HasDifferentValues)
{
_isRefreshing = true;
_textBox.Text = GetPath();
_isRefreshing = false;
}
}
}
}

View File

@@ -535,6 +535,15 @@ namespace FlaxEditor.CustomEditors.Editors
_groupsPool.Add(groups);
}
internal static GroupElement OnGroup(LayoutElementsContainer layout, string name)
{
// Add new group
var group = layout.Group(name);
group.Panel.Tag = group;
group.Panel.MouseButtonRightClicked += OnGroupPanelMouseButtonRightClicked;
return group;
}
internal static LayoutElementsContainer OnGroup(LayoutElementsContainer layout, EditorDisplayAttribute display)
{
if (display?.Group != null)

View File

@@ -25,6 +25,7 @@ namespace FlaxEditor.CustomEditors.Editors
new OptionType("Linear Gradient", typeof(LinearGradientBrush)),
new OptionType("Texture 9-Slicing", typeof(Texture9SlicingBrush)),
new OptionType("Sprite 9-Slicing", typeof(Sprite9SlicingBrush)),
new OptionType("Video", typeof(VideoBrush)),
};
}
}

View File

@@ -33,11 +33,25 @@ namespace FlaxEditor.CustomEditors.Editors
names.Add(mapping.Name);
}
_comboBox.Items = names;
if (Values[0] is InputEvent inputEvent && names.Contains(inputEvent.Name))
var prev = GetValue();
if (prev is InputEvent inputEvent && names.Contains(inputEvent.Name))
_comboBox.SelectedItem = inputEvent.Name;
else if (prev is string name && names.Contains(name))
_comboBox.SelectedItem = name;
_comboBox.SelectedIndexChanged += OnSelectedIndexChanged;
}
private object GetValue()
{
if (Values[0] is InputEvent inputEvent)
return inputEvent;
if (Values[0] is string str)
return str;
if (Values.Type.Type == typeof(string))
return string.Empty;
return null;
}
private void OnSetupContextMenu(PropertyNameLabel label, ContextMenu menu, CustomEditor linkedEditor)
{
var button = menu.AddButton("Set to null");
@@ -46,7 +60,16 @@ namespace FlaxEditor.CustomEditors.Editors
private void OnSelectedIndexChanged(ComboBox comboBox)
{
SetValue(comboBox.SelectedItem == null ? null : new InputEvent(comboBox.SelectedItem));
object value = null;
if (comboBox.SelectedItem != null)
{
var prev = GetValue();
if (prev is InputEvent)
value = new InputEvent(comboBox.SelectedItem);
else if (prev is string)
value = comboBox.SelectedItem;
}
SetValue(value);
}
/// <inheritdoc />
@@ -59,8 +82,11 @@ namespace FlaxEditor.CustomEditors.Editors
}
else
{
if (Values[0] is InputEvent inputEvent && _comboBox.Items.Contains(inputEvent.Name))
var prev = GetValue();
if (prev is InputEvent inputEvent && _comboBox.Items.Contains(inputEvent.Name))
_comboBox.SelectedItem = inputEvent.Name;
else if (prev is string name && _comboBox.Items.Contains(name))
_comboBox.SelectedItem = name;
else
_comboBox.SelectedItem = null;
}

View File

@@ -22,7 +22,7 @@ namespace FlaxEditor.CustomEditors.Elements
/// <summary>
/// [Deprecated on 26.05.2022, expires on 26.05.2024]
/// </summary>
[System.Obsolete("Deprecated in 1.4, use ValueBox instead")]
[System.Obsolete("Use ValueBox instead")]
public DoubleValueBox DoubleValue => ValueBox;
/// <summary>

View File

@@ -22,7 +22,7 @@ namespace FlaxEditor.CustomEditors.Elements
/// <summary>
/// [Deprecated on 26.05.2022, expires on 26.05.2024]
/// </summary>
[System.Obsolete("Deprecated in 1.4, use ValueBox instead")]
[System.Obsolete("Use ValueBox instead")]
public FloatValueBox FloatValue => ValueBox;
/// <summary>

View File

@@ -139,6 +139,11 @@ namespace FlaxEditor.CustomEditors
/// </summary>
public bool IsArray => Type != ScriptType.Null && Type.IsArray;
/// <summary>
/// True if member or type has <see cref="System.ObsoleteAttribute"/> that marks it as obsolete.
/// </summary>
public bool IsObsolete { get; }
/// <summary>
/// Gets the values types array (without duplicates).
/// </summary>
@@ -160,6 +165,7 @@ namespace FlaxEditor.CustomEditors
{
Info = info;
Type = Info.ValueType;
IsObsolete = Info.HasAttribute(typeof(ObsoleteAttribute), true);
}
/// <summary>

View File

@@ -7,6 +7,7 @@ using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using System.Threading.Tasks;
using FlaxEditor.Content;
using FlaxEditor.Content.Settings;
using FlaxEditor.Content.Thumbnails;
@@ -856,7 +857,7 @@ namespace FlaxEditor
/// New asset types allowed to create.
/// [Deprecated in v1.8]
/// </summary>
[Obsolete("Use CreateAsset with named tag.")]
[Obsolete("Use CreateAsset with named tag instead")]
public enum NewAssetType
{
/// <summary>
@@ -1037,7 +1038,7 @@ namespace FlaxEditor
/// </summary>
/// <param name="type">New asset type.</param>
/// <param name="outputPath">Output asset path.</param>
[Obsolete("Use CreateAsset with named tag.")]
[Obsolete("Use CreateAsset with named tag instead")]
public static bool CreateAsset(NewAssetType type, string outputPath)
{
// [Deprecated on 18.02.2024, expires on 18.02.2025]
@@ -1345,20 +1346,33 @@ namespace FlaxEditor
/// </summary>
public void BuildAllMeshesSDF()
{
// TODO: async maybe with progress reporting?
var models = new List<Model>();
Scene.ExecuteOnGraph(node =>
{
if (node is StaticModelNode staticModelNode && staticModelNode.Actor is StaticModel staticModel)
{
if (staticModel.DrawModes.HasFlag(DrawPass.GlobalSDF) && staticModel.Model != null && !staticModel.Model.IsVirtual && staticModel.Model.SDF.Texture == null)
var model = staticModel.Model;
if (staticModel.DrawModes.HasFlag(DrawPass.GlobalSDF) &&
model != null &&
!models.Contains(model) &&
!model.IsVirtual &&
model.SDF.Texture == null)
{
Log("Generating SDF for " + staticModel.Model);
if (!staticModel.Model.GenerateSDF())
staticModel.Model.Save();
models.Add(model);
}
}
return true;
});
Task.Run(() =>
{
for (int i = 0; i < models.Count; i++)
{
var model = models[i];
Log($"[{i}/{models.Count}] Generating SDF for {model}");
if (!model.GenerateSDF())
model.Save();
}
});
}
#endregion

View File

@@ -5,7 +5,6 @@ using System.IO;
using FlaxEditor.Content;
using FlaxEditor.GUI.Drag;
using FlaxEditor.Scripting;
using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
@@ -151,7 +150,7 @@ namespace FlaxEditor.GUI
TextAlignment.Center);
Render2D.DrawText(
style.FontSmall,
$"{TypeUtils.GetTypeDisplayName(Validator.AssetType.Type)}",
$"{Validator.AssetType.Type.GetTypeDisplayName()}",
new Rectangle(button1Rect.Right + 2, ButtonsSize + 2, sizeForTextLeft, ButtonsSize),
style.ForegroundGrey,
TextAlignment.Near,
@@ -180,7 +179,7 @@ namespace FlaxEditor.GUI
TextAlignment.Center);
Render2D.DrawText(
style.FontSmall,
$"{TypeUtils.GetTypeDisplayName(Validator.AssetType.Type)}",
$"{Validator.AssetType.Type.GetTypeDisplayName()}",
new Rectangle(button1Rect.Right + 2, ButtonsSize + 2, sizeForTextLeft, ButtonsSize),
style.ForegroundGrey,
TextAlignment.Near,
@@ -204,7 +203,7 @@ namespace FlaxEditor.GUI
TextAlignment.Center);
Render2D.DrawText(
style.FontSmall,
$"{TypeUtils.GetTypeDisplayName(Validator.AssetType.Type)}",
$"{Validator.AssetType.Type.GetTypeDisplayName()}",
new Rectangle(button1Rect.Right + 2, ButtonsSize + 2, sizeForTextLeft, ButtonsSize),
style.ForegroundGrey,
TextAlignment.Near,

View File

@@ -14,6 +14,11 @@ namespace FlaxEditor.GUI.Docking
{
private DockPanel _panel;
private double _dragEnterTime = -1;
#if PLATFORM_WINDOWS
private const bool HideTabForSingleTab = true;
#else
private const bool HideTabForSingleTab = false;
#endif
/// <summary>
/// The is mouse down flag (left button).
@@ -51,7 +56,7 @@ namespace FlaxEditor.GUI.Docking
public DockWindow StartDragAsyncWindow;
private Rectangle HeaderRectangle => new Rectangle(0, 0, Width, DockPanel.DefaultHeaderHeight);
private bool IsSingleFloatingWindow => _panel.TabsCount == 1 && _panel.IsFloating && _panel.ChildPanelsCount == 0;
private bool IsSingleFloatingWindow => HideTabForSingleTab && _panel.TabsCount == 1 && _panel.IsFloating && _panel.ChildPanelsCount == 0;
/// <summary>
/// Initializes a new instance of the <see cref="DockPanelProxy"/> class.

View File

@@ -610,7 +610,8 @@ Array<Guid> ManagedEditor::GetAssetReferences(const Guid& assetId)
Array<Guid> result;
if (auto* asset = Content::Load<Asset>(assetId))
{
asset->GetReferences(result);
Array<String> files;
asset->GetReferences(result, files);
}
return result;
}

View File

@@ -129,12 +129,9 @@ namespace FlaxEditor.Modules
for (int i = 0; i < Proxy.Count; i++)
{
if (Proxy[i].IsProxyFor(item))
{
return Proxy[i];
}
}
}
return null;
}
@@ -147,11 +144,8 @@ namespace FlaxEditor.Modules
for (int i = 0; i < Proxy.Count; i++)
{
if (Proxy[i].IsProxyFor<T>())
{
return Proxy[i];
}
}
return null;
}
@@ -164,17 +158,12 @@ namespace FlaxEditor.Modules
{
if (string.IsNullOrEmpty(extension))
throw new ArgumentNullException();
extension = StringUtils.NormalizeExtension(extension);
for (int i = 0; i < Proxy.Count; i++)
{
if (Proxy[i].FileExtension == extension)
{
if (string.Equals(Proxy[i].FileExtension, extension, StringComparison.Ordinal))
return Proxy[i];
}
}
return null;
}
@@ -189,29 +178,23 @@ namespace FlaxEditor.Modules
for (int i = 0; i < Proxy.Count; i++)
{
if (Proxy[i] is AssetProxy proxy && proxy.AcceptsAsset(typeName, path))
{
return proxy;
}
}
return null;
}
/// <summary>
/// Gets the virtual proxy object from given path.
/// <br></br>Useful if the asset that needs to be displayed is not a Flax asset but needs custom functionality.
/// </summary>
/// <param name="path">The asset path.</param>
/// <returns>Asset proxy or null if not found.</returns>
/// <returns>Asset proxy or null if cannot find.</returns>
public AssetProxy GetAssetVirtualProxy(string path)
{
for (int i = 0; i < Proxy.Count; i++)
{
if (Proxy[i] is AssetProxy proxy && proxy.IsVirtualProxy() && path.EndsWith(proxy.FileExtension, StringComparison.OrdinalIgnoreCase))
{
return proxy;
}
}
return null;
}
@@ -1026,7 +1009,9 @@ namespace FlaxEditor.Modules
item = proxy?.ConstructItem(path, assetInfo.TypeName, ref assetInfo.ID);
if (item == null)
{
item = new FileItem(path);
item = GetProxy(Path.GetExtension(path))?.ConstructItem(path);
if (item == null)
item = new FileItem(path);
}
}
@@ -1113,6 +1098,7 @@ namespace FlaxEditor.Modules
Proxy.Add(new VisualScriptProxy());
Proxy.Add(new BehaviorTreeProxy());
Proxy.Add(new LocalizedStringTableProxy());
Proxy.Add(new VideoProxy("mp4"));
Proxy.Add(new WidgetProxy());
Proxy.Add(new FileProxy());
Proxy.Add(new SpawnableJsonAssetProxy<PhysicalMaterial>());

View File

@@ -841,7 +841,6 @@ namespace FlaxEditor.Modules
{
// Open project, then close it
Editor.OpenProject(Editor.GameProject.ProjectPath);
Editor.Windows.MainWindow.Close(ClosingReason.User);
}
private void OnMenuFileShowHide(Control control)

View File

@@ -0,0 +1,69 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.CustomEditors.Dedicated
{
/// <summary>
/// Custom editor for <see cref="VideoPlayer"/>.
/// </summary>
[CustomEditor(typeof(VideoPlayer)), DefaultEditor]
public class VideoPlayerEditor : ActorEditor
{
private Label _infoLabel;
/// <inheritdoc />
public override void Initialize(LayoutElementsContainer layout)
{
base.Initialize(layout);
// Show playback options during simulation
if (Editor.IsPlayMode)
{
var playbackGroup = layout.Group("Playback");
playbackGroup.Panel.Open();
_infoLabel = playbackGroup.Label(string.Empty).Label;
_infoLabel.AutoHeight = true;
var grid = playbackGroup.CustomContainer<UniformGridPanel>();
var gridControl = grid.CustomControl;
gridControl.ClipChildren = false;
gridControl.Height = Button.DefaultHeight;
gridControl.SlotsHorizontally = 3;
gridControl.SlotsVertically = 1;
grid.Button("Play").Button.Clicked += () => Foreach(x => x.Play());
grid.Button("Pause").Button.Clicked += () => Foreach(x => x.Pause());
grid.Button("Stop").Button.Clicked += () => Foreach(x => x.Stop());
}
}
/// <inheritdoc />
public override void Refresh()
{
base.Refresh();
if (_infoLabel != null)
{
var text = string.Empty;
foreach (var value in Values)
{
if (value is VideoPlayer player)
text += $"Time: {player.Time:##0.0}s / {player.Duration:##0.0}s\nResolution: {player.Size.X}x{player.Size.Y}, Frame Rate: {player.FrameRate}";
}
_infoLabel.Text = text;
}
}
private void Foreach(Action<VideoPlayer> func)
{
foreach (var value in Values)
{
if (value is VideoPlayer player)
func(player);
}
}
}
}

View File

@@ -368,11 +368,13 @@ namespace FlaxEditor.Surface.Archetypes
Title = "Sample Global SDF",
Description = "Samples the Global SDF to get the distance to the closest surface (in world-space). Requires models SDF to be generated and checking `Enable Global SDF` in Graphics Settings.",
Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph,
Size = new Float2(200, 20),
Size = new Float2(200, 40),
DefaultValues = new object[] { 0 },
Elements = new[]
{
NodeElementArchetype.Factory.Output(0, "Distance", typeof(float), 0),
NodeElementArchetype.Factory.Input(0, "World Position", true, typeof(Float3), 1),
NodeElementArchetype.Factory.Input(1, "Start Cascade", true, typeof(int), 2, 0),
}
},
new NodeArchetype
@@ -382,11 +384,13 @@ namespace FlaxEditor.Surface.Archetypes
Description = "Samples the Global SDF to get the gradient and distance to the closest surface (in world-space). Normalize gradient to get SDF surface normal vector. Requires models SDF to be generated and checking `Enable Global SDF` in Graphics Settings.",
Flags = NodeFlags.MaterialGraph | NodeFlags.ParticleEmitterGraph,
Size = new Float2(260, 40),
DefaultValues = new object[] { 0 },
Elements = new[]
{
NodeElementArchetype.Factory.Output(0, "Gradient", typeof(Float3), 0),
NodeElementArchetype.Factory.Output(1, "Distance", typeof(float), 2),
NodeElementArchetype.Factory.Input(0, "World Position", true, typeof(Float3), 1),
NodeElementArchetype.Factory.Input(1, "Start Cascade", true, typeof(int), 3, 0),
}
},
new NodeArchetype

View File

@@ -104,6 +104,11 @@ namespace FlaxEditor.Surface
}
}
internal static GroupElement InitGraphParametersGroup(LayoutElementsContainer layout)
{
return CustomEditors.Editors.GenericEditor.OnGroup(layout, "Parameters");
}
private sealed class DummyMaterialSurfaceOwner : IVisjectSurfaceOwner
{
public Asset SurfaceAsset => null;

View File

@@ -146,19 +146,14 @@ namespace FlaxEditor.Utilities
/// <summary>
/// Formats the amount of bytes to get a human-readable data size in bytes with abbreviation. Eg. 32 kB
/// [Deprecated in v1.9]
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <returns>The formatted amount of bytes.</returns>
[Obsolete("Use FormatBytesCount with ulong instead")]
public static string FormatBytesCount(int bytes)
{
int order = 0;
while (bytes >= 1024 && order < MemorySizePostfixes.Length - 1)
{
order++;
bytes /= 1024;
}
return string.Format("{0:0.##} {1}", bytes, MemorySizePostfixes[order]);
return FormatBytesCount((ulong)bytes);
}
/// <summary>
@@ -169,12 +164,15 @@ namespace FlaxEditor.Utilities
public static string FormatBytesCount(ulong bytes)
{
int order = 0;
ulong bytesPrev = bytes;
while (bytes >= 1024 && order < MemorySizePostfixes.Length - 1)
{
bytesPrev = bytes;
order++;
bytes /= 1024;
}
if (order >= 3) // GB or higher use up to 2 decimal places for more precision
return string.Format("{0:0.##} {1}", FlaxEngine.Utils.RoundTo2DecimalPlaces(bytesPrev / 1024.0f), MemorySizePostfixes[order]);
return string.Format("{0:0.##} {1}", bytes, MemorySizePostfixes[order]);
}
@@ -1471,5 +1469,27 @@ namespace FlaxEditor.Utilities
inputActions.Add(options => options.GenerateScriptsProject, () => Editor.Instance.ProgressReporting.GenerateScriptsProjectFiles.RunAsync());
inputActions.Add(options => options.RecompileScripts, ScriptsBuilder.Compile);
}
internal static string ToPathProject(string path)
{
if (path != null)
{
// Convert into path relative to the project (cross-platform)
var projectFolder = Globals.ProjectFolder;
if (path.StartsWith(projectFolder))
path = path.Substring(projectFolder.Length + 1);
}
return path;
}
internal static string ToPathAbsolute(string path)
{
if (path != null)
{
// Convert into global path to if relative to the project
path = StringUtils.IsRelative(path) ? Path.Combine(Globals.ProjectFolder, path) : path;
}
return path;
}
}
}

View File

@@ -21,6 +21,7 @@
#include "Engine/Level/Actors/Sky.h"
#include "Engine/Level/Actors/SkyLight.h"
#include "Engine/Level/Actors/SpotLight.h"
#include "Engine/Video/VideoPlayer.h"
#define ICON_RADIUS 7.0f
@@ -283,6 +284,7 @@ bool ViewportIconsRendererService::Init()
MAP_TYPE(Sky, Skybox);
MAP_TYPE(SkyLight, SkyLight);
MAP_TYPE(SpotLight, PointLight);
MAP_TYPE(VideoPlayer, SceneAnimationPlayer);
#undef MAP_TYPE
return false;

View File

@@ -193,7 +193,7 @@ namespace FlaxEditor.Windows.Assets
group.Label("Frames: " + info.FramesCount);
group.Label("Channels: " + info.ChannelsCount);
group.Label("Keyframes: " + info.KeyframesCount);
group.Label("Memory Usage: " + Utilities.Utils.FormatBytesCount(info.MemoryUsage));
group.Label("Memory Usage: " + Utilities.Utils.FormatBytesCount((ulong)info.MemoryUsage));
}
base.Initialize(layout);

View File

@@ -249,7 +249,7 @@ namespace FlaxEditor.Windows.Assets
if (parameters.Length == 0)
return;
var parametersGroup = layout.Group("Parameters");
var parametersGroup = SurfaceUtils.InitGraphParametersGroup(layout);
var settingButton = parametersGroup.AddSettingsButton();
settingButton.Clicked += (image, button) => OnSettingsButtonClicked(image, button, proxy.Window);
var baseMaterial = materialInstance.BaseMaterial;

View File

@@ -1,7 +1,9 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using FlaxEditor.Content;
using FlaxEditor.Content.Import;
using FlaxEditor.CustomEditors;
@@ -11,6 +13,7 @@ using FlaxEditor.Viewport.Cameras;
using FlaxEditor.Viewport.Previews;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Json;
using FlaxEngine.Tools;
using FlaxEngine.Utilities;
using Object = FlaxEngine.Object;
@@ -185,6 +188,7 @@ namespace FlaxEditor.Windows.Assets
// SDF
{
var group = layout.Group("SDF");
var sdfOptions = proxy.Window._sdfOptions;
var sdf = proxy.Asset.SDF;
if (sdf.Texture != null)
@@ -204,11 +208,26 @@ namespace FlaxEditor.Windows.Assets
resolution.ValueBox.BoxValueChanged += b => { proxy.Window._importSettings.Settings.SDFResolution = b.Value; };
proxy.Window._importSettings.Settings.SDFResolution = sdf.ResolutionScale;
var backfacesThreshold = group.FloatValue("Backfaces Threshold", "Custom threshold (in range 0-1) for adjusting mesh internals detection based on the percentage of test rays hit triangle backfaces. Use lower value for more dense mesh.");
var gpu = group.Checkbox("Bake on GPU", "If checked, SDF generation will be calculated using GPU on Compute Shader, otherwise CPU will use Job System. GPU generation is fast but result in artifacts in various meshes (eg. foliage).");
gpu.CheckBox.Checked = sdfOptions.GPU;
var backfacesThresholdProp = group.AddPropertyItem("Backfaces Threshold", "Custom threshold (in range 0-1) for adjusting mesh internals detection based on the percentage of test rays hit triangle backfaces. Use lower value for more dense mesh.");
var backfacesThreshold = backfacesThresholdProp.FloatValue();
var backfacesThresholdLabel = backfacesThresholdProp.Labels.Last();
backfacesThreshold.ValueBox.MinValue = 0.001f;
backfacesThreshold.ValueBox.MaxValue = 1.0f;
backfacesThreshold.ValueBox.Value = proxy.Window._backfacesThreshold;
backfacesThreshold.ValueBox.BoxValueChanged += b => { proxy.Window._backfacesThreshold = b.Value; };
backfacesThreshold.ValueBox.Value = sdfOptions.BackfacesThreshold;
backfacesThreshold.ValueBox.BoxValueChanged += b => { proxy.Window._sdfOptions.BackfacesThreshold = b.Value; };
// Toggle Backfaces Threshold visibility (CPU-only option)
gpu.CheckBox.StateChanged += c =>
{
proxy.Window._sdfOptions.GPU = c.Checked;
backfacesThresholdLabel.Visible = !c.Checked;
backfacesThreshold.ValueBox.Visible = !c.Checked;
};
backfacesThresholdLabel.Visible = !gpu.CheckBox.Checked;
backfacesThreshold.ValueBox.Visible = !gpu.CheckBox.Checked;
var lodIndex = group.IntegerValue("LOD Index", "Index of the model Level of Detail to use for SDF data building. By default uses the lowest quality LOD for fast building.");
lodIndex.IntValue.MinValue = 0;
@@ -294,9 +313,22 @@ namespace FlaxEditor.Windows.Assets
private void OnRebuildSDF()
{
var proxy = (MeshesPropertiesProxy)Values[0];
proxy.Asset.GenerateSDF(proxy.Window._importSettings.Settings.SDFResolution, _sdfModelLodIndex.Value, true, proxy.Window._backfacesThreshold);
proxy.Window.MarkAsEdited();
Presenter.BuildLayoutOnUpdate();
proxy.Window.Enabled = false;
Task.Run(() =>
{
var sdfOptions = proxy.Window._sdfOptions;
bool failed = proxy.Asset.GenerateSDF(proxy.Window._importSettings.Settings.SDFResolution, _sdfModelLodIndex.Value, true, sdfOptions.BackfacesThreshold, sdfOptions.GPU);
FlaxEngine.Scripting.InvokeOnUpdate(() =>
{
proxy.Window.Enabled = true;
if (!failed)
proxy.Window.MarkAsEdited();
Presenter.BuildLayoutOnUpdate();
// Save some SDF options locally in the project cache
proxy.Window.Editor.ProjectCache.SetCustomData(JsonSerializer.GetStringID(proxy.Window.Item.ID) + ".SDF", JsonSerializer.Serialize(sdfOptions));
});
});
}
private void OnRemoveSDF()
@@ -774,17 +806,33 @@ namespace FlaxEditor.Windows.Assets
}
}
private struct ModelSdfOptions
{
public bool GPU;
public float BackfacesThreshold;
}
private readonly ModelPreview _preview;
private StaticModel _highlightActor;
private MeshDataCache _meshData;
private ModelImportSettings _importSettings = new ModelImportSettings();
private float _backfacesThreshold = 0.6f;
private ModelSdfOptions _sdfOptions;
private ToolStripButton _showCurrentLODButton;
/// <inheritdoc />
public ModelWindow(Editor editor, AssetItem item)
: base(editor, item)
{
// Try to restore SDF options from project cache (not saved in the asset)
if (Editor.ProjectCache.TryGetCustomData(JsonSerializer.GetStringID(Item.ID) + ".SDF", out string sdOptionsStr))
_sdfOptions = JsonSerializer.Deserialize<ModelSdfOptions>(sdOptionsStr);
else
_sdfOptions = new ModelSdfOptions
{
GPU = true,
BackfacesThreshold = 0.6f,
};
// Toolstrip
_toolstrip.AddSeparator();
_showCurrentLODButton = (ToolStripButton)_toolstrip.AddButton(editor.Icons.Info64, () => _preview.ShowCurrentLOD = !_preview.ShowCurrentLOD).LinkTooltip("Show LOD statistics");

View File

@@ -0,0 +1,234 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using FlaxEditor.Content;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Windows.Assets
{
/// <summary>
/// Editor window to view video media.
/// </summary>
public sealed class VideoWindow : EditorWindow, IContentItemOwner
{
private VideoItem _item;
private Image _frame;
private VideoPlayer _videoPlayer;
private Image _seekBegin, _seekEnd, _seekLeft, _seekRight, _playPause, _stop;
/// <inheritdoc />
public VideoWindow(Editor editor, VideoItem item)
: base(editor, false, ScrollBars.None)
{
_item = item;
_item.AddReference(this);
Title = _item.ShortName;
// Setup video player
_videoPlayer = new VideoPlayer
{
PlayOnStart = false,
Url = item.Path,
};
// Setup UI
var style = Style.Current;
var icons = Editor.Icons;
var playbackButtonsSize = 24.0f;
var playbackButtonsMouseOverColor = Color.FromBgra(0xFFBBBBBB);
_frame = new Image
{
Brush = new VideoBrush(_videoPlayer),
AnchorPreset = AnchorPresets.StretchAll,
Offsets = new Margin(0.0f, 0.0f, 0.0f, playbackButtonsSize),
Parent = this,
};
var playbackButtonsArea = new ContainerControl
{
AutoFocus = false,
ClipChildren = false,
BackgroundColor = style.LightBackground,
AnchorPreset = AnchorPresets.HorizontalStretchBottom,
Offsets = new Margin(0, 0, -playbackButtonsSize, playbackButtonsSize),
Parent = this
};
var playbackButtonsPanel = new ContainerControl
{
AutoFocus = false,
ClipChildren = false,
AnchorPreset = AnchorPresets.VerticalStretchCenter,
Offsets = Margin.Zero,
Parent = playbackButtonsArea,
};
_seekBegin = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Rewind to timeline start (Home)",
Brush = new SpriteBrush(icons.Skip64),
MouseOverColor = playbackButtonsMouseOverColor,
Rotation = 180.0f,
Parent = playbackButtonsPanel
};
_seekBegin.Clicked += (image, button) => SeekBegin();
playbackButtonsPanel.Width += playbackButtonsSize;
_seekLeft = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Move one frame back (Left Arrow)",
Brush = new SpriteBrush(icons.Left32),
MouseOverColor = playbackButtonsMouseOverColor,
Parent = playbackButtonsPanel
};
_seekLeft.Clicked += (image, button) => SeekLeft();
playbackButtonsPanel.Width += playbackButtonsSize;
_stop = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Stop playback",
Brush = new SpriteBrush(icons.Stop64),
MouseOverColor = playbackButtonsMouseOverColor,
Parent = playbackButtonsPanel
};
_stop.Clicked += (image, button) => Stop();
playbackButtonsPanel.Width += playbackButtonsSize;
_playPause = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Play/pause playback (Space)",
Brush = new SpriteBrush(icons.Play64),
MouseOverColor = playbackButtonsMouseOverColor,
Parent = playbackButtonsPanel
};
_playPause.Clicked += (image, button) => PlayPause();
playbackButtonsPanel.Width += playbackButtonsSize;
_seekRight = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Move one frame forward (Right Arrow)",
Brush = new SpriteBrush(icons.Right32),
MouseOverColor = playbackButtonsMouseOverColor,
Parent = playbackButtonsPanel
};
_seekRight.Clicked += (image, button) => SeekRight();
playbackButtonsPanel.Width += playbackButtonsSize;
_seekEnd = new Image(playbackButtonsPanel.Width, 0, playbackButtonsSize, playbackButtonsSize)
{
TooltipText = "Rewind to timeline end (End)",
Brush = new SpriteBrush(icons.Skip64),
MouseOverColor = playbackButtonsMouseOverColor,
Parent = playbackButtonsPanel
};
_seekEnd.Clicked += (image, button) => SeekEnd();
playbackButtonsPanel.Width += playbackButtonsSize;
playbackButtonsPanel.X = (playbackButtonsPanel.Parent.Width - playbackButtonsPanel.Width) * 0.5f;
}
private void PlayPause()
{
if (_videoPlayer.State == VideoPlayer.States.Playing)
_videoPlayer.Pause();
else
_videoPlayer.Play();
}
private void Stop()
{
_videoPlayer.Stop();
}
private void SeekBegin()
{
_videoPlayer.Time = 0.0f;
}
private void SeekEnd()
{
_videoPlayer.Time = _videoPlayer.Duration;
}
private void SeekLeft()
{
if (_videoPlayer.State == VideoPlayer.States.Paused)
_videoPlayer.Time -= 1.0f / _videoPlayer.FrameRate;
}
private void SeekRight()
{
if (_videoPlayer.State == VideoPlayer.States.Paused)
_videoPlayer.Time += 1.0f / _videoPlayer.FrameRate;
}
/// <inheritdoc />
public override bool OnKeyDown(KeyboardKeys key)
{
if (base.OnKeyDown(key))
return true;
switch (key)
{
case KeyboardKeys.ArrowLeft:
SeekLeft();
return true;
case KeyboardKeys.ArrowRight:
SeekRight();
return true;
case KeyboardKeys.Home:
SeekBegin();
return true;
case KeyboardKeys.End:
SeekEnd();
return true;
case KeyboardKeys.Spacebar:
PlayPause();
return true;
}
return false;
}
/// <inheritdoc />
public override void Update(float deltaTime)
{
base.Update(deltaTime);
// Update UI
var state = _videoPlayer.State;
var icons = Editor.Icons;
_stop.Enabled = state != VideoPlayer.States.Stopped;
_seekLeft.Enabled = _seekRight.Enabled = state != VideoPlayer.States.Playing;
((SpriteBrush)_playPause.Brush).Sprite = state == VideoPlayer.States.Playing ? icons.Pause64 : icons.Play64;
}
/// <inheritdoc />
public override void OnDestroy()
{
if (IsDisposing)
return;
_videoPlayer.Stop();
Object.Destroy(ref _videoPlayer);
_item.RemoveReference(this);
_item = null;
base.OnDestroy();
}
/// <inheritdoc />
public void OnItemDeleted(ContentItem item)
{
if (item == _item)
Close();
}
/// <inheritdoc />
public void OnItemRenamed(ContentItem item)
{
}
/// <inheritdoc />
public void OnItemReimported(ContentItem item)
{
}
/// <inheritdoc />
public void OnItemDispose(ContentItem item)
{
if (item == _item)
Close();
}
}
}

View File

@@ -767,13 +767,6 @@ namespace FlaxEditor.Windows
Platform = BuildPlatform.Windows64,
Mode = BuildConfiguration.Development,
},
new BuildTarget
{
Name = "Windows 32bit",
Output = "Output\\Win32",
Platform = BuildPlatform.Windows32,
Mode = BuildConfiguration.Development,
},
}
};
_data = presets;
@@ -793,9 +786,9 @@ namespace FlaxEditor.Windows
Array.Copy(_data[_selectedPresetIndex].Targets, targets, count);
targets[count] = new BuildTarget
{
Name = "Xbox One",
Output = "Output\\XboxOne",
Platform = BuildPlatform.XboxOne,
Name = "Windows 64bit",
Output = "Output\\Win64",
Platform = BuildPlatform.Windows64,
Mode = BuildConfiguration.Development,
};
_data[_selectedPresetIndex].Targets = targets;

View File

@@ -95,14 +95,6 @@ namespace FlaxEditor.Windows
set => Graphics.ShadowMapsQuality = value;
}
[DefaultValue(false)]
[EditorOrder(1320), EditorDisplay("Quality", "Allow CSM Blending"), Tooltip("Enables cascades splits blending for directional light shadows.")]
public bool AllowCSMBlending
{
get => Graphics.AllowCSMBlending;
set => Graphics.AllowCSMBlending = value;
}
[NoSerialize, DefaultValue(1.0f), Limit(0.05f, 5, 0)]
[EditorOrder(1400), EditorDisplay("Quality")]
[Tooltip("The scale of the rendering resolution relative to the output dimensions. If lower than 1 the scene and postprocessing will be rendered at a lower resolution and upscaled to the output backbuffer.")]

View File

@@ -366,13 +366,13 @@ namespace FlaxEditor.Windows
}
var clonePath = Path.Combine(Globals.ProjectFolder, "Plugins", pluginName);
if (!Directory.Exists(clonePath))
Directory.CreateDirectory(clonePath);
else
if (Directory.Exists(clonePath))
{
Editor.LogError("Plugin Name is already used. Pick a different Name.");
return;
}
Directory.CreateDirectory(clonePath);
try
{
// Start git clone
@@ -384,7 +384,32 @@ namespace FlaxEditor.Windows
LogOutput = true,
WaitForEnd = true
};
Platform.CreateProcess(ref settings);
var asSubmodule = Directory.Exists(Path.Combine(Globals.ProjectFolder, ".git"));
if (asSubmodule)
{
// Clone as submodule to the existing repo
settings.Arguments = $"submodule add {gitPath} \"Plugins/{pluginName}\"";
// Submodule add need the target folder to not exist
Directory.Delete(clonePath);
}
int result = Platform.CreateProcess(ref settings);
if (result != 0)
throw new Exception($"'{settings.FileName} {settings.Arguments}' failed with result {result}");
// Ensure that cloned repo exists
var checkPath = Path.Combine(clonePath, ".git");
if (asSubmodule)
{
if (!File.Exists(checkPath))
throw new Exception("Failed to clone repo.");
}
else
{
if (!Directory.Exists(checkPath))
throw new Exception("Failed to clone repo.");
}
}
catch (Exception e)
{

View File

@@ -50,7 +50,9 @@ namespace FlaxEditor.Windows.Profiler
{
Title = "Assets Memory Usage (CPU)",
AnchorPreset = AnchorPresets.HorizontalStretchTop,
FormatSample = v => Utilities.Utils.FormatBytesCount((int)v),
Offsets = Margin.Zero,
Height = SingleChart.DefaultHeight,
FormatSample = v => Utilities.Utils.FormatBytesCount((ulong)v),
Parent = mainPanel,
};
_memoryUsageChart.SelectedSampleChanged += OnSelectedSampleChanged;

View File

@@ -69,6 +69,8 @@ namespace FlaxEditor.Windows.Profiler
{
Title = "Update",
AnchorPreset = AnchorPresets.HorizontalStretchTop,
Offsets = Margin.Zero,
Height = SingleChart.DefaultHeight,
FormatSample = v => (Mathf.RoundToInt(v * 10.0f) / 10.0f) + " ms",
Parent = mainPanel,
};
@@ -173,7 +175,7 @@ namespace FlaxEditor.Windows.Profiler
private string FormatCellBytes(object x)
{
return Utilities.Utils.FormatBytesCount((int)x);
return Utilities.Utils.FormatBytesCount((ulong)x);
}
/// <inheritdoc />

View File

@@ -37,6 +37,8 @@ namespace FlaxEditor.Windows.Profiler
{
Title = "Draw (CPU)",
AnchorPreset = AnchorPresets.HorizontalStretchTop,
Offsets = Margin.Zero,
Height = SingleChart.DefaultHeight,
FormatSample = v => (Mathf.RoundToInt(v * 10.0f) / 10.0f) + " ms",
Parent = mainPanel,
};

View File

@@ -36,14 +36,14 @@ namespace FlaxEditor.Windows.Profiler
_nativeAllocationsChart = new SingleChart
{
Title = "Native Memory Allocation",
FormatSample = v => Utilities.Utils.FormatBytesCount((int)v),
FormatSample = v => Utilities.Utils.FormatBytesCount((ulong)v),
Parent = layout,
};
_nativeAllocationsChart.SelectedSampleChanged += OnSelectedSampleChanged;
_managedAllocationsChart = new SingleChart
{
Title = "Managed Memory Allocation",
FormatSample = v => Utilities.Utils.FormatBytesCount((int)v),
FormatSample = v => Utilities.Utils.FormatBytesCount((ulong)v),
Parent = layout,
};
_managedAllocationsChart.SelectedSampleChanged += OnSelectedSampleChanged;

View File

@@ -51,7 +51,9 @@ namespace FlaxEditor.Windows.Profiler
{
Title = "GPU Memory Usage",
AnchorPreset = AnchorPresets.HorizontalStretchTop,
FormatSample = v => Utilities.Utils.FormatBytesCount((int)v),
Offsets = Margin.Zero,
Height = SingleChart.DefaultHeight,
FormatSample = v => Utilities.Utils.FormatBytesCount((ulong)v),
Parent = mainPanel,
};
_memoryUsageChart.SelectedSampleChanged += OnSelectedSampleChanged;

View File

@@ -319,7 +319,7 @@ namespace FlaxEditor.Windows.Profiler
private static string FormatCellBytes(object x)
{
return Utilities.Utils.FormatBytesCount((int)x);
return Utilities.Utils.FormatBytesCount((ulong)x);
}
private static int SortRows(Control x, Control y)

View File

@@ -92,7 +92,7 @@ namespace FlaxEditor.Windows.Profiler
Parent = this,
};
_liveRecordingButton = toolstrip.AddButton(editor.Icons.Play64);
_liveRecordingButton.LinkTooltip("Live profiling events recording");
//_liveRecordingButton.LinkTooltip("Live profiling events recording");
_liveRecordingButton.AutoCheck = true;
_liveRecordingButton.Clicked += OnLiveRecordingChanged;
_clearButton = toolstrip.AddButton(editor.Icons.Rotate32, Clear);

View File

@@ -12,6 +12,7 @@ namespace FlaxEditor.Windows.Profiler
/// <seealso cref="FlaxEngine.GUI.Control" />
internal class SingleChart : Control
{
internal const float DefaultHeight = TitleHeight + 60;
private const float TitleHeight = 20;
private const float PointsOffset = 4;
private readonly SamplesBuffer<float> _samples;
@@ -63,7 +64,7 @@ namespace FlaxEditor.Windows.Profiler
/// </summary>
/// <param name="maxSamples">The maximum samples to collect.</param>
public SingleChart(int maxSamples = ProfilerMode.MaxSamples)
: base(0, 0, 100, 60 + TitleHeight)
: base(0, 0, 100, DefaultHeight)
{
_samples = new SamplesBuffer<float>(maxSamples);
_sample = string.Empty;

View File

@@ -232,12 +232,12 @@ void BehaviorTree::OnScriptsReloadEnd()
Graph.Setup(this);
}
void BehaviorTree::GetReferences(Array<Guid>& output) const
void BehaviorTree::GetReferences(Array<Guid>& assets, Array<String>& files) const
{
// Base
BinaryAsset::GetReferences(output);
BinaryAsset::GetReferences(assets, files);
Graph.GetReferences(output);
Graph.GetReferences(assets);
// Extract refs from serialized nodes data
for (const BehaviorTreeGraphNode& n : Graph.Nodes)
@@ -246,7 +246,7 @@ void BehaviorTree::GetReferences(Array<Guid>& output) const
continue;
const Variant& data = n.Values[1];
if (data.Type == VariantType::Blob)
JsonAssetBase::GetReferences(StringAnsiView((char*)data.AsBlob.Data, data.AsBlob.Length), output);
JsonAssetBase::GetReferences(StringAnsiView((char*)data.AsBlob.Data, data.AsBlob.Length), assets);
}
}

View File

@@ -98,7 +98,7 @@ public:
// [BinaryAsset]
void OnScriptingDispose() override;
#if USE_EDITOR
void GetReferences(Array<Guid>& output) const override;
void GetReferences(Array<Guid>& assets, Array<String>& files) const override;
#endif
protected:

View File

@@ -75,16 +75,16 @@ bool SceneAnimation::SaveTimeline(const BytesContainer& data)
#if USE_EDITOR
void SceneAnimation::GetReferences(Array<Guid>& output) const
void SceneAnimation::GetReferences(Array<Guid>& assets, Array<String>& files) const
{
// Base
BinaryAsset::GetReferences(output);
BinaryAsset::GetReferences(assets, files);
for (int32 i = 0; i < Tracks.Count(); i++)
{
const auto& track = Tracks[i];
if (track.Asset)
output.Add(track.Asset->GetID());
assets.Add(track.Asset->GetID());
}
}

View File

@@ -464,7 +464,7 @@ public:
public:
// [BinaryAsset]
#if USE_EDITOR
void GetReferences(Array<Guid>& output) const override;
void GetReferences(Array<Guid>& assets, Array<String>& files) const override;
#endif
protected:

View File

@@ -148,47 +148,6 @@ void Audio::SetEnableHRTF(bool value)
AudioBackend::Listener::ReinitializeAll();
}
void Audio::OnAddListener(AudioListener* listener)
{
ASSERT(!Listeners.Contains(listener));
if (Listeners.Count() >= AUDIO_MAX_LISTENERS)
{
LOG(Error, "Unsupported amount of the audio listeners!");
return;
}
if (Audio::Listeners.Count() > 0)
LOG(Warning, "There is more than 1 Audio listener active. Please make sure only exactly one is active at any given time.");
Listeners.Add(listener);
AudioBackend::Listener::OnAdd(listener);
}
void Audio::OnRemoveListener(AudioListener* listener)
{
if (!Listeners.Remove(listener))
{
AudioBackend::Listener::OnRemove(listener);
}
}
void Audio::OnAddSource(AudioSource* source)
{
ASSERT(!Sources.Contains(source));
Sources.Add(source);
AudioBackend::Source::OnAdd(source);
}
void Audio::OnRemoveSource(AudioSource* source)
{
if (!Sources.Remove(source))
{
AudioBackend::Source::OnRemove(source);
}
}
bool AudioService::Init()
{
PROFILE_CPU_NAMED("Audio.Init");

View File

@@ -97,11 +97,4 @@ public:
/// </summary>
/// <param name="value">The value.</param>
API_PROPERTY() static void SetEnableHRTF(bool value);
public:
static void OnAddListener(AudioListener* listener);
static void OnRemoveListener(AudioListener* listener);
static void OnAddSource(AudioSource* source);
static void OnRemoveSource(AudioSource* source);
};

View File

@@ -15,7 +15,6 @@ class AudioBackend
friend class AudioService;
public:
enum class FeatureFlags
{
None = 0,
@@ -26,41 +25,37 @@ public:
static AudioBackend* Instance;
private:
// Listener
virtual void Listener_OnAdd(AudioListener* listener) = 0;
virtual void Listener_OnRemove(AudioListener* listener) = 0;
virtual void Listener_VelocityChanged(AudioListener* listener) = 0;
virtual void Listener_TransformChanged(AudioListener* listener) = 0;
virtual void Listener_Reset() = 0;
virtual void Listener_VelocityChanged(const Vector3& velocity) = 0;
virtual void Listener_TransformChanged(const Vector3& position, const Quaternion& orientation) = 0;
virtual void Listener_ReinitializeAll() = 0;
// Source
virtual void Source_OnAdd(AudioSource* source) = 0;
virtual void Source_OnRemove(AudioSource* source) = 0;
virtual void Source_VelocityChanged(AudioSource* source) = 0;
virtual void Source_TransformChanged(AudioSource* source) = 0;
virtual void Source_VolumeChanged(AudioSource* source) = 0;
virtual void Source_PitchChanged(AudioSource* source) = 0;
virtual void Source_PanChanged(AudioSource* source) = 0;
virtual void Source_IsLoopingChanged(AudioSource* source) = 0;
virtual void Source_SpatialSetupChanged(AudioSource* source) = 0;
virtual void Source_ClipLoaded(AudioSource* source) = 0;
virtual void Source_Cleanup(AudioSource* source) = 0;
virtual void Source_Play(AudioSource* source) = 0;
virtual void Source_Pause(AudioSource* source) = 0;
virtual void Source_Stop(AudioSource* source) = 0;
virtual void Source_SetCurrentBufferTime(AudioSource* source, float value) = 0;
virtual float Source_GetCurrentBufferTime(const AudioSource* source) = 0;
virtual void Source_SetNonStreamingBuffer(AudioSource* source) = 0;
virtual void Source_GetProcessedBuffersCount(AudioSource* source, int32& processedBuffersCount) = 0;
virtual void Source_GetQueuedBuffersCount(AudioSource* source, int32& queuedBuffersCount) = 0;
virtual void Source_QueueBuffer(AudioSource* source, uint32 bufferId) = 0;
virtual void Source_DequeueProcessedBuffers(AudioSource* source) = 0;
virtual uint32 Source_Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler) = 0;
virtual void Source_Remove(uint32 sourceID) = 0;
virtual void Source_VelocityChanged(uint32 sourceID, const Vector3& velocity) = 0;
virtual void Source_TransformChanged(uint32 sourceID, const Vector3& position, const Quaternion& orientation) = 0;
virtual void Source_VolumeChanged(uint32 sourceID, float volume) = 0;
virtual void Source_PitchChanged(uint32 sourceID, float pitch) = 0;
virtual void Source_PanChanged(uint32 sourceID, float pan) = 0;
virtual void Source_IsLoopingChanged(uint32 sourceID, bool loop) = 0;
virtual void Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler) = 0;
virtual void Source_Play(uint32 sourceID) = 0;
virtual void Source_Pause(uint32 sourceID) = 0;
virtual void Source_Stop(uint32 sourceID) = 0;
virtual void Source_SetCurrentBufferTime(uint32 sourceID, float value) = 0;
virtual float Source_GetCurrentBufferTime(uint32 id) = 0;
virtual void Source_SetNonStreamingBuffer(uint32 sourceID, uint32 bufferID) = 0;
virtual void Source_GetProcessedBuffersCount(uint32 sourceID, int32& processedBuffersCount) = 0;
virtual void Source_GetQueuedBuffersCount(uint32 sourceID, int32& queuedBuffersCount) = 0;
virtual void Source_QueueBuffer(uint32 sourceID, uint32 bufferID) = 0;
virtual void Source_DequeueProcessedBuffers(uint32 sourceID) = 0;
// Buffer
virtual uint32 Buffer_Create() = 0;
virtual void Buffer_Delete(uint32 bufferId) = 0;
virtual void Buffer_Write(uint32 bufferId, byte* samples, const AudioDataInfo& info) = 0;
virtual void Buffer_Delete(uint32 bufferID) = 0;
virtual void Buffer_Write(uint32 bufferID, byte* samples, const AudioDataInfo& info) = 0;
// Base
virtual const Char* Base_Name() = 0;
@@ -73,35 +68,27 @@ private:
virtual void Base_Dispose() = 0;
public:
virtual ~AudioBackend()
{
}
public:
class Listener
{
public:
FORCE_INLINE static void OnAdd(AudioListener* listener)
FORCE_INLINE static void Reset()
{
Instance->Listener_OnAdd(listener);
Instance->Listener_Reset();
}
FORCE_INLINE static void OnRemove(AudioListener* listener)
FORCE_INLINE static void VelocityChanged(const Vector3& velocity)
{
Instance->Listener_OnRemove(listener);
Instance->Listener_VelocityChanged(velocity);
}
FORCE_INLINE static void VelocityChanged(AudioListener* listener)
FORCE_INLINE static void TransformChanged(const Vector3& position, const Quaternion& orientation)
{
Instance->Listener_VelocityChanged(listener);
}
FORCE_INLINE static void TransformChanged(AudioListener* listener)
{
Instance->Listener_TransformChanged(listener);
Instance->Listener_TransformChanged(position, orientation);
}
FORCE_INLINE static void ReinitializeAll()
@@ -113,130 +100,118 @@ public:
class Source
{
public:
FORCE_INLINE static void OnAdd(AudioSource* source)
FORCE_INLINE static uint32 Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler)
{
Instance->Source_OnAdd(source);
return Instance->Source_Add(format, position, orientation, volume, pitch, pan, loop, spatial, attenuation, minDistance, doppler);
}
FORCE_INLINE static void OnRemove(AudioSource* source)
FORCE_INLINE static void Remove(uint32 sourceID)
{
Instance->Source_OnRemove(source);
Instance->Source_Remove(sourceID);
}
FORCE_INLINE static void VelocityChanged(AudioSource* source)
FORCE_INLINE static void VelocityChanged(uint32 sourceID, const Vector3& velocity)
{
Instance->Source_VelocityChanged(source);
Instance->Source_VelocityChanged(sourceID, velocity);
}
FORCE_INLINE static void TransformChanged(AudioSource* source)
FORCE_INLINE static void TransformChanged(uint32 sourceID, const Vector3& position, const Quaternion& orientation)
{
Instance->Source_TransformChanged(source);
Instance->Source_TransformChanged(sourceID, position, orientation);
}
FORCE_INLINE static void VolumeChanged(AudioSource* source)
FORCE_INLINE static void VolumeChanged(uint32 sourceID, float volume)
{
Instance->Source_VolumeChanged(source);
Instance->Source_VolumeChanged(sourceID, volume);
}
FORCE_INLINE static void PitchChanged(AudioSource* source)
FORCE_INLINE static void PitchChanged(uint32 sourceID, float pitch)
{
Instance->Source_PitchChanged(source);
Instance->Source_PitchChanged(sourceID, pitch);
}
FORCE_INLINE static void PanChanged(AudioSource* source)
FORCE_INLINE static void PanChanged(uint32 sourceID, float pan)
{
Instance->Source_PanChanged(source);
Instance->Source_PanChanged(sourceID, pan);
}
FORCE_INLINE static void IsLoopingChanged(AudioSource* source)
FORCE_INLINE static void IsLoopingChanged(uint32 sourceID, bool loop)
{
Instance->Source_IsLoopingChanged(source);
Instance->Source_IsLoopingChanged(sourceID, loop);
}
FORCE_INLINE static void SpatialSetupChanged(AudioSource* source)
FORCE_INLINE static void SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler)
{
Instance->Source_SpatialSetupChanged(source);
Instance->Source_SpatialSetupChanged(sourceID, spatial, attenuation, minDistance, doppler);
}
FORCE_INLINE static void ClipLoaded(AudioSource* source)
FORCE_INLINE static void Play(uint32 sourceID)
{
Instance->Source_ClipLoaded(source);
Instance->Source_Play(sourceID);
}
FORCE_INLINE static void Cleanup(AudioSource* source)
FORCE_INLINE static void Pause(uint32 sourceID)
{
Instance->Source_Cleanup(source);
Instance->Source_Pause(sourceID);
}
FORCE_INLINE static void Play(AudioSource* source)
FORCE_INLINE static void Stop(uint32 sourceID)
{
Instance->Source_Play(source);
Instance->Source_Stop(sourceID);
}
FORCE_INLINE static void Pause(AudioSource* source)
FORCE_INLINE static void SetCurrentBufferTime(uint32 sourceID, float value)
{
Instance->Source_Pause(source);
Instance->Source_SetCurrentBufferTime(sourceID, value);
}
FORCE_INLINE static void Stop(AudioSource* source)
FORCE_INLINE static float GetCurrentBufferTime(uint32 sourceID)
{
Instance->Source_Stop(source);
return Instance->Source_GetCurrentBufferTime(sourceID);
}
FORCE_INLINE static void SetCurrentBufferTime(AudioSource* source, float value)
FORCE_INLINE static void SetNonStreamingBuffer(uint32 sourceID, uint32 bufferID)
{
Instance->Source_SetCurrentBufferTime(source, value);
Instance->Source_SetNonStreamingBuffer(sourceID, bufferID);
}
FORCE_INLINE static float GetCurrentBufferTime(const AudioSource* source)
FORCE_INLINE static void GetProcessedBuffersCount(uint32 sourceID, int32& processedBuffersCount)
{
return Instance->Source_GetCurrentBufferTime(source);
Instance->Source_GetProcessedBuffersCount(sourceID, processedBuffersCount);
}
FORCE_INLINE static void SetNonStreamingBuffer(AudioSource* source)
FORCE_INLINE static void GetQueuedBuffersCount(uint32 sourceID, int32& queuedBuffersCount)
{
Instance->Source_SetNonStreamingBuffer(source);
Instance->Source_GetQueuedBuffersCount(sourceID, queuedBuffersCount);
}
FORCE_INLINE static void GetProcessedBuffersCount(AudioSource* source, int32& processedBuffersCount)
FORCE_INLINE static void QueueBuffer(uint32 sourceID, uint32 bufferID)
{
Instance->Source_GetProcessedBuffersCount(source, processedBuffersCount);
Instance->Source_QueueBuffer(sourceID, bufferID);
}
FORCE_INLINE static void GetQueuedBuffersCount(AudioSource* source, int32& queuedBuffersCount)
FORCE_INLINE static void DequeueProcessedBuffers(uint32 sourceID)
{
Instance->Source_GetQueuedBuffersCount(source, queuedBuffersCount);
}
FORCE_INLINE static void QueueBuffer(AudioSource* source, uint32 bufferId)
{
Instance->Source_QueueBuffer(source, bufferId);
}
FORCE_INLINE static void DequeueProcessedBuffers(AudioSource* source)
{
Instance->Source_DequeueProcessedBuffers(source);
Instance->Source_DequeueProcessedBuffers(sourceID);
}
};
class Buffer
{
public:
FORCE_INLINE static uint32 Create()
{
return Instance->Buffer_Create();
}
FORCE_INLINE static void Delete(uint32 bufferId)
FORCE_INLINE static void Delete(uint32 bufferID)
{
Instance->Buffer_Delete(bufferId);
Instance->Buffer_Delete(bufferID);
}
FORCE_INLINE static void Write(uint32 bufferId, byte* samples, const AudioDataInfo& info)
FORCE_INLINE static void Write(uint32 bufferID, byte* samples, const AudioDataInfo& info)
{
Instance->Buffer_Write(bufferId, samples, info);
Instance->Buffer_Write(bufferID, samples, info);
}
};

View File

@@ -21,6 +21,13 @@ public:
Vector3 Velocity;
Vector3 Position;
Quaternion Orientation;
void Reset()
{
Velocity = Vector3::Zero;
Position = Vector3::Zero;
Orientation = Quaternion::Identity;
}
};
struct Source
@@ -153,18 +160,8 @@ public:
break;
case 2:
default: // TODO: implement multi-channel support (eg. 5.1, 7.1)
if (sourceChannels == 1)
{
outputMatrix[0] = channels[FrontLeft];
outputMatrix[1] = channels[FrontRight];
}
else if (sourceChannels == 2)
{
outputMatrix[0] = channels[FrontLeft];
outputMatrix[1] = 0.0f;
outputMatrix[2] = 0.0f;
outputMatrix[3] = channels[FrontRight];
}
outputMatrix[0] = channels[FrontLeft];
outputMatrix[sourceChannels + 1] = channels[FrontRight];
break;
}
}

View File

@@ -31,16 +31,16 @@ bool AudioClip::StreamingTask::Run()
for (int32 i = 0; i < queue.Count(); i++)
{
const auto idx = queue[i];
uint32& bufferId = clip->Buffers[idx];
if (bufferId == AUDIO_BUFFER_ID_INVALID)
uint32& bufferID = clip->Buffers[idx];
if (bufferID == 0)
{
bufferId = AudioBackend::Buffer::Create();
bufferID = AudioBackend::Buffer::Create();
}
else
{
// Release unused data
AudioBackend::Buffer::Delete(bufferId);
bufferId = AUDIO_BUFFER_ID_INVALID;
AudioBackend::Buffer::Delete(bufferID);
bufferID = 0;
}
}
@@ -267,7 +267,7 @@ Task* AudioClip::CreateStreamingTask(int32 residency)
for (int32 i = 0; i < StreamingQueue.Count(); i++)
{
const int32 idx = StreamingQueue[i];
if (Buffers[idx] == AUDIO_BUFFER_ID_INVALID)
if (Buffers[idx] == 0)
{
const auto task = (Task*)RequestChunkDataAsync(idx);
if (task)
@@ -383,8 +383,8 @@ Asset::LoadResult AudioClip::load()
void AudioClip::unload(bool isReloading)
{
bool hasAnyBuffer = false;
for (const AUDIO_BUFFER_ID_TYPE bufferId : Buffers)
hasAnyBuffer |= bufferId != AUDIO_BUFFER_ID_INVALID;
for (const uint32 bufferID : Buffers)
hasAnyBuffer |= bufferID != 0;
// Stop any audio sources that are using this clip right now
// TODO: find better way to collect audio sources using audio clip and impl it for AudioStreamingHandler too
@@ -400,10 +400,10 @@ void AudioClip::unload(bool isReloading)
StreamingQueue.Clear();
if (hasAnyBuffer && AudioBackend::Instance)
{
for (AUDIO_BUFFER_ID_TYPE bufferId : Buffers)
for (uint32 bufferID : Buffers)
{
if (bufferId != AUDIO_BUFFER_ID_INVALID)
AudioBackend::Buffer::Delete(bufferId);
if (bufferID != 0)
AudioBackend::Buffer::Delete(bufferID);
}
}
Buffers.Clear();
@@ -414,8 +414,8 @@ void AudioClip::unload(bool isReloading)
bool AudioClip::WriteBuffer(int32 chunkIndex)
{
// Ignore if buffer is not created
const uint32 bufferId = Buffers[chunkIndex];
if (bufferId == AUDIO_BUFFER_ID_INVALID)
const uint32 bufferID = Buffers[chunkIndex];
if (bufferID == 0)
return false;
// Ensure audio backend exists
@@ -476,6 +476,6 @@ bool AudioClip::WriteBuffer(int32 chunkIndex)
}
// Write samples to the audio buffer
AudioBackend::Buffer::Write(bufferId, data.Get(), info);
AudioBackend::Buffer::Write(bufferID, data.Get(), info);
return false;
}

View File

@@ -88,7 +88,7 @@ public:
/// <summary>
/// The audio backend buffers (internal ids) collection used by this audio clip.
/// </summary>
Array<AUDIO_BUFFER_ID_TYPE, FixedAllocation<ASSET_FILE_DATA_CHUNKS>> Buffers;
Array<uint32, FixedAllocation<ASSET_FILE_DATA_CHUNKS>> Buffers;
/// <summary>
/// The streaming cache. Contains indices of chunks to stream. If empty no streaming required. Managed by AudioStreamingHandler and used by the Audio streaming tasks.

View File

@@ -3,12 +3,14 @@
#include "AudioListener.h"
#include "Engine/Engine/Time.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Core/Log.h"
#include "AudioBackend.h"
#include "Audio.h"
AudioListener::AudioListener(const SpawnParams& params)
: Actor(params)
, _velocity(Vector3::Zero)
, _prevPos(Vector3::Zero)
{
}
@@ -27,7 +29,7 @@ void AudioListener::Update()
_prevPos = pos;
if (_velocity != prevVelocity)
{
AudioBackend::Listener::VelocityChanged(this);
AudioBackend::Listener::VelocityChanged(_velocity);
}
}
@@ -35,9 +37,20 @@ void AudioListener::OnEnable()
{
_prevPos = GetPosition();
_velocity = Vector3::Zero;
Audio::OnAddListener(this);
GetScene()->Ticking.Update.AddTick<AudioListener, &AudioListener::Update>(this);
if (Audio::Listeners.Count() >= AUDIO_MAX_LISTENERS)
{
LOG(Error, "Unsupported amount of the audio listeners!");
}
else
{
ASSERT(!Audio::Listeners.Contains(this));
if (Audio::Listeners.Count() > 0)
LOG(Warning, "There is more than one Audio Listener active. Please make sure only exactly one is active at any given time.");
Audio::Listeners.Add(this);
AudioBackend::Listener::Reset();
AudioBackend::Listener::TransformChanged(GetPosition(), GetOrientation());
GetScene()->Ticking.Update.AddTick<AudioListener, &AudioListener::Update>(this);
}
#if USE_EDITOR
GetSceneRendering()->AddViewportIcon(this);
#endif
@@ -51,8 +64,11 @@ void AudioListener::OnDisable()
#if USE_EDITOR
GetSceneRendering()->RemoveViewportIcon(this);
#endif
GetScene()->Ticking.Update.RemoveTick(this);
Audio::OnRemoveListener(this);
if (!Audio::Listeners.Remove(this))
{
GetScene()->Ticking.Update.RemoveTick(this);
AudioBackend::Listener::Reset();
}
// Base
Actor::OnDisable();
@@ -68,6 +84,6 @@ void AudioListener::OnTransformChanged()
if (IsActiveInHierarchy() && IsDuringPlay())
{
AudioBackend::Listener::TransformChanged(this);
AudioBackend::Listener::TransformChanged(GetPosition(), GetOrientation());
}
}

View File

@@ -32,8 +32,8 @@ void AudioSource::SetVolume(float value)
if (Math::NearEqual(_volume, value))
return;
_volume = value;
if (SourceIDs.HasItems())
AudioBackend::Source::VolumeChanged(this);
if (SourceID)
AudioBackend::Source::VolumeChanged(SourceID, _volume);
}
void AudioSource::SetPitch(float value)
@@ -42,8 +42,8 @@ void AudioSource::SetPitch(float value)
if (Math::NearEqual(_pitch, value))
return;
_pitch = value;
if (SourceIDs.HasItems())
AudioBackend::Source::PitchChanged(this);
if (SourceID)
AudioBackend::Source::PitchChanged(SourceID, _pitch);
}
void AudioSource::SetPan(float value)
@@ -52,8 +52,8 @@ void AudioSource::SetPan(float value)
if (Math::NearEqual(_pan, value))
return;
_pan = value;
if (SourceIDs.HasItems())
AudioBackend::Source::PanChanged(this);
if (SourceID)
AudioBackend::Source::PanChanged(SourceID, _pan);
}
void AudioSource::SetIsLooping(bool value)
@@ -63,8 +63,8 @@ void AudioSource::SetIsLooping(bool value)
_loop = value;
// When streaming we handle looping manually by the proper buffers submission
if (SourceIDs.HasItems() && !UseStreaming())
AudioBackend::Source::IsLoopingChanged(this);
if (SourceID && !UseStreaming())
AudioBackend::Source::IsLoopingChanged(SourceID, _loop);
}
void AudioSource::SetPlayOnStart(bool value)
@@ -83,8 +83,8 @@ void AudioSource::SetMinDistance(float value)
if (Math::NearEqual(_minDistance, value))
return;
_minDistance = value;
if (SourceIDs.HasItems())
AudioBackend::Source::SpatialSetupChanged(this);
if (SourceID)
AudioBackend::Source::SpatialSetupChanged(SourceID, Is3D(), _attenuation, _minDistance, _dopplerFactor);
}
void AudioSource::SetAttenuation(float value)
@@ -93,8 +93,8 @@ void AudioSource::SetAttenuation(float value)
if (Math::NearEqual(_attenuation, value))
return;
_attenuation = value;
if (SourceIDs.HasItems())
AudioBackend::Source::SpatialSetupChanged(this);
if (SourceID)
AudioBackend::Source::SpatialSetupChanged(SourceID, Is3D(), _attenuation, _minDistance, _dopplerFactor);
}
void AudioSource::SetDopplerFactor(float value)
@@ -103,8 +103,8 @@ void AudioSource::SetDopplerFactor(float value)
if (Math::NearEqual(_dopplerFactor, value))
return;
_dopplerFactor = value;
if (SourceIDs.HasItems())
AudioBackend::Source::SpatialSetupChanged(this);
if (SourceID)
AudioBackend::Source::SpatialSetupChanged(SourceID, Is3D(), _attenuation, _minDistance, _dopplerFactor);
}
void AudioSource::SetAllowSpatialization(bool value)
@@ -112,8 +112,8 @@ void AudioSource::SetAllowSpatialization(bool value)
if (_allowSpatialization == value)
return;
_allowSpatialization = value;
if (SourceIDs.HasItems())
AudioBackend::Source::SpatialSetupChanged(this);
if (SourceID)
AudioBackend::Source::SpatialSetupChanged(SourceID, Is3D(), _attenuation, _minDistance, _dopplerFactor);
}
void AudioSource::Play()
@@ -121,19 +121,26 @@ void AudioSource::Play()
auto state = _state;
if (state == States::Playing)
return;
if (Clip == nullptr)
if (Clip == nullptr || Clip->WaitForLoaded())
{
LOG(Warning, "Cannot play audio source without a clip ({0})", GetNamePath());
return;
}
if (SourceID == 0)
{
// Create audio source
SourceID = AudioBackend::Source::Add(Clip->Info(), GetPosition(), GetOrientation(), GetVolume(), GetPitch(), GetPan(), GetIsLooping() && !UseStreaming(), Is3D(), GetAttenuation(), GetMinDistance(), GetDopplerFactor());
if (SourceID == 0)
{
LOG(Warning, "Cannot create audio source ({0})", GetNamePath());
return;
}
}
_state = States::Playing;
_isActuallyPlayingSth = false;
// Don't block scripting if audio is not loaded or has missing streaming data
if (!Clip->IsLoaded())
return;
// Audio clips with disabled streaming are controlled by audio source, otherwise streaming manager will play it
if (Clip->IsStreamable())
{
@@ -152,10 +159,10 @@ void AudioSource::Play()
RequestStreamingBuffersUpdate();
}
}
else if (SourceIDs.HasItems())
else if (SourceID)
{
// Play it right away
SetNonStreamingBuffer();
AudioBackend::Source::SetNonStreamingBuffer(SourceID, Clip->Buffers[0]);
PlayInternal();
}
else
@@ -171,10 +178,9 @@ void AudioSource::Pause()
return;
_state = States::Paused;
if (_isActuallyPlayingSth)
{
AudioBackend::Source::Pause(this);
AudioBackend::Source::Pause(SourceID);
_isActuallyPlayingSth = false;
}
}
@@ -187,23 +193,22 @@ void AudioSource::Stop()
_state = States::Stopped;
_isActuallyPlayingSth = false;
_streamingFirstChunk = 0;
if (SourceIDs.HasItems())
AudioBackend::Source::Stop(this);
if (SourceID)
AudioBackend::Source::Stop(SourceID);
}
float AudioSource::GetTime() const
{
if (_state == States::Stopped || SourceIDs.IsEmpty() || !Clip->IsLoaded())
if (_state == States::Stopped || SourceID == 0 || !Clip->IsLoaded())
return 0.0f;
float time = AudioBackend::Source::GetCurrentBufferTime(this);
float time = AudioBackend::Source::GetCurrentBufferTime(SourceID);
if (UseStreaming())
{
// Apply time offset to the first streaming buffer binded to the source including the already queued buffers
int32 numProcessedBuffers = 0;
AudioBackend::Source::GetProcessedBuffersCount(const_cast<AudioSource*>(this), numProcessedBuffers);
AudioBackend::Source::GetProcessedBuffersCount(SourceID, numProcessedBuffers);
time += Clip->GetBufferStartTime(_streamingFirstChunk + numProcessedBuffers);
}
@@ -235,7 +240,7 @@ void AudioSource::SetTime(float time)
time = relativeTime;
}
AudioBackend::Source::SetCurrentBufferTime(this, time);
AudioBackend::Source::SetCurrentBufferTime(SourceID, time);
// Restore state if was stopped
if (isActuallyPlayingSth)
@@ -259,31 +264,29 @@ void AudioSource::RequestStreamingBuffersUpdate()
_needToUpdateStreamingBuffers = true;
}
void AudioSource::Cleanup()
{
_savedState = GetState();
_savedTime = GetTime();
Stop();
if (SourceIDs.HasItems())
{
AudioBackend::Source::Cleanup(this);
SourceIDs.Clear();
}
}
void AudioSource::OnClipChanged()
{
Stop();
_clipChanged = true;
// Destroy current source (will be created on the next play), because clip might use different spatial options or audio data format
if (SourceID)
{
AudioBackend::Source::Remove(SourceID);
SourceID = 0;
}
}
void AudioSource::OnClipLoaded()
{
AudioBackend::Source::ClipLoaded(this);
if (!SourceID)
return;
// Reset spatial and playback
AudioBackend::Source::IsLoopingChanged(SourceID, _loop && !UseStreaming());
AudioBackend::Source::SpatialSetupChanged(SourceID, Is3D(), _attenuation, _minDistance, _dopplerFactor);
// Start playing if source was waiting for the clip to load
if (SourceIDs.HasItems() && _state == States::Playing && !_isActuallyPlayingSth)
if (_state == States::Playing && !_isActuallyPlayingSth)
{
if (Clip->IsStreamable())
{
@@ -293,7 +296,7 @@ void AudioSource::OnClipLoaded()
else
{
// Play it right away
SetNonStreamingBuffer();
AudioBackend::Source::SetNonStreamingBuffer(SourceID, Clip->Buffers[0]);
PlayInternal();
}
}
@@ -301,42 +304,14 @@ void AudioSource::OnClipLoaded()
bool AudioSource::UseStreaming() const
{
return Clip && Clip->IsLoaded() && Clip->IsStreamable();
}
void AudioSource::Restore()
{
if (Clip)
{
if (_savedState != States::Stopped)
Play();
if (_savedState == States::Paused)
Pause();
SetTime(_savedTime);
if (_savedState != States::Stopped && UseStreaming())
RequestStreamingBuffersUpdate();
}
}
void AudioSource::SetNonStreamingBuffer()
{
ASSERT(Clip && !Clip->IsStreamable());
AudioBackend::Source::SetNonStreamingBuffer(this);
if (Clip == nullptr || Clip->WaitForLoaded())
return false;
return Clip->IsStreamable();
}
void AudioSource::PlayInternal()
{
if (_clipChanged && SourceIDs.HasItems())
{
// If clip was changed between source setup (OnEnable) and actual playback start then ensure to flush any runtime properties with the audio backend
_clipChanged = false;
AudioBackend::Source::SpatialSetupChanged(this);
}
AudioBackend::Source::Play(this);
AudioBackend::Source::Play(SourceID);
_isActuallyPlayingSth = true;
_startingToPlay = true;
}
@@ -415,9 +390,9 @@ void AudioSource::Update()
const auto prevVelocity = _velocity;
_velocity = (pos - _prevPos) / dt;
_prevPos = pos;
if (_velocity != prevVelocity)
if (_velocity != prevVelocity && Is3D())
{
AudioBackend::Source::VelocityChanged(this);
AudioBackend::Source::VelocityChanged(SourceID, _velocity);
}
// Reset starting to play value once time is greater than zero
@@ -429,7 +404,7 @@ void AudioSource::Update()
if (!UseStreaming() && Math::NearEqual(GetTime(), 0.0f) && _isActuallyPlayingSth && !_startingToPlay)
{
int32 queuedBuffers;
AudioBackend::Source::GetQueuedBuffersCount(this, queuedBuffers);
AudioBackend::Source::GetQueuedBuffersCount(SourceID, queuedBuffers);
if (queuedBuffers)
{
if (GetIsLooping())
@@ -445,27 +420,27 @@ void AudioSource::Update()
}
// Skip other update logic if it's not valid streamable source
if (!UseStreaming() || SourceIDs.IsEmpty())
if (!UseStreaming() || SourceID == 0)
return;
auto clip = Clip.Get();
clip->Locker.Lock();
// Handle streaming buffers queue submit (ensure that clip has loaded the first chunk with audio data)
if (_needToUpdateStreamingBuffers && clip->Buffers[_streamingFirstChunk] != AUDIO_BUFFER_ID_INVALID)
if (_needToUpdateStreamingBuffers && clip->Buffers[_streamingFirstChunk] != 0)
{
// Get buffers in a queue count
int32 numQueuedBuffers;
AudioBackend::Source::GetQueuedBuffersCount(this, numQueuedBuffers);
AudioBackend::Source::GetQueuedBuffersCount(SourceID, numQueuedBuffers);
// Queue missing buffers
uint32 bufferId;
if (numQueuedBuffers < 1 && (bufferId = clip->Buffers[_streamingFirstChunk]) != AUDIO_BUFFER_ID_INVALID)
uint32 bufferID;
if (numQueuedBuffers < 1 && (bufferID = clip->Buffers[_streamingFirstChunk]) != 0)
{
AudioBackend::Source::QueueBuffer(this, bufferId);
AudioBackend::Source::QueueBuffer(SourceID, bufferID);
}
if (numQueuedBuffers < 2 && _streamingFirstChunk + 1 < clip->Buffers.Count() && (bufferId = clip->Buffers[_streamingFirstChunk + 1]) != AUDIO_BUFFER_ID_INVALID)
if (numQueuedBuffers < 2 && _streamingFirstChunk + 1 < clip->Buffers.Count() && (bufferID = clip->Buffers[_streamingFirstChunk + 1]) != 0)
{
AudioBackend::Source::QueueBuffer(this, bufferId);
AudioBackend::Source::QueueBuffer(SourceID, bufferID);
}
// Clear flag
@@ -483,13 +458,13 @@ void AudioSource::Update()
{
// Get the processed buffers count
int32 numProcessedBuffers = 0;
AudioBackend::Source::GetProcessedBuffersCount(this, numProcessedBuffers);
AudioBackend::Source::GetProcessedBuffersCount(SourceID, numProcessedBuffers);
if (numProcessedBuffers > 0)
{
ASSERT(numProcessedBuffers <= ASSET_FILE_DATA_CHUNKS);
// Unbind processed buffers from the source
AudioBackend::Source::DequeueProcessedBuffers(this);
AudioBackend::Source::DequeueProcessedBuffers(SourceID);
// Move the chunk pointer (AudioStreamingHandler will request new chunks streaming)
_streamingFirstChunk += numProcessedBuffers;
@@ -526,27 +501,53 @@ void AudioSource::OnEnable()
{
_prevPos = GetPosition();
_velocity = Vector3::Zero;
_clipChanged = false;
Audio::OnAddSource(this);
// Add source
ASSERT_LOW_LAYER(!Audio::Sources.Contains(this));
Audio::Sources.Add(this);
GetScene()->Ticking.Update.AddTick<AudioSource, &AudioSource::Update>(this);
#if USE_EDITOR
GetSceneRendering()->AddViewportIcon(this);
#endif
// Restore playback state
if (Clip)
{
if (_savedState != States::Stopped)
Play();
if (_savedState == States::Paused)
Pause();
SetTime(_savedTime);
if (_savedState != States::Stopped && UseStreaming())
RequestStreamingBuffersUpdate();
}
// Base
Actor::OnEnable();
}
void AudioSource::OnDisable()
{
// Cache playback state
_savedState = GetState();
_savedTime = GetTime();
// End playback
Stop();
// Remove source
#if USE_EDITOR
GetSceneRendering()->RemoveViewportIcon(this);
#endif
GetScene()->Ticking.Update.RemoveTick(this);
Audio::OnRemoveSource(this);
if (SourceID)
{
AudioBackend::Source::Remove(SourceID);
SourceID = 0;
}
Audio::Sources.Remove(this);
// Base
Actor::OnDisable();
@@ -560,9 +561,9 @@ void AudioSource::OnTransformChanged()
_box = BoundingBox(_transform.Translation);
_sphere = BoundingSphere(_transform.Translation, 0.0f);
if (IsActiveInHierarchy() && SourceIDs.HasItems())
if (IsActiveInHierarchy() && SourceID && Is3D())
{
AudioBackend::Source::TransformChanged(this);
AudioBackend::Source::TransformChanged(SourceID, _transform.Translation, _transform.Orientation);
}
}

View File

@@ -5,10 +5,9 @@
#include "Engine/Level/Actor.h"
#include "Engine/Content/AssetReference.h"
#include "AudioClip.h"
#include "Config.h"
/// <summary>
/// Represents a source for emitting audio. Audio can be played spatially (gun shot), or normally (music). Each audio source must have an AudioClip to play - back, and it can also have a position in the case of spatial(3D) audio.
/// Represents a source for emitting audio. Audio can be played spatially (gun shot), or normally (music). Each audio source must have an AudioClip to play - back, and it can also have a position in the case of spatial (3D) audio.
/// </summary>
/// <remarks>
/// Whether or not an audio source is spatial is controlled by the assigned AudioClip.The volume and the pitch of a spatial audio source is controlled by its position and the AudioListener's position/direction/velocity.
@@ -19,6 +18,7 @@ class FLAXENGINE_API AudioSource : public Actor
DECLARE_SCENE_OBJECT(AudioSource);
friend class AudioStreamingHandler;
friend class AudioClip;
public:
/// <summary>
/// Valid states in which AudioSource can be in.
@@ -54,7 +54,6 @@ private:
bool _playOnStart;
float _startTime;
bool _allowSpatialization;
bool _clipChanged = false;
bool _isActuallyPlayingSth = false;
bool _startingToPlay = false;
@@ -67,9 +66,9 @@ private:
public:
/// <summary>
/// The internal IDs of this audio source used by the audio backend (unique ID per context/listener).
/// The internal ID of this audio source used by the audio backend. Empty if 0.
/// </summary>
Array<AUDIO_SOURCE_ID_TYPE, FixedAllocation<AUDIO_MAX_LISTENERS>> SourceIDs;
uint32 SourceID = 0;
/// <summary>
/// The audio clip asset used as a source of the sound.
@@ -142,7 +141,7 @@ public:
API_PROPERTY() void SetIsLooping(bool value);
/// <summary>
/// Determines whether the audio clip should auto play on level start.
/// Determines whether the audio clip should autoplay on level start.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(50), DefaultValue(false), EditorDisplay(\"Audio Source\", \"Play On Start\")")
FORCE_INLINE bool GetPlayOnStart() const
@@ -160,7 +159,7 @@ public:
}
/// <summary>
/// Determines whether the audio clip should auto play on game start.
/// Determines whether the audio clip should autoplay on game start.
/// </summary>
API_PROPERTY() void SetPlayOnStart(bool value);
@@ -212,7 +211,7 @@ public:
API_PROPERTY() void SetDopplerFactor(float value);
/// <summary>
/// If checked, source can play spatial 3d audio (when audio clip supports it), otherwise will always play as 2d sound. At 0, no distance attenuation ever occurs.
/// If checked, source can play spatial 3d audio (when audio clip supports it), otherwise will always play as 2d sound.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(80), DefaultValue(true), EditorDisplay(\"Audio Source\")")
FORCE_INLINE bool GetAllowSpatialization() const
@@ -261,7 +260,7 @@ public:
API_PROPERTY() void SetTime(float time);
/// <summary>
/// Returns true if the sound source is three dimensional (volume and pitch varies based on listener distance and velocity).
/// Returns true if the sound source is three-dimensional (volume and pitch varies based on listener distance and velocity).
/// </summary>
API_PROPERTY() bool Is3D() const;
@@ -270,17 +269,12 @@ public:
/// </summary>
API_PROPERTY() bool UseStreaming() const;
/// <summary>
/// Restores the saved time position and resumes/pauses the playback based on the state before. Used to restore audio source state after data rebuild (eg. by audio backend).
/// </summary>
void Restore();
public:
/// <summary>
/// Determines whether this audio source started playing audio via audio backend. After audio play it may wait for audio clip data to be loaded or streamed.
/// [Deprecated in v1.9]
/// </summary>
API_PROPERTY() DEPRECATED FORCE_INLINE bool IsActuallyPlayingSth() const
API_PROPERTY() DEPRECATED("Use IsActuallyPlaying instead.") FORCE_INLINE bool IsActuallyPlayingSth() const
{
return _isActuallyPlayingSth;
}
@@ -298,20 +292,10 @@ public:
/// </summary>
void RequestStreamingBuffersUpdate();
/// <summary>
/// Cleanups the cached data. Called by the Audio manager.
/// </summary>
void Cleanup();
private:
void OnClipChanged();
void OnClipLoaded();
/// <summary>
/// Sets the single buffer from the audio clip that is not using dynamic streaming
/// </summary>
void SetNonStreamingBuffer();
/// <summary>
/// Plays the audio source. Should have buffer(s) binded before.
/// </summary>

View File

@@ -3,18 +3,10 @@
#pragma once
#include "Engine/Core/Config.h"
#include "Engine/Content/Config.h"
// The maximum amount of listeners used at once
#define AUDIO_MAX_LISTENERS 8
#define AUDIO_MAX_LISTENERS 1
// The maximum amount of audio emitter buffers
#define AUDIO_MAX_SOURCE_BUFFERS (ASSET_FILE_DATA_CHUNKS)
// The type of the audio source IDs used to identify it (per listener)
#define AUDIO_SOURCE_ID_TYPE uint32
// The type of the audio buffer IDs used to identify it
#define AUDIO_BUFFER_ID_TYPE uint32
// The buffer ID that is invalid (unused)
#define AUDIO_BUFFER_ID_INVALID 0

View File

@@ -6,19 +6,15 @@
#include "Engine/Audio/Audio.h"
#include "Engine/Audio/AudioSource.h"
void AudioBackendNone::Listener_OnAdd(AudioListener* listener)
void AudioBackendNone::Listener_Reset()
{
}
void AudioBackendNone::Listener_OnRemove(AudioListener* listener)
void AudioBackendNone::Listener_VelocityChanged(const Vector3& velocity)
{
}
void AudioBackendNone::Listener_VelocityChanged(AudioListener* listener)
{
}
void AudioBackendNone::Listener_TransformChanged(AudioListener* listener)
void AudioBackendNone::Listener_TransformChanged(const Vector3& position, const Quaternion& orientation)
{
}
@@ -26,91 +22,82 @@ void AudioBackendNone::Listener_ReinitializeAll()
{
}
void AudioBackendNone::Source_OnAdd(AudioSource* source)
uint32 AudioBackendNone::Source_Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler)
{
source->Restore();
return 1;
}
void AudioBackendNone::Source_OnRemove(AudioSource* source)
{
source->Cleanup();
}
void AudioBackendNone::Source_VelocityChanged(AudioSource* source)
void AudioBackendNone::Source_Remove(uint32 sourceID)
{
}
void AudioBackendNone::Source_TransformChanged(AudioSource* source)
void AudioBackendNone::Source_VelocityChanged(uint32 sourceID, const Vector3& velocity)
{
}
void AudioBackendNone::Source_VolumeChanged(AudioSource* source)
void AudioBackendNone::Source_TransformChanged(uint32 sourceID, const Vector3& position, const Quaternion& orientation)
{
}
void AudioBackendNone::Source_PitchChanged(AudioSource* source)
void AudioBackendNone::Source_VolumeChanged(uint32 sourceID, float volume)
{
}
void AudioBackendNone::Source_PanChanged(AudioSource* source)
void AudioBackendNone::Source_PitchChanged(uint32 sourceID, float pitch)
{
}
void AudioBackendNone::Source_IsLoopingChanged(AudioSource* source)
void AudioBackendNone::Source_PanChanged(uint32 sourceID, float pan)
{
}
void AudioBackendNone::Source_SpatialSetupChanged(AudioSource* source)
void AudioBackendNone::Source_IsLoopingChanged(uint32 sourceID, bool loop)
{
}
void AudioBackendNone::Source_ClipLoaded(AudioSource* source)
void AudioBackendNone::Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler)
{
}
void AudioBackendNone::Source_Cleanup(AudioSource* source)
void AudioBackendNone::Source_Play(uint32 sourceID)
{
}
void AudioBackendNone::Source_Play(AudioSource* source)
void AudioBackendNone::Source_Pause(uint32 sourceID)
{
}
void AudioBackendNone::Source_Pause(AudioSource* source)
void AudioBackendNone::Source_Stop(uint32 sourceID)
{
}
void AudioBackendNone::Source_Stop(AudioSource* source)
void AudioBackendNone::Source_SetCurrentBufferTime(uint32 sourceID, float value)
{
}
void AudioBackendNone::Source_SetCurrentBufferTime(AudioSource* source, float value)
{
}
float AudioBackendNone::Source_GetCurrentBufferTime(const AudioSource* source)
float AudioBackendNone::Source_GetCurrentBufferTime(uint32 sourceID)
{
return 0.0f;
}
void AudioBackendNone::Source_SetNonStreamingBuffer(AudioSource* source)
void AudioBackendNone::Source_SetNonStreamingBuffer(uint32 sourceID, uint32 bufferID)
{
}
void AudioBackendNone::Source_GetProcessedBuffersCount(AudioSource* source, int32& processedBuffersCount)
void AudioBackendNone::Source_GetProcessedBuffersCount(uint32 sourceID, int32& processedBuffersCount)
{
processedBuffersCount = 0;
}
void AudioBackendNone::Source_GetQueuedBuffersCount(AudioSource* source, int32& queuedBuffersCount)
void AudioBackendNone::Source_GetQueuedBuffersCount(uint32 sourceID, int32& queuedBuffersCount)
{
}
void AudioBackendNone::Source_QueueBuffer(AudioSource* source, uint32 bufferId)
void AudioBackendNone::Source_QueueBuffer(uint32 sourceID, uint32 bufferID)
{
}
void AudioBackendNone::Source_DequeueProcessedBuffers(AudioSource* source)
void AudioBackendNone::Source_DequeueProcessedBuffers(uint32 sourceID)
{
}
@@ -119,11 +106,11 @@ uint32 AudioBackendNone::Buffer_Create()
return 1;
}
void AudioBackendNone::Buffer_Delete(uint32 bufferId)
void AudioBackendNone::Buffer_Delete(uint32 bufferID)
{
}
void AudioBackendNone::Buffer_Write(uint32 bufferId, byte* samples, const AudioDataInfo& info)
void AudioBackendNone::Buffer_Write(uint32 bufferID, byte* samples, const AudioDataInfo& info)
{
}

View File

@@ -12,37 +12,33 @@
class AudioBackendNone : public AudioBackend
{
public:
// [AudioBackend]
void Listener_OnAdd(AudioListener* listener) override;
void Listener_OnRemove(AudioListener* listener) override;
void Listener_VelocityChanged(AudioListener* listener) override;
void Listener_TransformChanged(AudioListener* listener) override;
void Listener_Reset() override;
void Listener_VelocityChanged(const Vector3& velocity) override;
void Listener_TransformChanged(const Vector3& position, const Quaternion& orientation) override;
void Listener_ReinitializeAll() override;
void Source_OnAdd(AudioSource* source) override;
void Source_OnRemove(AudioSource* source) override;
void Source_VelocityChanged(AudioSource* source) override;
void Source_TransformChanged(AudioSource* source) override;
void Source_VolumeChanged(AudioSource* source) override;
void Source_PitchChanged(AudioSource* source) override;
void Source_PanChanged(AudioSource* source) override;
void Source_IsLoopingChanged(AudioSource* source) override;
void Source_SpatialSetupChanged(AudioSource* source) override;
void Source_ClipLoaded(AudioSource* source) override;
void Source_Cleanup(AudioSource* source) override;
void Source_Play(AudioSource* source) override;
void Source_Pause(AudioSource* source) override;
void Source_Stop(AudioSource* source) override;
void Source_SetCurrentBufferTime(AudioSource* source, float value) override;
float Source_GetCurrentBufferTime(const AudioSource* source) override;
void Source_SetNonStreamingBuffer(AudioSource* source) override;
void Source_GetProcessedBuffersCount(AudioSource* source, int32& processedBuffersCount) override;
void Source_GetQueuedBuffersCount(AudioSource* source, int32& queuedBuffersCount) override;
void Source_QueueBuffer(AudioSource* source, uint32 bufferId) override;
void Source_DequeueProcessedBuffers(AudioSource* source) override;
uint32 Source_Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler) override;
void Source_Remove(uint32 sourceID) override;
void Source_VelocityChanged(uint32 sourceID, const Vector3& velocity) override;
void Source_TransformChanged(uint32 sourceID, const Vector3& position, const Quaternion& orientation) override;
void Source_VolumeChanged(uint32 sourceID, float volume) override;
void Source_PitchChanged(uint32 sourceID, float pitch) override;
void Source_PanChanged(uint32 sourceID, float pan) override;
void Source_IsLoopingChanged(uint32 sourceID, bool loop) override;
void Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler) override;
void Source_Play(uint32 sourceID) override;
void Source_Pause(uint32 sourceID) override;
void Source_Stop(uint32 sourceID) override;
void Source_SetCurrentBufferTime(uint32 sourceID, float value) override;
float Source_GetCurrentBufferTime(uint32 sourceID) override;
void Source_SetNonStreamingBuffer(uint32 sourceID, uint32 bufferID) override;
void Source_GetProcessedBuffersCount(uint32 sourceID, int32& processedBuffersCount) override;
void Source_GetQueuedBuffersCount(uint32 sourceID, int32& queuedBuffersCount) override;
void Source_QueueBuffer(uint32 sourceID, uint32 bufferID) override;
void Source_DequeueProcessedBuffers(uint32 sourceID) override;
uint32 Buffer_Create() override;
void Buffer_Delete(uint32 bufferId) override;
void Buffer_Write(uint32 bufferId, byte* samples, const AudioDataInfo& info) override;
void Buffer_Delete(uint32 bufferID) override;
void Buffer_Write(uint32 bufferID, byte* samples, const AudioDataInfo& info) override;
const Char* Base_Name() override;
FeatureFlags Base_Features() override;
void Base_OnActiveDeviceChanged() override;

View File

@@ -5,7 +5,9 @@
#include "AudioBackendOAL.h"
#include "Engine/Platform/StringUtils.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Tools/AudioTool/AudioTool.h"
#include "Engine/Engine/Units.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Audio/Audio.h"
#include "Engine/Audio/AudioListener.h"
@@ -19,12 +21,9 @@
#include <OpenAL/alc.h>
#include <OpenAL/alext.h>
#define ALC_MULTIPLE_LISTENERS 0
#define FLAX_COORD_SCALE 0.01f // units are meters
#define FLAX_DST_TO_OAL(x) x * FLAX_COORD_SCALE
#define FLAX_POS_TO_OAL(vec) ((ALfloat)vec.X * -FLAX_COORD_SCALE), ((ALfloat)vec.Y * FLAX_COORD_SCALE), ((ALfloat)vec.Z * FLAX_COORD_SCALE)
#define FLAX_VEL_TO_OAL(vec) ((ALfloat)vec.X * -(FLAX_COORD_SCALE*FLAX_COORD_SCALE)), ((ALfloat)vec.Y * (FLAX_COORD_SCALE*FLAX_COORD_SCALE)), ((ALfloat)vec.Z * (FLAX_COORD_SCALE*FLAX_COORD_SCALE))
#define FLAX_DST_TO_OAL(x) x * UNITS_TO_METERS_SCALE
#define FLAX_POS_TO_OAL(vec) ((ALfloat)vec.X * -UNITS_TO_METERS_SCALE), ((ALfloat)vec.Y * UNITS_TO_METERS_SCALE), ((ALfloat)vec.Z * UNITS_TO_METERS_SCALE)
#define FLAX_VEL_TO_OAL(vec) ((ALfloat)vec.X * -(UNITS_TO_METERS_SCALE*UNITS_TO_METERS_SCALE)), ((ALfloat)vec.Y * (UNITS_TO_METERS_SCALE*UNITS_TO_METERS_SCALE)), ((ALfloat)vec.Z * (UNITS_TO_METERS_SCALE*UNITS_TO_METERS_SCALE))
#if BUILD_RELEASE
#define ALC_CHECK_ERROR(method)
#else
@@ -39,155 +38,96 @@
}
#endif
#if ALC_MULTIPLE_LISTENERS
#define ALC_FOR_EACH_CONTEXT() \
for (int32 i = 0; i < Contexts.Count(); i++)
{ \
if (Contexts.Count() > 1) \
alcMakeContextCurrent(Contexts[i]);
#define ALC_GET_DEFAULT_CONTEXT() \
if (Contexts.Count() > 1) \
alcMakeContextCurrent(Contexts[0]);
#define ALC_GET_LISTENER_CONTEXT(listener) \
if (Contexts.Count() > 1) \
alcMakeContextCurrent(ALC::GetContext(listener)));
#else
#define ALC_FOR_EACH_CONTEXT() { int32 i = 0;
#define ALC_GET_DEFAULT_CONTEXT()
#define ALC_GET_LISTENER_CONTEXT(listener)
#endif
namespace ALC
{
ALCdevice* Device = nullptr;
Array<ALCcontext*, FixedAllocation<AUDIO_MAX_LISTENERS>> Contexts;
ALCcontext* Context = nullptr;
AudioBackend::FeatureFlags Features = AudioBackend::FeatureFlags::None;
CriticalSection Locker;
Dictionary<uint32, AudioDataInfo> SourceIDtoFormat;
bool IsExtensionSupported(const char* extension)
{
if (Device == nullptr)
return false;
const int32 length = StringUtils::Length(extension);
if ((length > 2) && (StringUtils::Compare(extension, "ALC", 3) == 0))
return alcIsExtensionPresent(Device, extension) != AL_FALSE;
return alIsExtensionPresent(extension) != AL_FALSE;
}
ALCcontext* GetContext(const class AudioListener* listener)
void ClearContext()
{
#if ALC_MULTIPLE_LISTENERS
const auto& listeners = Audio::Listeners;
if (listeners.HasItems())
if (Context)
{
ASSERT(listeners.Count() == Contexts.Count());
const int32 numContexts = Contexts.Count();
ALC_FOR_EACH_CONTEXT()
{
if (listeners[i] == listener)
return Contexts[i];
}
alcMakeContextCurrent(nullptr);
alcDestroyContext(Context);
Context = nullptr;
}
ASSERT(Contexts.HasItems());
#else
ASSERT(Contexts.Count() == 1);
#endif
return Contexts[0];
}
FORCE_INLINE const Array<ALCcontext*, FixedAllocation<AUDIO_MAX_LISTENERS>>& GetContexts()
{
return Contexts;
}
void ClearContexts()
{
alcMakeContextCurrent(nullptr);
for (ALCcontext* context : Contexts)
alcDestroyContext(context);
Contexts.Clear();
}
namespace Listener
{
void Rebuild(AudioListener* listener)
void Rebuild(const AudioListener* listener)
{
AudioBackend::Listener::TransformChanged(listener);
const Float3 velocity = listener->GetVelocity();
alListener3f(AL_VELOCITY, FLAX_VEL_TO_OAL(velocity));
alListenerf(AL_GAIN, Audio::GetVolume());
AudioBackend::Listener::Reset();
AudioBackend::Listener::TransformChanged(listener->GetPosition(), listener->GetOrientation());
AudioBackend::Listener::VelocityChanged(listener->GetVelocity());
}
}
namespace Source
{
void Rebuild(AudioSource* source)
void Rebuild(uint32& sourceID, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler)
{
ASSERT(source->SourceIDs.IsEmpty());
const bool is3D = source->Is3D();
const bool loop = source->GetIsLooping() && !source->UseStreaming();
ASSERT_LOW_LAYER(sourceID == 0);
alGenSources(1, &sourceID);
ASSERT_LOW_LAYER(sourceID != 0);
ALC_FOR_EACH_CONTEXT()
uint32 sourceID = 0;
alGenSources(1, &sourceID);
source->SourceIDs.Add(sourceID);
}
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSourcef(sourceID, AL_GAIN, source->GetVolume());
alSourcef(sourceID, AL_PITCH, source->GetPitch());
alSourcef(sourceID, AL_SEC_OFFSET, 0.0f);
alSourcei(sourceID, AL_LOOPING, loop);
alSourcei(sourceID, AL_SOURCE_RELATIVE, !is3D);
alSourcei(sourceID, AL_BUFFER, 0);
if (is3D)
{
alSourcef(sourceID, AL_GAIN, volume);
alSourcef(sourceID, AL_PITCH, pitch);
alSourcef(sourceID, AL_SEC_OFFSET, 0.0f);
alSourcei(sourceID, AL_LOOPING, loop);
alSourcei(sourceID, AL_SOURCE_RELATIVE, !spatial);
alSourcei(sourceID, AL_BUFFER, 0);
if (spatial)
{
#ifdef AL_SOFT_source_spatialize
alSourcei(sourceID, AL_SOURCE_SPATIALIZE_SOFT, AL_TRUE);
#endif
alSourcef(sourceID, AL_ROLLOFF_FACTOR, source->GetAttenuation());
alSourcef(sourceID, AL_DOPPLER_FACTOR, source->GetDopplerFactor());
alSourcef(sourceID, AL_REFERENCE_DISTANCE, FLAX_DST_TO_OAL(source->GetMinDistance()));
alSource3f(sourceID, AL_POSITION, FLAX_POS_TO_OAL(source->GetPosition()));
alSource3f(sourceID, AL_VELOCITY, FLAX_VEL_TO_OAL(source->GetVelocity()));
}
else
{
alSourcef(sourceID, AL_ROLLOFF_FACTOR, 0.0f);
alSourcef(sourceID, AL_DOPPLER_FACTOR, 1.0f);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, 0.0f);
alSource3f(sourceID, AL_POSITION, 0.0f, 0.0f, 0.0f);
alSource3f(sourceID, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
}
#ifdef AL_EXT_STEREO_ANGLES
const float panAngle = source->GetPan() * PI_HALF;
const ALfloat panAngles[2] = { (ALfloat)(PI / 6.0 - panAngle), (ALfloat)(-PI / 6.0 - panAngle) }; // Angles are specified counter-clockwise in radians
alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles);
alSourcei(sourceID, AL_SOURCE_SPATIALIZE_SOFT, AL_TRUE);
#endif
alSourcef(sourceID, AL_ROLLOFF_FACTOR, attenuation);
alSourcef(sourceID, AL_DOPPLER_FACTOR, doppler);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, FLAX_DST_TO_OAL(minDistance));
alSource3f(sourceID, AL_POSITION, FLAX_POS_TO_OAL(position));
alSource3f(sourceID, AL_VELOCITY, FLAX_VEL_TO_OAL(Vector3::Zero));
}
// Restore state after Cleanup
source->Restore();
else
{
alSourcef(sourceID, AL_ROLLOFF_FACTOR, 0.0f);
alSourcef(sourceID, AL_DOPPLER_FACTOR, 1.0f);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, 0.0f);
alSource3f(sourceID, AL_POSITION, 0.0f, 0.0f, 0.0f);
alSource3f(sourceID, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
}
#ifdef AL_EXT_STEREO_ANGLES
const float panAngle = pan * PI_HALF;
const ALfloat panAngles[2] = { (ALfloat)(PI / 6.0 - panAngle), (ALfloat)(-PI / 6.0 - panAngle) }; // Angles are specified counter-clockwise in radians
alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles);
#endif
}
}
void RebuildContexts(bool isChangingDevice)
struct AudioSourceState
{
AudioSource::States State;
float Time;
};
void RebuildContext(const Array<AudioSourceState>& states)
{
LOG(Info, "Rebuilding audio contexts");
if (!isChangingDevice)
{
for (AudioSource* source : Audio::Sources)
source->Cleanup();
}
ClearContexts();
ClearContext();
if (Device == nullptr)
return;
@@ -200,29 +140,45 @@ namespace ALC
attrList = attrsHrtf;
}
#if ALC_MULTIPLE_LISTENERS
const int32 numListeners = Audio::Listeners.Count();
const int32 numContexts = numListeners > 1 ? numListeners : 1;
Contexts.Resize(numContexts);
ALC_FOR_EACH_CONTEXT()
ALCcontext* context = alcCreateContext(Device, attrList);
Contexts[i] = context;
}
#else
Contexts.Resize(1);
Contexts[0] = alcCreateContext(Device, attrList);
#endif
// If only one context is available keep it active as an optimization.
// Audio listeners and sources will avoid excessive context switching in such case.
alcMakeContextCurrent(Contexts[0]);
Context = alcCreateContext(Device, attrList);
alcMakeContextCurrent(Context);
for (AudioListener* listener : Audio::Listeners)
Listener::Rebuild(listener);
for (AudioSource* source : Audio::Sources)
Source::Rebuild(source);
for (int32 i = 0; i < states.Count(); i++)
{
AudioSource* source = Audio::Sources[i];
Source::Rebuild(source->SourceID, source->GetPosition(), source->GetOrientation(), source->GetVolume(), source->GetPitch(), source->GetPan(), source->GetIsLooping() && !source->UseStreaming(), source->Is3D(), source->GetAttenuation(), source->GetMinDistance(), source->GetDopplerFactor());
if (states.HasItems())
{
// Restore playback state
auto& state = states[i];
if (state.State != AudioSource::States::Stopped)
source->Play();
if (state.State == AudioSource::States::Paused)
source->Pause();
if (state.State != AudioSource::States::Stopped)
source->SetTime(state.Time);
}
}
}
void RebuildContext(bool isChangingDevice)
{
Array<AudioSourceState> states;
if (!isChangingDevice)
{
states.EnsureCapacity(Audio::Sources.Count());
for (AudioSource* source : Audio::Sources)
{
states.Add({ source->GetState(), source->GetTime() });
source->Stop();
}
}
RebuildContext(states);
}
}
@@ -313,308 +269,212 @@ const Char* GetOpenALErrorString(int error)
return TEXT("???");
}
void AudioBackendOAL::Listener_OnAdd(AudioListener* listener)
void AudioBackendOAL::Listener_Reset()
{
#if ALC_MULTIPLE_LISTENERS
ALC::RebuildContexts(false);
#else
AudioBackend::Listener::TransformChanged(listener);
alListenerf(AL_GAIN, Audio::GetVolume());
#endif
}
void AudioBackendOAL::Listener_OnRemove(AudioListener* listener)
void AudioBackendOAL::Listener_VelocityChanged(const Vector3& velocity)
{
#if ALC_MULTIPLE_LISTENERS
ALC::RebuildContexts(false);
#endif
}
void AudioBackendOAL::Listener_VelocityChanged(AudioListener* listener)
{
ALC_GET_LISTENER_CONTEXT(listener)
const Float3 velocity = listener->GetVelocity();
alListener3f(AL_VELOCITY, FLAX_VEL_TO_OAL(velocity));
}
void AudioBackendOAL::Listener_TransformChanged(AudioListener* listener)
void AudioBackendOAL::Listener_TransformChanged(const Vector3& position, const Quaternion& orientation)
{
ALC_GET_LISTENER_CONTEXT(listener)
const Float3 position = listener->GetPosition();
const Quaternion orientation = listener->GetOrientation();
const Float3 flipX(-1, 1, 1);
const Float3 alOrientation[2] =
{
// Forward
orientation * Float3::Forward * flipX,
// Up
orientation * Float3::Up * flipX
};
alListenerfv(AL_ORIENTATION, (float*)alOrientation);
alListener3f(AL_POSITION, FLAX_POS_TO_OAL(position));
}
void AudioBackendOAL::Listener_ReinitializeAll()
{
ALC::RebuildContexts(false);
ALC::RebuildContext(false);
}
void AudioBackendOAL::Source_OnAdd(AudioSource* source)
uint32 AudioBackendOAL::Source_Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler)
{
ALC::Source::Rebuild(source);
uint32 sourceID = 0;
ALC::Source::Rebuild(sourceID, position, orientation, volume, pitch, pan, loop, spatial, attenuation, minDistance, doppler);
// Cache audio data format assigned on source (used in Source_GetCurrentBufferTime)
ALC::Locker.Lock();
ALC::SourceIDtoFormat[sourceID] = format;
ALC::Locker.Unlock();
return sourceID;
}
void AudioBackendOAL::Source_OnRemove(AudioSource* source)
void AudioBackendOAL::Source_Remove(uint32 sourceID)
{
source->Cleanup();
alSourcei(sourceID, AL_BUFFER, 0);
ALC_CHECK_ERROR(alSourcei);
alDeleteSources(1, &sourceID);
ALC_CHECK_ERROR(alDeleteSources);
ALC::Locker.Lock();
ALC::SourceIDtoFormat.Remove(sourceID);
ALC::Locker.Unlock();
}
void AudioBackendOAL::Source_VelocityChanged(AudioSource* source)
void AudioBackendOAL::Source_VelocityChanged(uint32 sourceID, const Vector3& velocity)
{
if (!source->Is3D())
return;
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSource3f(sourceID, AL_VELOCITY, FLAX_VEL_TO_OAL(source->GetVelocity()));
}
alSource3f(sourceID, AL_VELOCITY, FLAX_VEL_TO_OAL(velocity));
}
void AudioBackendOAL::Source_TransformChanged(AudioSource* source)
void AudioBackendOAL::Source_TransformChanged(uint32 sourceID, const Vector3& position, const Quaternion& orientation)
{
if (!source->Is3D())
return;
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSource3f(sourceID, AL_POSITION, FLAX_POS_TO_OAL(source->GetPosition()));
}
alSource3f(sourceID, AL_POSITION, FLAX_POS_TO_OAL(position));
}
void AudioBackendOAL::Source_VolumeChanged(AudioSource* source)
void AudioBackendOAL::Source_VolumeChanged(uint32 sourceID, float volume)
{
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSourcef(sourceID, AL_GAIN, source->GetVolume());
}
alSourcef(sourceID, AL_GAIN, volume);
}
void AudioBackendOAL::Source_PitchChanged(AudioSource* source)
void AudioBackendOAL::Source_PitchChanged(uint32 sourceID, float pitch)
{
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSourcef(sourceID, AL_PITCH, source->GetPitch());
}
alSourcef(sourceID, AL_PITCH, pitch);
}
void AudioBackendOAL::Source_PanChanged(AudioSource* source)
void AudioBackendOAL::Source_PanChanged(uint32 sourceID, float pan)
{
#ifdef AL_EXT_STEREO_ANGLES
const float panAngle = source->GetPan() * PI_HALF;
const float panAngle = pan * PI_HALF;
const ALfloat panAngles[2] = { (ALfloat)(PI / 6.0 - panAngle), (ALfloat)(-PI / 6.0 - panAngle) }; // Angles are specified counter-clockwise in radians
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles);
}
alSourcefv(sourceID, AL_STEREO_ANGLES, panAngles);
#endif
}
void AudioBackendOAL::Source_IsLoopingChanged(AudioSource* source)
void AudioBackendOAL::Source_IsLoopingChanged(uint32 sourceID, bool loop)
{
const bool loop = source->GetIsLooping() && !source->UseStreaming();
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSourcei(sourceID, AL_LOOPING, loop);
}
alSourcei(sourceID, AL_LOOPING, loop);
}
void AudioBackendOAL::Source_SpatialSetupChanged(AudioSource* source)
void AudioBackendOAL::Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler)
{
const bool is3D = source->Is3D();
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSourcei(sourceID, AL_SOURCE_RELATIVE, !is3D);
if (is3D)
{
alSourcei(sourceID, AL_SOURCE_RELATIVE, !spatial);
if (spatial)
{
#ifdef AL_SOFT_source_spatialize
alSourcei(sourceID, AL_SOURCE_SPATIALIZE_SOFT, AL_TRUE);
alSourcei(sourceID, AL_SOURCE_SPATIALIZE_SOFT, AL_TRUE);
#endif
alSourcef(sourceID, AL_ROLLOFF_FACTOR, source->GetAttenuation());
alSourcef(sourceID, AL_DOPPLER_FACTOR, source->GetDopplerFactor());
alSourcef(sourceID, AL_REFERENCE_DISTANCE, FLAX_DST_TO_OAL(source->GetMinDistance()));
}
else
{
alSourcef(sourceID, AL_ROLLOFF_FACTOR, 0.0f);
alSourcef(sourceID, AL_DOPPLER_FACTOR, 1.0f);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, 0.0f);
}
alSourcef(sourceID, AL_ROLLOFF_FACTOR, attenuation);
alSourcef(sourceID, AL_DOPPLER_FACTOR, doppler);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, FLAX_DST_TO_OAL(minDistance));
}
else
{
alSourcef(sourceID, AL_ROLLOFF_FACTOR, 0.0f);
alSourcef(sourceID, AL_DOPPLER_FACTOR, 1.0f);
alSourcef(sourceID, AL_REFERENCE_DISTANCE, 0.0f);
}
}
void AudioBackendOAL::Source_ClipLoaded(AudioSource* source)
void AudioBackendOAL::Source_Play(uint32 sourceID)
{
if (source->SourceIDs.Count() < ALC::Contexts.Count())
return;
const auto clip = source->Clip.Get();
const bool is3D = source->Is3D();
const bool loop = source->GetIsLooping() && !clip->IsStreamable();
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSourcei(sourceID, AL_SOURCE_RELATIVE, !is3D);
alSourcei(sourceID, AL_LOOPING, loop);
}
alSourcePlay(sourceID);
ALC_CHECK_ERROR(alSourcePlay);
}
void AudioBackendOAL::Source_Cleanup(AudioSource* source)
void AudioBackendOAL::Source_Pause(uint32 sourceID)
{
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSourcei(sourceID, AL_BUFFER, 0);
ALC_CHECK_ERROR(alSourcei);
alDeleteSources(1, &sourceID);
ALC_CHECK_ERROR(alDeleteSources);
}
alSourcePause(sourceID);
ALC_CHECK_ERROR(alSourcePause);
}
void AudioBackendOAL::Source_Play(AudioSource* source)
void AudioBackendOAL::Source_Stop(uint32 sourceID)
{
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
// Stop and rewind
alSourceRewind(sourceID);
ALC_CHECK_ERROR(alSourceRewind);
alSourcef(sourceID, AL_SEC_OFFSET, 0.0f);
// Play
alSourcePlay(sourceID);
ALC_CHECK_ERROR(alSourcePlay);
}
// Unset streaming buffers
alSourcei(sourceID, AL_BUFFER, 0);
ALC_CHECK_ERROR(alSourcei);
}
void AudioBackendOAL::Source_Pause(AudioSource* source)
void AudioBackendOAL::Source_SetCurrentBufferTime(uint32 sourceID, float value)
{
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
// Pause
alSourcePause(sourceID);
ALC_CHECK_ERROR(alSourcePause);
}
alSourcef(sourceID, AL_SEC_OFFSET, value);
}
void AudioBackendOAL::Source_Stop(AudioSource* source)
float AudioBackendOAL::Source_GetCurrentBufferTime(uint32 sourceID)
{
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
// Stop and rewind
alSourceRewind(sourceID);
ALC_CHECK_ERROR(alSourceRewind);
alSourcef(sourceID, AL_SEC_OFFSET, 0.0f);
// Unset streaming buffers
alSourcei(sourceID, AL_BUFFER, 0);
ALC_CHECK_ERROR(alSourcei);
}
}
void AudioBackendOAL::Source_SetCurrentBufferTime(AudioSource* source, float value)
{
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSourcef(sourceID, AL_SEC_OFFSET, value);
}
}
float AudioBackendOAL::Source_GetCurrentBufferTime(const AudioSource* source)
{
ALC_GET_DEFAULT_CONTEXT()
#if 0
float time;
alGetSourcef(source->SourceIDs[0], AL_SEC_OFFSET, &time);
alGetSourcef(sourceID, AL_SEC_OFFSET, &time);
#else
ASSERT(source->Clip && source->Clip->IsLoaded());
const AudioDataInfo& clipInfo = source->Clip->AudioHeader.Info;
ALC::Locker.Lock();
AudioDataInfo clipInfo = ALC::SourceIDtoFormat[sourceID];
ALC::Locker.Unlock();
ALint samplesPlayed;
alGetSourcei(source->SourceIDs[0], AL_SAMPLE_OFFSET, &samplesPlayed);
alGetSourcei(sourceID, AL_SAMPLE_OFFSET, &samplesPlayed);
const uint32 totalSamples = clipInfo.NumSamples / clipInfo.NumChannels;
const float time = (samplesPlayed % totalSamples) / static_cast<float>(Math::Max(1U, clipInfo.SampleRate));
if (totalSamples > 0)
samplesPlayed %= totalSamples;
const float time = samplesPlayed / static_cast<float>(Math::Max(1U, clipInfo.SampleRate));
#endif
return time;
}
void AudioBackendOAL::Source_SetNonStreamingBuffer(AudioSource* source)
void AudioBackendOAL::Source_SetNonStreamingBuffer(uint32 sourceID, uint32 bufferID)
{
const uint32 bufferId = source->Clip->Buffers[0];
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
alSourcei(sourceID, AL_BUFFER, bufferId);
ALC_CHECK_ERROR(alSourcei);
}
alSourcei(sourceID, AL_BUFFER, bufferID);
ALC_CHECK_ERROR(alSourcei);
}
void AudioBackendOAL::Source_GetProcessedBuffersCount(AudioSource* source, int32& processedBuffersCount)
void AudioBackendOAL::Source_GetProcessedBuffersCount(uint32 sourceID, int32& processedBuffersCount)
{
ALC_GET_DEFAULT_CONTEXT()
// Check the first context only
const uint32 sourceID = source->SourceIDs[0];
alGetSourcei(sourceID, AL_BUFFERS_PROCESSED, &processedBuffersCount);
ALC_CHECK_ERROR(alGetSourcei);
}
void AudioBackendOAL::Source_GetQueuedBuffersCount(AudioSource* source, int32& queuedBuffersCount)
void AudioBackendOAL::Source_GetQueuedBuffersCount(uint32 sourceID, int32& queuedBuffersCount)
{
ALC_GET_DEFAULT_CONTEXT()
// Check the first context only
const uint32 sourceID = source->SourceIDs[0];
alGetSourcei(sourceID, AL_BUFFERS_QUEUED, &queuedBuffersCount);
ALC_CHECK_ERROR(alGetSourcei);
}
void AudioBackendOAL::Source_QueueBuffer(AudioSource* source, uint32 bufferId)
void AudioBackendOAL::Source_QueueBuffer(uint32 sourceID, uint32 bufferID)
{
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
// Queue new buffer
alSourceQueueBuffers(sourceID, 1, &bufferId);
ALC_CHECK_ERROR(alSourceQueueBuffers);
}
// Queue new buffer
alSourceQueueBuffers(sourceID, 1, &bufferID);
ALC_CHECK_ERROR(alSourceQueueBuffers);
}
void AudioBackendOAL::Source_DequeueProcessedBuffers(AudioSource* source)
void AudioBackendOAL::Source_DequeueProcessedBuffers(uint32 sourceID)
{
ALuint buffers[AUDIO_MAX_SOURCE_BUFFERS];
ALC_FOR_EACH_CONTEXT()
const uint32 sourceID = source->SourceIDs[i];
int32 numProcessedBuffers;
alGetSourcei(sourceID, AL_BUFFERS_PROCESSED, &numProcessedBuffers);
alSourceUnqueueBuffers(sourceID, numProcessedBuffers, buffers);
ALC_CHECK_ERROR(alSourceUnqueueBuffers);
}
int32 numProcessedBuffers;
alGetSourcei(sourceID, AL_BUFFERS_PROCESSED, &numProcessedBuffers);
Array<ALuint, InlinedAllocation<AUDIO_MAX_SOURCE_BUFFERS>> buffers;
buffers.Resize(numProcessedBuffers);
alSourceUnqueueBuffers(sourceID, numProcessedBuffers, buffers.Get());
ALC_CHECK_ERROR(alSourceUnqueueBuffers);
}
uint32 AudioBackendOAL::Buffer_Create()
{
uint32 bufferId;
alGenBuffers(1, &bufferId);
uint32 bufferID;
alGenBuffers(1, &bufferID);
ALC_CHECK_ERROR(alGenBuffers);
return bufferId;
return bufferID;
}
void AudioBackendOAL::Buffer_Delete(uint32 bufferId)
void AudioBackendOAL::Buffer_Delete(uint32 bufferID)
{
alDeleteBuffers(1, &bufferId);
alDeleteBuffers(1, &bufferID);
ALC_CHECK_ERROR(alDeleteBuffers);
}
void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDataInfo& info)
void AudioBackendOAL::Buffer_Write(uint32 bufferID, byte* samples, const AudioDataInfo& info)
{
PROFILE_CPU();
@@ -633,19 +493,19 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa
AudioTool::ConvertToFloat(samples, info.BitDepth, sampleBufferFloat, info.NumSamples);
format = GetOpenALBufferFormat(info.NumChannels, 32);
alBufferData(bufferId, format, sampleBufferFloat, bufferSize, info.SampleRate);
alBufferData(bufferID, format, sampleBufferFloat, bufferSize, info.SampleRate);
ALC_CHECK_ERROR(alBufferData);
Allocator::Free(sampleBufferFloat);
}
else
{
LOG(Warning, "OpenAL doesn't support bit depth larger than 16. Your audio data will be truncated.");
LOG(Warning, "OpenAL doesn't support bit depth larger than 16. Audio data will be truncated.");
const uint32 bufferSize = info.NumSamples * 2;
byte* sampleBuffer16 = (byte*)Allocator::Allocate(bufferSize);
AudioTool::ConvertBitDepth(samples, info.BitDepth, sampleBuffer16, 16, info.NumSamples);
format = GetOpenALBufferFormat(info.NumChannels, 16);
alBufferData(bufferId, format, sampleBuffer16, bufferSize, info.SampleRate);
alBufferData(bufferID, format, sampleBuffer16, bufferSize, info.SampleRate);
ALC_CHECK_ERROR(alBufferData);
Allocator::Free(sampleBuffer16);
}
@@ -658,13 +518,13 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa
for (uint32 i = 0; i < info.NumSamples; i++)
sampleBuffer[i] = ((int8*)samples)[i] + 128;
alBufferData(bufferId, format, sampleBuffer, bufferSize, info.SampleRate);
alBufferData(bufferID, format, sampleBuffer, bufferSize, info.SampleRate);
ALC_CHECK_ERROR(alBufferData);
Allocator::Free(sampleBuffer);
}
else if (format)
{
alBufferData(bufferId, format, samples, info.NumSamples * (info.BitDepth / 8), info.SampleRate);
alBufferData(bufferID, format, samples, info.NumSamples * (info.BitDepth / 8), info.SampleRate);
ALC_CHECK_ERROR(alBufferData);
}
}
@@ -681,7 +541,7 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa
AudioTool::ConvertBitDepth(samples, info.BitDepth, sampleBuffer32, 32, info.NumSamples);
format = GetOpenALBufferFormat(info.NumChannels, 32);
alBufferData(bufferId, format, sampleBuffer32, bufferSize, info.SampleRate);
alBufferData(bufferID, format, sampleBuffer32, bufferSize, info.SampleRate);
ALC_CHECK_ERROR(alBufferData);
Allocator::Free(sampleBuffer32);
@@ -696,14 +556,14 @@ void AudioBackendOAL::Buffer_Write(uint32 bufferId, byte* samples, const AudioDa
sampleBuffer[i] = ((int8*)samples)[i] + 128;
format = GetOpenALBufferFormat(info.NumChannels, 16);
alBufferData(bufferId, format, sampleBuffer, bufferSize, info.SampleRate);
alBufferData(bufferID, format, sampleBuffer, bufferSize, info.SampleRate);
ALC_CHECK_ERROR(alBufferData);
Allocator::Free(sampleBuffer);
}
else if (format)
{
alBufferData(bufferId, format, samples, info.NumSamples * (info.BitDepth / 8), info.SampleRate);
alBufferData(bufferID, format, samples, info.NumSamples * (info.BitDepth / 8), info.SampleRate);
ALC_CHECK_ERROR(alBufferData);
}
}
@@ -727,9 +587,19 @@ AudioBackend::FeatureFlags AudioBackendOAL::Base_Features()
void AudioBackendOAL::Base_OnActiveDeviceChanged()
{
// Cleanup
Array<ALC::AudioSourceState> states;
states.EnsureCapacity(Audio::Sources.Count());
for (AudioSource* source : Audio::Sources)
source->Cleanup();
ALC::ClearContexts();
{
states.Add({ source->GetState(), source->GetTime() });
source->Stop();
if (source->SourceID)
{
Source_Remove(source->SourceID);
source->SourceID = 0;
}
}
ALC::ClearContext();
if (ALC::Device != nullptr)
{
alcCloseDevice(ALC::Device);
@@ -746,7 +616,7 @@ void AudioBackendOAL::Base_OnActiveDeviceChanged()
}
// Setup
ALC::RebuildContexts(true);
ALC::RebuildContext(states);
}
void AudioBackendOAL::Base_SetDopplerFactor(float value)
@@ -756,9 +626,7 @@ void AudioBackendOAL::Base_SetDopplerFactor(float value)
void AudioBackendOAL::Base_SetVolume(float value)
{
ALC_FOR_EACH_CONTEXT()
alListenerf(AL_GAIN, value);
}
alListenerf(AL_GAIN, value);
}
bool AudioBackendOAL::Base_Init()
@@ -862,7 +730,7 @@ bool AudioBackendOAL::Base_Init()
int32 clampedIndex = Math::Clamp(activeDeviceIndex, -1, Audio::Devices.Count() - 1);
if (clampedIndex == Audio::GetActiveDeviceIndex())
{
ALC::RebuildContexts(true);
ALC::RebuildContext(true);
}
Audio::SetActiveDeviceIndex(activeDeviceIndex);
#ifdef AL_SOFT_source_spatialize

View File

@@ -12,37 +12,33 @@
class AudioBackendOAL : public AudioBackend
{
public:
// [AudioBackend]
void Listener_OnAdd(AudioListener* listener) override;
void Listener_OnRemove(AudioListener* listener) override;
void Listener_VelocityChanged(AudioListener* listener) override;
void Listener_TransformChanged(AudioListener* listener) override;
void Listener_Reset() override;
void Listener_VelocityChanged(const Vector3& velocity) override;
void Listener_TransformChanged(const Vector3& position, const Quaternion& orientation) override;
void Listener_ReinitializeAll() override;
void Source_OnAdd(AudioSource* source) override;
void Source_OnRemove(AudioSource* source) override;
void Source_VelocityChanged(AudioSource* source) override;
void Source_TransformChanged(AudioSource* source) override;
void Source_VolumeChanged(AudioSource* source) override;
void Source_PitchChanged(AudioSource* source) override;
void Source_PanChanged(AudioSource* source) override;
void Source_IsLoopingChanged(AudioSource* source) override;
void Source_SpatialSetupChanged(AudioSource* source) override;
void Source_ClipLoaded(AudioSource* source) override;
void Source_Cleanup(AudioSource* source) override;
void Source_Play(AudioSource* source) override;
void Source_Pause(AudioSource* source) override;
void Source_Stop(AudioSource* source) override;
void Source_SetCurrentBufferTime(AudioSource* source, float value) override;
float Source_GetCurrentBufferTime(const AudioSource* source) override;
void Source_SetNonStreamingBuffer(AudioSource* source) override;
void Source_GetProcessedBuffersCount(AudioSource* source, int32& processedBuffersCount) override;
void Source_GetQueuedBuffersCount(AudioSource* source, int32& queuedBuffersCount) override;
void Source_QueueBuffer(AudioSource* source, uint32 bufferId) override;
void Source_DequeueProcessedBuffers(AudioSource* source) override;
uint32 Source_Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler) override;
void Source_Remove(uint32 sourceID) override;
void Source_VelocityChanged(uint32 sourceID, const Vector3& velocity) override;
void Source_TransformChanged(uint32 sourceID, const Vector3& position, const Quaternion& orientation) override;
void Source_VolumeChanged(uint32 sourceID, float volume) override;
void Source_PitchChanged(uint32 sourceID, float pitch) override;
void Source_PanChanged(uint32 sourceID, float pan) override;
void Source_IsLoopingChanged(uint32 sourceID, bool loop) override;
void Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler) override;
void Source_Play(uint32 sourceID) override;
void Source_Pause(uint32 sourceID) override;
void Source_Stop(uint32 sourceID) override;
void Source_SetCurrentBufferTime(uint32 sourceID, float value) override;
float Source_GetCurrentBufferTime(uint32 sourceID) override;
void Source_SetNonStreamingBuffer(uint32 sourceID, uint32 bufferID) override;
void Source_GetProcessedBuffersCount(uint32 sourceID, int32& processedBuffersCount) override;
void Source_GetQueuedBuffersCount(uint32 sourceID, int32& queuedBuffersCount) override;
void Source_QueueBuffer(uint32 sourceID, uint32 bufferID) override;
void Source_DequeueProcessedBuffers(uint32 sourceID) override;
uint32 Buffer_Create() override;
void Buffer_Delete(uint32 bufferId) override;
void Buffer_Write(uint32 bufferId, byte* samples, const AudioDataInfo& info) override;
void Buffer_Delete(uint32 bufferID) override;
void Buffer_Write(uint32 bufferID, byte* samples, const AudioDataInfo& info) override;
const Char* Base_Name() override;
FeatureFlags Base_Features() override;
void Base_OnActiveDeviceChanged() override;

View File

@@ -8,8 +8,6 @@
#include "Engine/Core/Collections/ChunkedArray.h"
#include "Engine/Core/Log.h"
#include "Engine/Audio/Audio.h"
#include "Engine/Audio/AudioSource.h"
#include "Engine/Audio/AudioListener.h"
#include "Engine/Threading/Threading.h"
#if PLATFORM_WINDOWS
@@ -21,11 +19,9 @@
// Include XAudio library
// Documentation: https://docs.microsoft.com/en-us/windows/desktop/xaudio2/xaudio2-apis-portal
#include <xaudio2.h>
//#include <xaudio2fx.h>
//#include <x3daudio.h>
// TODO: implement multi-channel support (eg. 5.1, 7.1)
#define MAX_INPUT_CHANNELS 2
#define MAX_INPUT_CHANNELS 6
#define MAX_OUTPUT_CHANNELS 2
#define MAX_CHANNELS_MATRIX_SIZE (MAX_INPUT_CHANNELS*MAX_OUTPUT_CHANNELS)
#if ENABLE_ASSERTION
@@ -42,33 +38,6 @@ namespace XAudio2
{
struct Listener : AudioBackendTools::Listener
{
AudioListener* AudioListener;
Listener()
{
Init();
}
void Init()
{
AudioListener = nullptr;
}
bool IsFree() const
{
return AudioListener == nullptr;
}
void UpdateTransform()
{
Position = AudioListener->GetPosition();
Orientation = AudioListener->GetOrientation();
}
void UpdateVelocity()
{
Velocity = AudioListener->GetVelocity();
}
};
class VoiceCallback : public IXAudio2VoiceCallback
@@ -104,7 +73,7 @@ namespace XAudio2
}
public:
AudioSource* Source;
uint32 SourceID;
void PeekSamples();
};
@@ -113,6 +82,7 @@ namespace XAudio2
{
IXAudio2SourceVoice* Voice;
WAVEFORMATEX Format;
AudioDataInfo Info;
XAUDIO2_SEND_DESCRIPTOR Destination;
float StartTimeForQueueBuffer;
float LastBufferStartTime;
@@ -121,6 +91,8 @@ namespace XAudio2
int32 Channels;
bool IsDirty;
bool IsPlaying;
bool IsLoop;
uint32 LastBufferID;
VoiceCallback Callback;
Source()
@@ -140,6 +112,8 @@ namespace XAudio2
IsDirty = false;
Is3D = false;
IsPlaying = false;
IsLoop = false;
LastBufferID = 0;
LastBufferStartSamplesPlayed = 0;
BuffersProcessed = 0;
}
@@ -148,17 +122,6 @@ namespace XAudio2
{
return Voice == nullptr;
}
void UpdateTransform(const AudioSource* source)
{
Position = source->GetPosition();
Orientation = source->GetOrientation();
}
void UpdateVelocity(const AudioSource* source)
{
Velocity = source->GetVelocity();
}
};
struct Buffer
@@ -186,43 +149,20 @@ namespace XAudio2
IXAudio2* Instance = nullptr;
IXAudio2MasteringVoice* MasteringVoice = nullptr;
int32 Channels;
DWORD ChannelMask;
bool ForceDirty = true;
AudioBackendTools::Settings Settings;
Listener Listeners[AUDIO_MAX_LISTENERS];
Listener Listener;
CriticalSection Locker;
ChunkedArray<Source, 32> Sources;
ChunkedArray<Buffer*, 64> Buffers; // TODO: use ChunkedArray for better performance or use buffers pool?
EngineCallback Callback;
Listener* GetListener()
Source* GetSource(uint32 sourceID)
{
for (int32 i = 0; i < AUDIO_MAX_LISTENERS; i++)
{
if (Listeners[i].AudioListener)
return &Listeners[i];
}
return nullptr;
}
Listener* GetListener(const AudioListener* listener)
{
for (int32 i = 0; i < AUDIO_MAX_LISTENERS; i++)
{
if (Listeners[i].AudioListener == listener)
return &Listeners[i];
}
return nullptr;
}
Source* GetSource(const AudioSource* source)
{
if (source->SourceIDs.Count() == 0)
if (sourceID == 0)
return nullptr;
const AUDIO_SOURCE_ID_TYPE sourceId = source->SourceIDs[0];
// 0 is invalid ID so shift them
return &Sources[sourceId - 1];
return &Sources[sourceID - 1]; // 0 is invalid ID so shift them
}
void MarkAllDirty()
@@ -230,9 +170,9 @@ namespace XAudio2
ForceDirty = true;
}
void QueueBuffer(Source* aSource, const AudioSource* source, const int32 bufferId, XAUDIO2_BUFFER& buffer)
void QueueBuffer(Source* aSource, const int32 bufferID, XAUDIO2_BUFFER& buffer)
{
Buffer* aBuffer = Buffers[bufferId - 1];
Buffer* aBuffer = Buffers[bufferID - 1];
buffer.pAudioData = aBuffer->Data.Get();
buffer.AudioBytes = aBuffer->Data.Count();
@@ -252,70 +192,37 @@ namespace XAudio2
void VoiceCallback::OnBufferEnd(void* pBufferContext)
{
auto aSource = GetSource(Source);
auto aSource = GetSource(SourceID);
if (aSource->IsPlaying)
aSource->BuffersProcessed++;
}
void VoiceCallback::PeekSamples()
{
auto aSource = GetSource(Source);
auto aSource = GetSource(SourceID);
XAUDIO2_VOICE_STATE state;
aSource->Voice->GetState(&state);
aSource->LastBufferStartSamplesPlayed = state.SamplesPlayed;
}
}
void AudioBackendXAudio2::Listener_OnAdd(AudioListener* listener)
void AudioBackendXAudio2::Listener_Reset()
{
// Get first free listener
XAudio2::Listener* aListener = nullptr;
for (int32 i = 0; i < AUDIO_MAX_LISTENERS; i++)
{
if (XAudio2::Listeners[i].IsFree())
{
aListener = &XAudio2::Listeners[i];
break;
}
}
ASSERT(aListener);
// Setup
aListener->AudioListener = listener;
aListener->UpdateTransform();
aListener->UpdateVelocity();
XAudio2::Listener.Reset();
XAudio2::MarkAllDirty();
}
void AudioBackendXAudio2::Listener_OnRemove(AudioListener* listener)
void AudioBackendXAudio2::Listener_VelocityChanged(const Vector3& velocity)
{
XAudio2::Listener* aListener = XAudio2::GetListener(listener);
if (aListener)
{
aListener->Init();
XAudio2::MarkAllDirty();
}
XAudio2::Listener.Velocity = velocity;
XAudio2::MarkAllDirty();
}
void AudioBackendXAudio2::Listener_VelocityChanged(AudioListener* listener)
void AudioBackendXAudio2::Listener_TransformChanged(const Vector3& position, const Quaternion& orientation)
{
XAudio2::Listener* aListener = XAudio2::GetListener(listener);
if (aListener)
{
aListener->UpdateVelocity();
XAudio2::MarkAllDirty();
}
}
void AudioBackendXAudio2::Listener_TransformChanged(AudioListener* listener)
{
XAudio2::Listener* aListener = XAudio2::GetListener(listener);
if (aListener)
{
aListener->UpdateTransform();
XAudio2::MarkAllDirty();
}
XAudio2::Listener.Position = position;
XAudio2::Listener.Orientation = orientation;
XAudio2::MarkAllDirty();
}
void AudioBackendXAudio2::Listener_ReinitializeAll()
@@ -323,17 +230,13 @@ void AudioBackendXAudio2::Listener_ReinitializeAll()
// TODO: Implement XAudio2 reinitialization; read HRTF audio value from Audio class
}
void AudioBackendXAudio2::Source_OnAdd(AudioSource* source)
uint32 AudioBackendXAudio2::Source_Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler)
{
// Skip if has no clip (needs audio data to create a source - needs data format information)
if (source->Clip == nullptr || !source->Clip->IsLoaded())
return;
auto clip = source->Clip.Get();
ScopeLock lock(XAudio2::Locker);
// Get first free source
XAudio2::Source* aSource = nullptr;
AUDIO_SOURCE_ID_TYPE sourceID;
uint32 sourceID = 0;
for (int32 i = 0; i < XAudio2::Sources.Count(); i++)
{
if (XAudio2::Sources[i].IsFree())
@@ -351,116 +254,124 @@ void AudioBackendXAudio2::Source_OnAdd(AudioSource* source)
XAudio2::Sources.Add(src);
aSource = &XAudio2::Sources[sourceID];
}
sourceID++; // 0 is invalid ID so shift them
// Initialize audio data format information (from clip)
const auto& header = clip->AudioHeader;
auto& format = aSource->Format;
format.wFormatTag = WAVE_FORMAT_PCM;
format.nChannels = clip->Is3D() ? 1 : header.Info.NumChannels; // 3d audio is always mono (AudioClip auto-converts before buffer write if FeatureFlags::SpatialMultiChannel is unset)
format.nSamplesPerSec = header.Info.SampleRate;
format.wBitsPerSample = header.Info.BitDepth;
format.nBlockAlign = (WORD)(format.nChannels * (format.wBitsPerSample / 8));
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
format.cbSize = 0;
aSource->Info = format;
auto& aFormat = aSource->Format;
aFormat.wFormatTag = WAVE_FORMAT_PCM;
aFormat.nChannels = spatial ? 1 : format.NumChannels; // 3d audio is always mono (AudioClip auto-converts before buffer write if FeatureFlags::SpatialMultiChannel is unset)
aFormat.nSamplesPerSec = format.SampleRate;
aFormat.wBitsPerSample = format.BitDepth;
aFormat.nBlockAlign = (WORD)(aFormat.nChannels * (aFormat.wBitsPerSample / 8));
aFormat.nAvgBytesPerSec = aFormat.nSamplesPerSec * aFormat.nBlockAlign;
aFormat.cbSize = 0;
// Setup dry effect
aSource->Destination.pOutputVoice = XAudio2::MasteringVoice;
// Create voice
const XAUDIO2_VOICE_SENDS sendList =
{
1,
&aSource->Destination
};
const XAUDIO2_VOICE_SENDS sendList = { 1, &aSource->Destination };
HRESULT hr = XAudio2::Instance->CreateSourceVoice(&aSource->Voice, &aSource->Format, 0, 2.0f, &aSource->Callback, &sendList);
XAUDIO2_CHECK_ERROR(CreateSourceVoice);
if (FAILED(hr))
return;
sourceID++; // 0 is invalid ID so shift them
source->SourceIDs.Add(sourceID);
return 0;
// Prepare source state
aSource->Callback.Source = source;
aSource->Callback.SourceID = sourceID;
aSource->IsDirty = true;
aSource->Is3D = source->Is3D();
aSource->Pitch = source->GetPitch();
aSource->Pan = source->GetPan();
aSource->DopplerFactor = source->GetDopplerFactor();
aSource->Volume = source->GetVolume();
aSource->MinDistance = source->GetMinDistance();
aSource->Attenuation = source->GetAttenuation();
aSource->Channels = format.nChannels;
aSource->UpdateTransform(source);
aSource->UpdateVelocity(source);
hr = aSource->Voice->SetVolume(source->GetVolume());
aSource->IsLoop = loop;
aSource->Is3D = spatial;
aSource->Pitch = pitch;
aSource->Pan = pan;
aSource->DopplerFactor = doppler;
aSource->Volume = volume;
aSource->MinDistance = minDistance;
aSource->Attenuation = attenuation;
aSource->Channels = aFormat.nChannels;
aSource->Position = position;
aSource->Orientation = orientation;
aSource->Velocity = Vector3::Zero;
hr = aSource->Voice->SetVolume(volume);
XAUDIO2_CHECK_ERROR(SetVolume);
source->Restore();
return sourceID;
}
void AudioBackendXAudio2::Source_OnRemove(AudioSource* source)
void AudioBackendXAudio2::Source_Remove(uint32 sourceID)
{
ScopeLock lock(XAudio2::Locker);
source->Cleanup();
auto aSource = XAudio2::GetSource(sourceID);
if (!aSource)
return;
// Free source
if (aSource->Voice)
{
aSource->Voice->DestroyVoice();
}
aSource->Init();
}
void AudioBackendXAudio2::Source_VelocityChanged(AudioSource* source)
void AudioBackendXAudio2::Source_VelocityChanged(uint32 sourceID, const Vector3& velocity)
{
auto aSource = XAudio2::GetSource(source);
auto aSource = XAudio2::GetSource(sourceID);
if (aSource)
{
aSource->UpdateVelocity(source);
aSource->Velocity = velocity;
aSource->IsDirty = true;
}
}
void AudioBackendXAudio2::Source_TransformChanged(AudioSource* source)
void AudioBackendXAudio2::Source_TransformChanged(uint32 sourceID, const Vector3& position, const Quaternion& orientation)
{
auto aSource = XAudio2::GetSource(source);
auto aSource = XAudio2::GetSource(sourceID);
if (aSource)
{
aSource->UpdateTransform(source);
aSource->Position = position;
aSource->Orientation = orientation;
aSource->IsDirty = true;
}
}
void AudioBackendXAudio2::Source_VolumeChanged(AudioSource* source)
void AudioBackendXAudio2::Source_VolumeChanged(uint32 sourceID, float volume)
{
auto aSource = XAudio2::GetSource(source);
auto aSource = XAudio2::GetSource(sourceID);
if (aSource && aSource->Voice)
{
aSource->Volume = source->GetVolume();
const HRESULT hr = aSource->Voice->SetVolume(source->GetVolume());
aSource->Volume = volume;
const HRESULT hr = aSource->Voice->SetVolume(volume);
XAUDIO2_CHECK_ERROR(SetVolume);
}
}
void AudioBackendXAudio2::Source_PitchChanged(AudioSource* source)
void AudioBackendXAudio2::Source_PitchChanged(uint32 sourceID, float pitch)
{
auto aSource = XAudio2::GetSource(source);
auto aSource = XAudio2::GetSource(sourceID);
if (aSource)
{
aSource->Pitch = source->GetPitch();
aSource->Pitch = pitch;
aSource->IsDirty = true;
}
}
void AudioBackendXAudio2::Source_PanChanged(AudioSource* source)
void AudioBackendXAudio2::Source_PanChanged(uint32 sourceID, float pan)
{
auto aSource = XAudio2::GetSource(source);
auto aSource = XAudio2::GetSource(sourceID);
if (aSource)
{
aSource->Pan = source->GetPan();
aSource->Pan = pan;
aSource->IsDirty = true;
}
}
void AudioBackendXAudio2::Source_IsLoopingChanged(AudioSource* source)
void AudioBackendXAudio2::Source_IsLoopingChanged(uint32 sourceID, bool loop)
{
auto aSource = XAudio2::GetSource(source);
ScopeLock lock(XAudio2::Locker);
auto aSource = XAudio2::GetSource(sourceID);
if (!aSource || !aSource->Voice)
return;
aSource->IsLoop = loop;
// Skip if has no buffers (waiting for data or sth)
XAUDIO2_VOICE_STATE state;
@@ -468,15 +379,12 @@ void AudioBackendXAudio2::Source_IsLoopingChanged(AudioSource* source)
if (state.BuffersQueued == 0)
return;
// Looping is defined during buffer submission so reset source buffer (this is called only for non-streamable sources that ue single buffer)
XAudio2::Locker.Lock();
const uint32 bufferId = source->Clip->Buffers[0];
XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1];
XAudio2::Locker.Unlock();
// Looping is defined during buffer submission so reset source buffer (this is called only for non-streamable sources that use a single buffer)
const uint32 bufferID = aSource->LastBufferID;
XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferID - 1];
HRESULT hr;
const bool isPlaying = source->IsActuallyPlayingSth();
const bool isPlaying = aSource->IsPlaying;
if (isPlaying)
{
hr = aSource->Voice->Stop();
@@ -492,7 +400,7 @@ void AudioBackendXAudio2::Source_IsLoopingChanged(AudioSource* source)
XAUDIO2_BUFFER buffer = { 0 };
buffer.pContext = aBuffer;
buffer.Flags = XAUDIO2_END_OF_STREAM;
if (source->GetIsLooping())
if (loop)
buffer.LoopCount = XAUDIO2_LOOP_INFINITE;
// Restore play position
@@ -501,7 +409,7 @@ void AudioBackendXAudio2::Source_IsLoopingChanged(AudioSource* source)
buffer.PlayLength = totalSamples - buffer.PlayBegin;
aSource->StartTimeForQueueBuffer = 0;
XAudio2::QueueBuffer(aSource, source, bufferId, buffer);
XAudio2::QueueBuffer(aSource, bufferID, buffer);
if (isPlaying)
{
@@ -510,48 +418,22 @@ void AudioBackendXAudio2::Source_IsLoopingChanged(AudioSource* source)
}
}
void AudioBackendXAudio2::Source_SpatialSetupChanged(AudioSource* source)
void AudioBackendXAudio2::Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler)
{
auto aSource = XAudio2::GetSource(source);
auto aSource = XAudio2::GetSource(sourceID);
if (aSource)
{
aSource->Is3D = source->Is3D();
aSource->MinDistance = source->GetMinDistance();
aSource->Attenuation = source->GetAttenuation();
aSource->DopplerFactor = source->GetDopplerFactor();
aSource->Is3D = spatial;
aSource->MinDistance = minDistance;
aSource->Attenuation = attenuation;
aSource->DopplerFactor = doppler;
aSource->IsDirty = true;
}
}
void AudioBackendXAudio2::Source_ClipLoaded(AudioSource* source)
void AudioBackendXAudio2::Source_Play(uint32 sourceID)
{
ScopeLock lock(XAudio2::Locker);
auto aSource = XAudio2::GetSource(source);
if (!aSource)
{
// Register source if clip was missing
Source_OnAdd(source);
}
}
void AudioBackendXAudio2::Source_Cleanup(AudioSource* source)
{
ScopeLock lock(XAudio2::Locker);
auto aSource = XAudio2::GetSource(source);
if (!aSource)
return;
// Free source
if (aSource->Voice)
{
aSource->Voice->DestroyVoice();
}
aSource->Init();
}
void AudioBackendXAudio2::Source_Play(AudioSource* source)
{
auto aSource = XAudio2::GetSource(source);
auto aSource = XAudio2::GetSource(sourceID);
if (aSource && aSource->Voice && !aSource->IsPlaying)
{
// Play
@@ -561,9 +443,9 @@ void AudioBackendXAudio2::Source_Play(AudioSource* source)
}
}
void AudioBackendXAudio2::Source_Pause(AudioSource* source)
void AudioBackendXAudio2::Source_Pause(uint32 sourceID)
{
auto aSource = XAudio2::GetSource(source);
auto aSource = XAudio2::GetSource(sourceID);
if (aSource && aSource->Voice && aSource->IsPlaying)
{
// Pause
@@ -573,9 +455,9 @@ void AudioBackendXAudio2::Source_Pause(AudioSource* source)
}
}
void AudioBackendXAudio2::Source_Stop(AudioSource* source)
void AudioBackendXAudio2::Source_Stop(uint32 sourceID)
{
auto aSource = XAudio2::GetSource(source);
auto aSource = XAudio2::GetSource(sourceID);
if (aSource && aSource->Voice)
{
aSource->StartTimeForQueueBuffer = 0.0f;
@@ -595,9 +477,9 @@ void AudioBackendXAudio2::Source_Stop(AudioSource* source)
}
}
void AudioBackendXAudio2::Source_SetCurrentBufferTime(AudioSource* source, float value)
void AudioBackendXAudio2::Source_SetCurrentBufferTime(uint32 sourceID, float value)
{
const auto aSource = XAudio2::GetSource(source);
const auto aSource = XAudio2::GetSource(sourceID);
if (aSource)
{
// Store start time so next buffer submitted will start from here (assumes audio is stopped)
@@ -605,60 +487,63 @@ void AudioBackendXAudio2::Source_SetCurrentBufferTime(AudioSource* source, float
}
}
float AudioBackendXAudio2::Source_GetCurrentBufferTime(const AudioSource* source)
float AudioBackendXAudio2::Source_GetCurrentBufferTime(uint32 sourceID)
{
float time = 0;
auto aSource = XAudio2::GetSource(source);
auto aSource = XAudio2::GetSource(sourceID);
if (aSource)
{
ASSERT(source->Clip && source->Clip->IsLoaded());
const auto& clipInfo = source->Clip->AudioHeader.Info;
const auto& clipInfo = aSource->Info;
XAUDIO2_VOICE_STATE state;
aSource->Voice->GetState(&state);
const uint32 numChannels = clipInfo.NumChannels;
const uint32 totalSamples = clipInfo.NumSamples / numChannels;
const uint32 totalSamples = clipInfo.NumSamples / clipInfo.NumChannels;
const uint32 sampleRate = clipInfo.SampleRate; // / clipInfo.NumChannels;
state.SamplesPlayed -= aSource->LastBufferStartSamplesPlayed % totalSamples; // Offset by the last buffer start to get time relative to its begin
time = aSource->LastBufferStartTime + (state.SamplesPlayed % totalSamples) / static_cast<float>(Math::Max(1U, sampleRate));
uint64 lastBufferStartSamplesPlayed = aSource->LastBufferStartSamplesPlayed;
if (totalSamples > 0)
lastBufferStartSamplesPlayed %= totalSamples;
state.SamplesPlayed -= lastBufferStartSamplesPlayed % totalSamples; // Offset by the last buffer start to get time relative to its begin
if (totalSamples > 0)
state.SamplesPlayed %= totalSamples;
time = aSource->LastBufferStartTime + state.SamplesPlayed / static_cast<float>(Math::Max(1U, sampleRate));
}
return time;
}
void AudioBackendXAudio2::Source_SetNonStreamingBuffer(AudioSource* source)
void AudioBackendXAudio2::Source_SetNonStreamingBuffer(uint32 sourceID, uint32 bufferID)
{
auto aSource = XAudio2::GetSource(source);
auto aSource = XAudio2::GetSource(sourceID);
if (!aSource)
return;
aSource->LastBufferID = bufferID; // Use for looping change
XAudio2::Locker.Lock();
const uint32 bufferId = source->Clip->Buffers[0];
XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1];
XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferID - 1];
XAudio2::Locker.Unlock();
XAUDIO2_BUFFER buffer = { 0 };
buffer.pContext = aBuffer;
buffer.Flags = XAUDIO2_END_OF_STREAM;
if (source->GetIsLooping())
if (aSource->IsLoop)
buffer.LoopCount = XAUDIO2_LOOP_INFINITE;
// Queue single buffer
XAudio2::QueueBuffer(aSource, source, bufferId, buffer);
XAudio2::QueueBuffer(aSource, bufferID, buffer);
}
void AudioBackendXAudio2::Source_GetProcessedBuffersCount(AudioSource* source, int32& processedBuffersCount)
void AudioBackendXAudio2::Source_GetProcessedBuffersCount(uint32 sourceID, int32& processedBuffersCount)
{
processedBuffersCount = 0;
auto aSource = XAudio2::GetSource(source);
auto aSource = XAudio2::GetSource(sourceID);
if (aSource && aSource->Voice)
{
processedBuffersCount = aSource->BuffersProcessed;
}
}
void AudioBackendXAudio2::Source_GetQueuedBuffersCount(AudioSource* source, int32& queuedBuffersCount)
void AudioBackendXAudio2::Source_GetQueuedBuffersCount(uint32 sourceID, int32& queuedBuffersCount)
{
queuedBuffersCount = 0;
auto aSource = XAudio2::GetSource(source);
auto aSource = XAudio2::GetSource(sourceID);
if (aSource && aSource->Voice)
{
XAUDIO2_VOICE_STATE state;
@@ -667,23 +552,24 @@ void AudioBackendXAudio2::Source_GetQueuedBuffersCount(AudioSource* source, int3
}
}
void AudioBackendXAudio2::Source_QueueBuffer(AudioSource* source, uint32 bufferId)
void AudioBackendXAudio2::Source_QueueBuffer(uint32 sourceID, uint32 bufferID)
{
auto aSource = XAudio2::GetSource(source);
auto aSource = XAudio2::GetSource(sourceID);
if (!aSource)
return;
aSource->LastBufferID = bufferID; // Use for looping change
XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1];
XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferID - 1];
XAUDIO2_BUFFER buffer = { 0 };
buffer.pContext = aBuffer;
XAudio2::QueueBuffer(aSource, source, bufferId, buffer);
XAudio2::QueueBuffer(aSource, bufferID, buffer);
}
void AudioBackendXAudio2::Source_DequeueProcessedBuffers(AudioSource* source)
void AudioBackendXAudio2::Source_DequeueProcessedBuffers(uint32 sourceID)
{
auto aSource = XAudio2::GetSource(source);
auto aSource = XAudio2::GetSource(sourceID);
if (aSource && aSource->Voice)
{
const HRESULT hr = aSource->Voice->FlushSourceBuffers();
@@ -694,7 +580,7 @@ void AudioBackendXAudio2::Source_DequeueProcessedBuffers(AudioSource* source)
uint32 AudioBackendXAudio2::Buffer_Create()
{
uint32 bufferId;
uint32 bufferID;
ScopeLock lock(XAudio2::Locker);
// Get first free buffer slot
@@ -705,7 +591,7 @@ uint32 AudioBackendXAudio2::Buffer_Create()
{
aBuffer = New<XAudio2::Buffer>();
XAudio2::Buffers[i] = aBuffer;
bufferId = i + 1;
bufferID = i + 1;
break;
}
}
@@ -714,28 +600,28 @@ uint32 AudioBackendXAudio2::Buffer_Create()
// Add new slot
aBuffer = New<XAudio2::Buffer>();
XAudio2::Buffers.Add(aBuffer);
bufferId = XAudio2::Buffers.Count();
bufferID = XAudio2::Buffers.Count();
}
aBuffer->Data.Resize(0);
return bufferId;
return bufferID;
}
void AudioBackendXAudio2::Buffer_Delete(uint32 bufferId)
void AudioBackendXAudio2::Buffer_Delete(uint32 bufferID)
{
ScopeLock lock(XAudio2::Locker);
XAudio2::Buffer*& aBuffer = XAudio2::Buffers[bufferId - 1];
XAudio2::Buffer*& aBuffer = XAudio2::Buffers[bufferID - 1];
aBuffer->Data.Resize(0);
Delete(aBuffer);
aBuffer = nullptr;
}
void AudioBackendXAudio2::Buffer_Write(uint32 bufferId, byte* samples, const AudioDataInfo& info)
void AudioBackendXAudio2::Buffer_Write(uint32 bufferID, byte* samples, const AudioDataInfo& info)
{
CHECK(info.NumChannels <= MAX_INPUT_CHANNELS);
XAudio2::Locker.Lock();
XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1];
XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferID - 1];
XAudio2::Locker.Unlock();
const uint32 samplesLength = info.NumSamples * info.BitDepth / 8;
@@ -796,7 +682,7 @@ bool AudioBackendXAudio2::Base_Init()
}
XAUDIO2_VOICE_DETAILS details;
XAudio2::MasteringVoice->GetVoiceDetails(&details);
#if 0
#if MAX_OUTPUT_CHANNELS > 2
// TODO: implement multi-channel support (eg. 5.1, 7.1)
XAudio2::Channels = details.InputChannels;
hr = XAudio2::MasteringVoice->GetChannelMask(&XAudio2::ChannelMask);
@@ -807,6 +693,7 @@ bool AudioBackendXAudio2::Base_Init()
}
#else
XAudio2::Channels = 2;
XAudio2::ChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
#endif
LOG(Info, "XAudio2: {0} channels at {1} kHz", XAudio2::Channels, details.InputSampleRate / 1000.0f);
@@ -821,7 +708,6 @@ bool AudioBackendXAudio2::Base_Init()
void AudioBackendXAudio2::Base_Update()
{
// Update dirty voices
const auto listener = XAudio2::GetListener();
float outputMatrix[MAX_CHANNELS_MATRIX_SIZE];
for (int32 i = 0; i < XAudio2::Sources.Count(); i++)
{
@@ -829,7 +715,7 @@ void AudioBackendXAudio2::Base_Update()
if (source.IsFree() || !(source.IsDirty || XAudio2::ForceDirty))
continue;
auto mix = AudioBackendTools::CalculateSoundMix(XAudio2::Settings, *listener, source, XAudio2::Channels);
auto mix = AudioBackendTools::CalculateSoundMix(XAudio2::Settings, XAudio2::Listener, source, XAudio2::Channels);
mix.VolumeIntoChannels();
AudioBackendTools::MapChannels(source.Channels, XAudio2::Channels, mix.Channels, outputMatrix);

View File

@@ -12,37 +12,33 @@
class AudioBackendXAudio2 : public AudioBackend
{
public:
// [AudioBackend]
void Listener_OnAdd(AudioListener* listener) override;
void Listener_OnRemove(AudioListener* listener) override;
void Listener_VelocityChanged(AudioListener* listener) override;
void Listener_TransformChanged(AudioListener* listener) override;
void Listener_Reset() override;
void Listener_VelocityChanged(const Vector3& velocity) override;
void Listener_TransformChanged(const Vector3& position, const Quaternion& orientation) override;
void Listener_ReinitializeAll() override;
void Source_OnAdd(AudioSource* source) override;
void Source_OnRemove(AudioSource* source) override;
void Source_VelocityChanged(AudioSource* source) override;
void Source_TransformChanged(AudioSource* source) override;
void Source_VolumeChanged(AudioSource* source) override;
void Source_PitchChanged(AudioSource* source) override;
void Source_PanChanged(AudioSource* source) override;
void Source_IsLoopingChanged(AudioSource* source) override;
void Source_SpatialSetupChanged(AudioSource* source) override;
void Source_ClipLoaded(AudioSource* source) override;
void Source_Cleanup(AudioSource* source) override;
void Source_Play(AudioSource* source) override;
void Source_Pause(AudioSource* source) override;
void Source_Stop(AudioSource* source) override;
void Source_SetCurrentBufferTime(AudioSource* source, float value) override;
float Source_GetCurrentBufferTime(const AudioSource* source) override;
void Source_SetNonStreamingBuffer(AudioSource* source) override;
void Source_GetProcessedBuffersCount(AudioSource* source, int32& processedBuffersCount) override;
void Source_GetQueuedBuffersCount(AudioSource* source, int32& queuedBuffersCount) override;
void Source_QueueBuffer(AudioSource* source, uint32 bufferId) override;
void Source_DequeueProcessedBuffers(AudioSource* source) override;
uint32 Source_Add(const AudioDataInfo& format, const Vector3& position, const Quaternion& orientation, float volume, float pitch, float pan, bool loop, bool spatial, float attenuation, float minDistance, float doppler) override;
void Source_Remove(uint32 sourceID) override;
void Source_VelocityChanged(uint32 sourceID, const Vector3& velocity) override;
void Source_TransformChanged(uint32 sourceID, const Vector3& position, const Quaternion& orientation) override;
void Source_VolumeChanged(uint32 sourceID, float volume) override;
void Source_PitchChanged(uint32 sourceID, float pitch) override;
void Source_PanChanged(uint32 sourceID, float pan) override;
void Source_IsLoopingChanged(uint32 sourceID, bool loop) override;
void Source_SpatialSetupChanged(uint32 sourceID, bool spatial, float attenuation, float minDistance, float doppler) override;
void Source_Play(uint32 sourceID) override;
void Source_Pause(uint32 sourceID) override;
void Source_Stop(uint32 sourceID) override;
void Source_SetCurrentBufferTime(uint32 sourceID, float value) override;
float Source_GetCurrentBufferTime(uint32 sourceID) override;
void Source_SetNonStreamingBuffer(uint32 sourceID, uint32 bufferID) override;
void Source_GetProcessedBuffersCount(uint32 sourceID, int32& processedBuffersCount) override;
void Source_GetQueuedBuffersCount(uint32 sourceID, int32& queuedBuffersCount) override;
void Source_QueueBuffer(uint32 sourceID, uint32 bufferID) override;
void Source_DequeueProcessedBuffers(uint32 sourceID) override;
uint32 Buffer_Create() override;
void Buffer_Delete(uint32 bufferId) override;
void Buffer_Write(uint32 bufferId, byte* samples, const AudioDataInfo& info) override;
void Buffer_Delete(uint32 bufferID) override;
void Buffer_Write(uint32 bufferID, byte* samples, const AudioDataInfo& info) override;
const Char* Base_Name() override;
FeatureFlags Base_Features() override;
void Base_OnActiveDeviceChanged() override;

View File

@@ -15,15 +15,10 @@ namespace CSG
public:
struct Node : RectPack<Node, float>
struct Node : RectPackNode<float>
{
Node(float xy, float wh)
: RectPack<Node, float>(xy, xy, wh, wh)
{
}
Node(float x, float y, float width, float height)
: RectPack<Node, float>(x, y, width, height)
Node(Size x, Size y, Size width, Size height)
: RectPackNode(x, y, width, height)
{
}
@@ -36,17 +31,17 @@ namespace CSG
private:
Node _root;
RectPackAtlas<Node> _root;
const float _atlasSize;
const float _chartsPadding;
public:
LightmapUVsPacker(float atlasSize, float chartsPadding)
: _root(chartsPadding, atlasSize - chartsPadding)
, _atlasSize(atlasSize)
: _atlasSize(atlasSize)
, _chartsPadding(chartsPadding)
{
_root.Init(atlasSize, atlasSize, chartsPadding);
}
~LightmapUVsPacker()
@@ -55,7 +50,7 @@ namespace CSG
Node* Insert(ChartType chart)
{
return _root.Insert(chart->Size.X, chart->Size.Y, _chartsPadding, chart, _atlasSize);
return _root.Insert(chart->Size.X, chart->Size.Y, chart, _atlasSize);
}
};
}

View File

@@ -537,6 +537,14 @@ void Asset::CancelStreaming()
#if USE_EDITOR
void Asset::GetReferences(Array<Guid>& assets, Array<String>& files) const
{
// Fallback to the old API
PRAGMA_DISABLE_DEPRECATION_WARNINGS;
GetReferences(assets);
PRAGMA_ENABLE_DEPRECATION_WARNINGS;
}
void Asset::GetReferences(Array<Guid>& output) const
{
// No refs by default
@@ -545,7 +553,8 @@ void Asset::GetReferences(Array<Guid>& output) const
Array<Guid> Asset::GetReferences() const
{
Array<Guid> result;
GetReferences(result);
Array<String> files;
GetReferences(result, files);
return result;
}

View File

@@ -179,10 +179,24 @@ public:
/// For some asset types (e.g. scene or prefab) it may contain invalid asset ids due to not perfect gather method,
/// which is optimized to perform scan very quickly. Before using those ids perform simple validation via Content cache API.
/// The result collection contains only 1-level-deep references (only direct ones) and is invalid if asset is not loaded.
/// Also the output data may have duplicated asset ids or even invalid ids (Guid::Empty).
/// Also, the output data may have duplicated asset ids or even invalid ids (Guid::Empty).
/// </remarks>
/// <param name="assets">The output collection of the asset ids referenced by this asset.</param>
/// <param name="files">The output list of file paths referenced by this asset. Files might come from project Content folder (relative path is preserved in cooked game), or external location (copied into Content root folder of cooked game).</param>
virtual void GetReferences(Array<Guid, HeapAllocation>& assets, Array<String, HeapAllocation>& files) const;
/// <summary>
/// Gets the asset references. Supported only in Editor.
/// [Deprecated in v1.9]
/// </summary>
/// <remarks>
/// For some asset types (e.g. scene or prefab) it may contain invalid asset ids due to not perfect gather method,
/// which is optimized to perform scan very quickly. Before using those ids perform simple validation via Content cache API.
/// The result collection contains only 1-level-deep references (only direct ones) and is invalid if asset is not loaded.
/// Also, the output data may have duplicated asset ids or even invalid ids (Guid::Empty).
/// </remarks>
/// <param name="output">The output collection of the asset ids referenced by this asset.</param>
virtual void GetReferences(Array<Guid, HeapAllocation>& output) const;
DEPRECATED("Use GetReferences with assets and files parameter instead") virtual void GetReferences(Array<Guid, HeapAllocation>& output) const;
/// <summary>
/// Gets the asset references. Supported only in Editor.
@@ -191,7 +205,7 @@ public:
/// For some asset types (e.g. scene or prefab) it may contain invalid asset ids due to not perfect gather method,
/// which is optimized to perform scan very quickly. Before using those ids perform simple validation via Content cache API.
/// The result collection contains only 1-level-deep references (only direct ones) and is invalid if asset is not loaded.
/// Also the output data may have duplicated asset ids or even invalid ids (Guid::Empty).
/// Also, the output data may have duplicated asset ids or even invalid ids (Guid::Empty).
/// </remarks>
/// <returns>The collection of the asset ids referenced by this asset.</returns>
API_FUNCTION() Array<Guid, HeapAllocation> GetReferences() const;

View File

@@ -93,7 +93,7 @@ public:
/// <param name="asset">The asset to set.</param>
AssetReference(T* asset)
{
OnSet(asset);
OnSet((Asset*)asset);
}
/// <summary>
@@ -215,7 +215,7 @@ public:
/// <param name="asset">The asset.</param>
void Set(T* asset)
{
OnSet(asset);
OnSet((Asset*)asset);
}
};

View File

@@ -222,12 +222,10 @@ void AnimationGraph::FindDependencies(AnimGraphBase* graph)
}
}
void AnimationGraph::GetReferences(Array<Guid>& output) const
void AnimationGraph::GetReferences(Array<Guid>& assets, Array<String>& files) const
{
// Base
BinaryAsset::GetReferences(output);
Graph.GetReferences(output);
BinaryAsset::GetReferences(assets, files);
Graph.GetReferences(assets);
}
#endif

View File

@@ -64,7 +64,7 @@ private:
public:
// [BinaryAsset]
#if USE_EDITOR
void GetReferences(Array<Guid>& output) const override;
void GetReferences(Array<Guid>& assets, Array<String>& files) const override;
#endif
protected:

View File

@@ -45,3 +45,13 @@ MaterialInstance* MaterialBase::CreateVirtualInstance()
instance->SetBaseMaterial(this);
return instance;
}
#if USE_EDITOR
void MaterialBase::GetReferences(Array<Guid>& assets, Array<String>& files) const
{
BinaryAsset::GetReferences(assets, files);
Params.GetReferences(assets);
}
#endif

View File

@@ -25,7 +25,7 @@ public:
Action ParamsChanged;
/// <summary>
/// Returns true if material is an material instance.
/// Returns true if material is a material instance.
/// </summary>
virtual bool IsMaterialInstance() const = 0;
@@ -77,12 +77,6 @@ public:
public:
// [BinaryAsset]
#if USE_EDITOR
void GetReferences(Array<Guid>& output) const override
{
// Base
BinaryAsset::GetReferences(output);
Params.GetReferences(output);
}
void GetReferences(Array<Guid>& assets, Array<String>& files) const override;
#endif
};

View File

@@ -123,13 +123,11 @@ bool MaterialInstance::IsMaterialInstance() const
#if USE_EDITOR
void MaterialInstance::GetReferences(Array<Guid>& output) const
void MaterialInstance::GetReferences(Array<Guid>& assets, Array<String>& files) const
{
// Base
MaterialBase::GetReferences(output);
MaterialBase::GetReferences(assets, files);
if (_baseMaterial)
output.Add(_baseMaterial->GetID());
assets.Add(_baseMaterial->GetID());
}
#endif

View File

@@ -55,7 +55,7 @@ public:
// [MaterialBase]
bool IsMaterialInstance() const override;
#if USE_EDITOR
void GetReferences(Array<Guid>& output) const override;
void GetReferences(Array<Guid>& assets, Array<String>& files) const override;
#endif
// [IMaterial]

View File

@@ -186,7 +186,7 @@ BoundingBox Model::GetBox(int32 lodIndex) const
return LODs[lodIndex].GetBox();
}
void Model::Draw(const RenderContext& renderContext, MaterialBase* material, const Matrix& world, StaticFlags flags, bool receiveDecals, int16 sortOrder) const
void Model::Draw(const RenderContext& renderContext, MaterialBase* material, const Matrix& world, StaticFlags flags, bool receiveDecals, int8 sortOrder) const
{
if (!CanBeRendered())
return;
@@ -650,7 +650,7 @@ bool Model::Save(bool withMeshDataFromGpu, const StringView& path)
#endif
bool Model::GenerateSDF(float resolutionScale, int32 lodIndex, bool cacheData, float backfacesThreshold)
bool Model::GenerateSDF(float resolutionScale, int32 lodIndex, bool cacheData, float backfacesThreshold, bool useGPU)
{
if (EnableModelSDF == 2)
return true; // Not supported
@@ -673,13 +673,20 @@ bool Model::GenerateSDF(float resolutionScale, int32 lodIndex, bool cacheData, f
#else
class MemoryWriteStream* outputStream = nullptr;
#endif
if (ModelTool::GenerateModelSDF(this, nullptr, resolutionScale, lodIndex, &SDF, outputStream, GetPath(), backfacesThreshold))
Locker.Unlock();
const bool failed = ModelTool::GenerateModelSDF(this, nullptr, resolutionScale, lodIndex, &SDF, outputStream, GetPath(), backfacesThreshold, useGPU);
Locker.Lock();
if (failed)
return true;
#if USE_EDITOR
// Set asset data
if (cacheData)
GetOrCreateChunk(15)->Data.Copy(sdfStream.GetHandle(), sdfStream.GetPosition());
{
auto chunk = GetOrCreateChunk(15);
chunk->Data.Copy(sdfStream.GetHandle(), sdfStream.GetPosition());
chunk->Flags |= FlaxChunkFlags::KeepInMemory; // Prevent GC-ing chunk data so it will be properly saved
}
#endif
return false;
@@ -788,15 +795,13 @@ void Model::CancelStreaming()
#if USE_EDITOR
void Model::GetReferences(Array<Guid>& output) const
void Model::GetReferences(Array<Guid>& assets, Array<String>& files) const
{
// Base
BinaryAsset::GetReferences(output);
BinaryAsset::GetReferences(assets, files);
for (int32 i = 0; i < MaterialSlots.Count(); i++)
{
output.Add(MaterialSlots[i].Material.GetID());
}
assets.Add(MaterialSlots[i].Material.GetID());
}
#endif

View File

@@ -182,7 +182,7 @@ public:
/// <param name="flags">The object static flags.</param>
/// <param name="receiveDecals">True if rendered geometry can receive decals, otherwise false.</param>
/// <param name="sortOrder">Object sorting key.</param>
API_FUNCTION() void Draw(API_PARAM(Ref) const RenderContext& renderContext, MaterialBase* material, API_PARAM(Ref) const Matrix& world, StaticFlags flags = StaticFlags::None, bool receiveDecals = true, int16 sortOrder = 0) const;
API_FUNCTION() void Draw(API_PARAM(Ref) const RenderContext& renderContext, MaterialBase* material, API_PARAM(Ref) const Matrix& world, StaticFlags flags = StaticFlags::None, bool receiveDecals = true, int8 sortOrder = 0) const;
/// <summary>
/// Draws the model.
@@ -227,8 +227,9 @@ public:
/// <param name="lodIndex">The index of the LOD to use for the SDF building.</param>
/// <param name="cacheData">If true, the generated SDF texture data will be cached on CPU (in asset chunk storage) to allow saving it later, otherwise it will be runtime for GPU-only. Ignored for virtual assets or in build.</param>
/// <param name="backfacesThreshold">Custom threshold (in range 0-1) for adjusting mesh internals detection based on the percentage of test rays hit triangle backfaces. Use lower value for more dense mesh.</param>
/// <param name="useGPU">Enables using GPU for SDF generation, otherwise CPU will be used (async via Job System).</param>
/// <returns>True if failed, otherwise false.</returns>
API_FUNCTION() bool GenerateSDF(float resolutionScale = 1.0f, int32 lodIndex = 6, bool cacheData = true, float backfacesThreshold = 0.6f);
API_FUNCTION() bool GenerateSDF(float resolutionScale = 1.0f, int32 lodIndex = 6, bool cacheData = true, float backfacesThreshold = 0.6f, bool useGPU = true);
/// <summary>
/// Sets set SDF data (releases the current one).
@@ -251,7 +252,7 @@ public:
void InitAsVirtual() override;
void CancelStreaming() override;
#if USE_EDITOR
void GetReferences(Array<Guid>& output) const override;
void GetReferences(Array<Guid>& assets, Array<String>& files) const override;
#endif
// [StreamableResource]

View File

@@ -68,12 +68,10 @@ private:
public:
// [BinaryAsset]
#if USE_EDITOR
void GetReferences(Array<Guid>& output) const override
void GetReferences(Array<Guid>& assets, Array<String>& files) const override
{
// Base
BinaryAsset::GetReferences(output);
output.Add(Skeleton.GetID());
BinaryAsset::GetReferences(assets, files);
assets.Add(Skeleton.GetID());
}
#endif

View File

@@ -974,15 +974,13 @@ void SkinnedModel::CancelStreaming()
#if USE_EDITOR
void SkinnedModel::GetReferences(Array<Guid>& output) const
void SkinnedModel::GetReferences(Array<Guid>& assets, Array<String>& files) const
{
// Base
BinaryAsset::GetReferences(output);
BinaryAsset::GetReferences(assets, files);
for (int32 i = 0; i < MaterialSlots.Count(); i++)
{
output.Add(MaterialSlots[i].Material.GetID());
}
assets.Add(MaterialSlots[i].Material.GetID());
}
#endif

View File

@@ -323,7 +323,7 @@ public:
void InitAsVirtual() override;
void CancelStreaming() override;
#if USE_EDITOR
void GetReferences(Array<Guid>& output) const override;
void GetReferences(Array<Guid>& assets, Array<String>& files) const override;
#endif
// [StreamableResource]

View File

@@ -278,12 +278,10 @@ public:
public:
// [BinaryAsset]
#if USE_EDITOR
void GetReferences(Array<Guid>& output) const override
void GetReferences(Array<Guid>& assets, Array<String>& files) const override
{
// Base
BinaryAsset::GetReferences(output);
Graph.GetReferences(output);
BinaryAsset::GetReferences(assets, files);
Graph.GetReferences(assets);
}
#endif

View File

@@ -3,6 +3,7 @@
#include "JsonAsset.h"
#if USE_EDITOR
#include "Engine/Platform/File.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Core/Types/DataContainer.h"
#include "Engine/Level/Level.h"
#else
@@ -109,41 +110,55 @@ uint64 JsonAssetBase::GetMemoryUsage() const
#if USE_EDITOR
void FindIds(ISerializable::DeserializeStream& node, Array<Guid>& output)
void FindIds(ISerializable::DeserializeStream& node, Array<Guid>& output, Array<String>& files, rapidjson_flax::Value* nodeName = nullptr)
{
if (node.IsObject())
{
for (auto i = node.MemberBegin(); i != node.MemberEnd(); ++i)
{
FindIds(i->value, output);
FindIds(i->value, output, files, &i->name);
}
}
else if (node.IsArray())
{
for (rapidjson::SizeType i = 0; i < node.Size(); i++)
{
FindIds(node[i], output);
FindIds(node[i], output, files);
}
}
else if (node.IsString())
else if (node.IsString() && node.GetStringLength() != 0)
{
if (node.GetStringLength() == 32)
{
// Try parse as Guid in format `N` (32 hex chars)
Guid id;
if (!Guid::Parse(node.GetStringAnsiView(), id))
{
output.Add(id);
return;
}
}
if (node.GetStringLength() < 512 &&
(!nodeName || nodeName->GetStringAnsiView() != "ImportPath")) // Ignore path in ImportPath from ModelPrefab (TODO: resave prefabs/scenes before cooking to get rid of editor-only data)
{
// Try to detect file paths
String path = node.GetText();
if (FileSystem::FileExists(path))
{
files.Add(MoveTemp(path));
}
}
}
}
void JsonAssetBase::GetReferences(const StringAnsiView& json, Array<Guid>& output)
void JsonAssetBase::GetReferences(const StringAnsiView& json, Array<Guid>& assets)
{
ISerializable::SerializeDocument document;
document.Parse(json.Get(), json.Length());
if (document.HasParseError())
return;
FindIds(document, output);
Array<String> files;
FindIds(document, assets, files);
}
bool JsonAssetBase::Save(const StringView& path) const
@@ -207,7 +222,7 @@ bool JsonAssetBase::Save(JsonWriter& writer) const
return false;
}
void JsonAssetBase::GetReferences(Array<Guid>& output) const
void JsonAssetBase::GetReferences(Array<Guid>& assets, Array<String>& files) const
{
if (Data == nullptr)
return;
@@ -219,7 +234,7 @@ void JsonAssetBase::GetReferences(Array<Guid>& output) const
// It produces many invalid ids (like refs to scene objects).
// But it's super fast, super low-memory and doesn't involve any advanced systems integration.
FindIds(*Data, output);
FindIds(*Data, assets, files);
}
#endif

View File

@@ -70,8 +70,8 @@ public:
/// Parses Json string to find any object references inside it. It can produce list of references to assets and/or scene objects. Supported only in Editor.
/// </summary>
/// <param name="json">The Json string.</param>
/// <param name="output">The output list of object IDs references by the asset (appended, not cleared).</param>
API_FUNCTION() static void GetReferences(const StringAnsiView& json, API_PARAM(Out) Array<Guid, HeapAllocation>& output);
/// <param name="assets">The output list of object IDs references by the asset (appended, not cleared).</param>
API_FUNCTION() static void GetReferences(const StringAnsiView& json, API_PARAM(Out) Array<Guid, HeapAllocation>& assets);
/// <summary>
/// Saves this asset to the file. Supported only in Editor.
@@ -97,7 +97,7 @@ public:
const String& GetPath() const override;
uint64 GetMemoryUsage() const override;
#if USE_EDITOR
void GetReferences(Array<Guid, HeapAllocation>& output) const override;
void GetReferences(Array<Guid, HeapAllocation>& assets, Array<String, HeapAllocation>& files) const override;
#endif
protected:

View File

@@ -11,7 +11,7 @@
enum class FlaxChunkFlags
{
/// <summary>
/// The none.
/// Nothing.
/// </summary>
None = 0,
@@ -19,6 +19,11 @@ enum class FlaxChunkFlags
/// Compress chunk data using LZ4 algorithm.
/// </summary>
CompressedLZ4 = 1,
/// <summary>
/// Prevents chunk file data from being unloaded if unused for a certain amount of time. Runtime-only flag, not saved with the asset.
/// </summary>
KeepInMemory = 2,
};
DECLARE_ENUM_OPERATORS(FlaxChunkFlags);

View File

@@ -943,7 +943,8 @@ bool FlaxStorage::Create(WriteStream* stream, const AssetInitData* data, int32 d
{
FlaxChunk* chunk = chunks[i];
stream->WriteBytes(&chunk->LocationInFile, sizeof(chunk->LocationInFile));
stream->WriteInt32((int32)chunk->Flags);
FlaxChunkFlags flags = chunk->Flags & ~(FlaxChunkFlags::KeepInMemory); // Skip saving runtime-only flags
stream->WriteInt32((int32)flags);
}
#if ASSETS_LOADING_EXTRA_VERIFICATION
@@ -1087,7 +1088,7 @@ bool FlaxStorage::LoadAssetHeader(const Entry& e, AssetInitData& data)
{
int32 chunkIndex;
stream->ReadInt32(&chunkIndex);
if (chunkIndex >= _chunks.Count())
if (chunkIndex < -1 || chunkIndex >= _chunks.Count())
{
LOG(Warning, "Invalid chunks mapping.");
return true;
@@ -1144,7 +1145,7 @@ bool FlaxStorage::LoadAssetHeader(const Entry& e, AssetInitData& data)
{
int32 chunkIndex;
stream->ReadInt32(&chunkIndex);
if (chunkIndex >= _chunks.Count())
if (chunkIndex < -1 || chunkIndex >= _chunks.Count())
{
LOG(Warning, "Invalid chunks mapping.");
return true;
@@ -1199,7 +1200,7 @@ bool FlaxStorage::LoadAssetHeader(const Entry& e, AssetInitData& data)
{
int32 chunkIndex;
stream->ReadInt32(&chunkIndex);
if (chunkIndex >= _chunks.Count())
if (chunkIndex < -1 || chunkIndex >= _chunks.Count())
{
LOG(Warning, "Invalid chunks mapping.");
return true;
@@ -1410,7 +1411,7 @@ void FlaxStorage::Tick(double time)
{
auto chunk = _chunks.Get()[i];
const bool wasUsed = (time - chunk->LastAccessTime) < unusedDataChunksLifetime;
if (!wasUsed && chunk->IsLoaded())
if (!wasUsed && chunk->IsLoaded() && EnumHasNoneFlags(chunk->Flags, FlaxChunkFlags::KeepInMemory))
{
chunk->Unload();
}

View File

@@ -71,10 +71,10 @@ void RepackMeshLightmapUVs(ModelData& data)
auto& lod = data.LODs[lodIndex];
// Build list of meshes with their area
struct LightmapUVsPack : RectPack<LightmapUVsPack, float>
struct LightmapUVsPack : RectPackNode<float>
{
LightmapUVsPack(float x, float y, float width, float height)
: RectPack<LightmapUVsPack, float>(x, y, width, height)
LightmapUVsPack(Size x, Size y, Size width, Size height)
: RectPackNode(x, y, width, height)
{
}
@@ -110,10 +110,11 @@ void RepackMeshLightmapUVs(ModelData& data)
{
bool failed = false;
const float chartsPadding = (4.0f / 256.0f) * atlasSize;
LightmapUVsPack root(chartsPadding, chartsPadding, atlasSize - chartsPadding, atlasSize - chartsPadding);
RectPackAtlas<LightmapUVsPack> atlas;
atlas.Init(atlasSize, atlasSize, chartsPadding);
for (auto& entry : entries)
{
entry.Slot = root.Insert(entry.Size, entry.Size, chartsPadding);
entry.Slot = atlas.Insert(entry.Size, entry.Size);
if (entry.Slot == nullptr)
{
// Failed to insert surface, increase atlas size and try again
@@ -130,7 +131,7 @@ void RepackMeshLightmapUVs(ModelData& data)
for (const auto& entry : entries)
{
Float2 uvOffset(entry.Slot->X * atlasSizeInv, entry.Slot->Y * atlasSizeInv);
Float2 uvScale((entry.Slot->Width - chartsPadding) * atlasSizeInv, (entry.Slot->Height - chartsPadding) * atlasSizeInv);
Float2 uvScale(entry.Slot->Width * atlasSizeInv, entry.Slot->Height * atlasSizeInv);
// TODO: SIMD
for (auto& uv : entry.Mesh->LightmapUVs)
{

View File

@@ -58,7 +58,7 @@ public:
}
template<typename ItemType>
FORCE_INLINE void Occupy(ItemType& item)
FORCE_INLINE void Occupy(ItemType&& item)
{
Memory::MoveItems(&Item, &item, 1);
_state = Occupied;

View File

@@ -5,6 +5,7 @@
#include "Engine/Platform/Platform.h"
#include "Engine/Core/Memory/Memory.h"
#include "Engine/Core/Memory/Allocation.h"
#include "Engine/Core/Math/Math.h"
/// <summary>
/// Template for ring buffer with variable capacity.
@@ -98,4 +99,10 @@ public:
Memory::DestructItems(Get() + Math::Min(_front, _back), _count);
_front = _back = _count = 0;
}
void Release()
{
Clear();
_allocation.Free();
}
};

View File

@@ -325,8 +325,8 @@ public:
Platform::Free(tmp);
}
template<typename T, typename AllocationType = HeapAllocation>
FORCE_INLINE static void MergeSort(Array<T, AllocationType>& data, Array<T, AllocationType>* tmp = nullptr)
template<typename T, typename AllocationType = HeapAllocation, typename TempAllocationType = HeapAllocation>
FORCE_INLINE static void MergeSort(Array<T, AllocationType>& data, Array<T, TempAllocationType>* tmp = nullptr)
{
if (tmp)
tmp->Resize(data.Count());

View File

@@ -21,7 +21,6 @@
#define ALIGN_BEGIN(_align)
#define ALIGN_END(_align) __attribute__( (aligned(_align) ) )
#define OFFSET_OF(X, Y) __builtin_offsetof(X, Y)
#define DEPRECATED [[deprecated]]
#define PRAGMA_DISABLE_DEPRECATION_WARNINGS \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"")
@@ -53,7 +52,6 @@
#define ALIGN_BEGIN(_align)
#define ALIGN_END(_align) __attribute__( (aligned(_align) ) )
#define OFFSET_OF(X, Y) __builtin_offsetof(X, Y)
#define DEPRECATED [[deprecated]]
#define PRAGMA_DISABLE_DEPRECATION_WARNINGS
#define PRAGMA_ENABLE_DEPRECATION_WARNINGS
@@ -80,7 +78,6 @@
#define ALIGN_BEGIN(_align) __declspec(align(_align))
#define ALIGN_END(_align)
#define OFFSET_OF(X, Y) offsetof(X, Y)
#define DEPRECATED __declspec(deprecated)
#undef __PRETTY_FUNCTION__
#define __PRETTY_FUNCTION__ __FUNCSIG__
#define PRAGMA_DISABLE_DEPRECATION_WARNINGS \
@@ -98,7 +95,12 @@
#endif
#define PACK_STRUCT(__Declaration__) PACK_BEGIN() __Declaration__ PACK_END()
#define PACK_STRUCT(_declaration) PACK_BEGIN() _declaration PACK_END()
#define _DEPRECATED_0() [[deprecated]]
#define _DEPRECATED_1(msg) [[deprecated(msg)]]
#define _DEPRECATED(_0, _1, LASTARG, ...) LASTARG
#define DEPRECATED(...) _DEPRECATED(, ##__VA_ARGS__, _DEPRECATED_1(__VA_ARGS__), _DEPRECATED_0())
// C++ 17
#if __cplusplus >= 201703L

View File

@@ -61,9 +61,10 @@ public:
/// <summary>
/// Enables cascades splits blending for directional light shadows.
/// [Deprecated in v1.9]
/// </summary>
API_FIELD(Attributes="EditorOrder(1320), DefaultValue(false), EditorDisplay(\"Quality\", \"Allow CSM Blending\")")
bool AllowCSMBlending = false;
DEPRECATED() bool AllowCSMBlending = false;
/// <summary>
/// Default probes cubemap resolution (use for Environment Probes, can be overriden per-actor).
@@ -83,6 +84,12 @@ public:
API_FIELD(Attributes="EditorOrder(2000), EditorDisplay(\"Global SDF\")")
bool EnableGlobalSDF = false;
/// <summary>
/// Draw distance of the Global SDF. Actual value can be large when using DDGI.
/// </summary>
API_FIELD(Attributes="EditorOrder(2001), EditorDisplay(\"Global SDF\"), Limit(1000), ValueCategory(Utils.ValueCategory.Distance)")
float GlobalSDFDistance = 15000.0f;
/// <summary>
/// The Global SDF quality. Controls the volume texture resolution and amount of cascades to use.
/// </summary>
@@ -109,6 +116,12 @@ public:
API_FIELD(Attributes="EditorOrder(2120), Limit(50, 1000), EditorDisplay(\"Global Illumination\")")
float GIProbesSpacing = 100;
/// <summary>
/// Enables cascades splits blending for Global Illumination.
/// </summary>
API_FIELD(Attributes="EditorOrder(2125), DefaultValue(false), EditorDisplay(\"Global Illumination\", \"GI Cascades Blending\")")
bool GICascadesBlending = false;
/// <summary>
/// The Global Surface Atlas resolution. Adjust it if atlas `flickers` due to overflow (eg. to 4096).
/// </summary>

View File

@@ -23,3 +23,8 @@
#define OUT_OF_MEMORY Platform::OutOfMemory(__LINE__, __FILE__)
#define MISSING_CODE(info) Platform::MissingCode(__LINE__, __FILE__, info)
#define NON_COPYABLE(type) type(type&&) = delete; type(const type&) = delete; type& operator=(const type&) = delete; type& operator=(type&&) = delete;
#define POD_COPYABLE(type) \
type(const type& other) { Platform::MemoryCopy(this, &other, sizeof(type)); } \
type(type&& other) noexcept { Platform::MemoryCopy(this, &other, sizeof(type)); } \
type& operator=(const type& other) { Platform::MemoryCopy(this, &other, sizeof(type)); return *this; } \
type& operator=(type&& other) noexcept { Platform::MemoryCopy(this, &other, sizeof(type)); return *this; }

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