From d2bf2f12a2e9feda945576be78e7474ae6d4f0f4 Mon Sep 17 00:00:00 2001 From: Misaki Date: Sun, 3 May 2026 17:05:52 +0900 Subject: [PATCH] Refactor asset streaming, error handling, and unit tests - Add new compile-time constants and update package versions - Refactor AssetEntry upload logic to return Result and propagate errors - Enhance error handling in ResourceStreamingProcessor uploads - Make ResourceStreamingContext a readonly struct - Implement IDisposable and finalizers for resource cleanup - Overhaul AssetManagerTest with async tests and improved mocks - Add mock implementations for graphics interfaces for testing - Refactor MockingCommandBuffer and MockingResourceDatabase for better simulation - Update internals visibility for unit testing --- src/Runtime/Ghost.Core/Ghost.Core.csproj | 8 +-- src/Runtime/Ghost.Engine/AssetManager.cs | 13 ++-- .../ResourceStreamingProcessor.cs | 17 +++-- src/Runtime/Ghost.Graphics/AssemblyInfo.cs | 2 +- .../IResourceStreamingProcessor.cs | 2 +- src/Runtime/Ghost.Graphics/RenderSystem.cs | 4 ++ .../Services/AsyncCopyPipeline.cs | 16 ++++- .../Services/ResourceManager.cs | 1 + .../AssetSystem/AssetManagerTest.cs | 72 ++++++++++++++++--- .../MockingCommandAllocator.cs | 19 +++++ .../MockingCommandBuffer.cs | 16 +++-- .../MockingEnvironment/MockingCommandQueue.cs | 48 +++++++++++++ .../MockingEnvironment/MockingFence.cs | 52 ++++++++++++++ .../MockingGraphicsEngine.cs | 72 +++++++++++++++++++ .../MockingEnvironment/MockingRenderDevice.cs | 27 +++++++ .../MockingResourceDatabase.cs | 17 +++-- 16 files changed, 346 insertions(+), 40 deletions(-) create mode 100644 src/Test/Ghost.UnitTest/MockingEnvironment/MockingCommandAllocator.cs create mode 100644 src/Test/Ghost.UnitTest/MockingEnvironment/MockingCommandQueue.cs create mode 100644 src/Test/Ghost.UnitTest/MockingEnvironment/MockingFence.cs create mode 100644 src/Test/Ghost.UnitTest/MockingEnvironment/MockingGraphicsEngine.cs create mode 100644 src/Test/Ghost.UnitTest/MockingEnvironment/MockingRenderDevice.cs diff --git a/src/Runtime/Ghost.Core/Ghost.Core.csproj b/src/Runtime/Ghost.Core/Ghost.Core.csproj index 955be26..85a2d22 100644 --- a/src/Runtime/Ghost.Core/Ghost.Core.csproj +++ b/src/Runtime/Ghost.Core/Ghost.Core.csproj @@ -8,26 +8,26 @@ - $(DefineConstants);MHP_ENABLE_SAFETY_CHECKS;MHP_ENABLE_MIMALLOC + $(DefineConstants);MHP_ENABLE_SAFETY_CHECKS;MHP_ENABLE_STACKTRACE;MHP_ENABLE_MIMALLOC;MHP_FASTMATH True True - $(DefineConstants);MHP_ENABLE_MIMALLOC + $(DefineConstants);MHP_ENABLE_MIMALLOC;MHP_FASTMATH True True - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/Runtime/Ghost.Engine/AssetManager.cs b/src/Runtime/Ghost.Engine/AssetManager.cs index 22047c1..229278c 100644 --- a/src/Runtime/Ghost.Engine/AssetManager.cs +++ b/src/Runtime/Ghost.Engine/AssetManager.cs @@ -1,7 +1,6 @@ using Ghost.Core; using Ghost.Core.Utilities; using Ghost.Graphics; -using Ghost.Graphics.Core; using Ghost.Graphics.RHI; using Misaki.HighPerformance.Jobs; using Misaki.HighPerformance.LowLevel; @@ -54,7 +53,7 @@ internal partial class AssetEntry { private static readonly Action[] s_onCreation = new Action[(int)AssetType.Unknown + 1]; private static readonly Func[] s_onParseRawData = new Func[(int)AssetType.Unknown + 1]; - private static readonly Action[] s_onRecordUpload = new Action[(int)AssetType.Unknown + 1]; + private static readonly Func[] s_onRecordUpload = new Func[(int)AssetType.Unknown + 1]; private static readonly Action[] s_onUploadComplete = new Action[(int)AssetType.Unknown + 1]; private static readonly Action[] s_onReleaseResource = new Action[(int)AssetType.Unknown + 1]; @@ -184,9 +183,9 @@ internal unsafe partial class AssetEntry } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void OnRecordUploadCommands(ResourceStreamingContext context) + public Result OnRecordUploadCommands(ResourceStreamingContext context) { - s_onRecordUpload[(int)_assetType]?.Invoke(this, context); + return s_onRecordUpload[(int)_assetType]?.Invoke(this, context) ?? Result.Failure("Unsupported asset type."); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -417,10 +416,10 @@ internal partial class AssetManager : IDisposable if (Interlocked.CompareExchange(ref entry.StateValue, (int)AssetState.Scheduled, (int)AssetState.Ready) == (int)AssetState.Ready) { - // Entry is in Ready state — the old texture is valid and will remain visible. - // Go directly to Scheduled → Loading → Loaded → Uploading → Ready again. + // Entry is in Ready state - the old texture is valid and will remain visible. + // Go directly to Scheduled -> Loading -> Loaded -> Uploading -> Ready again. // The swap cycle in RecordTextureUpload/OnTextureUploadComplete handles the - // v1 → v2 transition exactly like the fallback → v1 transition. + // v1 to v2 transition exactly like the fallback to v1 transition. EnsureScheduled(entry); } else diff --git a/src/Runtime/Ghost.Engine/ResourceStreamingProcessor.cs b/src/Runtime/Ghost.Engine/ResourceStreamingProcessor.cs index 37bcafd..87fa371 100644 --- a/src/Runtime/Ghost.Engine/ResourceStreamingProcessor.cs +++ b/src/Runtime/Ghost.Engine/ResourceStreamingProcessor.cs @@ -62,17 +62,26 @@ internal class ResourceStreamingProcessor : IResourceStreamingProcessor } // Record copy commands into cmdCopy - entry.OnRecordUploadCommands(context); + if (entry.OnRecordUploadCommands(context).IsFailure) + { + Logger.Error($"Failed to record upload commands for asset {entry.AssetId}. Skipping upload."); + continue; + } + entry.State = AssetState.Uploading; _pendingFinalize.Enqueue(entry); uploadCount++; } - var result = context.CopyPipeline.End(); - // 3. Submit the batch - if (uploadCount > 0 && result.IsSuccess) + if (context.CopyPipeline.End().IsFailure) + { + Logger.Error("Failed to submit copy command list for resource streaming."); + return; + } + + if (uploadCount > 0) { _pendingCopyFenceValue = context.CopyPipeline.SignaledFenceValue(); } diff --git a/src/Runtime/Ghost.Graphics/AssemblyInfo.cs b/src/Runtime/Ghost.Graphics/AssemblyInfo.cs index 650dddd..67789d0 100644 --- a/src/Runtime/Ghost.Graphics/AssemblyInfo.cs +++ b/src/Runtime/Ghost.Graphics/AssemblyInfo.cs @@ -5,6 +5,6 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Ghost.Editor")] [assembly: InternalsVisibleTo("Ghost.Editor.Core")] [assembly: InternalsVisibleTo("Ghost.Graphics.Test")] -[assembly: InternalsVisibleTo("Ghost.Graphics.Test-Winui")] +[assembly: InternalsVisibleTo("Ghost.UnitTest")] [assembly: EngineAssembly] \ No newline at end of file diff --git a/src/Runtime/Ghost.Graphics/IResourceStreamingProcessor.cs b/src/Runtime/Ghost.Graphics/IResourceStreamingProcessor.cs index 292a220..b8c546a 100644 --- a/src/Runtime/Ghost.Graphics/IResourceStreamingProcessor.cs +++ b/src/Runtime/Ghost.Graphics/IResourceStreamingProcessor.cs @@ -3,7 +3,7 @@ using Ghost.Graphics.Services; namespace Ghost.Graphics; -internal ref struct ResourceStreamingContext +internal readonly struct ResourceStreamingContext { public required AsyncCopyPipeline CopyPipeline { diff --git a/src/Runtime/Ghost.Graphics/RenderSystem.cs b/src/Runtime/Ghost.Graphics/RenderSystem.cs index b8aec1f..a8b5968 100644 --- a/src/Runtime/Ghost.Graphics/RenderSystem.cs +++ b/src/Runtime/Ghost.Graphics/RenderSystem.cs @@ -475,8 +475,12 @@ public class RenderSystem : IDisposable _renderPipeline.Dispose(); + _fence.Dispose(); + + _shaderLibrary.Dispose(); _resourceManager.Dispose(); _swapChainManager.Dispose(); + _asyncCopyPipeline.Dispose(); _graphicsEngine.Dispose(); diff --git a/src/Runtime/Ghost.Graphics/Services/AsyncCopyPipeline.cs b/src/Runtime/Ghost.Graphics/Services/AsyncCopyPipeline.cs index 9204a34..3ccc09c 100644 --- a/src/Runtime/Ghost.Graphics/Services/AsyncCopyPipeline.cs +++ b/src/Runtime/Ghost.Graphics/Services/AsyncCopyPipeline.cs @@ -3,7 +3,7 @@ using Ghost.Graphics.RHI; namespace Ghost.Graphics.Services; -public class AsyncCopyPipeline +public class AsyncCopyPipeline : IDisposable { private readonly IRenderDevice _device; @@ -26,6 +26,11 @@ public class AsyncCopyPipeline _fence.Name = "AsyncCopyPipeline_Fence"; } + ~AsyncCopyPipeline() + { + Dispose(); + } + internal void Begin() { _commandAllocator.Reset(); @@ -74,4 +79,13 @@ public class AsyncCopyPipeline { return _fence.WaitForValueAsync(_fenceValue); } + + public void Dispose() + { + _commandAllocator.Dispose(); + _commandBuffer.Dispose(); + _fence.Dispose(); + + GC.SuppressFinalize(this); + } } diff --git a/src/Runtime/Ghost.Graphics/Services/ResourceManager.cs b/src/Runtime/Ghost.Graphics/Services/ResourceManager.cs index 0f67e3e..12ea2bc 100644 --- a/src/Runtime/Ghost.Graphics/Services/ResourceManager.cs +++ b/src/Runtime/Ghost.Graphics/Services/ResourceManager.cs @@ -592,6 +592,7 @@ public sealed partial class ResourceManager : IDisposable _meshes.Dispose(); _materials.Dispose(); _shaders.Dispose(); + _computeShaders.Dispose(); _materialPalettes.Dispose(); _resourceDatabase.ReleaseResource(_paletteOffsetBuffer.AsResource()); diff --git a/src/Test/Ghost.UnitTest/AssetSystem/AssetManagerTest.cs b/src/Test/Ghost.UnitTest/AssetSystem/AssetManagerTest.cs index 4f27c5f..b19862b 100644 --- a/src/Test/Ghost.UnitTest/AssetSystem/AssetManagerTest.cs +++ b/src/Test/Ghost.UnitTest/AssetSystem/AssetManagerTest.cs @@ -1,41 +1,56 @@ +using Ghost.Core; using Ghost.Engine; +using Ghost.Graphics; +using Ghost.Graphics.RHI; +using Ghost.Graphics.Services; using Ghost.UnitTest.MockingEnvironment; using Misaki.HighPerformance.Jobs; +using Misaki.HighPerformance.LowLevel.Buffer; namespace Ghost.UnitTest.AssetSystem; [TestClass] +[DoNotParallelize] public class AssetManagerTest { - private MockingResourceDatabase _resourceDatabase = null!; - private MockingResourceAllocator _resourceAllocator = null!; + private MockingGraphicsEngine _graphicsEngine = null!; private MockingCommandBuffer _commandBuffer = null!; private MockingContentProvider _provider = null!; + private AsyncCopyPipeline _copyPipeline = null!; + private ResourceManager _resourceManager = null!; private ResourceStreamingProcessor _processor = null!; private JobScheduler _jobScheduler = null!; private AssetManager _assetManager = null!; + public TestContext TestContext + { + get; set; + } + [TestInitialize] public void Setup() { - _resourceDatabase = new MockingResourceDatabase(); - _resourceAllocator = new MockingResourceAllocator(_resourceDatabase); - _commandBuffer = new MockingCommandBuffer(_resourceDatabase); + AllocationManager.Initialize(AllocationManagerDesc.Default); + + _graphicsEngine = new MockingGraphicsEngine(); + _commandBuffer = (MockingCommandBuffer)_graphicsEngine.CreateCommandBuffer(); _provider = new MockingContentProvider(); + _copyPipeline = new AsyncCopyPipeline(_graphicsEngine); + _resourceManager = new ResourceManager(_graphicsEngine.Device, _graphicsEngine.ResourceAllocator, _graphicsEngine.ResourceDatabase); _processor = new ResourceStreamingProcessor(); var schedulerDesc = new JobSchedulerDesc { ThreadCount = 1, ThreadPriority = ThreadPriority.Normal, - DependencyChainCapacity = 1024, + DependencyChainCapacity = 64, }; _jobScheduler = new JobScheduler(in schedulerDesc); - _assetManager = new AssetManager(_resourceDatabase, _provider, _processor, _jobScheduler); + _assetManager = new AssetManager(_graphicsEngine.ResourceDatabase, _provider, _processor, _jobScheduler); } [TestCleanup] @@ -43,18 +58,55 @@ public class AssetManagerTest { _assetManager.Dispose(); _jobScheduler.Dispose(); + _resourceManager.Dispose(); + _copyPipeline.Dispose(); _commandBuffer.Dispose(); - _resourceAllocator.Dispose(); - _resourceDatabase.Dispose(); + _graphicsEngine.Dispose(); + + AllocationManager.Dispose(); } [TestMethod] - public void AssetManager_GetsAssetSuccessfully() + public async Task AssetManager_ResolveTextureThenBackgroundUpload() { var assetID = Guid.NewGuid(); _provider.AddMockTexture(assetID, readDelayMs: Random.Shared.Next(10, 50)); var handle = _assetManager.ResolveTexture(assetID); Assert.IsTrue(handle.IsValid); + + Assert.IsTrue(_assetManager.TryGetEntry(assetID, out var entry)); + Assert.IsGreaterThanOrEqualTo((int)AssetState.Scheduled, entry.StateValue); + + await Task.Delay(1000, TestContext.CancellationToken); + + Assert.IsGreaterThanOrEqualTo((int)AssetState.Loaded, entry.StateValue); + + var ctx = new ResourceStreamingContext + { + ResourceManager = _resourceManager, + ResourceDatabase = _graphicsEngine.ResourceDatabase, + ResourceAllocator = _graphicsEngine.ResourceAllocator, + CopyPipeline = _copyPipeline, + GraphicsCommandBuffer = _commandBuffer, + }; + + _processor.ProcessPendingUploads(ctx); + + await Task.Delay(1000, TestContext.CancellationToken); + + Assert.IsGreaterThanOrEqualTo((int)AssetState.Uploading, entry.StateValue); + + // Trigger the completion of the upload and the transition to shader resource state. + _processor.ProcessPendingUploads(ctx); + + Assert.IsGreaterThanOrEqualTo((int)AssetState.Ready, entry.StateValue); + + var (data, error) = _graphicsEngine.ResourceDatabase.GetResourceBarrierData(handle.AsResource()); + + Assert.AreEqual(Error.None, error); + Assert.AreEqual(BarrierAccess.ShaderResource, data.access); + Assert.AreEqual(BarrierLayout.ShaderResource, data.layout); + Assert.AreEqual(BarrierSync.AllShading, data.sync); } } diff --git a/src/Test/Ghost.UnitTest/MockingEnvironment/MockingCommandAllocator.cs b/src/Test/Ghost.UnitTest/MockingEnvironment/MockingCommandAllocator.cs new file mode 100644 index 0000000..37d6763 --- /dev/null +++ b/src/Test/Ghost.UnitTest/MockingEnvironment/MockingCommandAllocator.cs @@ -0,0 +1,19 @@ +using Ghost.Graphics.RHI; + +namespace Ghost.UnitTest.MockingEnvironment; + +internal class MockingCommandAllocator : ICommandAllocator +{ + public string Name + { + get; set; + } = "MockCommandAllocator"; + + public void Reset() + { + } + + public void Dispose() + { + } +} diff --git a/src/Test/Ghost.UnitTest/MockingEnvironment/MockingCommandBuffer.cs b/src/Test/Ghost.UnitTest/MockingEnvironment/MockingCommandBuffer.cs index 67778fc..9a06c84 100644 --- a/src/Test/Ghost.UnitTest/MockingEnvironment/MockingCommandBuffer.cs +++ b/src/Test/Ghost.UnitTest/MockingEnvironment/MockingCommandBuffer.cs @@ -8,7 +8,6 @@ internal class MockingCommandBuffer : ICommandBuffer { private readonly IResourceDatabase _resourceDatabase; - private string _name = "MockCommandBuffer"; private bool _isEmpty = true; // Tracking properties for test assertions @@ -16,19 +15,22 @@ internal class MockingCommandBuffer : ICommandBuffer public int CopyCallCount { get; private set; } public int UpdateSubResourcesCount { get; private set; } - public CommandBufferType Type => default; - public bool IsEmpty => _isEmpty; + public CommandBufferType Type + { + get; + } + public string Name { - get => _name; - set => _name = value; - } + get; set; + } = "MockingCommandBuffer"; - public MockingCommandBuffer(IResourceDatabase resourceDatabase) + public MockingCommandBuffer(IResourceDatabase resourceDatabase, CommandBufferType type) { _resourceDatabase = resourceDatabase; + Type = type; } public void Barrier(params scoped ReadOnlySpan barrierDescs) diff --git a/src/Test/Ghost.UnitTest/MockingEnvironment/MockingCommandQueue.cs b/src/Test/Ghost.UnitTest/MockingEnvironment/MockingCommandQueue.cs new file mode 100644 index 0000000..16777d0 --- /dev/null +++ b/src/Test/Ghost.UnitTest/MockingEnvironment/MockingCommandQueue.cs @@ -0,0 +1,48 @@ +using Ghost.Graphics.RHI; +using System.Diagnostics; + +namespace Ghost.UnitTest.MockingEnvironment; + +internal class MockingCommandQueue : ICommandQueue +{ + public CommandQueueType Type + { + get; + } + + public string Name + { + get; set; + } = "MockCommandQueue"; + + public MockingCommandQueue(CommandQueueType type) + { + Type = type; + } + + public ulong Signal(IFence fence, ulong value) + { + var mockingFence = fence as MockingFence; + Debug.Assert(mockingFence != null); + + mockingFence.Signal(value); + return value; + } + + public void Submit(ICommandBuffer commandBuffer) + { + } + + public void Submit(params scoped ReadOnlySpan commandBuffers) + { + } + + public void Wait(IFence fence, ulong value) + { + Thread.Sleep(Random.Shared.Next(10, 50)); + } + + public void Dispose() + { + } +} diff --git a/src/Test/Ghost.UnitTest/MockingEnvironment/MockingFence.cs b/src/Test/Ghost.UnitTest/MockingEnvironment/MockingFence.cs new file mode 100644 index 0000000..fbe9574 --- /dev/null +++ b/src/Test/Ghost.UnitTest/MockingEnvironment/MockingFence.cs @@ -0,0 +1,52 @@ +using Ghost.Graphics.RHI; + +namespace Ghost.UnitTest.MockingEnvironment; + +internal class MockingFence : IFence +{ + private readonly AutoResetEvent _fenceEvent; + + private ulong _currentValue; + + public ulong CompletedValue => _currentValue; + + public nint WaitHandle => _fenceEvent.SafeWaitHandle.DangerousGetHandle(); + + public string Name + { + get; set; + } = "MockingFence"; + + public MockingFence(ulong initialValue) + { + _fenceEvent = new AutoResetEvent(false); + _currentValue = initialValue; + } + + public void Signal(ulong value) + { + if (value > _currentValue) + { + _currentValue = value; + _fenceEvent.Set(); + } + } + + public void WaitForValue(ulong value) + { + if (value > _currentValue) + { + _fenceEvent.WaitOne(); + } + } + + public Task WaitForValueAsync(ulong value) + { + return Task.Run(() => { WaitForValue(value); }); + } + + public void Dispose() + { + _fenceEvent.Dispose(); + } +} diff --git a/src/Test/Ghost.UnitTest/MockingEnvironment/MockingGraphicsEngine.cs b/src/Test/Ghost.UnitTest/MockingEnvironment/MockingGraphicsEngine.cs new file mode 100644 index 0000000..84ffe90 --- /dev/null +++ b/src/Test/Ghost.UnitTest/MockingEnvironment/MockingGraphicsEngine.cs @@ -0,0 +1,72 @@ +using Ghost.Graphics.RHI; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ghost.UnitTest.MockingEnvironment; + +internal class MockingGraphicsEngine : IGraphicsEngine +{ + private readonly MockingRenderDevice _renderDevice; + private readonly MockingResourceDatabase _resourceDatabase; + private readonly MockingResourceAllocator _resourceAllocator; + + public IRenderDevice Device => _renderDevice; + + public IPipelineLibrary PipelineLibrary => throw new NotImplementedException(); + + public IResourceDatabase ResourceDatabase => _resourceDatabase; + + public IResourceAllocator ResourceAllocator => _resourceAllocator; + + public MockingGraphicsEngine() + { + _renderDevice = new MockingRenderDevice(); + _resourceDatabase = new MockingResourceDatabase(); + _resourceAllocator = new MockingResourceAllocator(_resourceDatabase); + } + + public void BeginFrame(ulong submittedFrame) + { + } + + public ICommandAllocator CreateCommandAllocator(CommandBufferType type = CommandBufferType.Graphics) + { + return new MockingCommandAllocator(); + } + + public ICommandBuffer CreateCommandBuffer(CommandBufferType type = CommandBufferType.Graphics) + { + return new MockingCommandBuffer(_resourceDatabase, type); + } + + public IFence CreateFence(ulong initialValue = 0) + { + return new MockingFence(initialValue); + } + + public ISwapChain CreateSwapChain(SwapChainDesc desc) + { + throw new NotImplementedException(); + } + + public void EndFrame(ulong completedFrame) + { + } + + public ICommandBuffer GetPooledCommandBuffer(CommandBufferType type = CommandBufferType.Graphics) + { + return new MockingCommandBuffer(_resourceDatabase, type); + } + + public void ReturnPooledCommandBuffer(ICommandBuffer commandBuffer) + { + } + + public void Dispose() + { + _resourceAllocator.Dispose(); + _resourceDatabase.Dispose(); + _renderDevice.Dispose(); + } +} diff --git a/src/Test/Ghost.UnitTest/MockingEnvironment/MockingRenderDevice.cs b/src/Test/Ghost.UnitTest/MockingEnvironment/MockingRenderDevice.cs new file mode 100644 index 0000000..f1e1e61 --- /dev/null +++ b/src/Test/Ghost.UnitTest/MockingEnvironment/MockingRenderDevice.cs @@ -0,0 +1,27 @@ +using Ghost.Graphics.RHI; + +namespace Ghost.UnitTest.MockingEnvironment; + +internal class MockingRenderDevice : IRenderDevice +{ + private readonly MockingCommandQueue _commandQueue = new MockingCommandQueue(CommandQueueType.Graphics); + private readonly MockingCommandQueue _computeCommandQueue = new MockingCommandQueue(CommandQueueType.Compute); + private readonly MockingCommandQueue _copyCommandQueue = new MockingCommandQueue(CommandQueueType.Copy); + + public ICommandQueue GraphicsQueue => _commandQueue; + + public ICommandQueue ComputeQueue => _computeCommandQueue; + + public ICommandQueue CopyQueue => _copyCommandQueue; + + public FeatureSupport FeatureSupport => (FeatureSupport)~0; + + public string Name + { + get; set; + } = "MockingRenderDevice"; + + public void Dispose() + { + } +} diff --git a/src/Test/Ghost.UnitTest/MockingEnvironment/MockingResourceDatabase.cs b/src/Test/Ghost.UnitTest/MockingEnvironment/MockingResourceDatabase.cs index e7b309e..7013beb 100644 --- a/src/Test/Ghost.UnitTest/MockingEnvironment/MockingResourceDatabase.cs +++ b/src/Test/Ghost.UnitTest/MockingEnvironment/MockingResourceDatabase.cs @@ -21,7 +21,6 @@ internal unsafe class MockingResourceDatabase : IResourceDatabase private int _samplerToken = 0; private static ulong GetKey(Handle handle) => ((ulong)handle.Generation << 32) | (uint)handle.ID; - private static ulong GetKey(Handle handle) where T : unmanaged => ((ulong)handle.Generation << 32) | (uint)handle.ID; public Handle AddMockResource(ResourceDesc desc, ResourceBarrierData barrierData, string? name) { @@ -88,7 +87,7 @@ internal unsafe class MockingResourceDatabase : IResourceDatabase public ulong GetIntermediateResourceSize(Handle resource, uint firstSubResource, uint numSubResources) { - return 1024 * 1024; // Mock size 1MB + return 0; // For testing, we can return 0 because we don't actually allocate memory. } public Result GetResourceBarrierData(Handle handle) @@ -151,9 +150,15 @@ internal unsafe class MockingResourceDatabase : IResourceDatabase public Handle Replace(Handle dst, Handle src) { - // For tests, replacing means taking the new handle (src) and disposing the old (dst) - ReleaseResource(dst); - return src; + if (_resources.TryGetValue(GetKey(dst), out var recordDst) && + _resources.TryGetValue(GetKey(src), out var recordSrc)) + { + _resources[GetKey(dst)] = recordSrc; + _resources[GetKey(src)] = recordDst; + } + + ReleaseResource(src); + return dst; } public Error SetResourceBarrierData(Handle handle, ResourceBarrierData data) @@ -164,8 +169,10 @@ internal unsafe class MockingResourceDatabase : IResourceDatabase { record.barrierData = data; } + return Error.None; } + return Error.NotFound; }