Update asset system for deferred allocation & add unit tests
Modernize Misaki.HighPerformance dependencies. Refactor texture asset creation to use deferred resource slots via CreateEmpty(). Remove fallback resource fields and update texture resolution logic. Add CreateEmpty() to resource database interfaces. Introduce comprehensive unit tests with mocks for asset management. Enable unsafe code in tests.
This commit is contained in:
60
src/Test/Ghost.UnitTest/AssetSystem/AssetManagerTest.cs
Normal file
60
src/Test/Ghost.UnitTest/AssetSystem/AssetManagerTest.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using Ghost.Engine;
|
||||
using Ghost.UnitTest.MockingEnvironment;
|
||||
using Misaki.HighPerformance.Jobs;
|
||||
|
||||
namespace Ghost.UnitTest.AssetSystem;
|
||||
|
||||
[TestClass]
|
||||
public class AssetManagerTest
|
||||
{
|
||||
private MockingResourceDatabase _resourceDatabase = null!;
|
||||
private MockingResourceAllocator _resourceAllocator = null!;
|
||||
private MockingCommandBuffer _commandBuffer = null!;
|
||||
private MockingContentProvider _provider = null!;
|
||||
|
||||
private ResourceStreamingProcessor _processor = null!;
|
||||
private JobScheduler _jobScheduler = null!;
|
||||
|
||||
private AssetManager _assetManager = null!;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
{
|
||||
_resourceDatabase = new MockingResourceDatabase();
|
||||
_resourceAllocator = new MockingResourceAllocator(_resourceDatabase);
|
||||
_commandBuffer = new MockingCommandBuffer(_resourceDatabase);
|
||||
_provider = new MockingContentProvider();
|
||||
|
||||
_processor = new ResourceStreamingProcessor();
|
||||
|
||||
var schedulerDesc = new JobSchedulerDesc
|
||||
{
|
||||
ThreadCount = 1,
|
||||
ThreadPriority = ThreadPriority.Normal,
|
||||
DependencyChainCapacity = 1024,
|
||||
};
|
||||
_jobScheduler = new JobScheduler(in schedulerDesc);
|
||||
|
||||
_assetManager = new AssetManager(_resourceDatabase, _provider, _processor, _jobScheduler);
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
_assetManager.Dispose();
|
||||
_jobScheduler.Dispose();
|
||||
_commandBuffer.Dispose();
|
||||
_resourceAllocator.Dispose();
|
||||
_resourceDatabase.Dispose();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AssetManager_GetsAssetSuccessfully()
|
||||
{
|
||||
var assetID = Guid.NewGuid();
|
||||
_provider.AddMockTexture(assetID, readDelayMs: Random.Shared.Next(10, 50));
|
||||
|
||||
var handle = _assetManager.ResolveTexture(assetID);
|
||||
Assert.IsTrue(handle.IsValid);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>x64;x86;ARM64</Platforms>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Graphics.RHI;
|
||||
|
||||
namespace Ghost.UnitTest.MockingEnvironment;
|
||||
|
||||
internal class MockingCommandBuffer : ICommandBuffer
|
||||
{
|
||||
private readonly IResourceDatabase _resourceDatabase;
|
||||
|
||||
private string _name = "MockCommandBuffer";
|
||||
private bool _isEmpty = true;
|
||||
|
||||
// Tracking properties for test assertions
|
||||
public int DrawCallCount { get; private set; }
|
||||
public int CopyCallCount { get; private set; }
|
||||
public int UpdateSubResourcesCount { get; private set; }
|
||||
|
||||
public CommandBufferType Type => default;
|
||||
|
||||
public bool IsEmpty => _isEmpty;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set => _name = value;
|
||||
}
|
||||
|
||||
public MockingCommandBuffer(IResourceDatabase resourceDatabase)
|
||||
{
|
||||
_resourceDatabase = resourceDatabase;
|
||||
}
|
||||
|
||||
public void Barrier(params scoped ReadOnlySpan<BarrierDesc> barrierDescs)
|
||||
{
|
||||
_isEmpty = false;
|
||||
lock (this)
|
||||
{
|
||||
foreach (var desc in barrierDescs)
|
||||
{
|
||||
var data = new ResourceBarrierData
|
||||
{
|
||||
access = desc.AccessAfter,
|
||||
layout = desc.LayoutAfter,
|
||||
sync = desc.SyncAfter
|
||||
};
|
||||
|
||||
_resourceDatabase.SetResourceBarrierData(desc.Resource, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Begin(ICommandAllocator allocator)
|
||||
{
|
||||
_isEmpty = true;
|
||||
DrawCallCount = 0;
|
||||
CopyCallCount = 0;
|
||||
UpdateSubResourcesCount = 0;
|
||||
}
|
||||
|
||||
public void BeginRenderPass(ReadOnlySpan<PassRenderTargetDesc> rtDescs, ref readonly PassDepthStencilDesc depthDesc, bool allowUAVWrites = false)
|
||||
{
|
||||
_isEmpty = false;
|
||||
}
|
||||
|
||||
public void ClearDepthStencilView(Handle<GPUTexture> depthStencil, bool inlcludeDepth, bool includeStencil, float clearDepth = 1, byte clearStencil = 0)
|
||||
{
|
||||
_isEmpty = false;
|
||||
}
|
||||
|
||||
public void ClearRenderTargetView(Handle<GPUTexture> renderTarget, Color128 clearColor)
|
||||
{
|
||||
_isEmpty = false;
|
||||
}
|
||||
|
||||
public void CopyBuffer(Handle<GPUBuffer> dest, Handle<GPUBuffer> src, ulong destOffset = 0, ulong srcOffset = 0, ulong numBytes = 0)
|
||||
{
|
||||
_isEmpty = false;
|
||||
CopyCallCount++;
|
||||
}
|
||||
|
||||
public void CopyTexture(Handle<GPUTexture> dst, TextureRegion? dstRegion, Handle<GPUTexture> src, TextureRegion? srcRegion)
|
||||
{
|
||||
_isEmpty = false;
|
||||
CopyCallCount++;
|
||||
}
|
||||
|
||||
public void DispatchCompute(uint threadGroupCountX, uint threadGroupCountY, uint threadGroupCountZ)
|
||||
{
|
||||
_isEmpty = false;
|
||||
}
|
||||
|
||||
public void DispatchGraph(ref readonly DispatchGraphDesc desc)
|
||||
{
|
||||
_isEmpty = false;
|
||||
}
|
||||
|
||||
public void DispatchMesh(uint threadGroupCountX, uint threadGroupCountY, uint threadGroupCountZ)
|
||||
{
|
||||
_isEmpty = false;
|
||||
}
|
||||
|
||||
public void DispatchRay()
|
||||
{
|
||||
_isEmpty = false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public void Draw(uint vertexCount, uint instanceCount = 1, uint startVertex = 0, uint startInstance = 0)
|
||||
{
|
||||
_isEmpty = false;
|
||||
DrawCallCount++;
|
||||
}
|
||||
|
||||
public void DrawIndexed(uint indexCount, uint instanceCount = 1, uint startIndex = 0, int baseVertex = 0, uint startInstance = 0)
|
||||
{
|
||||
_isEmpty = false;
|
||||
DrawCallCount++;
|
||||
}
|
||||
|
||||
public Result End()
|
||||
{
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
public void EndRenderPass()
|
||||
{
|
||||
}
|
||||
|
||||
public void ExecuteIndirect(ICommandSignature commandSignature, Handle<GPUBuffer> argumentBuffer, ulong argumentOffset, Handle<GPUBuffer> countBuffer, ulong countBufferOffset)
|
||||
{
|
||||
_isEmpty = false;
|
||||
}
|
||||
|
||||
public void SetConstantBufferView(uint slot, Handle<GPUBuffer> buffer)
|
||||
{
|
||||
_isEmpty = false;
|
||||
}
|
||||
|
||||
public void SetGraphicsRoot32Constants(uint rootIndex, ReadOnlySpan<uint> constantBuffer, uint offsetIn32Bits = 0)
|
||||
{
|
||||
_isEmpty = false;
|
||||
}
|
||||
|
||||
public void SetIndexBuffer(Handle<GPUBuffer> buffer, IndexType type, ulong offset = 0)
|
||||
{
|
||||
_isEmpty = false;
|
||||
}
|
||||
|
||||
public void SetPipelineState(Key128<PipelineState> pipelineKey)
|
||||
{
|
||||
_isEmpty = false;
|
||||
}
|
||||
|
||||
public void SetPrimitiveTopology(PrimitiveTopology topology)
|
||||
{
|
||||
_isEmpty = false;
|
||||
}
|
||||
|
||||
public void SetProgram(ref readonly SetProgramDesc desc)
|
||||
{
|
||||
_isEmpty = false;
|
||||
}
|
||||
|
||||
public void SetRenderTargets(ReadOnlySpan<Handle<GPUTexture>> renderTargets, Handle<GPUTexture> depthTarget)
|
||||
{
|
||||
_isEmpty = false;
|
||||
}
|
||||
|
||||
public void SetScissorRect(ScissorRectDesc rect)
|
||||
{
|
||||
_isEmpty = false;
|
||||
}
|
||||
|
||||
public void SetVertexBuffer(uint slot, Handle<GPUBuffer> buffer, ulong offset = 0)
|
||||
{
|
||||
_isEmpty = false;
|
||||
}
|
||||
|
||||
public void SetViewport(ViewportDesc viewport)
|
||||
{
|
||||
_isEmpty = false;
|
||||
}
|
||||
|
||||
public void UpdateSubResources(Handle<GPUResource> resource, Handle<GPUResource> intermediate, params scoped ReadOnlySpan<SubResourceData> subResources)
|
||||
{
|
||||
_isEmpty = false;
|
||||
UpdateSubResourcesCount++;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Engine;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Ghost.UnitTest.MockingEnvironment;
|
||||
|
||||
internal class MockingContentProvider : IContentProvider
|
||||
{
|
||||
public class MockAssetData
|
||||
{
|
||||
public AssetType type;
|
||||
public byte[] data = Array.Empty<byte>();
|
||||
public Guid[] dependencies = Array.Empty<Guid>();
|
||||
|
||||
// This is crucial for multi-threaded testing: we can inject random or fixed
|
||||
// delays to ensure our locking and state machines actually get stressed.
|
||||
public int readDelayMs = 0;
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<Guid, MockAssetData> _assets = new();
|
||||
|
||||
public void AddMockAsset(Guid guid, MockAssetData data)
|
||||
{
|
||||
_assets[guid] = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to create a valid dummy texture byte stream that the AssetEntry can parse.
|
||||
/// </summary>
|
||||
public unsafe void AddMockTexture(Guid guid, uint width = 4, uint height = 4, int readDelayMs = 0)
|
||||
{
|
||||
var header = new TextureContentHeader
|
||||
{
|
||||
width = width,
|
||||
height = height,
|
||||
bpc = 8,
|
||||
mipLevels = 1,
|
||||
dimension = 2, // Texture2D
|
||||
colorComponents = 4
|
||||
};
|
||||
|
||||
// Header size is strictly 64 bytes due to [StructLayout(LayoutKind.Sequential, Size = 64)]
|
||||
var headerSize = 64;
|
||||
var pixelDataSize = width * height * 4;
|
||||
|
||||
var buffer = new byte[headerSize + pixelDataSize];
|
||||
|
||||
fixed (byte* pBuffer = buffer)
|
||||
{
|
||||
*(TextureContentHeader*)pBuffer = header;
|
||||
// The rest of the array remains 0 (black/transparent pixels) which is fine for tests
|
||||
}
|
||||
|
||||
AddMockAsset(guid, new MockAssetData
|
||||
{
|
||||
type = AssetType.Texture,
|
||||
data = buffer,
|
||||
readDelayMs = readDelayMs
|
||||
});
|
||||
}
|
||||
|
||||
public AssetType GetAssetType(Guid guid)
|
||||
{
|
||||
return _assets.TryGetValue(guid, out var asset) ? asset.type : AssetType.Unknown;
|
||||
}
|
||||
|
||||
public Guid[] GetDependencies(Guid guid)
|
||||
{
|
||||
return _assets.TryGetValue(guid, out var asset) ? asset.dependencies : Array.Empty<Guid>();
|
||||
}
|
||||
|
||||
public bool HasAsset(Guid guid)
|
||||
{
|
||||
return _assets.ContainsKey(guid);
|
||||
}
|
||||
|
||||
public Result<Stream> OpenRead(Guid guid, CancellationToken token = default)
|
||||
{
|
||||
if (_assets.TryGetValue(guid, out var asset))
|
||||
{
|
||||
if (asset.readDelayMs > 0)
|
||||
{
|
||||
// Inject our simulated I/O latency to widen race condition windows.
|
||||
// In a real multi-threaded test, this forces the executing thread to yield
|
||||
// and lets other threads interact with the AssetManager in the meantime.
|
||||
Thread.Sleep(asset.readDelayMs);
|
||||
}
|
||||
|
||||
// Return a fast, in-memory stream representing our file
|
||||
return Result<Stream>.Success(new MemoryStream(asset.data, writable: false));
|
||||
}
|
||||
|
||||
return Result<Stream>.Failure($"Mock asset {guid} not found.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
|
||||
namespace Ghost.UnitTest.MockingEnvironment;
|
||||
|
||||
internal class MockingResourceAllocator : IResourceAllocator
|
||||
{
|
||||
private readonly MockingResourceDatabase _database;
|
||||
|
||||
public MockingResourceAllocator(MockingResourceDatabase database)
|
||||
{
|
||||
_database = database;
|
||||
}
|
||||
|
||||
public Handle<GPUResource> Allocate(ref readonly AllocationDesc desc, string? name = null)
|
||||
{
|
||||
var barrier = new ResourceBarrierData { layout = BarrierLayout.Common, access = BarrierAccess.NoAccess, sync = BarrierSync.None };
|
||||
// Passing a mock buffer desc for raw allocation representation
|
||||
var bufferDesc = new BufferDesc { Size = desc.Size, Usage = BufferUsage.None };
|
||||
return _database.AddMockResource(ResourceDesc.Buffer(bufferDesc), barrier, name);
|
||||
}
|
||||
|
||||
public Handle<GPUBuffer> CreateBuffer(ref readonly BufferDesc desc, string? name = null, CreationOptions options = default)
|
||||
{
|
||||
var barrier = new ResourceBarrierData { layout = BarrierLayout.Undefined, access = BarrierAccess.Common, sync = BarrierSync.None };
|
||||
var handle = _database.AddMockResource(ResourceDesc.Buffer(desc), barrier, name);
|
||||
return handle.AsBuffer();
|
||||
}
|
||||
|
||||
public Identifier<Sampler> CreateSampler(ref readonly SamplerDesc desc)
|
||||
{
|
||||
return _database.AddSampler(in desc, 1);
|
||||
}
|
||||
|
||||
public Handle<GPUTexture> CreateTexture(ref readonly TextureDesc desc, string? name = null, CreationOptions options = default, AdditionalTextureDesc additionalDesc = default)
|
||||
{
|
||||
var barrier = new ResourceBarrierData { layout = BarrierLayout.Common, access = BarrierAccess.Common, sync = BarrierSync.None };
|
||||
var handle = _database.AddMockResource(ResourceDesc.Texture(desc), barrier, name);
|
||||
return handle.AsTexture();
|
||||
}
|
||||
|
||||
public ResourceSizeInfo GetSizeInfo(ResourceDesc desc)
|
||||
{
|
||||
return new ResourceSizeInfo
|
||||
{
|
||||
Size = 1048576, // 1MB mock
|
||||
Alignment = 65536, // 64KB aligned
|
||||
Offset = 0
|
||||
};
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Handled by dependency injection usually.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Ghost.UnitTest.MockingEnvironment;
|
||||
|
||||
internal unsafe class MockingResourceDatabase : IResourceDatabase
|
||||
{
|
||||
internal class MockResourceRecord
|
||||
{
|
||||
public ResourceDesc desc;
|
||||
public ResourceBarrierData barrierData;
|
||||
public string? name;
|
||||
public int refCount = 1;
|
||||
public bool isShared;
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, MockResourceRecord> _resources = new();
|
||||
private readonly ConcurrentDictionary<Identifier<Sampler>, SamplerDesc> _samplers = new();
|
||||
private int _nextToken = 0;
|
||||
private int _samplerToken = 0;
|
||||
|
||||
private static ulong GetKey(Handle<GPUResource> handle) => ((ulong)handle.Generation << 32) | (uint)handle.ID;
|
||||
private static ulong GetKey<T>(Handle<T> handle) where T : unmanaged => ((ulong)handle.Generation << 32) | (uint)handle.ID;
|
||||
|
||||
public Handle<GPUResource> AddMockResource(ResourceDesc desc, ResourceBarrierData barrierData, string? name)
|
||||
{
|
||||
var id = Interlocked.Increment(ref _nextToken);
|
||||
var generation = 1;
|
||||
var handle = new Handle<GPUResource>(id, generation);
|
||||
|
||||
_resources.TryAdd(GetKey(handle), new MockResourceRecord
|
||||
{
|
||||
desc = desc,
|
||||
barrierData = barrierData,
|
||||
name = name
|
||||
});
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
public Identifier<Sampler> AddSampler(ref readonly SamplerDesc desc, int id)
|
||||
{
|
||||
var newId = new Identifier<Sampler>(id);
|
||||
_samplers.TryAdd(newId, desc);
|
||||
return newId;
|
||||
}
|
||||
|
||||
public Handle<GPUResource> CreateShared(Handle<GPUResource> src)
|
||||
{
|
||||
if (_resources.TryGetValue(GetKey(src), out var record))
|
||||
{
|
||||
lock (record)
|
||||
{
|
||||
record.refCount++;
|
||||
record.isShared = true;
|
||||
}
|
||||
|
||||
// To simulate sharing, we create a new handle mapping to the same conceptual resource.
|
||||
// For simplicity, we just clone the dict entry with a new ID
|
||||
var id = Interlocked.Increment(ref _nextToken);
|
||||
var generation = 1;
|
||||
var handle = new Handle<GPUResource>(id, generation);
|
||||
|
||||
_resources.TryAdd(GetKey(handle), record);
|
||||
return handle;
|
||||
}
|
||||
|
||||
return Handle<GPUResource>.Invalid;
|
||||
}
|
||||
|
||||
public Handle<GPUResource> CreateEmpty()
|
||||
{
|
||||
var id = Interlocked.Increment(ref _nextToken);
|
||||
var generation = 1;
|
||||
var handle = new Handle<GPUResource>(id, generation);
|
||||
|
||||
_resources.TryAdd(GetKey(handle), new MockResourceRecord());
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
public uint GetBindlessIndex(Handle<GPUResource> handle, BindlessAccess access = BindlessAccess.ShaderResource)
|
||||
{
|
||||
// Mock bindless index
|
||||
return (uint)handle.ID;
|
||||
}
|
||||
|
||||
public ulong GetIntermediateResourceSize(Handle<GPUResource> resource, uint firstSubResource, uint numSubResources)
|
||||
{
|
||||
return 1024 * 1024; // Mock size 1MB
|
||||
}
|
||||
|
||||
public Result<ResourceBarrierData, Error> GetResourceBarrierData(Handle<GPUResource> handle)
|
||||
{
|
||||
if (_resources.TryGetValue(GetKey(handle), out var record))
|
||||
return record.barrierData;
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
public Result<ResourceDesc, Error> GetResourceDescription(Handle<GPUResource> handle)
|
||||
{
|
||||
if (_resources.TryGetValue(GetKey(handle), out var record))
|
||||
return record.desc;
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
public string? GetResourceName(Handle<GPUResource> handle)
|
||||
{
|
||||
if (_resources.TryGetValue(GetKey(handle), out var record))
|
||||
return record.name;
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool HasResource(Handle<GPUResource> handle)
|
||||
{
|
||||
return _resources.ContainsKey(GetKey(handle));
|
||||
}
|
||||
|
||||
public void* MapResource(Handle<GPUResource> handle, uint subResource, ResourceRange? readRange)
|
||||
{
|
||||
// Real pointers are tricky in mocks unless native mem is allocated.
|
||||
// Usually unit tests don't do CPU readbacks directly on the raw pointer unless necessary.
|
||||
throw new NotSupportedException("MapResource is not supported in MockingResourceDatabase. Use a custom mechanism for tests.");
|
||||
}
|
||||
|
||||
public void ReleaseResource(Handle<GPUResource> handle)
|
||||
{
|
||||
ReleaseResourceImmediately(handle); // Simplified for testing
|
||||
}
|
||||
|
||||
public void ReleaseResourceImmediately(Handle<GPUResource> handle)
|
||||
{
|
||||
if (_resources.TryGetValue(GetKey(handle), out var record))
|
||||
{
|
||||
lock (record)
|
||||
{
|
||||
record.refCount--;
|
||||
if (record.refCount <= 0)
|
||||
{
|
||||
_resources.TryRemove(GetKey(handle), out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseSampler(Identifier<Sampler> id)
|
||||
{
|
||||
_samplers.TryRemove(id, out _);
|
||||
}
|
||||
|
||||
public Handle<GPUResource> Replace(Handle<GPUResource> dst, Handle<GPUResource> src)
|
||||
{
|
||||
// For tests, replacing means taking the new handle (src) and disposing the old (dst)
|
||||
ReleaseResource(dst);
|
||||
return src;
|
||||
}
|
||||
|
||||
public Error SetResourceBarrierData(Handle<GPUResource> handle, ResourceBarrierData data)
|
||||
{
|
||||
if (_resources.TryGetValue(GetKey(handle), out var record))
|
||||
{
|
||||
lock (record)
|
||||
{
|
||||
record.barrierData = data;
|
||||
}
|
||||
return Error.None;
|
||||
}
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
public Error Swap(Handle<GPUResource> handleA, Handle<GPUResource> handleB)
|
||||
{
|
||||
if (_resources.TryGetValue(GetKey(handleA), out var recordA) &&
|
||||
_resources.TryGetValue(GetKey(handleB), out var recordB))
|
||||
{
|
||||
_resources[GetKey(handleA)] = recordB;
|
||||
_resources[GetKey(handleB)] = recordA;
|
||||
return Error.None;
|
||||
}
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
public bool TryGetSampler(ref readonly SamplerDesc desc, out Identifier<Sampler> id)
|
||||
{
|
||||
foreach (var kvp in _samplers)
|
||||
{
|
||||
// Simple generic mock check
|
||||
id = kvp.Key;
|
||||
return true;
|
||||
}
|
||||
id = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public Error UnmapResource(Handle<GPUResource> handle, uint subResource, ResourceRange? writtenRange)
|
||||
{
|
||||
return Error.None;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_resources.Clear();
|
||||
_samplers.Clear();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user