Merge remote-tracking branch 'origin/1.9'
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
28
Source/Editor/Content/Items/VideoItem.cs
Normal file
28
Source/Editor/Content/Items/VideoItem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
48
Source/Editor/Content/Proxy/VideoProxy.cs
Normal file
48
Source/Editor/Content/Proxy/VideoProxy.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -325,9 +325,7 @@ bool AndroidPlatformTools::OnPostProcess(CookingData& data)
|
||||
|
||||
const auto buildSettings = BuildSettings::Get();
|
||||
if (buildSettings->SkipPackaging)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
GameCooker::PackageFiles();
|
||||
|
||||
// Validate environment variables
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>());
|
||||
|
||||
@@ -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)
|
||||
|
||||
69
Source/Editor/SceneGraph/Actors/VideoPlayerEditor.cs
Normal file
69
Source/Editor/SceneGraph/Actors/VideoPlayerEditor.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
234
Source/Editor/Windows/Assets/VideoWindow.cs
Normal file
234
Source/Editor/Windows/Assets/VideoWindow.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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.")]
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user