Compare commits
3 Commits
2cbe1ca789
...
b84ee586bf
| Author | SHA1 | Date | |
|---|---|---|---|
| b84ee586bf | |||
| 52dfa12e84 | |||
| 326aee2b1c |
@@ -209,8 +209,6 @@ internal class MeshAssetHandler : IImportableAssetHandler, IPackableAssetHandler
|
|||||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly JobScheduler _jobScheduler = EditorApplication.GetService<EngineCore>().JobScheduler;
|
|
||||||
|
|
||||||
public IAssetSettings? CreateDefaultSettings(string ext)
|
public IAssetSettings? CreateDefaultSettings(string ext)
|
||||||
{
|
{
|
||||||
if (string.Equals(ext, ".obj", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(ext, ".obj", StringComparison.OrdinalIgnoreCase))
|
||||||
@@ -264,10 +262,12 @@ internal class MeshAssetHandler : IImportableAssetHandler, IPackableAssetHandler
|
|||||||
var meshSettings = ResolveSettings(sourcePath, settings);
|
var meshSettings = ResolveSettings(sourcePath, settings);
|
||||||
|
|
||||||
using var root = new MeshNode();
|
using var root = new MeshNode();
|
||||||
|
var result = await MeshProcessor.ParseMeshAsync(root, sourcePath, AllocationHandle.TLSF, meshSettings, token).ConfigureAwait(false);
|
||||||
|
|
||||||
var parseJob = new MeshParsingJob(root, sourcePath, AllocationHandle.Persistent, meshSettings);
|
if (result.IsFailure)
|
||||||
var handle = _jobScheduler.Schedule(in parseJob);
|
{
|
||||||
await _jobScheduler.WaitAsync(handle, token);
|
return Result.Failure(result.Message);
|
||||||
|
}
|
||||||
|
|
||||||
var manifest = new ModelManifest
|
var manifest = new ModelManifest
|
||||||
{
|
{
|
||||||
@@ -376,8 +376,8 @@ internal class MeshAssetHandler : IImportableAssetHandler, IPackableAssetHandler
|
|||||||
|
|
||||||
private async ValueTask<(int materialSlotCount, int lodLevelCount)> WriteMeshContentAsync(string targetPath, GeometryMeshNode geometry, CancellationToken token)
|
private async ValueTask<(int materialSlotCount, int lodLevelCount)> WriteMeshContentAsync(string targetPath, GeometryMeshNode geometry, CancellationToken token)
|
||||||
{
|
{
|
||||||
using var meshletData = await MeshProcessor.BuildMeshletsAsync(_jobScheduler, geometry.Vertices, geometry.Indices, geometry.MaterialParts, token).ConfigureAwait(false);
|
using var meshletData = await MeshProcessor.BuildMeshletsAsync(geometry.Vertices, geometry.Indices, geometry.MaterialParts, token).ConfigureAwait(false);
|
||||||
await MeshProcessor.BuildClusterLodHierarchyAsync(_jobScheduler, meshletData.Share(), token).ConfigureAwait(false);
|
await MeshProcessor.BuildClusterLodHierarchyAsync(meshletData.Share(), token).ConfigureAwait(false);
|
||||||
|
|
||||||
var bounds = ComputeBounds(geometry.Vertices);
|
var bounds = ComputeBounds(geometry.Vertices);
|
||||||
var header = new MeshContentHeader
|
var header = new MeshContentHeader
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using Ghost.Graphics.RHI;
|
|||||||
using Ghost.Graphics.Utilities;
|
using Ghost.Graphics.Utilities;
|
||||||
using Ghost.MeshOptimizer;
|
using Ghost.MeshOptimizer;
|
||||||
using Ghost.Ufbx;
|
using Ghost.Ufbx;
|
||||||
using Misaki.HighPerformance.Jobs;
|
|
||||||
using Misaki.HighPerformance.LowLevel;
|
using Misaki.HighPerformance.LowLevel;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
@@ -15,7 +14,7 @@ using System.Text;
|
|||||||
|
|
||||||
namespace Ghost.Editor.Core.Assets;
|
namespace Ghost.Editor.Core.Assets;
|
||||||
|
|
||||||
internal readonly unsafe struct MeshParsingJob : IJob
|
internal unsafe class MeshParsingJob
|
||||||
{
|
{
|
||||||
private struct GeometryPart : IDisposable
|
private struct GeometryPart : IDisposable
|
||||||
{
|
{
|
||||||
@@ -38,12 +37,18 @@ internal readonly unsafe struct MeshParsingJob : IJob
|
|||||||
private readonly AllocationHandle _allocationHandle;
|
private readonly AllocationHandle _allocationHandle;
|
||||||
private readonly MeshAssetSettings _settings;
|
private readonly MeshAssetSettings _settings;
|
||||||
|
|
||||||
|
private readonly TaskCompletionSource<Result> _taskCompletionSource;
|
||||||
|
|
||||||
|
public Task<Result> Task => _taskCompletionSource.Task;
|
||||||
|
|
||||||
public MeshParsingJob(MeshNode rootNode, string filePath, AllocationHandle allocationHandle, MeshAssetSettings settings)
|
public MeshParsingJob(MeshNode rootNode, string filePath, AllocationHandle allocationHandle, MeshAssetSettings settings)
|
||||||
{
|
{
|
||||||
_rootNode = rootNode;
|
_rootNode = rootNode;
|
||||||
_filePath = filePath;
|
_filePath = filePath;
|
||||||
_allocationHandle = allocationHandle;
|
_allocationHandle = allocationHandle;
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
|
|
||||||
|
_taskCompletionSource = new TaskCompletionSource<Result>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -319,7 +324,7 @@ internal readonly unsafe struct MeshParsingJob : IJob
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Execute(ref readonly JobExecutionContext context)
|
public Result Execute()
|
||||||
{
|
{
|
||||||
var error = new ufbx_error();
|
var error = new ufbx_error();
|
||||||
var load_Opts = new ufbx_load_opts
|
var load_Opts = new ufbx_load_opts
|
||||||
@@ -352,15 +357,20 @@ internal readonly unsafe struct MeshParsingJob : IJob
|
|||||||
using var scene = new DisposablePtr<ufbx_scene>(ufbx_scene.LoadFile((sbyte*)str.GetUnsafePtr(), &load_Opts, &error));
|
using var scene = new DisposablePtr<ufbx_scene>(ufbx_scene.LoadFile((sbyte*)str.GetUnsafePtr(), &load_Opts, &error));
|
||||||
if (scene.Get() == null)
|
if (scene.Get() == null)
|
||||||
{
|
{
|
||||||
Logger.Error(error.description.ToString());
|
return Result.Failure(error.description.ToString());
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ParseHierarchy(scene.Get()->root_node, _rootNode, AllocationHandle.TLSF);
|
ParseHierarchy(scene.Get()->root_node, _rootNode, AllocationHandle.TLSF);
|
||||||
|
|
||||||
|
return Result.Success();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal partial class MeshProcessor
|
internal static partial class MeshProcessor
|
||||||
{
|
{
|
||||||
|
public static Task<Result> ParseMeshAsync(MeshNode root, string sourcePath, AllocationHandle allocationHandle, MeshAssetSettings meshSettings, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var parseJob = new MeshParsingJob(root, sourcePath, allocationHandle, meshSettings);
|
||||||
|
return Task.Run(parseJob.Execute, token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using Misaki.HighPerformance.Mathematics;
|
|||||||
using Misaki.HighPerformance.Mathematics.Geometry;
|
using Misaki.HighPerformance.Mathematics.Geometry;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using TerraFX.Interop.Windows;
|
||||||
|
|
||||||
namespace Ghost.Editor.Core.Assets;
|
namespace Ghost.Editor.Core.Assets;
|
||||||
|
|
||||||
@@ -785,15 +786,14 @@ internal static unsafe partial class MeshProcessor
|
|||||||
|
|
||||||
internal static partial class MeshProcessor
|
internal static partial class MeshProcessor
|
||||||
{
|
{
|
||||||
|
private class MeshletBuildJob
|
||||||
private struct MeshletBuildJob : IJob
|
|
||||||
{
|
{
|
||||||
public ClodConfig clodConfig;
|
public ClodConfig clodConfig;
|
||||||
public ClodMesh clodMesh;
|
public ClodMesh clodMesh;
|
||||||
|
|
||||||
public MeshletContext context;
|
public MeshletContext context;
|
||||||
|
|
||||||
public readonly void Execute(ref readonly JobExecutionContext ctx)
|
public void Execute()
|
||||||
{
|
{
|
||||||
Build(in clodConfig, in clodMesh, context, MeshletOutputCallback);
|
Build(in clodConfig, in clodMesh, context, MeshletOutputCallback);
|
||||||
}
|
}
|
||||||
@@ -804,9 +804,7 @@ internal static partial class MeshProcessor
|
|||||||
/// Each <see cref="MaterialPartInfo"/> describes a material partition's index range within the unified buffer.
|
/// Each <see cref="MaterialPartInfo"/> describes a material partition's index range within the unified buffer.
|
||||||
/// Meshlets are built per-part and tagged with the corresponding <c>localMaterialIndex</c>.
|
/// Meshlets are built per-part and tagged with the corresponding <c>localMaterialIndex</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static async Task<DisposablePtr<MeshletMeshData>> BuildMeshletsAsync(JobScheduler jobScheduler,
|
public static async Task<DisposablePtr<MeshletMeshData>> BuildMeshletsAsync(ReadOnlyView<Vertex> vertices, ReadOnlyView<uint> indices, ReadOnlyView<MaterialPartInfo> parts, CancellationToken token)
|
||||||
ReadOnlyView<Vertex> vertices, ReadOnlyView<uint> indices, ReadOnlyView<MaterialPartInfo> parts,
|
|
||||||
CancellationToken token)
|
|
||||||
{
|
{
|
||||||
Logger.DebugAssert(vertices.Count > 0, "Mesh must have vertices to build meshlets.");
|
Logger.DebugAssert(vertices.Count > 0, "Mesh must have vertices to build meshlets.");
|
||||||
Logger.DebugAssert(indices.Count > 0, "Mesh must have indices to build meshlets.");
|
Logger.DebugAssert(indices.Count > 0, "Mesh must have indices to build meshlets.");
|
||||||
@@ -836,8 +834,6 @@ internal static partial class MeshProcessor
|
|||||||
simplifyFallbackSloppy = true,
|
simplifyFallbackSloppy = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
var jobs = new MeshletBuildJob[parts.Length];
|
|
||||||
|
|
||||||
IntPtr meshletData;
|
IntPtr meshletData;
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
@@ -851,6 +847,7 @@ internal static partial class MeshProcessor
|
|||||||
for (var i = 0; i < parts.Length; i++)
|
for (var i = 0; i < parts.Length; i++)
|
||||||
{
|
{
|
||||||
ref readonly var part = ref parts[i];
|
ref readonly var part = ref parts[i];
|
||||||
|
MeshletBuildJob job;
|
||||||
|
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
@@ -874,21 +871,15 @@ internal static partial class MeshProcessor
|
|||||||
materialIndex = part.materialIndex
|
materialIndex = part.materialIndex
|
||||||
};
|
};
|
||||||
|
|
||||||
var job = new MeshletBuildJob
|
job = new MeshletBuildJob
|
||||||
{
|
{
|
||||||
clodConfig = config,
|
clodConfig = config,
|
||||||
clodMesh = clodMesh,
|
clodMesh = clodMesh,
|
||||||
context = context
|
context = context
|
||||||
};
|
};
|
||||||
|
|
||||||
jobs[i] = job;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var job in jobs)
|
await Task.Run(job.Execute, token);
|
||||||
{
|
|
||||||
var handle = jobScheduler.Schedule(in job);
|
|
||||||
await jobScheduler.WaitAsync(handle, token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe
|
unsafe
|
||||||
@@ -1176,20 +1167,19 @@ internal static partial class MeshProcessor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe struct BuildClusterLodHierarchyJob : IJob
|
private unsafe class BuildClusterLodHierarchyJob
|
||||||
{
|
{
|
||||||
public MeshletMeshData* meshletData;
|
public MeshletMeshData* meshletData;
|
||||||
|
|
||||||
public readonly void Execute(ref readonly JobExecutionContext ctx)
|
public void Execute()
|
||||||
{
|
{
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
using var meshletIndices = new UnsafeArray<int>(meshletData->meshletCount, AllocationHandle.TLSF);
|
||||||
using var meshletIndices = new UnsafeArray<int>(meshletData->meshletCount, scope.AllocationHandle);
|
|
||||||
for (var i = 0; i < meshletData->meshletCount; i++)
|
for (var i = 0; i < meshletData->meshletCount; i++)
|
||||||
{
|
{
|
||||||
meshletIndices[i] = i;
|
meshletIndices[i] = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
var binaryNodes = new UnsafeList<TempBinaryNode>(meshletData->meshletCount * 2, scope.AllocationHandle);
|
var binaryNodes = new UnsafeList<TempBinaryNode>(meshletData->meshletCount * 2, AllocationHandle.TLSF);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -1240,14 +1230,13 @@ internal static partial class MeshProcessor
|
|||||||
/// Builds a cluster LOD hierarchy from the input meshlet data.
|
/// Builds a cluster LOD hierarchy from the input meshlet data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="meshletData">The meshlet data.</param>
|
/// <param name="meshletData">The meshlet data.</param>
|
||||||
public static async Task BuildClusterLodHierarchyAsync(JobScheduler jobScheduler, SharedPtr<MeshletMeshData> meshletData, CancellationToken token)
|
public static Task BuildClusterLodHierarchyAsync(SharedPtr<MeshletMeshData> meshletData, CancellationToken token)
|
||||||
{
|
{
|
||||||
if (meshletData.GetRef().meshletCount == 0)
|
if (meshletData.GetRef().meshletCount == 0)
|
||||||
{
|
{
|
||||||
return;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
JobHandle handle;
|
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
var job = new BuildClusterLodHierarchyJob
|
var job = new BuildClusterLodHierarchyJob
|
||||||
@@ -1255,9 +1244,7 @@ internal static partial class MeshProcessor
|
|||||||
meshletData = meshletData.Get()
|
meshletData = meshletData.Get()
|
||||||
};
|
};
|
||||||
|
|
||||||
handle = jobScheduler.Schedule(in job);
|
return Task.Run(job.Execute, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
await jobScheduler.WaitAsync(handle, token);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,4 +10,7 @@ public interface IInspectable
|
|||||||
UIElement? CreateHeader();
|
UIElement? CreateHeader();
|
||||||
|
|
||||||
IInspectorModel CreateInspectorModel();
|
IInspectorModel CreateInspectorModel();
|
||||||
|
|
||||||
|
// void OnSelected();
|
||||||
|
// void OnDeselected();
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using Ghost.Editor.Core.Utilities;
|
using Ghost.Editor.Core.Utilities;
|
||||||
using Microsoft.UI.Dispatching;
|
using Microsoft.UI.Dispatching;
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace Ghost.Editor.Core;
|
namespace Ghost.Editor.Core;
|
||||||
|
|
||||||
@@ -89,12 +90,25 @@ public static class EditorApplication
|
|||||||
public static T GetService<T>()
|
public static T GetService<T>()
|
||||||
where T : class
|
where T : class
|
||||||
{
|
{
|
||||||
if (s_serviceProvider?.GetService(typeof(T)) is not T service)
|
if (TryGetService<T>(out var service))
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"{typeof(T)} needs to be registered in ConfigureServices.");
|
return service;
|
||||||
}
|
}
|
||||||
|
|
||||||
return service;
|
throw new ArgumentException("Requested service of type " + typeof(T).FullName + " is not registered.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryGetService<T>([NotNullWhen(true)] out T? service)
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
if (s_serviceProvider?.GetService(typeof(T)) is T resolvedService)
|
||||||
|
{
|
||||||
|
service = resolvedService;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
service = null;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void Shutdown()
|
internal static void Shutdown()
|
||||||
|
|||||||
@@ -3,14 +3,13 @@ using Ghost.Editor.Core.SceneGraph;
|
|||||||
using Ghost.Engine;
|
using Ghost.Engine;
|
||||||
using Ghost.Engine.Core;
|
using Ghost.Engine.Core;
|
||||||
using Ghost.Entities;
|
using Ghost.Entities;
|
||||||
|
using Misaki.HighPerformance.Jobs;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
namespace Ghost.Editor.Core.Services;
|
namespace Ghost.Editor.Core.Services;
|
||||||
|
|
||||||
public class EditorWorldService : IDisposable
|
public class EditorWorldService : IDisposable
|
||||||
{
|
{
|
||||||
private const int DEFAULT_ENTITY_CAPACITY = 1024;
|
|
||||||
|
|
||||||
public World EditorWorld
|
public World EditorWorld
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
@@ -27,9 +26,9 @@ public class EditorWorldService : IDisposable
|
|||||||
public event Action<Entity, string>? EntityNameChanged;
|
public event Action<Entity, string>? EntityNameChanged;
|
||||||
public event Action? SceneGraphRebuilt;
|
public event Action? SceneGraphRebuilt;
|
||||||
|
|
||||||
public EditorWorldService()
|
public EditorWorldService(JobScheduler? jobScheduler = null)
|
||||||
{
|
{
|
||||||
EditorWorld = World.Create(entityCapacity: DEFAULT_ENTITY_CAPACITY);
|
EditorWorld = World.Create(jobScheduler, 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Entity CreateEntity(string name, ushort sceneID, Entity parent = default)
|
public Entity CreateEntity(string name, ushort sceneID, Entity parent = default)
|
||||||
|
|||||||
@@ -55,9 +55,16 @@ internal sealed partial class ImportCoordinator : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask EnqueueAsync(ImportJob job, CancellationToken token = default)
|
public ValueTask EnqueueAsync(ImportJob job, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return _importChannel.Writer.WriteAsync(job, token);
|
return _importChannel.Writer.WriteAsync(job, token);
|
||||||
}
|
}
|
||||||
|
catch (ChannelClosedException)
|
||||||
|
{
|
||||||
|
return ValueTask.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task WorkerLoop(CancellationToken token)
|
private async Task WorkerLoop(CancellationToken token)
|
||||||
{
|
{
|
||||||
@@ -209,6 +216,15 @@ internal sealed partial class ImportCoordinator : IDisposable
|
|||||||
{
|
{
|
||||||
_importChannel.Writer.TryComplete();
|
_importChannel.Writer.TryComplete();
|
||||||
_cts.Cancel();
|
_cts.Cancel();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Task.WaitAll(_workers);
|
||||||
|
}
|
||||||
|
catch (AggregateException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
_cts.Dispose();
|
_cts.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,13 +46,6 @@
|
|||||||
<Project Path="Runtime/Ghost.Graphics/Ghost.Graphics.csproj" />
|
<Project Path="Runtime/Ghost.Graphics/Ghost.Graphics.csproj" />
|
||||||
</Folder>
|
</Folder>
|
||||||
<Folder Name="/Test/">
|
<Folder Name="/Test/">
|
||||||
<Project Path="Test/Ghost.Entities.Test/Ghost.Entities.Test.csproj" />
|
|
||||||
<Project Path="Test/Ghost.Graphics.Test/Ghost.Graphics.Test.csproj">
|
|
||||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
|
||||||
<Platform Solution="*|x64" Project="x64" />
|
|
||||||
<Platform Solution="*|x86" Project="x86" />
|
|
||||||
<Deploy />
|
|
||||||
</Project>
|
|
||||||
<Project Path="Test/Ghost.MicroTest/Ghost.MicroTest.csproj" Id="8c8ffa4b-e1e4-46a1-9221-7b508a109edd" />
|
<Project Path="Test/Ghost.MicroTest/Ghost.MicroTest.csproj" Id="8c8ffa4b-e1e4-46a1-9221-7b508a109edd" />
|
||||||
<Project Path="Test/Ghost.Shader.Test/Ghost.Shader.Test.csproj" />
|
<Project Path="Test/Ghost.Shader.Test/Ghost.Shader.Test.csproj" />
|
||||||
<Project Path="Test/Ghost.TestCore/Ghost.TestCore.csproj" />
|
<Project Path="Test/Ghost.TestCore/Ghost.TestCore.csproj" />
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ public sealed partial class EngineCore : IDisposable
|
|||||||
private readonly RenderSystem _renderSystem;
|
private readonly RenderSystem _renderSystem;
|
||||||
private readonly AssetManager _assetManager;
|
private readonly AssetManager _assetManager;
|
||||||
|
|
||||||
internal JobScheduler JobScheduler => _jobScheduler;
|
public JobScheduler JobScheduler => _jobScheduler;
|
||||||
internal RenderSystem RenderSystem => _renderSystem;
|
public RenderSystem RenderSystem => _renderSystem;
|
||||||
internal AssetManager AssetManager => _assetManager;
|
public AssetManager AssetManager => _assetManager;
|
||||||
|
|
||||||
public EngineCore(IContentProvider contentProvider, IShaderCompilationBridge? shaderCompilationBridge = null)
|
public EngineCore(IContentProvider contentProvider, IShaderCompilationBridge? shaderCompilationBridge = null)
|
||||||
{
|
{
|
||||||
@@ -55,6 +55,11 @@ public sealed partial class EngineCore : IDisposable
|
|||||||
_assetManager = new AssetManager(_renderSystem.GraphicsEngine.ResourceDatabase, _renderSystem.ResourceManager, _contentProvider, _streamingProcessor, _jobScheduler);
|
_assetManager = new AssetManager(_renderSystem.GraphicsEngine.ResourceDatabase, _renderSystem.ResourceManager, _contentProvider, _streamingProcessor, _jobScheduler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void Start()
|
||||||
|
{
|
||||||
|
_renderSystem.Start();
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_assetManager.Dispose();
|
_assetManager.Dispose();
|
||||||
@@ -62,11 +67,3 @@ public sealed partial class EngineCore : IDisposable
|
|||||||
_jobScheduler.Dispose();
|
_jobScheduler.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[GenerateShaderProperty("TestShader")]
|
|
||||||
public partial struct TestShaderProperty
|
|
||||||
{
|
|
||||||
public Texture2DHandle texture;
|
|
||||||
public uint someValue;
|
|
||||||
public float3 otherValue;
|
|
||||||
}
|
|
||||||
@@ -293,6 +293,8 @@ internal unsafe struct Archetype : IDisposable
|
|||||||
|
|
||||||
if (sharedInfos.Count > 0)
|
if (sharedInfos.Count > 0)
|
||||||
{
|
{
|
||||||
|
sharedInfos.AsSpan().Sort(static (a, b) => a.id.Value.CompareTo(b.id.Value));
|
||||||
|
|
||||||
var offset = 0;
|
var offset = 0;
|
||||||
|
|
||||||
_sharedLayouts = new UnsafeArray<SharedComponentLayout>(sharedInfos.Count, AllocationHandle.Persistent);
|
_sharedLayouts = new UnsafeArray<SharedComponentLayout>(sharedInfos.Count, AllocationHandle.Persistent);
|
||||||
@@ -431,7 +433,7 @@ internal unsafe struct Archetype : IDisposable
|
|||||||
for (var i = 0; i < _chunkGroups.Count; i++)
|
for (var i = 0; i < _chunkGroups.Count; i++)
|
||||||
{
|
{
|
||||||
var group = _chunkGroups[i];
|
var group = _chunkGroups[i];
|
||||||
if (group.sharedDataHash == sharedDataHash)
|
if (group.sharedDataHash == sharedDataHash && group.sharedData.AsSpan().SequenceEqual(sharedData))
|
||||||
{
|
{
|
||||||
groupIndex = i;
|
groupIndex = i;
|
||||||
group.refCount++;
|
group.refCount++;
|
||||||
@@ -499,7 +501,7 @@ internal unsafe struct Archetype : IDisposable
|
|||||||
for (var i = 0; i < _chunkGroups.Count; i++)
|
for (var i = 0; i < _chunkGroups.Count; i++)
|
||||||
{
|
{
|
||||||
var group = _chunkGroups[i];
|
var group = _chunkGroups[i];
|
||||||
if (group.sharedDataHash == sharedDataHash)
|
if (group.sharedDataHash == sharedDataHash && group.sharedData.AsSpan().SequenceEqual(sharedData))
|
||||||
{
|
{
|
||||||
groupIndex = i;
|
groupIndex = i;
|
||||||
group.refCount++;
|
group.refCount++;
|
||||||
|
|||||||
@@ -38,7 +38,15 @@ internal struct ComponentInfo
|
|||||||
public static class ComponentTypeID<T>
|
public static class ComponentTypeID<T>
|
||||||
where T : unmanaged, IComponent
|
where T : unmanaged, IComponent
|
||||||
{
|
{
|
||||||
public static readonly Identifier<IComponent> Value = ComponentRegistry.GetOrRegisterComponentID<T>();
|
public static readonly Identifier<IComponent> Value;
|
||||||
|
public static readonly bool IsShared = typeof(ISharedComponent).IsAssignableFrom(typeof(T));
|
||||||
|
public static readonly bool IsEnableable = typeof(IEnableableComponent).IsAssignableFrom(typeof(T));
|
||||||
|
public static readonly bool IsCleanup = typeof(ICleanupComponent).IsAssignableFrom(typeof(T));
|
||||||
|
|
||||||
|
static ComponentTypeID()
|
||||||
|
{
|
||||||
|
Value = ComponentRegistry.GetOrRegisterComponentID<T>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static class ComponentRegistry
|
internal static class ComponentRegistry
|
||||||
@@ -341,32 +349,97 @@ public partial class ComponentManager : IDisposable
|
|||||||
|
|
||||||
public struct SharedComponentSet : IDisposable
|
public struct SharedComponentSet : IDisposable
|
||||||
{
|
{
|
||||||
|
private struct Element : IComparable<Element>
|
||||||
|
{
|
||||||
|
public int componentID;
|
||||||
|
public int offset;
|
||||||
|
public int size;
|
||||||
|
|
||||||
|
public readonly int CompareTo(Element other) => componentID.CompareTo(other.componentID);
|
||||||
|
}
|
||||||
|
|
||||||
private BufferWriter _writer;
|
private BufferWriter _writer;
|
||||||
|
private UnsafeList<Element> _elements;
|
||||||
|
private bool _isSorted;
|
||||||
|
|
||||||
public SharedComponentSet(int capacity, AllocationHandle allocationHandle)
|
public SharedComponentSet(int capacity, AllocationHandle allocationHandle)
|
||||||
{
|
{
|
||||||
_writer = new BufferWriter(capacity, allocationHandle);
|
_writer = new BufferWriter(capacity, allocationHandle);
|
||||||
|
_elements = new UnsafeList<Element>(4, allocationHandle);
|
||||||
|
_isSorted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void With<T>(scoped in T data)
|
public void With<T>(scoped in T data)
|
||||||
where T : unmanaged, ISharedComponent
|
where T : unmanaged, ISharedComponent
|
||||||
{
|
{
|
||||||
|
var id = ComponentTypeID<T>.Value.Value;
|
||||||
|
var offset = _writer.Position;
|
||||||
|
int size;
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
size = sizeof(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_elements.Count > 0 && _elements[_elements.Count - 1].componentID > id)
|
||||||
|
{
|
||||||
|
_isSorted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_elements.Add(new Element
|
||||||
|
{
|
||||||
|
componentID = id,
|
||||||
|
offset = offset,
|
||||||
|
size = size
|
||||||
|
});
|
||||||
|
|
||||||
_writer.Write(in data);
|
_writer.Write(in data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly ReadOnlySpan<byte> AsSpan()
|
public ReadOnlySpan<byte> AsSpan()
|
||||||
{
|
{
|
||||||
|
if (!_isSorted && _elements.Count > 1)
|
||||||
|
{
|
||||||
|
SortData();
|
||||||
|
}
|
||||||
|
|
||||||
return _writer.AsSpan();
|
return _writer.AsSpan();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SortData()
|
||||||
|
{
|
||||||
|
_elements.AsSpan().Sort();
|
||||||
|
|
||||||
|
using var tempBuffer = new UnsafeArray<byte>(_writer.Position, AllocationHandle.Temp);
|
||||||
|
var srcSpan = _writer.AsSpan();
|
||||||
|
var dstSpan = tempBuffer.AsSpan();
|
||||||
|
|
||||||
|
var currentOffset = 0;
|
||||||
|
for (var i = 0; i < _elements.Count; i++)
|
||||||
|
{
|
||||||
|
var el = _elements[i];
|
||||||
|
srcSpan.Slice(el.offset, el.size).CopyTo(dstSpan.Slice(currentOffset, el.size));
|
||||||
|
|
||||||
|
el.offset = currentOffset;
|
||||||
|
_elements[i] = el;
|
||||||
|
|
||||||
|
currentOffset += el.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
dstSpan.CopyTo(srcSpan);
|
||||||
|
_isSorted = true;
|
||||||
|
}
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
_writer.Reset();
|
_writer.Reset();
|
||||||
|
_elements.Clear();
|
||||||
|
_isSorted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_writer.Dispose();
|
_writer.Dispose();
|
||||||
|
_elements.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,26 +488,24 @@ public struct ComponentSet : IDisposable, IEquatable<ComponentSet>
|
|||||||
{
|
{
|
||||||
_components = new UnsafeArray<Identifier<IComponent>>(components.Length, allocationHandle);
|
_components = new UnsafeArray<Identifier<IComponent>>(components.Length, allocationHandle);
|
||||||
components.CopyTo(_components.AsSpan());
|
components.CopyTo(_components.AsSpan());
|
||||||
|
_components.AsSpan().Sort(static (a, b) => a.Value.CompareTo(b.Value));
|
||||||
_hashCode = -1;
|
|
||||||
_sharedHashCode = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ComponentSet(AllocationHandle allocationHandle, ReadOnlySpan<Identifier<IComponent>> components, ReadOnlySpan<byte> sharedData)
|
|
||||||
{
|
|
||||||
_components = new UnsafeArray<Identifier<IComponent>>(components.Length, allocationHandle);
|
|
||||||
components.CopyTo(_components.AsSpan());
|
|
||||||
|
|
||||||
_sharedData = new UnsafeArray<byte>(sharedData.Length, allocationHandle);
|
|
||||||
sharedData.CopyTo(_sharedData);
|
|
||||||
|
|
||||||
_hashCode = -1;
|
_hashCode = -1;
|
||||||
_sharedHashCode = -1;
|
_sharedHashCode = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ComponentSet(AllocationHandle allocationHandle, ReadOnlySpan<Identifier<IComponent>> components, SharedComponentSet sharedComponentSet)
|
public ComponentSet(AllocationHandle allocationHandle, ReadOnlySpan<Identifier<IComponent>> components, SharedComponentSet sharedComponentSet)
|
||||||
: this(allocationHandle, components, sharedComponentSet.AsSpan())
|
|
||||||
{
|
{
|
||||||
|
_components = new UnsafeArray<Identifier<IComponent>>(components.Length, allocationHandle);
|
||||||
|
components.CopyTo(_components.AsSpan());
|
||||||
|
_components.AsSpan().Sort(static (a, b) => a.Value.CompareTo(b.Value));
|
||||||
|
|
||||||
|
var sharedData = sharedComponentSet.AsSpan();
|
||||||
|
_sharedData = new UnsafeArray<byte>(sharedData.Length, allocationHandle);
|
||||||
|
sharedData.CopyTo(_sharedData);
|
||||||
|
|
||||||
|
_hashCode = -1;
|
||||||
|
_sharedHashCode = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -445,7 +516,7 @@ public struct ComponentSet : IDisposable, IEquatable<ComponentSet>
|
|||||||
|
|
||||||
public readonly bool Equals(ComponentSet other)
|
public readonly bool Equals(ComponentSet other)
|
||||||
{
|
{
|
||||||
return _hashCode == other._hashCode && _sharedHashCode == other._sharedHashCode;
|
return _hashCode == other._hashCode && _sharedHashCode == other._sharedHashCode && _components.AsSpan().SequenceEqual(other._components.AsSpan()) && _sharedData.AsSpan().SequenceEqual(other._sharedData.AsSpan());
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
@@ -520,6 +591,10 @@ public ref struct ComponentSetView : IEquatable<ComponentSetView>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="ComponentSetView"/> with the specified component identifiers and no shared component data.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="components">The collection of component identifiers.</param>
|
||||||
public ComponentSetView(ReadOnlySpan<Identifier<IComponent>> components)
|
public ComponentSetView(ReadOnlySpan<Identifier<IComponent>> components)
|
||||||
{
|
{
|
||||||
_components = components;
|
_components = components;
|
||||||
@@ -527,6 +602,14 @@ public ref struct ComponentSetView : IEquatable<ComponentSetView>
|
|||||||
_sharedHashCode = -1;
|
_sharedHashCode = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new instance of <see cref="ComponentSetView"/> with the specified component identifiers and shared component data.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This API does not sort the shared component data internally. Which means [A, B] is different from [B, A] during chunk grouping.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="components">The collection of component identifiers.</param>
|
||||||
|
/// <param name="sharedData">The shared component data.</param>
|
||||||
public ComponentSetView(ReadOnlySpan<Identifier<IComponent>> components, ReadOnlySpan<byte> sharedData)
|
public ComponentSetView(ReadOnlySpan<Identifier<IComponent>> components, ReadOnlySpan<byte> sharedData)
|
||||||
{
|
{
|
||||||
_components = components;
|
_components = components;
|
||||||
@@ -535,6 +618,11 @@ public ref struct ComponentSetView : IEquatable<ComponentSetView>
|
|||||||
_sharedHashCode = -1;
|
_sharedHashCode = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of <see cref="ComponentSetView"/> with the specified component identifiers and shared component set.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="components">The collection of component identifiers.</param>
|
||||||
|
/// <param name="sharedComponentSet">The shared component set.</param>
|
||||||
public ComponentSetView(ReadOnlySpan<Identifier<IComponent>> components, SharedComponentSet sharedComponentSet)
|
public ComponentSetView(ReadOnlySpan<Identifier<IComponent>> components, SharedComponentSet sharedComponentSet)
|
||||||
: this(components, sharedComponentSet.AsSpan())
|
: this(components, sharedComponentSet.AsSpan())
|
||||||
{
|
{
|
||||||
@@ -542,7 +630,7 @@ public ref struct ComponentSetView : IEquatable<ComponentSetView>
|
|||||||
|
|
||||||
public readonly bool Equals(ComponentSetView other)
|
public readonly bool Equals(ComponentSetView other)
|
||||||
{
|
{
|
||||||
return _hashCode == other._hashCode && _sharedHashCode == other._sharedHashCode;
|
return _hashCode == other._hashCode && _sharedHashCode == other._sharedHashCode && _components.SequenceEqual(other._components) && _sharedData.SequenceEqual(other._sharedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#if false
|
||||||
|
|
||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
@@ -235,3 +237,5 @@ internal sealed unsafe class SharedComponentStore : IDisposable
|
|||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -59,6 +59,14 @@ public unsafe partial struct EntityQuery
|
|||||||
private readonly VirtualStack.Scope _scope;
|
private readonly VirtualStack.Scope _scope;
|
||||||
private UnsafeList<int> _changedComponentIDs;
|
private UnsafeList<int> _changedComponentIDs;
|
||||||
|
|
||||||
|
private int _reqCount;
|
||||||
|
private fixed int _reqOffsets[16];
|
||||||
|
private int _reqDisCount;
|
||||||
|
private fixed int _reqDisOffsets[16];
|
||||||
|
private int _rejCount;
|
||||||
|
private fixed int _rejOffsets[16];
|
||||||
|
private bool _requiresFiltering;
|
||||||
|
|
||||||
private ref Archetype _currentArchetype;
|
private ref Archetype _currentArchetype;
|
||||||
private ref Chunk _currentChunk;
|
private ref Chunk _currentChunk;
|
||||||
private byte* _chunkBasePtr;
|
private byte* _chunkBasePtr;
|
||||||
@@ -102,13 +110,50 @@ public unsafe partial struct EntityQuery
|
|||||||
<# if (i > 1) { #>
|
<# if (i > 1) { #>
|
||||||
public QueryItem Current => new(
|
public QueryItem Current => new(
|
||||||
<# for (var j = 0; j < i; j++) { #>
|
<# for (var j = 0; j < i; j++) { #>
|
||||||
ref *(T<#= j #>*)(_compBasePtrs[<#= j #>] + _currentEntityIndex * sizeof(T<#= j #>))<#= j < i - 1 ? "," : "" #>
|
ref (ComponentTypeID<T<#= j #>>.IsShared ? ref ((T<#= j #>*)_compBasePtrs[<#= j #>])[0] : ref ((T<#= j #>*)_compBasePtrs[<#= j #>])[_currentEntityIndex])<#= j < i - 1 ? "," : "" #>
|
||||||
<# } #>
|
<# } #>
|
||||||
);
|
);
|
||||||
<# } else { #>
|
<# } else { #>
|
||||||
public ref T0 Current => ref *(T0*)(_compBasePtrs[0] + _currentEntityIndex * sizeof(T0));
|
public ref T0 Current => ref (ComponentTypeID<T0>.IsShared ? ref ((T0*)_compBasePtrs[0])[0] : ref ((T0*)_compBasePtrs[0])[_currentEntityIndex]);
|
||||||
<# } #>
|
<# } #>
|
||||||
|
|
||||||
|
private void SetArchetype(int index)
|
||||||
|
{
|
||||||
|
_currentArchetypeIndex = index;
|
||||||
|
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[index]);
|
||||||
|
|
||||||
|
_requiresFiltering = RequiresEnableableFiltering(in _currentArchetype, in _mask);
|
||||||
|
if (_requiresFiltering)
|
||||||
|
{
|
||||||
|
_reqCount = 0;
|
||||||
|
var itE = _mask.requireEnabled.GetIterator();
|
||||||
|
while (itE.Next(out var id) && _reqCount < 16)
|
||||||
|
{
|
||||||
|
var layoutResult = _currentArchetype.GetLayout(id);
|
||||||
|
if (layoutResult.Error == Error.None && layoutResult.Value.enableBitsOffset != -1)
|
||||||
|
_reqOffsets[_reqCount++] = layoutResult.Value.enableBitsOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
_reqDisCount = 0;
|
||||||
|
itE = _mask.requireDisabled.GetIterator();
|
||||||
|
while (itE.Next(out var id) && _reqDisCount < 16)
|
||||||
|
{
|
||||||
|
var layoutResult = _currentArchetype.GetLayout(id);
|
||||||
|
if (layoutResult.Error == Error.None && layoutResult.Value.enableBitsOffset != -1)
|
||||||
|
_reqDisOffsets[_reqDisCount++] = layoutResult.Value.enableBitsOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
_rejCount = 0;
|
||||||
|
itE = _mask.rejectIfEnabled.GetIterator();
|
||||||
|
while (itE.Next(out var id) && _rejCount < 16)
|
||||||
|
{
|
||||||
|
var layoutResult = _currentArchetype.GetLayout(id);
|
||||||
|
if (layoutResult.Error == Error.None && layoutResult.Value.enableBitsOffset != -1)
|
||||||
|
_rejOffsets[_rejCount++] = layoutResult.Value.enableBitsOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void SetChunk(int chunkIndex)
|
private void SetChunk(int chunkIndex)
|
||||||
{
|
{
|
||||||
@@ -116,13 +161,21 @@ public unsafe partial struct EntityQuery
|
|||||||
_chunkBasePtr = _currentChunk.GetUnsafePtr();
|
_chunkBasePtr = _currentChunk.GetUnsafePtr();
|
||||||
_currentChunkEntityCount = _currentChunk._count;
|
_currentChunkEntityCount = _currentChunk._count;
|
||||||
|
|
||||||
for (var index = 0; index < <#= i #>; index++)
|
<# for (var index = 0; index < i; index++) { #>
|
||||||
|
if (ComponentTypeID<T<#= index #>>.IsShared)
|
||||||
{
|
{
|
||||||
var layout = _currentArchetype.GetLayout(_compTypeIDs[index])
|
var layout = _currentArchetype.GetSharedLayout(_compTypeIDs[<#= index #>]).GetValueOrThrow();
|
||||||
.GetValueOrThrow();
|
_offsets[<#= index #>] = layout.offset;
|
||||||
_offsets[index] = layout.offset;
|
var sharedSpan = _currentArchetype._chunkGroups[_currentChunk._groupIndex].sharedData.AsSpan();
|
||||||
_compBasePtrs[index] = (long)(_chunkBasePtr + _offsets[index]);
|
_compBasePtrs[<#= index #>] = (long)Unsafe.AsPointer(ref System.Runtime.InteropServices.MemoryMarshal.GetReference(sharedSpan)) + _offsets[<#= index #>];
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var layout = _currentArchetype.GetLayout(_compTypeIDs[<#= index #>]).GetValueOrThrow();
|
||||||
|
_offsets[<#= index #>] = layout.offset;
|
||||||
|
_compBasePtrs[<#= index #>] = (long)(_chunkBasePtr + _offsets[<#= index #>]);
|
||||||
|
}
|
||||||
|
<# } #>
|
||||||
|
|
||||||
for (var i = 0; i < _changedComponentIDs.Count; i++)
|
for (var i = 0; i < _changedComponentIDs.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -138,12 +191,29 @@ public unsafe partial struct EntityQuery
|
|||||||
if (_currentEntityIndex < _currentChunk._count)
|
if (_currentEntityIndex < _currentChunk._count)
|
||||||
{
|
{
|
||||||
var pChunkData = _currentChunk.GetUnsafePtr();
|
var pChunkData = _currentChunk.GetUnsafePtr();
|
||||||
if (IsEntityValid(pChunkData, _currentEntityIndex, in _currentArchetype, in _mask))
|
if (!_requiresFiltering)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
var valid = true;
|
||||||
|
for (var h = 0; h < _reqCount; h++)
|
||||||
|
{
|
||||||
|
if (!CheckBit(pChunkData + _reqOffsets[h], _currentEntityIndex)) { valid = false; break; }
|
||||||
|
}
|
||||||
|
if (!valid) continue;
|
||||||
|
|
||||||
|
for (var h = 0; h < _reqDisCount; h++)
|
||||||
|
{
|
||||||
|
if (CheckBit(pChunkData + _reqDisOffsets[h], _currentEntityIndex)) { valid = false; break; }
|
||||||
|
}
|
||||||
|
if (!valid) continue;
|
||||||
|
|
||||||
|
for (var h = 0; h < _rejCount; h++)
|
||||||
|
{
|
||||||
|
if (CheckBit(pChunkData + _rejOffsets[h], _currentEntityIndex)) { valid = false; break; }
|
||||||
|
}
|
||||||
|
if (valid) return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentChunkIndex++;
|
_currentChunkIndex++;
|
||||||
@@ -158,7 +228,7 @@ public unsafe partial struct EntityQuery
|
|||||||
_currentArchetypeIndex++;
|
_currentArchetypeIndex++;
|
||||||
if (_currentArchetypeIndex < _matchingArchetypes.Count)
|
if (_currentArchetypeIndex < _matchingArchetypes.Count)
|
||||||
{
|
{
|
||||||
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[_currentArchetypeIndex]);
|
SetArchetype(_currentArchetypeIndex);
|
||||||
|
|
||||||
_currentChunkIndex = 0;
|
_currentChunkIndex = 0;
|
||||||
if (_currentArchetype.ChunkCount > 0)
|
if (_currentArchetype.ChunkCount > 0)
|
||||||
@@ -187,7 +257,7 @@ public unsafe partial struct EntityQuery
|
|||||||
|
|
||||||
if (_matchingArchetypes.Count > 0)
|
if (_matchingArchetypes.Count > 0)
|
||||||
{
|
{
|
||||||
_currentArchetype = ref _world.ComponentManager.GetArchetypeReference(_matchingArchetypes[0]);
|
SetArchetype(0);
|
||||||
if (_currentArchetype.ChunkCount > 0)
|
if (_currentArchetype.ChunkCount > 0)
|
||||||
{
|
{
|
||||||
SetChunk(0);
|
SetChunk(0);
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
<#@ import namespace="System.Linq" #>
|
<#@ import namespace="System.Linq" #>
|
||||||
<#@ import namespace="System.Text" #>
|
<#@ import namespace="System.Text" #>
|
||||||
<#@ include file="Helpers.ttinclude" #>
|
<#@ include file="Helpers.ttinclude" #>
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using Ghost.Core;
|
||||||
|
|
||||||
namespace Ghost.Entities;
|
namespace Ghost.Entities;
|
||||||
|
|
||||||
public unsafe partial struct EntityQuery
|
public unsafe partial struct EntityQuery
|
||||||
@@ -42,12 +45,12 @@ public unsafe partial struct EntityQuery
|
|||||||
|
|
||||||
var changedCompCount = 0;
|
var changedCompCount = 0;
|
||||||
|
|
||||||
var it = _mask.writeAccess.GetIterator();
|
var writeIt = _mask.writeAccess.GetIterator();
|
||||||
while (it.Next(out var id))
|
while (writeIt.Next(out var id))
|
||||||
{
|
{
|
||||||
for (var i =0; i < <#= i #>; i++)
|
for (var idx = 0; idx < <#= i #>; idx++)
|
||||||
{
|
{
|
||||||
if (id == compTypeIDs[i])
|
if (id == compTypeIDs[idx])
|
||||||
{
|
{
|
||||||
changedCompIDs[changedCompCount] = id;
|
changedCompIDs[changedCompCount] = id;
|
||||||
changedCompCount++;
|
changedCompCount++;
|
||||||
@@ -56,30 +59,72 @@ public unsafe partial struct EntityQuery
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < _matchingArchetypes.Count; i++)
|
var reqOffsets = stackalloc int[16];
|
||||||
|
var reqDisOffsets = stackalloc int[16];
|
||||||
|
var rejOffsets = stackalloc int[16];
|
||||||
|
|
||||||
|
for (var archIndex = 0; archIndex < _matchingArchetypes.Count; archIndex++)
|
||||||
{
|
{
|
||||||
ref var archetype = ref world.ComponentManager.GetArchetypeReference(_matchingArchetypes[i]);
|
ref var archetype = ref world.ComponentManager.GetArchetypeReference(_matchingArchetypes[archIndex]);
|
||||||
var hasAllComponents = true;
|
var hasAllComponents = true;
|
||||||
for (var index = 0; index < <#= i #>; index++)
|
<# for (var index = 0; index < i; index++) { #>
|
||||||
|
if (ComponentTypeID<T<#= index #>>.IsShared)
|
||||||
{
|
{
|
||||||
var layoutResult = archetype.GetLayout(compTypeIDs[index]);
|
var layoutResult = archetype.GetSharedLayout(compTypeIDs[<#= index #>]);
|
||||||
if (!layoutResult)
|
if (!layoutResult) { hasAllComponents = false; goto skipArchetype; }
|
||||||
|
offsets[<#= index #>] = layoutResult.Value.offset;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
hasAllComponents = false;
|
var layoutResult = archetype.GetLayout(compTypeIDs[<#= index #>]);
|
||||||
break;
|
if (!layoutResult) { hasAllComponents = false; goto skipArchetype; }
|
||||||
|
offsets[<#= index #>] = layoutResult.Value.offset;
|
||||||
}
|
}
|
||||||
|
<# } #>
|
||||||
offsets[index] = layoutResult.Value.offset;
|
skipArchetype:
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasAllComponents)
|
if (!hasAllComponents)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var requiresFiltering = RequiresEnableableFiltering(in archetype, in _mask);
|
||||||
|
|
||||||
|
var reqCount = 0;
|
||||||
|
var reqDisCount = 0;
|
||||||
|
var rejCount = 0;
|
||||||
|
|
||||||
|
if (requiresFiltering)
|
||||||
|
{
|
||||||
|
var itE = _mask.requireEnabled.GetIterator();
|
||||||
|
while (itE.Next(out var id) && reqCount < 16)
|
||||||
|
{
|
||||||
|
var layoutResult = archetype.GetLayout(id);
|
||||||
|
if (layoutResult.Error == Error.None && layoutResult.Value.enableBitsOffset != -1)
|
||||||
|
reqOffsets[reqCount++] = layoutResult.Value.enableBitsOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
itE = _mask.requireDisabled.GetIterator();
|
||||||
|
while (itE.Next(out var id) && reqDisCount < 16)
|
||||||
|
{
|
||||||
|
var layoutResult = archetype.GetLayout(id);
|
||||||
|
if (layoutResult.Error == Error.None && layoutResult.Value.enableBitsOffset != -1)
|
||||||
|
reqDisOffsets[reqDisCount++] = layoutResult.Value.enableBitsOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
itE = _mask.rejectIfEnabled.GetIterator();
|
||||||
|
while (itE.Next(out var id) && rejCount < 16)
|
||||||
|
{
|
||||||
|
var layoutResult = archetype.GetLayout(id);
|
||||||
|
if (layoutResult.Error == Error.None && layoutResult.Value.enableBitsOffset != -1)
|
||||||
|
rejOffsets[rejCount++] = layoutResult.Value.enableBitsOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (var chunkIndex = 0; chunkIndex < archetype.ChunkCount; chunkIndex++)
|
for (var chunkIndex = 0; chunkIndex < archetype.ChunkCount; chunkIndex++)
|
||||||
{
|
{
|
||||||
ref var chunk = ref archetype.GetChunkReference(chunkIndex);
|
ref var chunk = ref archetype.GetChunkReference(chunkIndex);
|
||||||
|
if (chunk._count == 0) continue;
|
||||||
|
|
||||||
var pChunkData = chunk.GetUnsafePtr();
|
var pChunkData = chunk.GetUnsafePtr();
|
||||||
|
|
||||||
for (var j = 0; j < changedCompCount; j++)
|
for (var j = 0; j < changedCompCount; j++)
|
||||||
@@ -87,28 +132,70 @@ public unsafe partial struct EntityQuery
|
|||||||
archetype.MarkChanged(chunkIndex, changedCompIDs[j], globalVersion);
|
archetype.MarkChanged(chunkIndex, changedCompIDs[j], globalVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var index = 0; index < <#= i #>; index++)
|
<# for (var index = 0; index < i; index++) { #>
|
||||||
|
if (ComponentTypeID<T<#= index #>>.IsShared)
|
||||||
{
|
{
|
||||||
basePtrs[index] = pChunkData + offsets[index];
|
var sharedSpan = archetype._chunkGroups[chunk._groupIndex].sharedData.AsSpan();
|
||||||
|
basePtrs[<#= index #>] = (byte*)Unsafe.AsPointer(ref System.Runtime.InteropServices.MemoryMarshal.GetReference(sharedSpan)) + offsets[<#= index #>];
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
basePtrs[<#= index #>] = pChunkData + offsets[<#= index #>];
|
||||||
|
}
|
||||||
|
<# } #>
|
||||||
|
|
||||||
|
if (!requiresFiltering)
|
||||||
|
{
|
||||||
for (var entityIndex = 0; entityIndex < chunk._count; entityIndex++)
|
for (var entityIndex = 0; entityIndex < chunk._count; entityIndex++)
|
||||||
{
|
{
|
||||||
if (!IsEntityValid(pChunkData, entityIndex, in archetype, in _mask))
|
<# if (isForEachWithEntity) { #>
|
||||||
|
var pEntity = (Entity*)(pChunkData + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex));
|
||||||
|
<# } #>
|
||||||
|
action(<# if (isForEachWithEntity) { #>*pEntity, <# } #>
|
||||||
|
<# for (var localIndex = 0; localIndex < i; localIndex++) { #>
|
||||||
|
ref (ComponentTypeID<T<#= localIndex #>>.IsShared ? ref ((T<#= localIndex #>*)basePtrs[<#= localIndex #>])[0] : ref ((T<#= localIndex #>*)basePtrs[<#= localIndex #>])[entityIndex])<#= localIndex < i - 1 ? "," : "" #>
|
||||||
|
<# } #>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
continue;
|
var ulongCount = (chunk._count + 63) / 64;
|
||||||
|
for (var block = 0; block < ulongCount; block++)
|
||||||
|
{
|
||||||
|
var validMask = ulong.MaxValue;
|
||||||
|
var remaining = chunk._count - (block * 64);
|
||||||
|
if (remaining < 64) validMask = (1UL << remaining) - 1UL;
|
||||||
|
|
||||||
|
for (var h = 0; h < reqCount; h++)
|
||||||
|
{
|
||||||
|
validMask &= ((ulong*)(pChunkData + reqOffsets[h]))[block];
|
||||||
}
|
}
|
||||||
|
|
||||||
<# for (var localIndex = 0; localIndex < i; localIndex++) { #>
|
for (var h = 0; h < reqDisCount; h++)
|
||||||
var pComp<#= localIndex #> = (T<#= localIndex #>*)(basePtrs[<#= localIndex #>] + (sizeof(T<#= localIndex #>) * entityIndex));
|
{
|
||||||
<# } #>
|
validMask &= ~((ulong*)(pChunkData + reqDisOffsets[h]))[block];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var h = 0; h < rejCount; h++)
|
||||||
|
{
|
||||||
|
validMask &= ~((ulong*)(pChunkData + rejOffsets[h]))[block];
|
||||||
|
}
|
||||||
|
|
||||||
|
while (validMask != 0)
|
||||||
|
{
|
||||||
|
var bit = System.Numerics.BitOperations.TrailingZeroCount(validMask);
|
||||||
|
var entityIndex = (block * 64) + bit;
|
||||||
|
|
||||||
<# if (isForEachWithEntity) { #>
|
<# if (isForEachWithEntity) { #>
|
||||||
var pEntity = (Entity*)(pChunkData + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex));
|
var pEntity = (Entity*)(pChunkData + archetype.EntityIDsOffset + (sizeof(Entity) * entityIndex));
|
||||||
action(*pEntity, <#= AppendParameters(i, "ref *pComp{0}") #>);
|
|
||||||
<# } else { #>
|
|
||||||
action(<#= AppendParameters(i, "ref *pComp{0}") #>);
|
|
||||||
<# } #>
|
<# } #>
|
||||||
|
action(<# if (isForEachWithEntity) { #>*pEntity, <# } #>
|
||||||
|
<# for (var localIndex = 0; localIndex < i; localIndex++) { #>
|
||||||
|
ref (ComponentTypeID<T<#= localIndex #>>.IsShared ? ref ((T<#= localIndex #>*)basePtrs[<#= localIndex #>])[0] : ref ((T<#= localIndex #>*)basePtrs[<#= localIndex #>])[entityIndex])<#= localIndex < i - 1 ? "," : "" #>
|
||||||
|
<# } #>);
|
||||||
|
validMask ^= (1UL << bit);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
using Misaki.HighPerformance.Jobs;
|
using Misaki.HighPerformance.Jobs;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ghost.Entities;
|
namespace Ghost.Entities;
|
||||||
|
|
||||||
@@ -21,6 +23,24 @@ public interface IJobEntity<<#= generics #>>
|
|||||||
void Execute(Entity entity, <#= AppendParameters(i, "ref T{0} component{0}") #>, ref readonly JobExecutionContext ctx);
|
void Execute(Entity entity, <#= AppendParameters(i, "ref T{0} component{0}") #>, ref readonly JobExecutionContext ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal unsafe struct JobBatchContext<#= i #>
|
||||||
|
{
|
||||||
|
public byte* chunk;
|
||||||
|
public uint* chunkVersions;
|
||||||
|
public byte* sharedDataBlob;
|
||||||
|
public int chunkCount;
|
||||||
|
public int entityOffset;
|
||||||
|
|
||||||
|
<# for (var j = 0; j < i; j++){ #>
|
||||||
|
public int offset<#= j #>;
|
||||||
|
public int enableOff<#= j #>;
|
||||||
|
public int versionIndex<#= j #>;
|
||||||
|
|
||||||
|
<# } #>
|
||||||
|
public int hiddenEnableCount;
|
||||||
|
public fixed int hiddenEnableOffsets[16];
|
||||||
|
}
|
||||||
|
|
||||||
internal unsafe struct JobEntityBatch<TJob, <#= generics #>> : IJobParallelFor
|
internal unsafe struct JobEntityBatch<TJob, <#= generics #>> : IJobParallelFor
|
||||||
where TJob : unmanaged, IJobEntity<<#= generics #>>
|
where TJob : unmanaged, IJobEntity<<#= generics #>>
|
||||||
<#= restrictions #>
|
<#= restrictions #>
|
||||||
@@ -29,39 +49,31 @@ internal unsafe struct JobEntityBatch<TJob, <#= generics #>> : IJobParallelFor
|
|||||||
public fixed bool componentRW[<#= i #>];
|
public fixed bool componentRW[<#= i #>];
|
||||||
|
|
||||||
public TJob userJob;
|
public TJob userJob;
|
||||||
|
public UnsafeList<JobBatchContext<#= i #>> batches;
|
||||||
public UnsafeList<IntPtr> chunks;
|
public EntityQueryMask mask;
|
||||||
public UnsafeList<IntPtr> chunkVersions;
|
|
||||||
public UnsafeList<int> chunkCount;
|
|
||||||
public UnsafeList<int> entityOffset;
|
|
||||||
|
|
||||||
<# for (var j = 0; j < i; j++){ #>
|
|
||||||
public UnsafeList<int> offsets<#= j #>;
|
|
||||||
public UnsafeList<int> bitsOffsets<#= j #>;
|
|
||||||
public UnsafeList<int> versionIndices<#= j #>;
|
|
||||||
|
|
||||||
<# } #>
|
|
||||||
public uint version;
|
public uint version;
|
||||||
|
|
||||||
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
|
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx)
|
||||||
{
|
{
|
||||||
// 1. Get the specific pChunk for this thread
|
ref var batch = ref ((JobBatchContext<#= i #>*)batches.GetUnsafePtr())[loopIndex];
|
||||||
var pChunk = (byte*)chunks[loopIndex];
|
|
||||||
var pVersions = (uint*)chunkVersions[loopIndex];
|
var pChunk = batch.chunk;
|
||||||
var count = chunkCount[loopIndex];
|
var pVersions = batch.chunkVersions;
|
||||||
|
var pSharedBlob = batch.sharedDataBlob;
|
||||||
|
var count = batch.chunkCount;
|
||||||
|
|
||||||
<# for (var j = 0; j < i; j++){ #>
|
<# for (var j = 0; j < i; j++){ #>
|
||||||
var off<#= j #> = offsets<#= j #>[loopIndex];
|
var off<#= j #> = batch.offset<#= j #>;
|
||||||
var enableOff<#= j #> = bitsOffsets<#= j #>[loopIndex];
|
var enableOff<#= j #> = batch.enableOff<#= j #>;
|
||||||
var versionIndex<#= j #> = versionIndices<#= j #>[loopIndex];
|
var versionIndex<#= j #> = batch.versionIndex<#= j #>;
|
||||||
|
|
||||||
<# } #>
|
<# } #>
|
||||||
var pEntity = (Entity*)(pChunk + entityOffset[loopIndex]);
|
|
||||||
|
var pEntity = (Entity*)(pChunk + batch.entityOffset);
|
||||||
|
|
||||||
<# for (var j = 0; j < i; j++){ #>
|
<# for (var j = 0; j < i; j++){ #>
|
||||||
var ptr<#= j #> = (<#= "T" + j #>*)(pChunk + off<#= j #>);
|
var ptr<#= j #> = (T<#= j #>*)(ComponentTypeID<T<#= j #>>.IsShared ? (pSharedBlob + off<#= j #>) : (pChunk + off<#= j #>));
|
||||||
<# } #>
|
<# } #>
|
||||||
|
|
||||||
// 2. Update versions for RW components
|
|
||||||
<# for (var j = 0; j < i; j++){ #>
|
<# for (var j = 0; j < i; j++){ #>
|
||||||
if (componentRW[<#= j #>])
|
if (componentRW[<#= j #>])
|
||||||
{
|
{
|
||||||
@@ -69,17 +81,43 @@ internal unsafe struct JobEntityBatch<TJob, <#= generics #>> : IJobParallelFor
|
|||||||
}
|
}
|
||||||
|
|
||||||
<# } #>
|
<# } #>
|
||||||
// 3. Iterate all entities in this chunk
|
|
||||||
for (var i = 0; i < count; i++)
|
// Execute batch
|
||||||
|
var ulongCount = (count + 63) / 64;
|
||||||
|
for (var block = 0; block < ulongCount; block++)
|
||||||
{
|
{
|
||||||
|
var validMask = ulong.MaxValue;
|
||||||
|
var remaining = count - (block * 64);
|
||||||
|
if (remaining < 64) validMask = (1UL << remaining) - 1UL;
|
||||||
|
|
||||||
|
// Enforce enableable bits checking based on components required by Job Signature
|
||||||
<# for (var j = 0; j < i; j++){ #>
|
<# for (var j = 0; j < i; j++){ #>
|
||||||
if (enableOff<#= j #> != -1 && !EntityQuery.CheckBit(pChunk + enableOff<#= j #>, i))
|
if (enableOff<#= j #> != -1)
|
||||||
{
|
{
|
||||||
continue;
|
var pMask = (ulong*)(pChunk + enableOff<#= j #>);
|
||||||
|
validMask &= pMask[block];
|
||||||
|
}
|
||||||
|
<# } #>
|
||||||
|
|
||||||
|
// Enforce EntityQuery Mask hidden enableable constraints
|
||||||
|
for (var h = 0; h < batch.hiddenEnableCount; h++)
|
||||||
|
{
|
||||||
|
var pMask = (ulong*)(pChunk + batch.hiddenEnableOffsets[h]);
|
||||||
|
validMask &= pMask[block];
|
||||||
}
|
}
|
||||||
|
|
||||||
<# } #>
|
while (validMask != 0)
|
||||||
userJob.Execute(pEntity[i], <#= AppendParameters(i, "ref ptr{0}[i]") #>, in ctx);
|
{
|
||||||
|
var bit = System.Numerics.BitOperations.TrailingZeroCount(validMask);
|
||||||
|
var i_ent = (block * 64) + bit;
|
||||||
|
|
||||||
|
userJob.Execute(pEntity[i_ent],
|
||||||
|
<# for (var j = 0; j < i; j++){ #>
|
||||||
|
ref (ComponentTypeID<T<#= j #>>.IsShared ? ref ptr<#= j #>[0] : ref ptr<#= j #>[i_ent])<#= j < i - 1 ? "," : "" #>
|
||||||
|
<# } #>, in ctx);
|
||||||
|
|
||||||
|
validMask ^= (1UL << bit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,30 +132,11 @@ public unsafe partial struct EntityQuery
|
|||||||
#>
|
#>
|
||||||
private struct DisposeJobEntity<#= i #> : IJob
|
private struct DisposeJobEntity<#= i #> : IJob
|
||||||
{
|
{
|
||||||
public UnsafeList<IntPtr> chunks;
|
public UnsafeList<JobBatchContext<#= i #>> batches;
|
||||||
public UnsafeList<IntPtr> chunkVersions;
|
|
||||||
public UnsafeList<int> chunkEntityCounts;
|
|
||||||
public UnsafeList<int> entityOffsets;
|
|
||||||
|
|
||||||
<# for (var j = 0; j < i; j++){ #>
|
|
||||||
public UnsafeList<int> offsets<#= j #>;
|
|
||||||
public UnsafeList<int> bitsOffsets<#= j #>;
|
|
||||||
public UnsafeList<int> versionIndices<#= j #>;
|
|
||||||
|
|
||||||
<# } #>
|
|
||||||
public void Execute(ref readonly JobExecutionContext ctx)
|
public void Execute(ref readonly JobExecutionContext ctx)
|
||||||
{
|
{
|
||||||
chunks.Dispose();
|
batches.Dispose();
|
||||||
chunkVersions.Dispose();
|
|
||||||
chunkEntityCounts.Dispose();
|
|
||||||
entityOffsets.Dispose();
|
|
||||||
|
|
||||||
<# for (var j = 0; j < i; j++){ #>
|
|
||||||
offsets<#= j #>.Dispose();
|
|
||||||
bitsOffsets<#= j #>.Dispose();
|
|
||||||
versionIndices<#= j #>.Dispose();
|
|
||||||
|
|
||||||
<# } #>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,19 +155,9 @@ public unsafe partial struct EntityQuery
|
|||||||
throw new InvalidOperationException("The World has no JobScheduler assigned.");
|
throw new InvalidOperationException("The World has no JobScheduler assigned.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Flatten the World
|
var batches = new UnsafeList<JobBatchContext<#= i #>>(128, TempJobAllocator.AllocationHandle);
|
||||||
var chunks = new UnsafeList<IntPtr>(128, TempJobAllocator.AllocationHandle);
|
var hiddenOffsets = stackalloc int[16];
|
||||||
var chunkVersions = new UnsafeList<IntPtr>(128, TempJobAllocator.AllocationHandle);
|
|
||||||
var chunkEntityCounts = new UnsafeList<int>(128, TempJobAllocator.AllocationHandle);
|
|
||||||
var entityOffsets = new UnsafeList<int>(128, TempJobAllocator.AllocationHandle);
|
|
||||||
|
|
||||||
<# for (var j = 0; j < i; j++){ #>
|
|
||||||
var offsets<#= j #> = new UnsafeList<int>(128, TempJobAllocator.AllocationHandle);
|
|
||||||
var bitsOffsets<#= j #> = new UnsafeList<int>(128, TempJobAllocator.AllocationHandle);
|
|
||||||
var versionIndices<#= j #> = new UnsafeList<int>(128, TempJobAllocator.AllocationHandle);
|
|
||||||
|
|
||||||
<# } #>
|
|
||||||
// Iterate the Query's matching archetypes
|
|
||||||
foreach (var archID in _matchingArchetypes)
|
foreach (var archID in _matchingArchetypes)
|
||||||
{
|
{
|
||||||
ref var arch = ref world.ComponentManager.GetArchetypeReference(archID);
|
ref var arch = ref world.ComponentManager.GetArchetypeReference(archID);
|
||||||
@@ -158,79 +167,112 @@ public unsafe partial struct EntityQuery
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get offsets ONCE per archetype
|
|
||||||
<# for (var j = 0; j < i; j++){ #>
|
<# for (var j = 0; j < i; j++){ #>
|
||||||
var layout<#= j #> = arch.GetLayout(ComponentTypeID<T<#= j #>>.Value).GetValueOrThrow();
|
int off<#= j #>;
|
||||||
<# } #>
|
int enableOff<#= j #>;
|
||||||
|
int versionIdx<#= j #>;
|
||||||
// Add all chunks from this archetype
|
if (ComponentTypeID<T<#= j #>>.IsShared)
|
||||||
for (var i = 0; i < arch.ChunkCount; i++)
|
|
||||||
{
|
{
|
||||||
ref var chunkRef = ref arch.GetChunkReference(i);
|
var layout = arch.GetSharedLayout(ComponentTypeID<T<#= j #>>.Value).GetValueOrThrow();
|
||||||
|
off<#= j #> = layout.offset;
|
||||||
|
enableOff<#= j #> = -1;
|
||||||
|
versionIdx<#= j #> = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var layout = arch.GetLayout(ComponentTypeID<T<#= j #>>.Value).GetValueOrThrow();
|
||||||
|
off<#= j #> = layout.offset;
|
||||||
|
enableOff<#= j #> = layout.enableBitsOffset;
|
||||||
|
versionIdx<#= j #> = layout.versionIndex;
|
||||||
|
}
|
||||||
|
<# } #>
|
||||||
|
|
||||||
chunks.Add((IntPtr)chunkRef.GetUnsafePtr());
|
var hiddenCount = 0;
|
||||||
chunkVersions.Add((IntPtr)chunkRef.GetVersionUnsafePtr());
|
var itE = _mask.requireEnabled.GetIterator();
|
||||||
chunkEntityCounts.Add(chunkRef._count);
|
while (itE.Next(out var id) && hiddenCount < 16)
|
||||||
entityOffsets.Add(arch.EntityIDsOffset);
|
{
|
||||||
|
var found = false;
|
||||||
|
<# for (var j = 0; j < i; j++){ #>
|
||||||
|
if (id == ComponentTypeID<T<#= j #>>.Value) found = true;
|
||||||
|
<# } #>
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
var layout = arch.GetLayout(id);
|
||||||
|
if (layout.Error == Error.None && layout.Value.enableBitsOffset != -1)
|
||||||
|
{
|
||||||
|
hiddenOffsets[hiddenCount++] = layout.Value.enableBitsOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var chunkIdx = 0; chunkIdx < arch.ChunkCount; chunkIdx++)
|
||||||
|
{
|
||||||
|
ref var chunkRef = ref arch.GetChunkReference(chunkIdx);
|
||||||
|
|
||||||
|
byte* pSharedBlob = null;
|
||||||
|
if (arch._chunkGroups.Count > 0 && chunkRef._groupIndex >= 0 && chunkRef._groupIndex < arch._chunkGroups.Count)
|
||||||
|
{
|
||||||
|
var sharedSpan = arch._chunkGroups[chunkRef._groupIndex].sharedData;
|
||||||
|
if (sharedSpan.IsCreated)
|
||||||
|
{
|
||||||
|
pSharedBlob = (byte*)sharedSpan.GetUnsafePtr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ctx = new JobBatchContext<#= i #>
|
||||||
|
{
|
||||||
|
chunk = chunkRef.GetUnsafePtr(),
|
||||||
|
chunkVersions = chunkRef.GetVersionUnsafePtr(),
|
||||||
|
chunkCount = chunkRef._count,
|
||||||
|
entityOffset = arch.EntityIDsOffset,
|
||||||
|
sharedDataBlob = pSharedBlob,
|
||||||
|
|
||||||
<# for (var j = 0; j < i; j++){ #>
|
<# for (var j = 0; j < i; j++){ #>
|
||||||
offsets<#= j #>.Add(layout<#= j #>.offset);
|
offset<#= j #> = off<#= j #>,
|
||||||
bitsOffsets<#= j #>.Add(layout<#= j #>.enableBitsOffset);
|
enableOff<#= j #> = enableOff<#= j #>,
|
||||||
versionIndices<#= j #>.Add(layout<#= j #>.versionIndex);
|
versionIndex<#= j #> = versionIdx<#= j #>,
|
||||||
|
|
||||||
<# } #>
|
<# } #>
|
||||||
|
hiddenEnableCount = hiddenCount,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var h = 0; h < hiddenCount; h++)
|
||||||
|
{
|
||||||
|
ctx.hiddenEnableOffsets[h] = hiddenOffsets[h];
|
||||||
|
}
|
||||||
|
batches.Add(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Create the Runner
|
|
||||||
var runner = new JobEntityBatch<TJob, <#= generics #>>
|
var runner = new JobEntityBatch<TJob, <#= generics #>>
|
||||||
{
|
{
|
||||||
userJob = jobData,
|
userJob = jobData,
|
||||||
chunks = chunks,
|
batches = batches,
|
||||||
chunkVersions = chunkVersions,
|
mask = _mask,
|
||||||
chunkCount = chunkEntityCounts,
|
|
||||||
entityOffset = entityOffsets,
|
|
||||||
|
|
||||||
<# for (var j = 0; j < i; j++){ #>
|
|
||||||
offsets<#= j #> = offsets<#= j #>,
|
|
||||||
bitsOffsets<#= j #> = bitsOffsets<#= j #>,
|
|
||||||
versionIndices<#= j #> = versionIndices<#= j #>,
|
|
||||||
|
|
||||||
<# } #>
|
|
||||||
version = world.Version,
|
version = world.Version,
|
||||||
};
|
};
|
||||||
|
|
||||||
runner.componentIDs[0] = ComponentTypeID<T0>.Value;
|
<# for (var j = 0; j < i; j++){ #>
|
||||||
|
runner.componentIDs[<#= j #>] = ComponentTypeID<T<#= j #>>.Value;
|
||||||
|
<# } #>
|
||||||
|
|
||||||
var it = _mask.writeAccess.GetIterator();
|
var it = _mask.writeAccess.GetIterator();
|
||||||
while (it.Next(out var id))
|
while (it.Next(out var id))
|
||||||
{
|
{
|
||||||
for (var i =0; i < 1; i++)
|
for (var idx = 0; idx < <#= i #>; idx++)
|
||||||
{
|
{
|
||||||
if (id == runner.componentIDs[i])
|
if (id == runner.componentIDs[idx])
|
||||||
{
|
{
|
||||||
runner.componentRW[i] = true;
|
runner.componentRW[idx] = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var jobHandle = world.JobScheduler.ScheduleParallelFor(ref runner, chunks.Count, batchSize, dependency);
|
var jobHandle = world.JobScheduler.ScheduleParallelFor(ref runner, batches.Count, batchSize, dependency);
|
||||||
|
|
||||||
// 3. Dispose the temp lists
|
|
||||||
var disposeJob = new DisposeJobEntity<#= i #>
|
var disposeJob = new DisposeJobEntity<#= i #>
|
||||||
{
|
{
|
||||||
chunks = chunks,
|
batches = batches,
|
||||||
chunkVersions = chunkVersions,
|
|
||||||
chunkEntityCounts = chunkEntityCounts,
|
|
||||||
entityOffsets = entityOffsets,
|
|
||||||
|
|
||||||
<# for (var j = 0; j < i; j++){ #>
|
|
||||||
offsets<#= j #> = offsets<#= j #>,
|
|
||||||
bitsOffsets<#= j #> = bitsOffsets<#= j #>,
|
|
||||||
versionIndices<#= j #> = versionIndices<#= j #>,
|
|
||||||
|
|
||||||
<# } #>
|
|
||||||
};
|
};
|
||||||
|
|
||||||
world.JobScheduler.Schedule(ref disposeJob, jobHandle);
|
world.JobScheduler.Schedule(ref disposeJob, jobHandle);
|
||||||
|
|||||||
@@ -153,10 +153,6 @@ public partial class World : IDisposable, IEquatable<World>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
~World()
|
|
||||||
{
|
|
||||||
Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal void PlaybackEntityCommandBuffers()
|
internal void PlaybackEntityCommandBuffers()
|
||||||
@@ -291,10 +287,13 @@ public partial class World : IDisposable, IEquatable<World>
|
|||||||
_entityManager.Dispose();
|
_entityManager.Dispose();
|
||||||
_entityCommandBuffer.Dispose();
|
_entityCommandBuffer.Dispose();
|
||||||
|
|
||||||
|
if (_threadLocalECBs.IsCreated)
|
||||||
|
{
|
||||||
for (var i = 0; i < _threadLocalECBs.Length; i++)
|
for (var i = 0; i < _threadLocalECBs.Length; i++)
|
||||||
{
|
{
|
||||||
_threadLocalECBs[i].Dispose();
|
_threadLocalECBs[i].Dispose();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_componentManager.Dispose();
|
_componentManager.Dispose();
|
||||||
_systemManager.Dispose();
|
_systemManager.Dispose();
|
||||||
|
|||||||
@@ -408,23 +408,6 @@ public class RenderSystem : IDisposable
|
|||||||
_resizeRequest.AddOrUpdate(swapChain, newSize, (_, _) => newSize);
|
_resizeRequest.AddOrUpdate(swapChain, newSize, (_, _) => newSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool TryAcquireCPUFrame()
|
|
||||||
{
|
|
||||||
Logger.DebugAssert(!_disposed, "Cannot acquire CPU frame on a disposed RenderSystem.");
|
|
||||||
|
|
||||||
var requiredGpuFence = _cpuFenceValue < (ulong)_frameResources.Length ? 0 : _cpuFenceValue - (ulong)_frameResources.Length + 1;
|
|
||||||
|
|
||||||
if (requiredGpuFence > 0 && _fence.CompletedValue < requiredGpuFence)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var eventIndex = (int)(_cpuFenceValue % (ulong)_frameResources.Length);
|
|
||||||
ref var frameResource = ref _frameResources[eventIndex];
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool WaitForGPUReady(int timeOut = -1)
|
public bool WaitForGPUReady(int timeOut = -1)
|
||||||
{
|
{
|
||||||
Logger.DebugAssert(!_disposed, "Cannot wait for GPU ready on a disposed RenderSystem.");
|
Logger.DebugAssert(!_disposed, "Cannot wait for GPU ready on a disposed RenderSystem.");
|
||||||
|
|||||||
@@ -1,115 +0,0 @@
|
|||||||
using Ghost.TestCore;
|
|
||||||
using Misaki.HighPerformance.Jobs;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
|
||||||
using Misaki.HighPerformance.Mathematics;
|
|
||||||
|
|
||||||
namespace Ghost.Entities.Test;
|
|
||||||
|
|
||||||
internal struct TestEntityQueryJob : IJobEntity<Transform>
|
|
||||||
{
|
|
||||||
public readonly void Execute(Entity entity, ref Transform transform, ref readonly JobExecutionContext ctx)
|
|
||||||
{
|
|
||||||
transform.position += new float3(5, 5, 5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal struct TestChunkQueryJob : IJobChunk
|
|
||||||
{
|
|
||||||
public readonly void Execute(ChunkView view, ref readonly JobExecutionContext ctx)
|
|
||||||
{
|
|
||||||
var random = new random((uint)ctx.ThreadIndex + 1u);
|
|
||||||
|
|
||||||
var transforms = view.GetComponentDataRW<Transform>();
|
|
||||||
for (var i = 0; i < view.EntityCount; i++)
|
|
||||||
{
|
|
||||||
transforms[i].position += random.NextFloat3();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public partial class EntityQueryTest : ITest
|
|
||||||
{
|
|
||||||
private JobScheduler _jobScheduler = null!;
|
|
||||||
private World _world = null!;
|
|
||||||
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
_jobScheduler = new JobScheduler(4);
|
|
||||||
_world = World.Create(_jobScheduler);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Run()
|
|
||||||
{
|
|
||||||
var entities = (Span<Entity>)stackalloc Entity[1000];
|
|
||||||
|
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
|
||||||
using var set = new ComponentSet(scope.AllocationHandle, ComponentTypeID<Transform>.Value);
|
|
||||||
|
|
||||||
_world.EntityManager.CreateEntities(entities, set);
|
|
||||||
|
|
||||||
var queryID = new QueryBuilder().WithAllRW<Transform>().Build(_world);
|
|
||||||
ref var query = ref _world.ComponentManager.GetEntityQueryReference(queryID);
|
|
||||||
|
|
||||||
_world.AdvanceVersion();
|
|
||||||
|
|
||||||
var testJob = new TestChunkQueryJob();
|
|
||||||
var handle = query.ScheduleChunkParallel(testJob, 1, JobHandle.Invalid);
|
|
||||||
_jobScheduler.Wait(handle);
|
|
||||||
|
|
||||||
query.ForEach<Transform>((e, ref t) =>
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Entity {e} Has Position: {t.position}");
|
|
||||||
});
|
|
||||||
|
|
||||||
foreach (var (entity, transform) in query.GetEntityComponentIterator<Transform>())
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Entity {entity} Updated Position: {transform.Get().position}");
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var chunk in query.GetChunkIterator())
|
|
||||||
{
|
|
||||||
var transforms = chunk.GetComponentData<Transform>();
|
|
||||||
var chunkEntities = chunk.GetEntities();
|
|
||||||
|
|
||||||
// if (chunk.HasChanged<Transform>(0))
|
|
||||||
{
|
|
||||||
// var bits = chunk.GetEnableBits<Transform>();
|
|
||||||
|
|
||||||
// var it = bits.GetIterator();
|
|
||||||
// while (it.Next(out var index) && index < chunk.EntityCount)
|
|
||||||
for (var index = 0; index < chunk.EntityCount; index++)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Entity {chunkEntities[index]} Updated Position: {transforms[index].position}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_world.EntityManager.DestroyEntities(entities);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Cleanup()
|
|
||||||
{
|
|
||||||
_world.Dispose();
|
|
||||||
_jobScheduler.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct Transform : IEnableableComponent
|
|
||||||
{
|
|
||||||
public float3 position;
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"Position: {position}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct Mesh : IComponent
|
|
||||||
{
|
|
||||||
public int index;
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"Index: {index}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
|
||||||
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
|
|
||||||
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.15.8" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\Runtime\Ghost.Entities\Ghost.Entities.csproj" />
|
|
||||||
<ProjectReference Include="..\..\Test\Ghost.TestCore\Ghost.TestCore.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using BenchmarkDotNet.Running;
|
|
||||||
using Ghost.Entities.Test;
|
|
||||||
|
|
||||||
//AllocationManager.EnableDebugLayer();
|
|
||||||
//TestRunner.Run<SerializationTest>();
|
|
||||||
//AllocationManager.Dispose();
|
|
||||||
|
|
||||||
BenchmarkRunner.Run<QueryBenchmark>();
|
|
||||||
//var test = new QueryBenchmark();
|
|
||||||
//test.Setup();
|
|
||||||
//test.QueryEntities();
|
|
||||||
//test.Cleanup();
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
using BenchmarkDotNet.Attributes;
|
|
||||||
using BenchmarkDotNet.Diagnosers;
|
|
||||||
using Ghost.Core;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ghost.Entities.Test;
|
|
||||||
|
|
||||||
internal class GameObject
|
|
||||||
{
|
|
||||||
public Vector4 Position { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
internal struct Position : IComponent
|
|
||||||
{
|
|
||||||
public Vector4 value;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HardwareCounters(HardwareCounter.CacheMisses, HardwareCounter.LlcReference, HardwareCounter.InstructionRetired)]
|
|
||||||
public class QueryBenchmark
|
|
||||||
{
|
|
||||||
private World _world = null!;
|
|
||||||
private Identifier<EntityQuery> _queryIdentifier;
|
|
||||||
|
|
||||||
private GameObject[] _gameObjects = null!;
|
|
||||||
|
|
||||||
private float _dt = Random.Shared.NextSingle();
|
|
||||||
|
|
||||||
[GlobalSetup]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
_world = World.Create(entityCapacity: 1_000_000);
|
|
||||||
_gameObjects = new GameObject[1_000_000];
|
|
||||||
|
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
|
||||||
var componentSet = new ComponentSet(scope.AllocationHandle, ComponentTypeID<Position>.Value);
|
|
||||||
_world.EntityManager.CreateEntities(1_000_000, componentSet);
|
|
||||||
|
|
||||||
_queryIdentifier = new QueryBuilder().WithAllRW<Position>().Build(_world);
|
|
||||||
|
|
||||||
for (var i = 0; i < 1_000_000; i++)
|
|
||||||
{
|
|
||||||
_gameObjects[i] = new GameObject { Position = new Vector4(i, i, i, 0) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[GlobalCleanup]
|
|
||||||
public void Cleanup()
|
|
||||||
{
|
|
||||||
_world.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
|
||||||
public void QueryGameObjects()
|
|
||||||
{
|
|
||||||
for (var i = 0; i < _gameObjects.Length; i++)
|
|
||||||
{
|
|
||||||
_gameObjects[i].Position += new Vector4(_dt, _dt, _dt, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Results: 620 us, 98.5% cache hits (14731 misses), 13260812 instructions retired, 4x faster than QueryGameObjects
|
|
||||||
|
|
||||||
[Benchmark(Baseline = true)]
|
|
||||||
public void QueryEntities()
|
|
||||||
{
|
|
||||||
ref var query = ref _world.ComponentManager.GetEntityQueryReference(_queryIdentifier);
|
|
||||||
foreach (var chunkView in query.GetChunkIterator())
|
|
||||||
{
|
|
||||||
var positions = chunkView.GetComponentDataRW<Position>();
|
|
||||||
ref var address = ref MemoryMarshal.GetReference(positions);
|
|
||||||
|
|
||||||
for (var i = 0; i < positions.Length; i++)
|
|
||||||
{
|
|
||||||
Unsafe.Add(ref address, i).value += new Vector4(_dt, _dt, _dt, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
using Ghost.TestCore;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
|
||||||
using Misaki.HighPerformance.Mathematics;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
namespace Ghost.Entities.Test;
|
|
||||||
|
|
||||||
public class SerializationTest : ITest
|
|
||||||
{
|
|
||||||
private World _world = null!;
|
|
||||||
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
_world = World.Create();
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe void Run()
|
|
||||||
{
|
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
|
||||||
var set1 = new ComponentSet(scope.AllocationHandle, ComponentTypeID<Transform>.Value);
|
|
||||||
var set2 = new ComponentSet(scope.AllocationHandle, ComponentTypeID<Transform>.Value, ComponentTypeID<Mesh>.Value);
|
|
||||||
|
|
||||||
var e1 = _world.EntityManager.CreateEntity(set1);
|
|
||||||
var e2 = _world.EntityManager.CreateEntity(set2);
|
|
||||||
|
|
||||||
_world.EntityManager.SetComponent(e1, new Transform { position = new float3(1, 2, 3) });
|
|
||||||
_world.EntityManager.SetComponent(e2, new Transform { position = new float3(4, 5, 6) });
|
|
||||||
_world.EntityManager.SetComponent(e2, new Mesh { index = 42 });
|
|
||||||
|
|
||||||
using var stream = new MemoryStream();
|
|
||||||
var serializeOptions = new JsonSerializerOptions
|
|
||||||
{
|
|
||||||
IncludeFields = true
|
|
||||||
};
|
|
||||||
|
|
||||||
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true });
|
|
||||||
|
|
||||||
writer.WriteStartObject();
|
|
||||||
writer.WriteString("Name", "world 1");
|
|
||||||
writer.WriteStartArray("Entities");
|
|
||||||
|
|
||||||
for (var i = 0; i < _world.ComponentManager.ArchetypeCount; i++)
|
|
||||||
{
|
|
||||||
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(i);
|
|
||||||
|
|
||||||
for (var j = 0; j < archetype.ChunkCount; j++)
|
|
||||||
{
|
|
||||||
ref var chunk = ref archetype.GetChunkReference(j);
|
|
||||||
for (var k = 0; k < chunk._count; k++)
|
|
||||||
{
|
|
||||||
writer.WriteStartObject();
|
|
||||||
|
|
||||||
var entity = *(Entity*)(chunk.GetUnsafePtr() + archetype.EntityIDsOffset + k * sizeof(Entity));
|
|
||||||
writer.WriteNumber("ID", entity.ID);
|
|
||||||
writer.WriteStartArray("Components");
|
|
||||||
|
|
||||||
foreach (var layout in archetype._layouts)
|
|
||||||
{
|
|
||||||
var type = ComponentRegistry.s_runtimeIDToType[layout.componentID];
|
|
||||||
var size = ComponentRegistry.GetComponentInfo(layout.componentID).size;
|
|
||||||
|
|
||||||
if (type.AssemblyQualifiedName == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.WriteStartObject();
|
|
||||||
writer.WriteString("Type", type.AssemblyQualifiedName);
|
|
||||||
writer.WritePropertyName("Data");
|
|
||||||
|
|
||||||
var pComponentData = chunk.GetUnsafePtr() + layout.offset + (k * size);
|
|
||||||
var instace = Marshal.PtrToStructure((nint)pComponentData, type);
|
|
||||||
JsonSerializer.Serialize(writer, instace, type, serializeOptions);
|
|
||||||
|
|
||||||
writer.WriteEndObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.WriteEndArray();
|
|
||||||
writer.WriteEndObject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.WriteEndArray();
|
|
||||||
writer.WriteEndObject();
|
|
||||||
|
|
||||||
writer.Flush();
|
|
||||||
|
|
||||||
var data = stream.ToArray();
|
|
||||||
|
|
||||||
var json = System.Text.Encoding.UTF8.GetString(data);
|
|
||||||
Console.WriteLine(json);
|
|
||||||
|
|
||||||
|
|
||||||
var reader = new Utf8JsonReader(data);
|
|
||||||
|
|
||||||
var root = JsonDocument.ParseValue(ref reader).RootElement;
|
|
||||||
var name = root.GetProperty("Name").GetString();
|
|
||||||
Console.WriteLine($"Deserialized World Name: {name}");
|
|
||||||
|
|
||||||
var entityData = new List<(int EntityID, Type ComponentType, object Instance)>();
|
|
||||||
|
|
||||||
foreach (var entityElement in root.GetProperty("Entities").EnumerateArray())
|
|
||||||
{
|
|
||||||
var id = entityElement.GetProperty("ID").GetInt32();
|
|
||||||
|
|
||||||
// Access the new "Components" array
|
|
||||||
var componentsElement = entityElement.GetProperty("Components");
|
|
||||||
|
|
||||||
foreach (var componentElement in componentsElement.EnumerateArray())
|
|
||||||
{
|
|
||||||
var typeName = componentElement.GetProperty("Type").GetString();
|
|
||||||
var dataElement = componentElement.GetProperty("Data");
|
|
||||||
|
|
||||||
var type = Type.GetType(typeName!);
|
|
||||||
if (type == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var instance = dataElement.Deserialize(type, serializeOptions);
|
|
||||||
if (instance != null)
|
|
||||||
{
|
|
||||||
entityData.Add((id, type, instance));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var (id, type, instance) in entityData)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Entity ID: {id}, Component: {type.Name}, Data: {instance}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Cleanup()
|
|
||||||
{
|
|
||||||
_world.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
using Ghost.TestCore;
|
|
||||||
|
|
||||||
namespace Ghost.Entities.Test;
|
|
||||||
|
|
||||||
internal class SystemTest : ITest
|
|
||||||
{
|
|
||||||
private World _world = null!;
|
|
||||||
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
_world = World.Create();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Run()
|
|
||||||
{
|
|
||||||
var group = _world.SystemManager.GetSystem<DefaultSystemGroup>();
|
|
||||||
group.AddSystem<TestSystemB>();
|
|
||||||
group.AddSystem<TestSystemA>();
|
|
||||||
|
|
||||||
group.SortSystems();
|
|
||||||
|
|
||||||
_world.SystemManager.InitializeAll(new TimeData());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Cleanup()
|
|
||||||
{
|
|
||||||
_world.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class TestSystemA : SystemBase
|
|
||||||
{
|
|
||||||
protected override void OnInitialize(ref readonly SystemAPI systemAPI)
|
|
||||||
{
|
|
||||||
Console.WriteLine("TestSystemA Initialized");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[UpdateAfter<TestSystemA>]
|
|
||||||
internal class TestSystemB : SystemBase
|
|
||||||
{
|
|
||||||
protected override void OnInitialize(ref readonly SystemAPI systemAPI)
|
|
||||||
{
|
|
||||||
Console.WriteLine("TestSystemB Initialized");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 432 B |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 637 B |
|
Before Width: | Height: | Size: 283 B |
|
Before Width: | Height: | Size: 456 B |
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,120 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
|
||||||
<UserControl
|
|
||||||
x:Class="Ghost.Graphics.Test.Controls.DebugConsole"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:local="using:Ghost.Graphics.Test.Controls"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
mc:Ignorable="d">
|
|
||||||
|
|
||||||
<UserControl.Resources>
|
|
||||||
<local:LogLevelToColorConverter x:Key="LogLevelToColorConverter" />
|
|
||||||
<local:LogLevelToSymbolConverter x:Key="LogLevelToSymbolConverter" />
|
|
||||||
|
|
||||||
<DataTemplate x:Key="LogItemTemplate">
|
|
||||||
<Border Padding="8,4" Background="Transparent">
|
|
||||||
<Grid>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
|
|
||||||
<TextBlock
|
|
||||||
Grid.Column="0"
|
|
||||||
Margin="0,0,8,0"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
FontFamily="Segoe UI Symbol"
|
|
||||||
Foreground="{Binding Level, Converter={StaticResource LogLevelToColorConverter}}"
|
|
||||||
Text="{Binding Level, Converter={StaticResource LogLevelToSymbolConverter}}" />
|
|
||||||
|
|
||||||
<TextBlock
|
|
||||||
Grid.Column="1"
|
|
||||||
Margin="0,0,8,0"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
FontFamily="Consolas"
|
|
||||||
Foreground="Gray"
|
|
||||||
Text="{Binding Timestamp}" />
|
|
||||||
|
|
||||||
<TextBlock
|
|
||||||
Grid.Column="2"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Text="{Binding Message}"
|
|
||||||
TextWrapping="Wrap" />
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
</DataTemplate>
|
|
||||||
</UserControl.Resources>
|
|
||||||
|
|
||||||
<Grid>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="*" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<!-- Toolbar -->
|
|
||||||
<Border
|
|
||||||
Grid.Row="0"
|
|
||||||
Background="{ThemeResource SystemControlBackgroundAltMediumBrush}"
|
|
||||||
BorderBrush="{ThemeResource SystemControlForegroundBaseLowBrush}"
|
|
||||||
BorderThickness="0,0,0,1">
|
|
||||||
<StackPanel Margin="8,4" Orientation="Horizontal">
|
|
||||||
<Button
|
|
||||||
x:Name="ClearButton"
|
|
||||||
Margin="0,0,8,0"
|
|
||||||
Click="ClearButton_Click"
|
|
||||||
Content="Clear" />
|
|
||||||
<CheckBox
|
|
||||||
x:Name="AutoScrollCheckBox"
|
|
||||||
Margin="0,0,8,0"
|
|
||||||
Content="Auto Scroll"
|
|
||||||
IsChecked="True" />
|
|
||||||
<CheckBox
|
|
||||||
x:Name="ShowStackTraceCheckBox"
|
|
||||||
Margin="0,0,8,0"
|
|
||||||
Checked="ShowStackTraceCheckBox_Checked"
|
|
||||||
Content="Stack Trace"
|
|
||||||
Unchecked="ShowStackTraceCheckBox_Unchecked" />
|
|
||||||
|
|
||||||
<!-- Log level filters -->
|
|
||||||
<TextBlock
|
|
||||||
Margin="16,0,8,0"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Text="Show:" />
|
|
||||||
<CheckBox
|
|
||||||
x:Name="ShowInfoCheckBox"
|
|
||||||
Margin="0,0,4,0"
|
|
||||||
Content="Info"
|
|
||||||
IsChecked="True" />
|
|
||||||
<CheckBox
|
|
||||||
x:Name="ShowWarningCheckBox"
|
|
||||||
Margin="0,0,4,0"
|
|
||||||
Content="Warning"
|
|
||||||
IsChecked="True" />
|
|
||||||
<CheckBox
|
|
||||||
x:Name="ShowErrorCheckBox"
|
|
||||||
Margin="0,0,4,0"
|
|
||||||
Content="Error"
|
|
||||||
IsChecked="True" />
|
|
||||||
<CheckBox
|
|
||||||
x:Name="ShowDebugCheckBox"
|
|
||||||
Margin="0,0,4,0"
|
|
||||||
Content="Debug"
|
|
||||||
IsChecked="True" />
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- Log display -->
|
|
||||||
<ScrollViewer
|
|
||||||
x:Name="LogScrollViewer"
|
|
||||||
Grid.Row="1"
|
|
||||||
HorizontalScrollBarVisibility="Auto"
|
|
||||||
HorizontalScrollMode="Auto"
|
|
||||||
VerticalScrollBarVisibility="Auto"
|
|
||||||
VerticalScrollMode="Auto"
|
|
||||||
ZoomMode="Disabled">
|
|
||||||
<ItemsRepeater x:Name="LogItemsRepeater" ItemTemplate="{StaticResource LogItemTemplate}" />
|
|
||||||
</ScrollViewer>
|
|
||||||
</Grid>
|
|
||||||
</UserControl>
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
using Ghost.Core;
|
|
||||||
using Microsoft.UI;
|
|
||||||
using Microsoft.UI.Xaml;
|
|
||||||
using Microsoft.UI.Xaml.Controls;
|
|
||||||
using Microsoft.UI.Xaml.Data;
|
|
||||||
using Microsoft.UI.Xaml.Media;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
|
|
||||||
namespace Ghost.Graphics.Test.Controls;
|
|
||||||
|
|
||||||
public sealed partial class DebugConsole : UserControl
|
|
||||||
{
|
|
||||||
private readonly ObservableCollection<LogMessage> _filteredLogs = [];
|
|
||||||
|
|
||||||
public DebugConsole()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
|
|
||||||
LogItemsRepeater.ItemsSource = _filteredLogs;
|
|
||||||
|
|
||||||
Logger.Impl.OnLogAdded += OnLogAdded;
|
|
||||||
Logger.Impl.OnLogsCleared += OnLogCleared;
|
|
||||||
|
|
||||||
// Subscribe to filter changes
|
|
||||||
ShowInfoCheckBox.Checked += OnFilterChanged;
|
|
||||||
ShowInfoCheckBox.Unchecked += OnFilterChanged;
|
|
||||||
ShowWarningCheckBox.Checked += OnFilterChanged;
|
|
||||||
ShowWarningCheckBox.Unchecked += OnFilterChanged;
|
|
||||||
ShowErrorCheckBox.Checked += OnFilterChanged;
|
|
||||||
ShowErrorCheckBox.Unchecked += OnFilterChanged;
|
|
||||||
ShowDebugCheckBox.Checked += OnFilterChanged;
|
|
||||||
ShowDebugCheckBox.Unchecked += OnFilterChanged;
|
|
||||||
|
|
||||||
// Load existing logs
|
|
||||||
RefreshLogs();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnLogAdded(LogMessage message)
|
|
||||||
{
|
|
||||||
if (ShouldShowLogItem(message))
|
|
||||||
{
|
|
||||||
DispatcherQueue.TryEnqueue(() =>
|
|
||||||
{
|
|
||||||
_filteredLogs.Add(message);
|
|
||||||
if (AutoScrollCheckBox.IsChecked == true)
|
|
||||||
{
|
|
||||||
LogScrollViewer.ScrollToVerticalOffset(LogScrollViewer.ScrollableHeight);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnLogCleared()
|
|
||||||
{
|
|
||||||
DispatcherQueue.TryEnqueue(_filteredLogs.Clear);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnFilterChanged(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
RefreshLogs();
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ShouldShowLogItem(LogMessage message)
|
|
||||||
{
|
|
||||||
return message.Level switch
|
|
||||||
{
|
|
||||||
LogLevel.Info => ShowInfoCheckBox.IsChecked == true,
|
|
||||||
LogLevel.Warning => ShowWarningCheckBox.IsChecked == true,
|
|
||||||
LogLevel.Error => ShowErrorCheckBox.IsChecked == true,
|
|
||||||
LogLevel.Debug => ShowDebugCheckBox.IsChecked == true,
|
|
||||||
_ => true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RefreshLogs()
|
|
||||||
{
|
|
||||||
_filteredLogs.Clear();
|
|
||||||
|
|
||||||
foreach (var log in Logger.Logs)
|
|
||||||
{
|
|
||||||
if (ShouldShowLogItem(log))
|
|
||||||
{
|
|
||||||
_filteredLogs.Add(log);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (AutoScrollCheckBox.IsChecked == true)
|
|
||||||
{
|
|
||||||
LogScrollViewer.ScrollToVerticalOffset(LogScrollViewer.ScrollableHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ClearButton_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
Logger.Impl.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ShowStackTraceCheckBox_Checked(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
Logger.Impl.CaptureStackTrace = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ShowStackTraceCheckBox_Unchecked(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
Logger.Impl.CaptureStackTrace = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converter for log level to color
|
|
||||||
public class LogLevelToColorConverter : IValueConverter
|
|
||||||
{
|
|
||||||
public object Convert(object value, Type targetType, object parameter, string language)
|
|
||||||
{
|
|
||||||
if (value is LogLevel level)
|
|
||||||
{
|
|
||||||
return level switch
|
|
||||||
{
|
|
||||||
LogLevel.Info => new SolidColorBrush(Colors.DodgerBlue),
|
|
||||||
LogLevel.Warning => new SolidColorBrush(Colors.Orange),
|
|
||||||
LogLevel.Error => new SolidColorBrush(Colors.Red),
|
|
||||||
LogLevel.Debug => new SolidColorBrush(Colors.Gray),
|
|
||||||
_ => new SolidColorBrush(Colors.Black)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return new SolidColorBrush(Colors.Black);
|
|
||||||
}
|
|
||||||
|
|
||||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converter for log level to symbol
|
|
||||||
public class LogLevelToSymbolConverter : IValueConverter
|
|
||||||
{
|
|
||||||
public object Convert(object value, Type targetType, object parameter, string language)
|
|
||||||
{
|
|
||||||
if (value is LogLevel level)
|
|
||||||
{
|
|
||||||
return level switch
|
|
||||||
{
|
|
||||||
LogLevel.Info => "ℹ",
|
|
||||||
LogLevel.Warning => "⚠",
|
|
||||||
LogLevel.Error => "✖",
|
|
||||||
LogLevel.Debug => "🐛",
|
|
||||||
_ => "•"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return "•";
|
|
||||||
}
|
|
||||||
|
|
||||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>WinExe</OutputType>
|
|
||||||
<TargetFramework>net10.0-windows10.0.22621.0</TargetFramework>
|
|
||||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
|
||||||
<RootNamespace>Ghost.Graphics.Test</RootNamespace>
|
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
|
||||||
<Platforms>x86;x64;ARM64</Platforms>
|
|
||||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
|
||||||
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
|
|
||||||
<UseWinUI>true</UseWinUI>
|
|
||||||
<EnableMsixTooling>true</EnableMsixTooling>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Page Remove="UnitTestApp.xaml" />
|
|
||||||
<ApplicationDefinition Include="UnitTestApp.xaml" />
|
|
||||||
<ProjectCapability Include="TestContainer" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Content Include="Assets\SplashScreen.scale-200.png" />
|
|
||||||
<Content Include="Assets\LockScreenLogo.scale-200.png" />
|
|
||||||
<Content Include="Assets\Square150x150Logo.scale-200.png" />
|
|
||||||
<Content Include="Assets\Square44x44Logo.scale-200.png" />
|
|
||||||
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
|
|
||||||
<Content Include="Assets\StoreLogo.png" />
|
|
||||||
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Manifest Include="$(ApplicationManifest)" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
|
|
||||||
Tools extension to be activated for this project even if the Windows App SDK Nuget
|
|
||||||
package has not yet been restored.
|
|
||||||
-->
|
|
||||||
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
|
||||||
<ProjectCapability Include="Msix" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.TestPlatform.TestHost" Version="18.5.1" />
|
|
||||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.28000.1839" />
|
|
||||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="2.1.3" />
|
|
||||||
<PackageReference Include="MSTest.TestAdapter" Version="4.2.3" />
|
|
||||||
<PackageReference Include="MSTest.TestFramework" Version="4.2.3" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\Runtime\Ghost.Engine\Ghost.Engine.csproj" />
|
|
||||||
<ProjectReference Include="..\..\Test\Ghost.TestCore\Ghost.TestCore.csproj" />
|
|
||||||
<ProjectReference Include="..\..\Runtime\Ghost.Graphics\Ghost.Graphics.csproj" />
|
|
||||||
<ProjectReference Include="..\..\Editor\Ghost.DSL\Ghost.DSL.csproj" />
|
|
||||||
<ProjectReference Include="..\..\ThridParty\Ghost.Ufbx\Ghost.Ufbx.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
|
|
||||||
Explorer "Package and Publish" context menu entry to be enabled for this project even if
|
|
||||||
the Windows App SDK Nuget package has not yet been restored.
|
|
||||||
-->
|
|
||||||
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
|
||||||
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<!-- Publish Properties -->
|
|
||||||
<PropertyGroup>
|
|
||||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
|
||||||
<PublishReadyToRun Condition="'$(Configuration)'=='Debug_Editor'">False</PublishReadyToRun>
|
|
||||||
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
|
||||||
<SupportedOSPlatformVersion>10.0.20348.0</SupportedOSPlatformVersion>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
|
|
||||||
<DebugType>embedded</DebugType>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_Editor|x86'">
|
|
||||||
<DebugType>embedded</DebugType>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
|
||||||
<DebugType>embedded</DebugType>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_Editor|x64'">
|
|
||||||
<DebugType>embedded</DebugType>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
|
||||||
<DebugType>embedded</DebugType>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_Editor|ARM64'">
|
|
||||||
<DebugType>embedded</DebugType>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<Package
|
|
||||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
|
||||||
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
|
||||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
|
||||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
|
||||||
IgnorableNamespaces="uap rescap">
|
|
||||||
|
|
||||||
<Identity
|
|
||||||
Name="7329af59-6d61-48e9-9041-8f2d3d23696b"
|
|
||||||
Publisher="CN=Misaki"
|
|
||||||
Version="1.0.0.0" />
|
|
||||||
|
|
||||||
<mp:PhoneIdentity PhoneProductId="7329af59-6d61-48e9-9041-8f2d3d23696b" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
|
||||||
|
|
||||||
<Properties>
|
|
||||||
<DisplayName>Ghost.UnitTest</DisplayName>
|
|
||||||
<PublisherDisplayName>Misaki</PublisherDisplayName>
|
|
||||||
<Logo>Assets\StoreLogo.png</Logo>
|
|
||||||
</Properties>
|
|
||||||
|
|
||||||
<Dependencies>
|
|
||||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
|
||||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
|
||||||
</Dependencies>
|
|
||||||
|
|
||||||
<Resources>
|
|
||||||
<Resource Language="x-generate"/>
|
|
||||||
</Resources>
|
|
||||||
|
|
||||||
<Applications>
|
|
||||||
<Application Id="App"
|
|
||||||
Executable="$targetnametoken$.exe"
|
|
||||||
EntryPoint="$targetentrypoint$">
|
|
||||||
<uap:VisualElements
|
|
||||||
DisplayName="Ghost.Graphics.Test"
|
|
||||||
Description="Ghost.UnitTest"
|
|
||||||
BackgroundColor="transparent"
|
|
||||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
|
||||||
Square44x44Logo="Assets\Square44x44Logo.png">
|
|
||||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
|
|
||||||
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
|
||||||
</uap:VisualElements>
|
|
||||||
</Application>
|
|
||||||
</Applications>
|
|
||||||
|
|
||||||
<Capabilities>
|
|
||||||
<rescap:Capability Name="runFullTrust" />
|
|
||||||
</Capabilities>
|
|
||||||
</Package>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"profiles": {
|
|
||||||
"Ghost.Graphics.Test (Package)": {
|
|
||||||
"commandName": "MsixPackage",
|
|
||||||
"nativeDebugging": true
|
|
||||||
},
|
|
||||||
"Ghost.Graphics.Test (Unpackaged)": {
|
|
||||||
"commandName": "Project"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
#include "F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/Shaders/Includes/Properties.hlsl"
|
|
||||||
#include "F:/csharp/GhostEngine/src/Runtime//Ghost.Graphics/Shaders/Includes/Common.hlsl"
|
|
||||||
|
|
||||||
struct PixelInput
|
|
||||||
{
|
|
||||||
float4 position : SV_POSITION;
|
|
||||||
float4 color : COLOR;
|
|
||||||
float4 uv : TEXCOORD0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Meshlet
|
|
||||||
{
|
|
||||||
float4 boundingSphere;
|
|
||||||
float3 boundingBoxMin;
|
|
||||||
float3 boundingBoxMax;
|
|
||||||
uint vertexOffset;
|
|
||||||
uint triangleOffset;
|
|
||||||
uint groupIndex;
|
|
||||||
float parentError;
|
|
||||||
uint packedCounts; // byte vertexCount, byte triangleCount, byte localMaterialIndex, byte lodLevel
|
|
||||||
};
|
|
||||||
|
|
||||||
[numthreads(64, 1, 1)] // 64 threads for max 64 vertices and up to 124 triangles
|
|
||||||
[outputtopology("triangle")]
|
|
||||||
void MSMain(
|
|
||||||
uint3 groupThreadID : SV_GroupThreadID,
|
|
||||||
uint groupID : SV_GroupID,
|
|
||||||
out vertices PixelInput outVerts[64],
|
|
||||||
out indices uint3 outTris[124])
|
|
||||||
{
|
|
||||||
PerObjectData perObjectData = LoadData<PerObjectData>(g_PushConstantData.perObjectBuffer, 0);
|
|
||||||
|
|
||||||
ByteAddressBuffer meshletBuffer = GET_BUFFER(perObjectData.meshletBuffer);
|
|
||||||
Meshlet m = meshletBuffer.Load<Meshlet>(groupID.x * sizeof(Meshlet));
|
|
||||||
|
|
||||||
uint vertexCount = m.packedCounts & 0xFF;
|
|
||||||
uint triangleCount = (m.packedCounts >> 8) & 0xFF;
|
|
||||||
|
|
||||||
SetMeshOutputCounts(vertexCount, triangleCount);
|
|
||||||
|
|
||||||
ByteAddressBuffer meshletVerticesBuffer = GET_BUFFER(perObjectData.meshletVerticesBuffer);
|
|
||||||
ByteAddressBuffer meshletTrianglesBuffer = GET_BUFFER(perObjectData.meshletTrianglesBuffer);
|
|
||||||
|
|
||||||
// Write vertex output
|
|
||||||
if (groupThreadID.x < vertexCount)
|
|
||||||
{
|
|
||||||
uint vertexIndex = meshletVerticesBuffer.Load((m.vertexOffset + groupThreadID.x) * 4);
|
|
||||||
ByteAddressBuffer vertices = GET_BUFFER(perObjectData.vertexBuffer);
|
|
||||||
Vertex v = vertices.Load<Vertex>(vertexIndex * sizeof(Vertex));
|
|
||||||
|
|
||||||
// Basic MVP transform not needed if already in world space, but usually we need localToWorld and ViewProj
|
|
||||||
PerViewData perViewData = LoadData<PerViewData>(g_PushConstantData.perViewBuffer, 0);
|
|
||||||
float4 worldPos = mul(perObjectData.localToWorld, float4(v.position.xyz, 1.0f));
|
|
||||||
outVerts[groupThreadID.x].position = mul(perViewData.viewMatrix, worldPos);
|
|
||||||
outVerts[groupThreadID.x].position = mul(perViewData.projectionMatrix, outVerts[groupThreadID.x].position);
|
|
||||||
|
|
||||||
outVerts[groupThreadID.x].color = v.color;
|
|
||||||
outVerts[groupThreadID.x].uv = v.uv;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write triangle output (1 thread processes 1 triangle)
|
|
||||||
// We could pack 3 indices in a uint or just use byte offset
|
|
||||||
// In our CPU code, we packed it as individual bytes, so 3 bytes per triangle.
|
|
||||||
// For 124 triangles, we have 372 bytes.
|
|
||||||
if (groupThreadID.x < triangleCount)
|
|
||||||
{
|
|
||||||
uint triangleIndex = groupThreadID.x;
|
|
||||||
uint baseOffset = m.triangleOffset + triangleIndex * 3;
|
|
||||||
|
|
||||||
// Load 4 bytes to get the 3 index bytes
|
|
||||||
// Needs byte-aligned loading
|
|
||||||
uint wordOffset = baseOffset & ~3;
|
|
||||||
uint shift = (baseOffset & 3) * 8;
|
|
||||||
uint packedIndices1 = meshletTrianglesBuffer.Load(wordOffset);
|
|
||||||
uint packedIndices2 = meshletTrianglesBuffer.Load(wordOffset + 4);
|
|
||||||
|
|
||||||
uint64_t combined = ((uint64_t)packedIndices2 << 32) | packedIndices1;
|
|
||||||
uint packedIndices = (uint)(combined >> shift);
|
|
||||||
|
|
||||||
uint i0 = packedIndices & 0xFF;
|
|
||||||
uint i1 = (packedIndices >> 8) & 0xFF;
|
|
||||||
uint i2 = (packedIndices >> 16) & 0xFF;
|
|
||||||
|
|
||||||
outTris[triangleIndex] = uint3(i0, i1, i2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 PSMain(PixelInput input) : SV_TARGET
|
|
||||||
{
|
|
||||||
PerMaterialData perMaterialData = LoadData<PerMaterialData>(g_PushConstantData.perMaterialBuffer, 0);
|
|
||||||
|
|
||||||
float4 color1 = SAMPLE_TEXTURE2D(perMaterialData.texture1, perMaterialData.tex_sampler, input.uv.xy);
|
|
||||||
float4 color2 = SAMPLE_TEXTURE2D(perMaterialData.texture2, perMaterialData.tex_sampler, input.uv.xy);
|
|
||||||
float4 color3 = SAMPLE_TEXTURE2D(perMaterialData.texture3, perMaterialData.tex_sampler, input.uv.xy);
|
|
||||||
float4 color4 = SAMPLE_TEXTURE2D(perMaterialData.texture4, perMaterialData.tex_sampler, input.uv.xy);
|
|
||||||
|
|
||||||
float4 blendedColor = (color1 + color2 + color3 + color4) * 0.25f;
|
|
||||||
return perMaterialData.color * blendedColor + input.color;
|
|
||||||
}
|
|
||||||
@@ -1,373 +0,0 @@
|
|||||||
#if false
|
|
||||||
|
|
||||||
using Ghost.Core;
|
|
||||||
using Ghost.Core.Graphics;
|
|
||||||
using Ghost.DSL.ShaderCompiler;
|
|
||||||
using Ghost.Graphics.Core;
|
|
||||||
using Ghost.Graphics.RenderGraphModule;
|
|
||||||
using Ghost.Graphics.RHI;
|
|
||||||
using Misaki.HighPerformance.Mathematics;
|
|
||||||
using Misaki.HighPerformance.Mathematics.Geometry;
|
|
||||||
using Misaki.HighPerformance.Utilities;
|
|
||||||
|
|
||||||
namespace Ghost.Graphics.Test.RenderPipeline;
|
|
||||||
|
|
||||||
public unsafe partial class TestRenderPipeline : IRenderPipeline
|
|
||||||
{
|
|
||||||
private class MeshletDebugPassData
|
|
||||||
{
|
|
||||||
public RenderList renderList;
|
|
||||||
public Handle<Material> material;
|
|
||||||
public uint globalIndex;
|
|
||||||
public uint viewIndex;
|
|
||||||
public uint instanceIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly RenderSystem _renderSystem;
|
|
||||||
|
|
||||||
private readonly RenderGraph _renderGraph;
|
|
||||||
private Handle<Shader> _meshletShader;
|
|
||||||
private Handle<Material> _meshletMaterial;
|
|
||||||
|
|
||||||
private bool _disposed;
|
|
||||||
|
|
||||||
~TestRenderPipeline()
|
|
||||||
{
|
|
||||||
Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal TestRenderPipeline(RenderSystem renderSystem)
|
|
||||||
{
|
|
||||||
_renderSystem = renderSystem;
|
|
||||||
|
|
||||||
_renderGraph = new RenderGraph(renderSystem.ResourceManager,
|
|
||||||
renderSystem.GraphicsEngine.ResourceAllocator,
|
|
||||||
renderSystem.GraphicsEngine.ResourceDatabase,
|
|
||||||
renderSystem.GraphicsEngine.PipelineLibrary,
|
|
||||||
renderSystem.GraphicsEngine.ShaderCompiler);
|
|
||||||
|
|
||||||
var shaderDescriptor = DSLShaderCompiler.CompileGraphicsShader("F:/csharp/GhostEngine/src/Runtime/Ghost.Graphics/test.gshdr").GetValueOrThrow();
|
|
||||||
|
|
||||||
var config = new ShaderCompilationConfig
|
|
||||||
{
|
|
||||||
optimizeLevel = CompilerOptimizeLevel.O3,
|
|
||||||
options = CompilerOption.KeepReflections,
|
|
||||||
model = shaderDescriptor.shaderModel
|
|
||||||
};
|
|
||||||
|
|
||||||
ref readonly var pass = ref shaderDescriptor.passes[0];
|
|
||||||
var emptyKeywords = new LocalKeywordSet();
|
|
||||||
var compiled = renderSystem.GraphicsEngine.ShaderCompiler.CompilePass(in pass, in config, in emptyKeywords).GetValueOrThrow();
|
|
||||||
|
|
||||||
_meshletShader = renderSystem.ResourceManager.CreateGraphicsShader(shaderDescriptor, [compiled]);
|
|
||||||
_meshletMaterial = renderSystem.ResourceManager.CreateMaterial(_meshletShader);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float3 IntersectFrustumPlanes(float4 p0, float4 p1, float4 p2)
|
|
||||||
{
|
|
||||||
var n0 = p0.xyz;
|
|
||||||
var n1 = p1.xyz;
|
|
||||||
var n2 = p2.xyz;
|
|
||||||
|
|
||||||
float det = math.dot(math.cross(n0, n1), n2);
|
|
||||||
return (math.cross(n2, n1) * p0.w + math.cross(n0, n2) * p1.w - math.cross(n0, n1) * p2.w) * (1.0f / det);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Frustum CreateFrustum(float nearClip, float farClip, float4x4 vp, float3 viewDir, float3 viewPos)
|
|
||||||
{
|
|
||||||
var frustum = new Frustum();
|
|
||||||
Frustum.CalculateFrustumPlanes(vp, ref frustum.planes);
|
|
||||||
|
|
||||||
// We need to recalculate the near and far planes otherwise it does not work for oblique projection matrices used for reflection.
|
|
||||||
var nearPlane = Plane.CreateFromUnitNormalAndPointInPlane(viewDir, viewPos);
|
|
||||||
nearPlane.Distance -= nearClip;
|
|
||||||
|
|
||||||
var farPlane = Plane.CreateFromUnitNormalAndPointInPlane(-viewDir, viewPos);
|
|
||||||
farPlane.Distance += farClip;
|
|
||||||
|
|
||||||
frustum.planes[4] = nearPlane;
|
|
||||||
frustum.planes[5] = farPlane;
|
|
||||||
|
|
||||||
frustum.corners[0] = IntersectFrustumPlanes(frustum.planes[0], frustum.planes[3], frustum.planes[4]);
|
|
||||||
frustum.corners[1] = IntersectFrustumPlanes(frustum.planes[1], frustum.planes[3], frustum.planes[4]);
|
|
||||||
frustum.corners[2] = IntersectFrustumPlanes(frustum.planes[0], frustum.planes[2], frustum.planes[4]);
|
|
||||||
frustum.corners[3] = IntersectFrustumPlanes(frustum.planes[1], frustum.planes[2], frustum.planes[4]);
|
|
||||||
frustum.corners[4] = IntersectFrustumPlanes(frustum.planes[0], frustum.planes[3], frustum.planes[5]);
|
|
||||||
frustum.corners[5] = IntersectFrustumPlanes(frustum.planes[1], frustum.planes[3], frustum.planes[5]);
|
|
||||||
frustum.corners[6] = IntersectFrustumPlanes(frustum.planes[0], frustum.planes[2], frustum.planes[5]);
|
|
||||||
frustum.corners[7] = IntersectFrustumPlanes(frustum.planes[1], frustum.planes[2], frustum.planes[5]);
|
|
||||||
return frustum;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Render(RenderContext ctx, int frameIndex, IRenderPayload payload)
|
|
||||||
{
|
|
||||||
var testPayload = (TestRenderPayload)payload;
|
|
||||||
|
|
||||||
var resourceManager = _renderSystem.ResourceManager;
|
|
||||||
var resourceDatabase = _renderSystem.GraphicsEngine.ResourceDatabase;
|
|
||||||
|
|
||||||
var requests = testPayload.renderRequests;
|
|
||||||
|
|
||||||
for (var i = 0; i < requests.Count; i++)
|
|
||||||
{
|
|
||||||
ref readonly var request = ref requests[i];
|
|
||||||
|
|
||||||
// 1. Allocate and populate Instance Data buffer
|
|
||||||
var instanceCount = request.opaqueRenderList.TotalRecordCount;
|
|
||||||
if (instanceCount == 0)
|
|
||||||
{
|
|
||||||
continue; // Nothing to render
|
|
||||||
}
|
|
||||||
|
|
||||||
Handle<GPUTexture> rt;
|
|
||||||
if (request.swapChainIndex < 0)
|
|
||||||
{
|
|
||||||
rt = request.colorTarget;
|
|
||||||
}
|
|
||||||
else if (_renderSystem.SwapChainManager.TryGetSwapChain(request.swapChainIndex, out var swapChain))
|
|
||||||
{
|
|
||||||
rt = swapChain.GetCurrentBackBuffer();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var rtResult = _renderSystem.GraphicsEngine.ResourceDatabase.GetResourceDescription(rt.AsResource());
|
|
||||||
if (rtResult.IsFailure)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var rtSize = new uint2(rtResult.Value.TextureDescriptor.Width, rtResult.Value.TextureDescriptor.Height);
|
|
||||||
var aspectScreen = (float)rtSize.x / rtSize.y;
|
|
||||||
|
|
||||||
|
|
||||||
// NOTE: We assume camera's scale is always (1, 1, 1). Otherwise fastinverse will fail and we need to use regular inverse which is more expensive.
|
|
||||||
var viewMatrix = math.fastinverse(request.view.localToWorld);
|
|
||||||
|
|
||||||
var vfov = 2.0f * math.atan(request.view.sensorSize.y / (2.0f * request.view.focalLength));
|
|
||||||
var hfov = 2.0f * math.atan(request.view.sensorSize.x / (2.0f * request.view.focalLength));
|
|
||||||
var aspectSensor = request.view.sensorSize.x / request.view.sensorSize.y;
|
|
||||||
|
|
||||||
float vfovF;
|
|
||||||
switch (request.view.gateFit)
|
|
||||||
{
|
|
||||||
case GateFit.Vertical:
|
|
||||||
vfovF = vfov;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GateFit.Horizontal:
|
|
||||||
// Adjust VFOV so that the sensor width fits the screen width
|
|
||||||
var horizontalAspectBuffer = math.tan(hfov * 0.5f);
|
|
||||||
vfovF = 2.0f * math.atan(horizontalAspectBuffer / aspectScreen);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GateFit.Fill:
|
|
||||||
if (aspectSensor > aspectScreen)
|
|
||||||
{
|
|
||||||
goto case GateFit.Vertical;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
goto case GateFit.Horizontal;
|
|
||||||
}
|
|
||||||
|
|
||||||
case GateFit.Overscan:
|
|
||||||
if (aspectSensor > aspectScreen)
|
|
||||||
{
|
|
||||||
goto case GateFit.Horizontal;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
goto case GateFit.Vertical;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
vfovF = vfov;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var m_11 = 1.0f / math.tan(vfovF * 0.5f);
|
|
||||||
var m_00 = m_11 / aspectScreen;
|
|
||||||
var m_22 = request.view.farClipPlane / (request.view.farClipPlane - request.view.nearClipPlane);
|
|
||||||
var m_23 = -(request.view.farClipPlane * request.view.nearClipPlane) / (request.view.farClipPlane - request.view.nearClipPlane);
|
|
||||||
|
|
||||||
var projectionMatrix = new float4x4
|
|
||||||
(
|
|
||||||
m_00, 0, 0, 0,
|
|
||||||
0, m_11, 0, 0,
|
|
||||||
0, 0, m_22, m_23,
|
|
||||||
0, 0, 1, 0
|
|
||||||
);
|
|
||||||
|
|
||||||
//var vp = math.mul(projectionMatrix, viewMatrix);
|
|
||||||
//var viewDir = math.normalize(request.view.localToWorld.c2.xyz);
|
|
||||||
//var viewPos = request.view.localToWorld.c3.xyz;
|
|
||||||
//var frustum = CreateFrustum(request.view.nearClipPlane, request.view.farClipPlane, vp, viewDir, viewPos);
|
|
||||||
|
|
||||||
var instanceDataSize = (uint)(instanceCount * sizeof(InstanceData));
|
|
||||||
var instanceBufferDesc = new BufferDesc
|
|
||||||
{
|
|
||||||
Size = instanceDataSize,
|
|
||||||
Stride = (uint)sizeof(InstanceData),
|
|
||||||
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
|
|
||||||
HeapType = HeapType.Upload, // Upload directly for simplicity in testing
|
|
||||||
};
|
|
||||||
|
|
||||||
var instanceBufferHandle = resourceManager.CreateTransientBuffer(in instanceBufferDesc, "Instance Buffer");
|
|
||||||
var instanceBufferResource = instanceBufferHandle.AsResource();
|
|
||||||
|
|
||||||
var instanceDataArray = new InstanceData[instanceCount];
|
|
||||||
var instanceIdx = 0;
|
|
||||||
foreach (var record in request.opaqueRenderList)
|
|
||||||
{
|
|
||||||
var (mesh, error) = resourceManager.GetMeshReference(record.mesh);
|
|
||||||
if (error.IsFailure)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
(var mat, error) = resourceManager.GetMaterialReference(_meshletMaterial);
|
|
||||||
if (error.IsFailure)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
instanceDataArray[instanceIdx++] = new InstanceData
|
|
||||||
{
|
|
||||||
localToWorld = record.localToWorld,
|
|
||||||
meshBuffer = resourceDatabase.GetBindlessIndex(mesh.Get().MeshDataBuffer.AsResource()),
|
|
||||||
materialBuffer = resourceDatabase.GetBindlessIndex(mat.Get()._cBufferCache.GpuResource.AsResource())
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.CommandBuffer.Barrier(BarrierDesc.Buffer(instanceBufferResource, BarrierSync.Copy, BarrierAccess.CopyDest));
|
|
||||||
ctx.UploadBuffer(instanceBufferHandle, instanceDataArray.AsSpan());
|
|
||||||
ctx.CommandBuffer.Barrier(BarrierDesc.Buffer(instanceBufferResource, BarrierSync.AllShading, BarrierAccess.ShaderResource));
|
|
||||||
|
|
||||||
// 2. Allocate and populate View Data buffer
|
|
||||||
var viewBufferDesc = new BufferDesc
|
|
||||||
{
|
|
||||||
Size = (uint)sizeof(ViewData),
|
|
||||||
Stride = (uint)sizeof(ViewData),
|
|
||||||
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
|
|
||||||
HeapType = HeapType.Upload,
|
|
||||||
};
|
|
||||||
|
|
||||||
var viewBufferHandle = resourceManager.CreateTransientBuffer(in viewBufferDesc, "View Buffer");
|
|
||||||
var viewBufferResource = viewBufferHandle.AsResource();
|
|
||||||
|
|
||||||
var viewData = new ViewData
|
|
||||||
{
|
|
||||||
viewMatrix = viewMatrix,
|
|
||||||
projectionMatrix = projectionMatrix,
|
|
||||||
cameraPosition = request.view.localToWorld.c3.xyz,
|
|
||||||
nearClip = request.view.nearClipPlane,
|
|
||||||
cameraDirection = viewMatrix.c2.xyz, // check if that's correct orientation
|
|
||||||
farClip = request.view.farClipPlane,
|
|
||||||
screenSize = new float4(rtSize.x, rtSize.y, 1.0f / rtSize.x, 1.0f / rtSize.y)
|
|
||||||
};
|
|
||||||
|
|
||||||
ctx.CommandBuffer.Barrier(BarrierDesc.Buffer(viewBufferResource, BarrierSync.Copy, BarrierAccess.CopyDest));
|
|
||||||
ctx.UploadBuffer(viewBufferHandle, new ReadOnlySpan<ViewData>(in viewData));
|
|
||||||
ctx.CommandBuffer.Barrier(BarrierDesc.Buffer(viewBufferResource, BarrierSync.AllShading, BarrierAccess.ShaderResource));
|
|
||||||
|
|
||||||
// 3. Allocate and populate Global Frame Data buffer
|
|
||||||
var frameDataSize = (uint)sizeof(FrameData);
|
|
||||||
var frameBufferDesc = new BufferDesc
|
|
||||||
{
|
|
||||||
Size = frameDataSize,
|
|
||||||
Stride = frameDataSize,
|
|
||||||
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
|
|
||||||
HeapType = HeapType.Upload,
|
|
||||||
};
|
|
||||||
|
|
||||||
var frameBufferHandle = resourceManager.CreateTransientBuffer(in frameBufferDesc, "Frame Buffer");
|
|
||||||
var frameBufferResource = frameBufferHandle.AsResource();
|
|
||||||
|
|
||||||
var frameData = new FrameData
|
|
||||||
{
|
|
||||||
instanceBuffer = resourceDatabase.GetBindlessIndex(instanceBufferResource)
|
|
||||||
};
|
|
||||||
|
|
||||||
ctx.CommandBuffer.Barrier(BarrierDesc.Buffer(frameBufferResource, BarrierSync.Copy, BarrierAccess.CopyDest));
|
|
||||||
ctx.UploadBuffer(frameBufferHandle, new ReadOnlySpan<FrameData>(in frameData));
|
|
||||||
ctx.CommandBuffer.Barrier(BarrierDesc.Buffer(frameBufferResource, BarrierSync.AllShading, BarrierAccess.ShaderResource));
|
|
||||||
|
|
||||||
_renderGraph.Reset();
|
|
||||||
|
|
||||||
var backBuffer = _renderGraph.ImportTexture(rt, "BackBuffer");
|
|
||||||
|
|
||||||
MeshletDebugPass(backBuffer, request.opaqueRenderList,
|
|
||||||
uint.MaxValue,
|
|
||||||
resourceDatabase.GetBindlessIndex(viewBufferResource));
|
|
||||||
|
|
||||||
var viewState = new ViewState(rtSize.x, rtSize.y, rtSize.x, rtSize.y);
|
|
||||||
_renderGraph.Compile(viewState);
|
|
||||||
_renderGraph.Execute(ctx.CommandBuffer);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (request.swapChainIndex >= 0)
|
|
||||||
{
|
|
||||||
_renderSystem.SwapChainManager.ReleaseSwapChain(request.swapChainIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void MeshletDebugPass(Identifier<RGTexture> backbuffer, RenderList renderList, uint globalIndex, uint viewIndex)
|
|
||||||
{
|
|
||||||
using (var builder = _renderGraph.AddRasterRenderPass<MeshletDebugPassData>("Meshlet Debug Pass", out var passData))
|
|
||||||
{
|
|
||||||
var depth = builder.CreateTexture(RGTextureDesc.RelativeDepth(1.0f), "Depth Texture");
|
|
||||||
|
|
||||||
passData.renderList = renderList;
|
|
||||||
passData.globalIndex = globalIndex;
|
|
||||||
passData.viewIndex = viewIndex;
|
|
||||||
passData.material = _meshletMaterial;
|
|
||||||
|
|
||||||
builder.SetColorAttachment(backbuffer, 0);
|
|
||||||
builder.SetDepthAttachment(depth);
|
|
||||||
|
|
||||||
builder.SetRenderFunc<MeshletDebugPassData>(static (data, ctx) =>
|
|
||||||
{
|
|
||||||
ctx.SetGlobalData(data.globalIndex, data.viewIndex);
|
|
||||||
ctx.SetActiveMaterial(data.material);
|
|
||||||
|
|
||||||
var instanceIndex = 0u;
|
|
||||||
foreach (var record in data.renderList)
|
|
||||||
{
|
|
||||||
ctx.SetInstanceIndex(instanceIndex);
|
|
||||||
|
|
||||||
var meshRefResult = ctx.ResourceManager.GetMeshReference(record.mesh);
|
|
||||||
if (meshRefResult.IsSuccess)
|
|
||||||
{
|
|
||||||
var meshletCount = (uint)meshRefResult.Value.MeshletData.meshletCount;
|
|
||||||
ctx.DispatchMesh(new uint3(meshletCount, 1, 1));
|
|
||||||
}
|
|
||||||
instanceIndex++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderSystem.ResourceManager.ReleaseMaterial(_meshletMaterial);
|
|
||||||
_renderSystem.ResourceManager.ReleaseShader(_meshletShader);
|
|
||||||
|
|
||||||
_renderGraph.Dispose();
|
|
||||||
|
|
||||||
_disposed = true;
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
#if false
|
|
||||||
|
|
||||||
using Ghost.Graphics.Core;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
|
||||||
|
|
||||||
namespace Ghost.Graphics.Test.RenderPipeline;
|
|
||||||
|
|
||||||
internal sealed class TestRenderPayload : IRenderPayload
|
|
||||||
{
|
|
||||||
public UnsafeList<RenderRequest> renderRequests;
|
|
||||||
|
|
||||||
public TestRenderPayload()
|
|
||||||
{
|
|
||||||
renderRequests = new UnsafeList<RenderRequest>(2, Allocator.Persistent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Reset()
|
|
||||||
{
|
|
||||||
for (int i = 0; i < renderRequests.Count; i++)
|
|
||||||
{
|
|
||||||
renderRequests[i].Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
renderRequests.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
renderRequests.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal sealed class TestRenderPipelineSettings : IRenderPipelineSettings
|
|
||||||
{
|
|
||||||
public IRenderPipeline CreatePipeline(RenderSystem renderSystem)
|
|
||||||
{
|
|
||||||
return new TestRenderPipeline(renderSystem);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IRenderPayload CreatePayload(RenderSystem renderSystem)
|
|
||||||
{
|
|
||||||
return new TestRenderPayload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
using Ghost.Core;
|
|
||||||
using Ghost.Engine.Components;
|
|
||||||
using Ghost.Engine.Utilities;
|
|
||||||
using Ghost.Entities;
|
|
||||||
using Misaki.HighPerformance.Mathematics;
|
|
||||||
|
|
||||||
namespace Ghost.Graphics.Test.Systems;
|
|
||||||
|
|
||||||
internal class CameraMovingSystem : ISystem
|
|
||||||
{
|
|
||||||
private Identifier<EntityQuery> _cameraQueryID;
|
|
||||||
|
|
||||||
private random _random;
|
|
||||||
private float3 _target;
|
|
||||||
|
|
||||||
public void Initialize(ref readonly SystemAPI systemAPI)
|
|
||||||
{
|
|
||||||
_cameraQueryID = QueryBuilder.New()
|
|
||||||
.WithAll<Camera, LocalToWorld>()
|
|
||||||
.Build(systemAPI.World, true);
|
|
||||||
|
|
||||||
_random = new random(123456);
|
|
||||||
_target = _random.NextFloat3(-20, 20);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update(ref readonly SystemAPI systemAPI)
|
|
||||||
{
|
|
||||||
ref var cameraQuery = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_cameraQueryID);
|
|
||||||
|
|
||||||
foreach (ref var ltw in cameraQuery.GetComponentIterator<LocalToWorld>())
|
|
||||||
{
|
|
||||||
var position = ltw.matrix.c3.xyz;
|
|
||||||
if (math.distance(position, _target) < 0.1f)
|
|
||||||
{
|
|
||||||
_target = _random.NextFloat3(-20, 20);
|
|
||||||
}
|
|
||||||
|
|
||||||
var newPosition = math.lerp(position, _target, 0.025f);
|
|
||||||
var forward = math.normalize(new float3(0f, 0.5f, 0f) - newPosition);
|
|
||||||
|
|
||||||
var rotation = quaternion.LookRotation(forward, math.up());
|
|
||||||
var matrix = float4x4.TRS(newPosition, rotation, float3.one);
|
|
||||||
ltw.matrix = matrix;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Cleanup(ref readonly SystemAPI systemAPI)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
#if false
|
|
||||||
using Ghost.Core;
|
|
||||||
using Ghost.Engine;
|
|
||||||
using Ghost.Engine.Components;
|
|
||||||
using Ghost.Engine.Systems;
|
|
||||||
using Ghost.Entities;
|
|
||||||
using Ghost.Graphics.Core;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
|
||||||
using Misaki.HighPerformance.Mathematics;
|
|
||||||
|
|
||||||
namespace Ghost.Graphics.Test.Systems;
|
|
||||||
|
|
||||||
//[RenderPipelineSystem<TestRenderPipelineSettings>]
|
|
||||||
[UpdateAfter<CameraMovingSystem>]
|
|
||||||
public class RenderExtractionSystem : ISystem
|
|
||||||
{
|
|
||||||
private RenderSystem _renderSystem = null!;
|
|
||||||
|
|
||||||
private Identifier<EntityQuery> _cameraQueryID;
|
|
||||||
private Identifier<EntityQuery> _meshQueryID;
|
|
||||||
|
|
||||||
public void Initialize(ref readonly SystemAPI systemAPI)
|
|
||||||
{
|
|
||||||
_renderSystem = systemAPI.World.GetService<RenderSystem>();
|
|
||||||
|
|
||||||
var builder = new QueryBuilder();
|
|
||||||
|
|
||||||
_cameraQueryID = builder
|
|
||||||
.WithAll<Camera, LocalToWorld>()
|
|
||||||
.Build(systemAPI.World, false);
|
|
||||||
|
|
||||||
builder.Clear();
|
|
||||||
|
|
||||||
_meshQueryID = builder
|
|
||||||
.WithAll<MeshInstance, LocalToWorld>()
|
|
||||||
.Build(systemAPI.World, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update(ref readonly SystemAPI systemAPI)
|
|
||||||
{
|
|
||||||
if (_meshQueryID.IsInvalid)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ref var cameraQuery = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_cameraQueryID);
|
|
||||||
ref var meshQuery = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_meshQueryID);
|
|
||||||
|
|
||||||
foreach (var (cam, camLtw) in cameraQuery.GetComponentIterator<Camera, LocalToWorld>())
|
|
||||||
{
|
|
||||||
ref readonly var camRef = ref cam.Get();
|
|
||||||
ref readonly var camLtwRef = ref camLtw.Get();
|
|
||||||
|
|
||||||
// TODO: Classify transparent objects into a separate render list and render via oit.
|
|
||||||
var renderList = new RenderList(1, 64, AllocationHandle.FreeList);
|
|
||||||
var transparentRenderList = new RenderList(1, 64, AllocationHandle.FreeList);
|
|
||||||
var shadowCasterRenderList = new RenderList(1, 64, AllocationHandle.FreeList);
|
|
||||||
|
|
||||||
// TODO: This chould be done in earallel jobs.
|
|
||||||
foreach (var chunk in meshQuery.GetChunkIterator())
|
|
||||||
{
|
|
||||||
var meshInstances = chunk.GetComponentData<MeshInstance>();
|
|
||||||
var localToWorlds = chunk.GetComponentData<LocalToWorld>();
|
|
||||||
|
|
||||||
for (var i = 0; i < chunk.EntityCount; i++)
|
|
||||||
{
|
|
||||||
ref readonly var meshInstance = ref meshInstances[i];
|
|
||||||
if ((meshInstance.renderingLayerMask & camRef.renderingLayerMask) == 0u)
|
|
||||||
{
|
|
||||||
// Not in the same rendering layer, skip.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ref readonly var meshLtw = ref localToWorlds[i];
|
|
||||||
|
|
||||||
var meshPosition = meshLtw.matrix.c3.xyz;
|
|
||||||
var camPosition = camLtwRef.matrix.c3.xyz;
|
|
||||||
var distance = math.distance(meshPosition, camPosition);
|
|
||||||
|
|
||||||
// TODO: Use bounding sphere or AABB for better culling. Currently it just uses the pivot point which can cause popping when the pivot is far from the actual geometry.
|
|
||||||
if (distance < camRef.nearClipPlane || distance > camRef.farClipPlane)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (meshInstance.shadowCastingMode != ShadowCastingMode.ShadowsOnly)
|
|
||||||
{
|
|
||||||
renderList.Add(new RenderRecord
|
|
||||||
{
|
|
||||||
localToWorld = meshLtw.matrix,
|
|
||||||
mesh = meshInstance.mesh,
|
|
||||||
materialPalette = meshInstance.materialPalette,
|
|
||||||
renderingLayerMask = meshInstance.renderingLayerMask,
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (meshInstance.shadowCastingMode != ShadowCastingMode.Off)
|
|
||||||
{
|
|
||||||
shadowCasterRenderList.Add(new RenderRecord
|
|
||||||
{
|
|
||||||
localToWorld = meshLtw.matrix,
|
|
||||||
mesh = meshInstance.mesh,
|
|
||||||
materialPalette = meshInstance.materialPalette,
|
|
||||||
renderingLayerMask = meshInstance.renderingLayerMask,
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var request = new RenderRequest
|
|
||||||
{
|
|
||||||
swapChainIndex = camRef.swapChainIndex,
|
|
||||||
colorTarget = camRef.colorTarget,
|
|
||||||
depthTarget = camRef.depthTarget,
|
|
||||||
opaqueRenderList = renderList,
|
|
||||||
shadowCasterRenderList = shadowCasterRenderList,
|
|
||||||
transparentRenderList = transparentRenderList,
|
|
||||||
view = new RenderView
|
|
||||||
{
|
|
||||||
localToWorld = camLtwRef.matrix,
|
|
||||||
//viewMatrix = viewMatrix,
|
|
||||||
//projectionMatrix = projectionMatrix,
|
|
||||||
//position = camLtwRef.matrix.c3.xyz,
|
|
||||||
|
|
||||||
//frustum = frustum,
|
|
||||||
nearClipPlane = camRef.nearClipPlane,
|
|
||||||
farClipPlane = camRef.farClipPlane,
|
|
||||||
|
|
||||||
sensorSize = camRef.sensorSize,
|
|
||||||
gateFit = camRef.gateFit,
|
|
||||||
iso = camRef.iso,
|
|
||||||
shutterSpeed = camRef.shutterSpeed,
|
|
||||||
aperture = camRef.aperture,
|
|
||||||
focalLength = camRef.focalLength,
|
|
||||||
focusDistance = camRef.focusDistance,
|
|
||||||
|
|
||||||
renderingLayerMask = camRef.renderingLayerMask,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
((TestRenderPayload)_renderSystem.GetCurrentFramePayload()).renderRequests.Add(request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Cleanup(ref readonly SystemAPI systemAPI)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
|
||||||
<Application
|
|
||||||
x:Class="Ghost.Graphics.Test.UnitTestApp"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:local="using:Ghost.Graphics.Test">
|
|
||||||
<Application.Resources>
|
|
||||||
<ResourceDictionary>
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
|
||||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
|
||||||
<ResourceDictionary Source="ms-appx:///Microsoft.UI.Xaml/DensityStyles/Compact.xaml" />
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
|
||||||
<!-- Other app resources here -->
|
|
||||||
</ResourceDictionary>
|
|
||||||
</Application.Resources>
|
|
||||||
</Application>
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
using Ghost.Core;
|
|
||||||
using Ghost.Graphics.Test.Windows;
|
|
||||||
|
|
||||||
using Microsoft.UI.Xaml;
|
|
||||||
|
|
||||||
// To learn more about WinUI, the WinUI project structure,
|
|
||||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
|
||||||
|
|
||||||
namespace Ghost.Graphics.Test;
|
|
||||||
/// <summary>
|
|
||||||
/// Provides application-specific behavior to supplement the default Application class.
|
|
||||||
/// </summary>
|
|
||||||
public partial class UnitTestApp : Application
|
|
||||||
{
|
|
||||||
private Window? _window;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes the singleton application object. This is the first line of authored code
|
|
||||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
|
||||||
/// </summary>
|
|
||||||
public UnitTestApp()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invoked when the application is launched.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="args">Details about the launch request and process.</param>
|
|
||||||
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
|
||||||
{
|
|
||||||
_window = new GraphicsTestWindow();
|
|
||||||
_window.Activate();
|
|
||||||
|
|
||||||
UnhandledException += (sender, e) =>
|
|
||||||
{
|
|
||||||
Logger.Error(e.Exception);
|
|
||||||
#if DEBUG
|
|
||||||
System.Diagnostics.Debugger.Break();
|
|
||||||
#endif
|
|
||||||
Environment.FailFast("Unhandled exception", e.Exception);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
using Ghost.Core;
|
|
||||||
using Ghost.Graphics.RHI;
|
|
||||||
using Ghost.Graphics.Utilities;
|
|
||||||
using Ghost.MeshOptimizer;
|
|
||||||
using Ghost.Ufbx;
|
|
||||||
using Misaki.HighPerformance.LowLevel;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
|
||||||
using Misaki.HighPerformance.Mathematics;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Ghost.Graphics.Test.Utilities;
|
|
||||||
|
|
||||||
internal static class MeshUtility
|
|
||||||
{
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static float4 ComputeTangent(float3 t, float3 n, float3 b)
|
|
||||||
{
|
|
||||||
var proj = n * math.dot(n, t);
|
|
||||||
t = math.normalize(t - proj);
|
|
||||||
var w = math.dot(math.cross(n.xyz, t.xyz), b.xyz) < 0.0f ? -1.0f : 1.0f;
|
|
||||||
return new float4(t.xyz, w);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static unsafe Result LoadMesh(string filePath, AllocationHandle allocationHandle, out UnsafeList<Vertex> vertices, out UnsafeList<uint> indices)
|
|
||||||
{
|
|
||||||
vertices = default;
|
|
||||||
indices = default;
|
|
||||||
|
|
||||||
if (!File.Exists(filePath))
|
|
||||||
{
|
|
||||||
return Result.Failure("Invalid file path.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Path.GetExtension(filePath).Equals(".obj", StringComparison.OrdinalIgnoreCase)
|
|
||||||
&& !Path.GetExtension(filePath).Equals(".fbx", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return Result.Failure("Unsupported file format. Only .obj and .fbx are supported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var error = new ufbx_error();
|
|
||||||
var load_Opts = new ufbx_load_opts
|
|
||||||
{
|
|
||||||
target_axes = ufbx_coordinate_axes.left_handed_y_up,
|
|
||||||
obj_axes = ufbx_coordinate_axes.right_handed_y_up,
|
|
||||||
// Force X-axis mirroring to correctly convert handedness to Left-Handed,
|
|
||||||
// while preserving correct left/right orientation when viewed from the front.
|
|
||||||
handedness_conversion_axis = ufbx_mirror_axis.UFBX_MIRROR_AXIS_X,
|
|
||||||
space_conversion = ufbx_space_conversion.UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY,
|
|
||||||
};
|
|
||||||
|
|
||||||
using var str = new UnsafeArray<byte>(Encoding.UTF8.GetByteCount(filePath) + 1, AllocationHandle.FreeList);
|
|
||||||
var count = Encoding.UTF8.GetBytes(filePath, str.AsSpan());
|
|
||||||
str[count] = 0;
|
|
||||||
|
|
||||||
using var scene = new DisposablePtr<ufbx_scene>(ufbx_scene.LoadFile((sbyte*)str.GetUnsafePtr(), &load_Opts, &error));
|
|
||||||
if (scene.Get() == null)
|
|
||||||
{
|
|
||||||
return Result.Failure(error.description.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
using var flatVertices = new UnsafeList<Vertex>(1024, AllocationHandle.FreeList);
|
|
||||||
|
|
||||||
var needComputeNormals = false;
|
|
||||||
|
|
||||||
for (var i = 0u; i < scene.Get()->nodes.count; i++)
|
|
||||||
{
|
|
||||||
var data = scene.Get()->nodes.data;
|
|
||||||
var node = scene.Get()->nodes.data[i];
|
|
||||||
if (node->is_root)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node->mesh != null)
|
|
||||||
{
|
|
||||||
var pMesh = node->mesh;
|
|
||||||
if (pMesh->num_faces == 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var maxScratchIndices = (int)(pMesh->max_face_triangles * 3u);
|
|
||||||
|
|
||||||
using var triIndicesArray = new UnsafeArray<uint>(maxScratchIndices, AllocationHandle.FreeList);
|
|
||||||
|
|
||||||
for (var j = 0u; j < pMesh->num_faces; j++)
|
|
||||||
{
|
|
||||||
var face = pMesh->faces.data[j];
|
|
||||||
|
|
||||||
var numTris = UfbxApi.TriangulateFace(triIndicesArray.AsSpan(0, maxScratchIndices), pMesh, face);
|
|
||||||
|
|
||||||
var totalIndices = numTris * 3;
|
|
||||||
for (var k = 0; k < totalIndices; k++)
|
|
||||||
{
|
|
||||||
var ufbxTopologyIndex = triIndicesArray[k];
|
|
||||||
|
|
||||||
var posIdx = pMesh->vertex_position.indices.data[ufbxTopologyIndex];
|
|
||||||
var normIdx = pMesh->vertex_normal.exists ? pMesh->vertex_normal.indices.data[ufbxTopologyIndex] : uint.MaxValue;
|
|
||||||
var tanIdx = pMesh->vertex_tangent.exists ? pMesh->vertex_tangent.indices.data[ufbxTopologyIndex] : uint.MaxValue;
|
|
||||||
var uvIdx = pMesh->vertex_uv.exists ? pMesh->vertex_uv.indices.data[ufbxTopologyIndex] : uint.MaxValue;
|
|
||||||
var colIdx = pMesh->vertex_color.exists ? pMesh->vertex_color.indices.data[ufbxTopologyIndex] : uint.MaxValue;
|
|
||||||
var btanIdx = pMesh->vertex_bitangent.exists ? pMesh->vertex_bitangent.indices.data[ufbxTopologyIndex] : uint.MaxValue;
|
|
||||||
|
|
||||||
var position = pMesh->vertex_position.values.data[posIdx];
|
|
||||||
var normal = normIdx != uint.MaxValue ? pMesh->vertex_normal.values.data[normIdx] : default;
|
|
||||||
var uv = uvIdx != uint.MaxValue ? pMesh->vertex_uv.values.data[uvIdx] : default;
|
|
||||||
var color = colIdx != uint.MaxValue ? pMesh->vertex_color.values.data[colIdx] : default;
|
|
||||||
|
|
||||||
var vertex = new Vertex
|
|
||||||
{
|
|
||||||
position = new float3(position.x, position.y, position.z),
|
|
||||||
normal = new float3(normal.x, normal.y, normal.z),
|
|
||||||
uv = new float2(uv.x, uv.y),
|
|
||||||
color = new Color128(color.x, color.y, color.z, color.w)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (tanIdx != uint.MaxValue)
|
|
||||||
{
|
|
||||||
var mt = pMesh->vertex_tangent.values.data[tanIdx];
|
|
||||||
var mb = btanIdx != uint.MaxValue ? pMesh->vertex_bitangent.values.data[btanIdx] : default;
|
|
||||||
|
|
||||||
var t = new float3(mt.x, mt.y, mt.z);
|
|
||||||
var n = vertex.normal;
|
|
||||||
var b = btanIdx != uint.MaxValue ? new float3(mb.x, mb.y, mb.z) : math.cross(n, t);
|
|
||||||
vertex.tangent = ComputeTangent(t, n, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
var newIndex = (uint)flatVertices.Count;
|
|
||||||
|
|
||||||
flatVertices.Add(vertex);
|
|
||||||
|
|
||||||
if (!needComputeNormals)
|
|
||||||
{
|
|
||||||
needComputeNormals = normIdx == uint.MaxValue || tanIdx == uint.MaxValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var numIndices = (uint)flatVertices.Count;
|
|
||||||
|
|
||||||
using var weldedIndices = new UnsafeArray<uint>((int)numIndices, AllocationHandle.FreeList);
|
|
||||||
using var cachedIndices = new UnsafeArray<uint>((int)numIndices, AllocationHandle.FreeList);
|
|
||||||
|
|
||||||
var stream = new ufbx_vertex_stream
|
|
||||||
{
|
|
||||||
data = flatVertices.GetUnsafePtr(),
|
|
||||||
vertex_count = numIndices,
|
|
||||||
vertex_size = (nuint)sizeof(Vertex)
|
|
||||||
};
|
|
||||||
|
|
||||||
var numUniqueVertices = UfbxApi.GenerateIndices([stream], weldedIndices, null, &error);
|
|
||||||
if (numUniqueVertices == 0 && error.type != ufbx_error_type.UFBX_ERROR_NONE)
|
|
||||||
{
|
|
||||||
return Result.Failure($"Welding failed: {error.description}");
|
|
||||||
}
|
|
||||||
|
|
||||||
MeshOptApi.OptimizeVertexCache((uint*)cachedIndices.GetUnsafePtr(), (uint*)weldedIndices.GetUnsafePtr(), numIndices, numUniqueVertices);
|
|
||||||
|
|
||||||
vertices = new UnsafeList<Vertex>((int)numUniqueVertices, allocationHandle);
|
|
||||||
indices = new UnsafeList<uint>((int)numIndices, allocationHandle);
|
|
||||||
|
|
||||||
var finalVertexCount = MeshOptApi.OptimizeVertexFetch(vertices.GetUnsafePtr(), (uint*)cachedIndices.GetUnsafePtr(), numIndices, flatVertices.GetUnsafePtr(), numIndices, (nuint)sizeof(Vertex));
|
|
||||||
|
|
||||||
vertices.UnsafeSetCount((int)finalVertexCount);
|
|
||||||
|
|
||||||
MemoryUtility.MemCpy(indices.GetUnsafePtr(), cachedIndices.GetUnsafePtr(), numIndices * sizeof(uint));
|
|
||||||
indices.UnsafeSetCount((int)numIndices);
|
|
||||||
|
|
||||||
if (needComputeNormals)
|
|
||||||
{
|
|
||||||
MeshBuilder.ComputeNormal(vertices, indices);
|
|
||||||
MeshBuilder.ComputeTangents(vertices, indices);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.Success();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
|
||||||
<Window
|
|
||||||
x:Class="Ghost.Graphics.Test.Windows.DebugOutputWindow"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:controls="using:Ghost.Graphics.Test.Controls"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:local="using:Ghost.Graphics.Test.Windows"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
Title="DebugOutputWindow"
|
|
||||||
mc:Ignorable="d">
|
|
||||||
|
|
||||||
<Window.SystemBackdrop>
|
|
||||||
<MicaBackdrop />
|
|
||||||
</Window.SystemBackdrop>
|
|
||||||
|
|
||||||
<Grid>
|
|
||||||
<controls:DebugConsole HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
|
|
||||||
</Grid>
|
|
||||||
</Window>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using Microsoft.UI.Xaml;
|
|
||||||
|
|
||||||
namespace Ghost.Graphics.Test.Windows;
|
|
||||||
|
|
||||||
internal sealed partial class DebugOutputWindow : Window
|
|
||||||
{
|
|
||||||
public DebugOutputWindow()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
|
||||||
<Window
|
|
||||||
x:Class="Ghost.Graphics.Test.Windows.GraphicsTestWindow"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:controls="using:Ghost.Graphics.Test.Controls"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:local="using:Ghost.Graphics.Test.Windows"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
Title="GraphicsTestWindow"
|
|
||||||
mc:Ignorable="d">
|
|
||||||
|
|
||||||
<Window.SystemBackdrop>
|
|
||||||
<MicaBackdrop />
|
|
||||||
</Window.SystemBackdrop>
|
|
||||||
|
|
||||||
<Grid>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="*" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="300" MinHeight="150" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<!-- Main test content area -->
|
|
||||||
<SwapChainPanel
|
|
||||||
x:Name="Panel"
|
|
||||||
Grid.Row="0"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
VerticalAlignment="Stretch" />
|
|
||||||
|
|
||||||
<!-- Splitter -->
|
|
||||||
<Border
|
|
||||||
Grid.Row="1"
|
|
||||||
Height="4"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Background="{ThemeResource SystemControlBackgroundBaseLowBrush}" />
|
|
||||||
</Grid>
|
|
||||||
</Window>
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
using Ghost.Core;
|
|
||||||
using Ghost.Entities;
|
|
||||||
using Ghost.Graphics.Core;
|
|
||||||
using Ghost.Graphics.RHI;
|
|
||||||
using Microsoft.UI.Xaml;
|
|
||||||
using Microsoft.UI.Xaml.Controls;
|
|
||||||
using Microsoft.UI.Xaml.Media;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
|
||||||
using Misaki.HighPerformance.Mathematics;
|
|
||||||
|
|
||||||
namespace Ghost.Graphics.Test.Windows;
|
|
||||||
|
|
||||||
public sealed partial class GraphicsTestWindow : Window
|
|
||||||
{
|
|
||||||
private RenderSystem? _renderSystem;
|
|
||||||
private ISwapChain? _swapChain;
|
|
||||||
//private JobScheduler _jobScheduler;
|
|
||||||
private World? _world;
|
|
||||||
|
|
||||||
private Handle<Mesh> _meshHandle;
|
|
||||||
|
|
||||||
private bool _isFirstActivationHandled;
|
|
||||||
|
|
||||||
public GraphicsTestWindow()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
|
|
||||||
Activated += GraphicsTestWindow_Activated;
|
|
||||||
Closed += GraphicsTestWindow_Closed;
|
|
||||||
|
|
||||||
Panel.SizeChanged += SwapChainPanel_SizeChanged;
|
|
||||||
Panel.CompositionScaleChanged += SwapChainPanel_CompositionScaleChanged;
|
|
||||||
|
|
||||||
AllocationManager.Initialize(AllocationManagerDesc.Default);
|
|
||||||
|
|
||||||
//_jobScheduler = new JobScheduler(Environment.ProcessorCount - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GraphicsTestWindow_Activated(object sender, WindowActivatedEventArgs e)
|
|
||||||
{
|
|
||||||
#if false
|
|
||||||
if (_isFirstActivationHandled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
_isFirstActivationHandled = true;
|
|
||||||
|
|
||||||
_renderSystem = new RenderSystem(new RenderSystemDesc()
|
|
||||||
{
|
|
||||||
FrameBufferCount = 2,
|
|
||||||
GraphicsAPI = GraphicsAPI.Direct3D12,
|
|
||||||
InitialRenderPipelineSettings = new RenderPipeline.TestRenderPipelineSettings()
|
|
||||||
});
|
|
||||||
|
|
||||||
_swapChain = _renderSystem.SwapChainManager.EnsureSwapChain(0, new SwapChainDesc
|
|
||||||
{
|
|
||||||
Width = (uint)AppWindow.Size.Width,
|
|
||||||
Height = (uint)AppWindow.Size.Height,
|
|
||||||
ScaleX = Panel.CompositionScaleX,
|
|
||||||
ScaleY = Panel.CompositionScaleY,
|
|
||||||
Format = TextureFormat.B8G8R8A8_UNorm,
|
|
||||||
Target = SwapChainTarget.FromCompositionSurface(Panel)
|
|
||||||
});
|
|
||||||
|
|
||||||
_renderSystem.Start();
|
|
||||||
|
|
||||||
// ECS Setup
|
|
||||||
_world = World.Create();
|
|
||||||
_world.AddService(_renderSystem);
|
|
||||||
|
|
||||||
// Add Systems
|
|
||||||
var group = _world.SystemManager.GetSystem<DefaultSystemGroup>();
|
|
||||||
group.AddSystem<RenderExtractionSystem>();
|
|
||||||
group.AddSystem<CameraMovingSystem>();
|
|
||||||
group.SortSystems();
|
|
||||||
|
|
||||||
_world.SystemManager.InitializeAll();
|
|
||||||
|
|
||||||
// Create Camera Entity
|
|
||||||
|
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
|
||||||
using var camSet = new ComponentSet(scope.AllocationHandle, ComponentTypeID<Camera>.Value, ComponentTypeID<LocalToWorld>.Value);
|
|
||||||
var cameraEntity = _world.EntityManager.CreateEntity(camSet);
|
|
||||||
|
|
||||||
_world.EntityManager.SetComponent(cameraEntity, new Camera
|
|
||||||
{
|
|
||||||
swapChainIndex = 0,
|
|
||||||
depthTarget = Handle<GPUTexture>.Invalid,
|
|
||||||
nearClipPlane = 0.1f,
|
|
||||||
farClipPlane = 1000.0f,
|
|
||||||
focalLength = 50.0f,
|
|
||||||
sensorSize = new float2(36.0f, 24.0f),
|
|
||||||
gateFit = GateFit.Vertical,
|
|
||||||
renderingLayerMask = RenderingLayerMask.All,
|
|
||||||
});
|
|
||||||
|
|
||||||
_world.EntityManager.SetComponent(cameraEntity, new LocalToWorld
|
|
||||||
{
|
|
||||||
matrix = float4x4.TRS(new float3(0.0f, 1.0f, 5.0f), quaternion.EulerXYZ(new float3(0, math.radians(180.0f), 0)), float3.one)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create Mesh Entity
|
|
||||||
//MeshBuilder.CreateCube(0.75f, default, Allocator.Persistent, out var vertices, out var indices);
|
|
||||||
Utilities.MeshUtility.LoadMesh("F:/c/SimpleRayTracer/native/assets/bunny.obj", AllocationHandle.Persistent, out var vertices, out var indices).ThrowIfFailed();
|
|
||||||
|
|
||||||
// TODO: Put this to the beginning of the frame without creating another command buffer?
|
|
||||||
using var directCmd = _renderSystem.GraphicsEngine.CreateCommandBuffer(CommandBufferType.Graphics);
|
|
||||||
var ctx = new RenderContext(_renderSystem.ResourceManager, _renderSystem.GraphicsEngine, directCmd);
|
|
||||||
|
|
||||||
using var cmdAllocator = _renderSystem.GraphicsEngine.CreateCommandAllocator(CommandBufferType.Graphics);
|
|
||||||
directCmd.Begin(cmdAllocator);
|
|
||||||
|
|
||||||
_meshHandle = ctx.CreateMesh(vertices, indices, true);
|
|
||||||
ctx.UpdateObjectData(_meshHandle);
|
|
||||||
|
|
||||||
directCmd.End().ThrowIfFailed();
|
|
||||||
|
|
||||||
// Maybe async upload support in the future?
|
|
||||||
_renderSystem.GraphicsEngine.Device.GraphicsQueue.Submit(directCmd);
|
|
||||||
|
|
||||||
using var meshSet = new ComponentSet(scope.AllocationHandle, ComponentTypeID<MeshInstance>.Value, ComponentTypeID<LocalToWorld>.Value);
|
|
||||||
var meshEntity = _world.EntityManager.CreateEntity(meshSet);
|
|
||||||
_world.EntityManager.SetComponent(meshEntity, new MeshInstance
|
|
||||||
{
|
|
||||||
mesh = _meshHandle,
|
|
||||||
renderingLayerMask = RenderingLayerMask.All,
|
|
||||||
shadowCastingMode = Engine.ShadowCastingMode.On
|
|
||||||
});
|
|
||||||
|
|
||||||
_world.EntityManager.SetComponent(meshEntity, new LocalToWorld
|
|
||||||
{
|
|
||||||
matrix = float4x4.TRS(float3.zero, quaternion.EulerXYZ(new float3(0, 0, 0)), float3.one)
|
|
||||||
});
|
|
||||||
|
|
||||||
CompositionTarget.Rendering += OnRendering;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GraphicsTestWindow_Closed(object sender, WindowEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
CompositionTarget.Rendering -= OnRendering;
|
|
||||||
_renderSystem?.Stop();
|
|
||||||
|
|
||||||
if (_world != null)
|
|
||||||
{
|
|
||||||
World.Destroy(_world.ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderSystem?.ResourceManager.ReleaseMesh(_meshHandle);
|
|
||||||
|
|
||||||
//_jobScheduler.Dispose();
|
|
||||||
_renderSystem?.Dispose();
|
|
||||||
|
|
||||||
AllocationManager.Dispose();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
System.Diagnostics.Debugger.Break();
|
|
||||||
#endif
|
|
||||||
Environment.FailFast("Failed to close the window properly.", ex);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SwapChainPanel_SizeChanged(object sender, SizeChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (_renderSystem == null || _swapChain == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var newWidth = (uint)(Panel.ActualWidth * Panel.CompositionScaleX);
|
|
||||||
var newHeight = (uint)(Panel.ActualHeight * Panel.CompositionScaleY);
|
|
||||||
|
|
||||||
if (newWidth < 8 || newHeight < 8)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderSystem.RequestSwapChainResize(_swapChain, new uint2(newWidth, newHeight));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SwapChainPanel_CompositionScaleChanged(SwapChainPanel sender, object args)
|
|
||||||
{
|
|
||||||
_swapChain?.SetScale(sender.CompositionScaleX, sender.CompositionScaleY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnRendering(object? sender, object e)
|
|
||||||
{
|
|
||||||
if (_renderSystem == null || _world == null || _swapChain == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_renderSystem.TryAcquireCPUFrame())
|
|
||||||
{
|
|
||||||
_world.SystemManager.UpdateAll(default);
|
|
||||||
_renderSystem.SignalCPUReady();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
|
||||||
<assemblyIdentity version="1.0.0.0" name="Ghost.Graphics.Test.app"/>
|
|
||||||
|
|
||||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
|
||||||
<application>
|
|
||||||
<!-- The ID below informs the system that this application is compatible with OS features first introduced in Windows 10.
|
|
||||||
It is necessary to support features in unpackaged applications, for example the custom titlebar implementation.
|
|
||||||
For more info see https://docs.microsoft.com/windows/apps/windows-app-sdk/use-windows-app-sdk-run-time#declare-os-compatibility-in-your-application-manifest -->
|
|
||||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
|
||||||
</application>
|
|
||||||
</compatibility>
|
|
||||||
|
|
||||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
|
||||||
<windowsSettings>
|
|
||||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
|
||||||
</windowsSettings>
|
|
||||||
</application>
|
|
||||||
</assembly>
|
|
||||||
@@ -20,22 +20,15 @@ public class AssetCatalogTests
|
|||||||
[TestCleanup]
|
[TestCleanup]
|
||||||
public void Cleanup()
|
public void Cleanup()
|
||||||
{
|
{
|
||||||
SqliteConnection.ClearAllPools();
|
var connectionString = new SqliteConnectionStringBuilder
|
||||||
var dir = Path.GetDirectoryName(_dbPath);
|
|
||||||
if (dir != null && Directory.Exists(dir))
|
|
||||||
{
|
{
|
||||||
try
|
DataSource = _dbPath,
|
||||||
{
|
ForeignKeys = true,
|
||||||
Directory.Delete(dir, true);
|
Pooling = true
|
||||||
}
|
}.ToString();
|
||||||
catch (IOException)
|
|
||||||
{
|
using var connection = new SqliteConnection(connectionString);
|
||||||
// Sometimes SQLite holds a lock for a bit longer
|
SqliteConnection.ClearPool(connection);
|
||||||
Thread.Sleep(100);
|
|
||||||
if (Directory.Exists(dir))
|
|
||||||
Directory.Delete(dir, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ public class AssetManagerTest
|
|||||||
[TestInitialize]
|
[TestInitialize]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
AllocationManager.Initialize(AllocationManagerDesc.Default);
|
|
||||||
|
|
||||||
_graphicsEngine = new MockingGraphicsEngine();
|
_graphicsEngine = new MockingGraphicsEngine();
|
||||||
_commandBuffer = (MockingCommandBuffer)_graphicsEngine.CreateCommandBuffer();
|
_commandBuffer = (MockingCommandBuffer)_graphicsEngine.CreateCommandBuffer();
|
||||||
_provider = new MockingContentProvider();
|
_provider = new MockingContentProvider();
|
||||||
@@ -62,8 +60,6 @@ public class AssetManagerTest
|
|||||||
_copyPipeline.Dispose();
|
_copyPipeline.Dispose();
|
||||||
_commandBuffer.Dispose();
|
_commandBuffer.Dispose();
|
||||||
_graphicsEngine.Dispose();
|
_graphicsEngine.Dispose();
|
||||||
|
|
||||||
AllocationManager.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|||||||
@@ -26,21 +26,15 @@ public class ImportCoordinatorTests
|
|||||||
[TestCleanup]
|
[TestCleanup]
|
||||||
public void Cleanup()
|
public void Cleanup()
|
||||||
{
|
{
|
||||||
SqliteConnection.ClearAllPools();
|
var connectionString = new SqliteConnectionStringBuilder
|
||||||
var dir = Path.GetDirectoryName(_libraryRoot);
|
|
||||||
if (dir != null && Directory.Exists(dir))
|
|
||||||
{
|
{
|
||||||
try
|
DataSource = _dbPath,
|
||||||
{
|
ForeignKeys = true,
|
||||||
Directory.Delete(dir, true);
|
Pooling = true
|
||||||
}
|
}.ToString();
|
||||||
catch (IOException)
|
|
||||||
{
|
using var connection = new SqliteConnection(connectionString);
|
||||||
Thread.Sleep(100);
|
SqliteConnection.ClearPool(connection);
|
||||||
if (Directory.Exists(dir))
|
|
||||||
Directory.Delete(dir, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using Ghost.Editor.Core;
|
|||||||
using Ghost.Editor.Core.Assets;
|
using Ghost.Editor.Core.Assets;
|
||||||
using Ghost.Editor.Core.Services;
|
using Ghost.Editor.Core.Services;
|
||||||
using Ghost.Engine.Streaming;
|
using Ghost.Engine.Streaming;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
@@ -20,47 +19,20 @@ public class MeshAssetHandlerTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
private string _projectRoot = null!;
|
private string _projectRoot = null!;
|
||||||
private string _previousCurrentDirectory = null!;
|
|
||||||
|
|
||||||
[TestInitialize]
|
[TestInitialize]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
AllocationManager.Initialize(AllocationManagerDesc.Default);
|
|
||||||
|
|
||||||
_previousCurrentDirectory = Environment.CurrentDirectory;
|
|
||||||
_projectRoot = Path.Combine(Path.GetTempPath(), "GhostEngineTests", Guid.NewGuid().ToString());
|
_projectRoot = Path.Combine(Path.GetTempPath(), "GhostEngineTests", Guid.NewGuid().ToString());
|
||||||
Directory.CreateDirectory(_projectRoot);
|
Directory.CreateDirectory(_projectRoot);
|
||||||
EditorApplication.Initialize(new EmptyServiceProvider(), _projectRoot, "MeshImportTest");
|
EditorApplication.Initialize(new EmptyServiceProvider(), _projectRoot, "MeshImportTest");
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCleanup]
|
|
||||||
public void Cleanup()
|
|
||||||
{
|
|
||||||
AllocationManager.Dispose();
|
|
||||||
Environment.CurrentDirectory = _previousCurrentDirectory;
|
|
||||||
|
|
||||||
if (Directory.Exists(_projectRoot))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Directory.Delete(_projectRoot, true);
|
|
||||||
}
|
|
||||||
catch (IOException)
|
|
||||||
{
|
|
||||||
Thread.Sleep(100);
|
|
||||||
if (Directory.Exists(_projectRoot))
|
|
||||||
{
|
|
||||||
Directory.Delete(_projectRoot, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task FBXAssetHandler_ImportsObjAsManifestAndMeshSubAssets()
|
public async Task FBXAssetHandler_ImportsObjAsManifestAndMeshSubAssets()
|
||||||
{
|
{
|
||||||
var sourcePath = Path.Combine(EditorApplication.AssetsFolderPath, "kit.obj");
|
var sourcePath = Path.Combine(EditorApplication.AssetsFolderPath, "kit.obj");
|
||||||
await File.WriteAllTextAsync(sourcePath, CreateTwoObjectObj());
|
await File.WriteAllTextAsync(sourcePath, CreateTwoObjectObj(), TestContext.CancellationToken);
|
||||||
|
|
||||||
var parentGuid = Guid.NewGuid();
|
var parentGuid = Guid.NewGuid();
|
||||||
var targetPath = ImportCoordinator.GetImportedAssetPath(parentGuid);
|
var targetPath = ImportCoordinator.GetImportedAssetPath(parentGuid);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
using Ghost.Entities;
|
using Ghost.Entities;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ghost.UnitTest.ECS;
|
namespace Ghost.UnitTest.ECS;
|
||||||
@@ -53,11 +54,11 @@ public class SharedComponentTests
|
|||||||
var sharedValue = new SharedGroup { groupID = groupID };
|
var sharedValue = new SharedGroup { groupID = groupID };
|
||||||
|
|
||||||
// Build shared data blob: SharedGroup is the only shared component.
|
// Build shared data blob: SharedGroup is the only shared component.
|
||||||
Span<byte> sharedData = stackalloc byte[sizeof(SharedGroup)];
|
using var sharedSet = new SharedComponentSet(sizeof(SharedGroup), AllocationHandle.Persistent);
|
||||||
MemoryMarshal.Write(sharedData, sharedValue);
|
sharedSet.With(sharedValue);
|
||||||
|
|
||||||
Identifier<IComponent>[] ids = [ComponentTypeID<Tag>.Value, ComponentTypeID<SharedGroup>.Value];
|
Identifier<IComponent>[] ids = [ComponentTypeID<Tag>.Value, ComponentTypeID<SharedGroup>.Value];
|
||||||
var set = new ComponentSetView(ids, sharedData);
|
var set = new ComponentSetView(ids, sharedSet);
|
||||||
|
|
||||||
Span<Entity> result = stackalloc Entity[1];
|
Span<Entity> result = stackalloc Entity[1];
|
||||||
_world.EntityManager.CreateEntities(result, set);
|
_world.EntityManager.CreateEntities(result, set);
|
||||||
@@ -464,4 +465,37 @@ public class SharedComponentTests
|
|||||||
|
|
||||||
Assert.IsTrue(groups.ContainsKey(2) && groups[2] == 1, "Group 2 entity should be untouched.");
|
Assert.IsTrue(groups.ContainsKey(2) && groups[2] == 1, "Group 2 entity should be untouched.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════════════════
|
||||||
|
// 9. Canonical Ordering
|
||||||
|
// ══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanonicalOrdering_SharedComponentSet_IndependentOfInsertionOrder()
|
||||||
|
{
|
||||||
|
using var sharedSet1 = new SharedComponentSet(64, AllocationHandle.Temp);
|
||||||
|
sharedSet1.With(new SharedGroup { groupID = 42 });
|
||||||
|
sharedSet1.With(new SharedGroup2 { subID = 99 });
|
||||||
|
|
||||||
|
using var sharedSet2 = new SharedComponentSet(64, AllocationHandle.Temp);
|
||||||
|
sharedSet2.With(new SharedGroup2 { subID = 99 });
|
||||||
|
sharedSet2.With(new SharedGroup { groupID = 42 });
|
||||||
|
|
||||||
|
Identifier<IComponent>[] ids1 = [ComponentTypeID<Tag>.Value, ComponentTypeID<SharedGroup>.Value, ComponentTypeID<SharedGroup2>.Value];
|
||||||
|
var set1 = new ComponentSetView(ids1, sharedSet1);
|
||||||
|
|
||||||
|
Identifier<IComponent>[] ids2 = [ComponentTypeID<Tag>.Value, ComponentTypeID<SharedGroup2>.Value, ComponentTypeID<SharedGroup>.Value];
|
||||||
|
var set2 = new ComponentSetView(ids2, sharedSet2);
|
||||||
|
|
||||||
|
Span<Entity> result1 = stackalloc Entity[1];
|
||||||
|
_world.EntityManager.CreateEntities(result1, set1);
|
||||||
|
|
||||||
|
Span<Entity> result2 = stackalloc Entity[1];
|
||||||
|
_world.EntityManager.CreateEntities(result2, set2);
|
||||||
|
|
||||||
|
var groups = CollectGroupCounts();
|
||||||
|
|
||||||
|
Assert.AreEqual(1, groups.Count, "Expected exactly 1 chunk group due to canonical sorting of SharedComponentSet.");
|
||||||
|
Assert.AreEqual(2, groups[42], "Both entities should be grouped under groupID 42.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,13 @@ public class WorldTests
|
|||||||
private struct CompB : IComponentData { public int value; }
|
private struct CompB : IComponentData { public int value; }
|
||||||
|
|
||||||
private World _world = null!;
|
private World _world = null!;
|
||||||
|
private EntityManager _entityManager;
|
||||||
|
|
||||||
[TestInitialize]
|
[TestInitialize]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
_world = World.Create(null, 64);
|
_world = World.Create(entityCapacity: 128);
|
||||||
|
_entityManager = _world.EntityManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCleanup]
|
[TestCleanup]
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
|
||||||
|
[assembly: DoNotParallelize]
|
||||||
|
|
||||||
namespace Ghost.UnitTest;
|
namespace Ghost.UnitTest;
|
||||||
|
|
||||||
[TestClass]
|
[TestClass]
|
||||||
|
|||||||
@@ -54,18 +54,6 @@ public class ShaderLibraryTest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestInitialize]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
AllocationManager.Initialize(AllocationManagerDesc.Default);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestCleanup]
|
|
||||||
public void Cleanup()
|
|
||||||
{
|
|
||||||
AllocationManager.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public unsafe void TestInvalidateShaderCache_EvictsPipelinesAndClearsCache()
|
public unsafe void TestInvalidateShaderCache_EvictsPipelinesAndClearsCache()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,22 +36,26 @@ public unsafe class ComponentDescriptorTests
|
|||||||
Assert.AreEqual(4, descriptor.Properties.Length, "Should have exactly 4 visible properties (HiddenFloat is ignored).");
|
Assert.AreEqual(4, descriptor.Properties.Length, "Should have exactly 4 visible properties (HiddenFloat is ignored).");
|
||||||
|
|
||||||
var p0 = descriptor.Properties[0];
|
var p0 = descriptor.Properties[0];
|
||||||
Assert.AreEqual("IntValue", p0.Name);
|
Assert.AreEqual("intValue", p0.Name);
|
||||||
|
Assert.AreEqual("IntValue", p0.DisplayName);
|
||||||
Assert.AreEqual(typeof(int), p0.FieldType);
|
Assert.AreEqual(typeof(int), p0.FieldType);
|
||||||
Assert.AreEqual(0, p0.OffsetInComponent);
|
Assert.AreEqual(0, p0.OffsetInComponent);
|
||||||
|
|
||||||
var p1 = descriptor.Properties[1];
|
var p1 = descriptor.Properties[1];
|
||||||
|
Assert.AreEqual("doubleValue", p1.Name);
|
||||||
Assert.AreEqual("Custom Name", p1.DisplayName);
|
Assert.AreEqual("Custom Name", p1.DisplayName);
|
||||||
Assert.AreEqual(typeof(double), p1.FieldType);
|
Assert.AreEqual(typeof(double), p1.FieldType);
|
||||||
// Offset of double after int+float is 8 (with alignment)
|
// Offset of double after int+float is 8 (with alignment)
|
||||||
Assert.AreEqual((int)Marshal.OffsetOf<TestComponent>("DoubleValue"), p1.OffsetInComponent);
|
Assert.AreEqual((int)Marshal.OffsetOf<TestComponent>("doubleValue"), p1.OffsetInComponent);
|
||||||
|
|
||||||
var p2 = descriptor.Properties[2];
|
var p2 = descriptor.Properties[2];
|
||||||
Assert.AreEqual("IsReadOnly", p2.Name);
|
Assert.AreEqual("isReadOnly", p2.Name);
|
||||||
|
Assert.AreEqual("IsReadOnly", p2.DisplayName);
|
||||||
Assert.IsTrue(p2.IsReadOnly);
|
Assert.IsTrue(p2.IsReadOnly);
|
||||||
|
|
||||||
var p3 = descriptor.Properties[3];
|
var p3 = descriptor.Properties[3];
|
||||||
Assert.AreEqual("Position", p3.Name);
|
Assert.AreEqual("position", p3.Name);
|
||||||
|
Assert.AreEqual("Position", p3.DisplayName);
|
||||||
Assert.AreEqual(typeof(float3), p3.FieldType);
|
Assert.AreEqual(typeof(float3), p3.FieldType);
|
||||||
Assert.IsNull(p3.Children); // float3 is a primitive so it has no children
|
Assert.IsNull(p3.Children); // float3 is a primitive so it has no children
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,6 @@ public class SceneSerializationTests
|
|||||||
[TestInitialize]
|
[TestInitialize]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
AllocationManager.Initialize(AllocationManagerDesc.Default);
|
|
||||||
|
|
||||||
_previousCurrentDirectory = Environment.CurrentDirectory;
|
_previousCurrentDirectory = Environment.CurrentDirectory;
|
||||||
_projectRoot = Path.Combine(Path.GetTempPath(), "GhostEngineTests", Guid.NewGuid().ToString());
|
_projectRoot = Path.Combine(Path.GetTempPath(), "GhostEngineTests", Guid.NewGuid().ToString());
|
||||||
Directory.CreateDirectory(_projectRoot);
|
Directory.CreateDirectory(_projectRoot);
|
||||||
@@ -51,7 +49,6 @@ public class SceneSerializationTests
|
|||||||
{
|
{
|
||||||
_worldService.Dispose();
|
_worldService.Dispose();
|
||||||
|
|
||||||
AllocationManager.Dispose();
|
|
||||||
Environment.CurrentDirectory = _previousCurrentDirectory;
|
Environment.CurrentDirectory = _previousCurrentDirectory;
|
||||||
|
|
||||||
if (Directory.Exists(_projectRoot))
|
if (Directory.Exists(_projectRoot))
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace Ghost.FMOD.Core
|
|||||||
These properties take advantage of the fact that numbuffers is always zero or one
|
These properties take advantage of the fact that numbuffers is always zero or one
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public int numchannels
|
public readonly int numchannels
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
@@ -43,7 +43,7 @@ namespace Ghost.FMOD.Core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IntPtr buffer
|
public readonly IntPtr buffer
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
@@ -307,7 +307,7 @@ namespace Ghost.FMOD.Core
|
|||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
|
||||||
private IntPtr[] spectrum_internal;
|
private IntPtr[] spectrum_internal;
|
||||||
|
|
||||||
public float[][] spectrum
|
public readonly float[][] spectrum
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
@@ -332,7 +332,7 @@ namespace Ghost.FMOD.Core
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void getSpectrum(int channel, ref float[] buffer)
|
public readonly void getSpectrum(int channel, ref float[] buffer)
|
||||||
{
|
{
|
||||||
var bufferLength = Math.Min(buffer.Length, length);
|
var bufferLength = Math.Min(buffer.Length, length);
|
||||||
Marshal.Copy(spectrum_internal[channel], buffer, 0, bufferLength);
|
Marshal.Copy(spectrum_internal[channel], buffer, 0, bufferLength);
|
||||||
@@ -442,14 +442,14 @@ namespace Ghost.FMOD.Core
|
|||||||
public DSP_GETLISTENERATTRIBUTES_FUNC getlistenerattributes;
|
public DSP_GETLISTENERATTRIBUTES_FUNC getlistenerattributes;
|
||||||
public DSP_LOG_FUNC log;
|
public DSP_LOG_FUNC log;
|
||||||
public DSP_GETUSERDATA_FUNC getuserdata;
|
public DSP_GETUSERDATA_FUNC getuserdata;
|
||||||
public DSP_STATE_DFT_FUNCTIONS dft
|
public readonly DSP_STATE_DFT_FUNCTIONS dft
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return Marshal.PtrToStructure<DSP_STATE_DFT_FUNCTIONS>(dft_internal);
|
return Marshal.PtrToStructure<DSP_STATE_DFT_FUNCTIONS>(dft_internal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public DSP_STATE_PAN_FUNCTIONS pan
|
public readonly DSP_STATE_PAN_FUNCTIONS pan
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
@@ -470,7 +470,7 @@ namespace Ghost.FMOD.Core
|
|||||||
private IntPtr functions_internal;
|
private IntPtr functions_internal;
|
||||||
public int systemobject;
|
public int systemobject;
|
||||||
|
|
||||||
public DSP_STATE_FUNCTIONS functions
|
public readonly DSP_STATE_FUNCTIONS functions
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|||||||