feat: implement asynchronous asset management system with texture streaming support
This commit is contained in:
15
src/Runtime/Ghost.Core/Common.cs
Normal file
15
src/Runtime/Ghost.Core/Common.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Ghost.Core;
|
||||
|
||||
public enum AssetType : byte
|
||||
{
|
||||
Texture = 0,
|
||||
Mesh = 1,
|
||||
Material = 2,
|
||||
Shaders = 3,
|
||||
Audio = 4,
|
||||
Scene = 5,
|
||||
Video = 6,
|
||||
Json = 7,
|
||||
|
||||
Unknown = 64,
|
||||
}
|
||||
@@ -19,9 +19,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Misaki.HighPerformance" Version="1.0.7" />
|
||||
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.6.1" />
|
||||
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.13">
|
||||
<PackageReference Include="Misaki.HighPerformance" Version="1.0.8" />
|
||||
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="2.0.0" />
|
||||
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.14">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -14,7 +12,7 @@ public enum LogLevel
|
||||
Debug
|
||||
}
|
||||
|
||||
public class LogMessage
|
||||
public readonly struct LogMessage
|
||||
{
|
||||
public LogLevel Level
|
||||
{
|
||||
@@ -55,34 +53,21 @@ public class LogMessage
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class LogCollection : ReadOnlyObservableCollection<LogMessage>
|
||||
{
|
||||
public LogCollection(ObservableCollection<LogMessage> list)
|
||||
: base(list)
|
||||
{
|
||||
}
|
||||
|
||||
public event NotifyCollectionChangedEventHandler? LogChanged;
|
||||
|
||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
|
||||
{
|
||||
base.OnCollectionChanged(args);
|
||||
LogChanged?.Invoke(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
public interface ILogger
|
||||
{
|
||||
LogCollection Logs
|
||||
IReadOnlyCollection<LogMessage> Logs
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public bool CaptureStackTrace
|
||||
bool CaptureStackTrace
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
event Action<LogMessage> OnLogAdded;
|
||||
event Action OnLogsCleared;
|
||||
|
||||
void Log(string message, LogLevel level);
|
||||
void Log(Exception exception);
|
||||
void Assert(bool condition, string message);
|
||||
@@ -94,42 +79,48 @@ public static class Logger
|
||||
// TODO: Add file logging.
|
||||
private class LoggerImpl : ILogger
|
||||
{
|
||||
private readonly ObservableCollection<LogMessage> _logs = new();
|
||||
private readonly LogCollection _readOnly;
|
||||
private readonly Lock _lock = new();
|
||||
private readonly List<LogMessage> _logs = new List<LogMessage>();
|
||||
private readonly Lock _lock = new Lock();
|
||||
|
||||
public LogCollection Logs => _readOnly;
|
||||
public IReadOnlyCollection<LogMessage> Logs => _logs;
|
||||
|
||||
public bool CaptureStackTrace
|
||||
{
|
||||
get; set;
|
||||
} = true;
|
||||
|
||||
public LoggerImpl()
|
||||
{
|
||||
_readOnly = new LogCollection(_logs);
|
||||
}
|
||||
public event Action<LogMessage>? OnLogAdded;
|
||||
public event Action? OnLogsCleared;
|
||||
|
||||
[StackTraceHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Log(string message, LogLevel level)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var stackTrace = CaptureStackTrace ? new StackTrace(true).ToString() : null;
|
||||
_logs.Add(new LogMessage(level, message, stackTrace));
|
||||
var logMessage = new LogMessage(level, message, stackTrace);
|
||||
|
||||
_logs.Add(logMessage);
|
||||
OnLogAdded?.Invoke(logMessage);
|
||||
}
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Log(Exception exception)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_logs.Add(new LogMessage(LogLevel.Error, exception.Message, exception.StackTrace));
|
||||
var logMessage = new LogMessage(LogLevel.Error, exception.Message, exception.StackTrace);
|
||||
|
||||
_logs.Add(logMessage);
|
||||
OnLogAdded?.Invoke(logMessage);
|
||||
}
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Assert(bool condition, string message)
|
||||
{
|
||||
if (!condition)
|
||||
@@ -138,11 +129,13 @@ public static class Logger
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Clear(bool includeFile = false)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_logs.Clear();
|
||||
OnLogsCleared?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,7 +143,7 @@ public static class Logger
|
||||
private static readonly LoggerImpl s_logger = new LoggerImpl();
|
||||
|
||||
public static ILogger Impl => s_logger;
|
||||
public static LogCollection Logs => s_logger.Logs;
|
||||
public static IReadOnlyCollection<LogMessage> Logs => s_logger.Logs;
|
||||
|
||||
[StackTraceHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
@@ -67,6 +67,8 @@ public unsafe ref struct SpanWriter
|
||||
readonly get => _position;
|
||||
set => _position = value;
|
||||
}
|
||||
|
||||
public readonly int RemainingBytes => _buffer.Length - _position;
|
||||
|
||||
public SpanWriter(Span<byte> buffer)
|
||||
{
|
||||
@@ -154,7 +156,7 @@ public unsafe struct BufferReader
|
||||
|
||||
public unsafe ref struct SpanReader
|
||||
{
|
||||
private readonly Span<byte> _buffer;
|
||||
private readonly ReadOnlySpan<byte> _buffer;
|
||||
private int _position;
|
||||
|
||||
public int Position
|
||||
@@ -163,7 +165,9 @@ public unsafe ref struct SpanReader
|
||||
set => _position = value;
|
||||
}
|
||||
|
||||
public SpanReader(Span<byte> buffer)
|
||||
public readonly int RemainingBytes => _buffer.Length - _position;
|
||||
|
||||
public SpanReader(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
_buffer = buffer;
|
||||
_position = 0;
|
||||
@@ -172,7 +176,7 @@ public unsafe ref struct SpanReader
|
||||
public T Read<T>()
|
||||
where T : unmanaged
|
||||
{
|
||||
var value = Unsafe.ReadUnaligned<T>(ref _buffer[_position]);
|
||||
var value = Unsafe.ReadUnaligned<T>(in _buffer[_position]);
|
||||
_position += Unsafe.SizeOf<T>();
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -3,5 +3,7 @@ using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Ghost.Editor")]
|
||||
[assembly: InternalsVisibleTo("Ghost.Editor.Core")]
|
||||
[assembly: InternalsVisibleTo("Ghost.UnitTest")]
|
||||
|
||||
|
||||
[assembly: EngineAssembly]
|
||||
@@ -1,42 +0,0 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Utilities;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Engine.AssetLoader;
|
||||
|
||||
internal sealed class TextureLoader : IRuntimeAssetLoader
|
||||
{
|
||||
public static readonly AssetType AssetType = AssetType.Texture;
|
||||
|
||||
public async ValueTask<Result<Asset>> LoadAsync(Stream cookedData, Guid id, CancellationToken token)
|
||||
{
|
||||
var header = new TextureContentHeader();
|
||||
cookedData.ReadExactly(MemoryMarshal.AsBytes(new Span<TextureContentHeader>(ref header)));
|
||||
|
||||
var alignment = header.depth switch
|
||||
{
|
||||
8 => MemoryUtility.AlignOf<byte>(),
|
||||
16 => MemoryUtility.AlignOf<ushort>(),
|
||||
32 => MemoryUtility.AlignOf<float>(),
|
||||
_ => MemoryUtility.AlignOf<float>()
|
||||
};
|
||||
|
||||
var data = new MemoryBlock((nuint)(cookedData.Length - cookedData.Position), alignment, AllocationHandle.Persistent);
|
||||
|
||||
// C# built-in collections use int for indexing, so we need to ensure that the buffer size does not exceed int.MaxValue
|
||||
var maxBufferSize = (int)Math.Min(0x7effffffu, header.width * header.height * header.depth / 8u * header.colorComponents);
|
||||
var offset = 0u;
|
||||
|
||||
while (offset < data.Size)
|
||||
{
|
||||
using var memoryManager = NativeMemoryManager<byte>.FromMemoryBlock(data, (int)offset, maxBufferSize);
|
||||
|
||||
await cookedData.ReadExactlyAsync(memoryManager.Memory, token);
|
||||
offset += (uint)memoryManager.Memory.Length;
|
||||
}
|
||||
|
||||
return new TextureAsset(ref data, header, id);
|
||||
}
|
||||
}
|
||||
@@ -1,104 +1,146 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Utilities;
|
||||
using Ghost.Engine.AssetLoader;
|
||||
using Ghost.Graphics;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Utilities;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace Ghost.Engine;
|
||||
|
||||
public partial class AssetManager
|
||||
internal partial class AssetEntry
|
||||
{
|
||||
private partial class AssetEntry
|
||||
private unsafe class TextureData
|
||||
{
|
||||
private static TextureFormat GetTextureFormat(uint depth, uint colorComponents)
|
||||
public TextureDesc desc;
|
||||
public TextureContentHeader header;
|
||||
public byte* pData;
|
||||
public nuint dataSize;
|
||||
}
|
||||
|
||||
private static void RegisterTextureCallback()
|
||||
{
|
||||
s_onCreation[(int)AssetType.Texture] = static (e) =>
|
||||
{
|
||||
return colorComponents switch
|
||||
// This will create a new slot in the database, but not allocation any GPU resource.
|
||||
// Everything in the slot will have the same value as the fallback texture, expect the slot will be marked as shared.
|
||||
var handle = e._resourceDatabase.CreateShared(e._assetManager.FallbackTexture.AsResource()).AsTexture();
|
||||
e.SetStorage(handle);
|
||||
};
|
||||
|
||||
s_onParseRawData[(int)AssetType.Texture] = static (e) => e.ParseTextureData();
|
||||
s_onRecordUpload[(int)AssetType.Texture] = static (e, ctx) => e.RecordTextureUpload(ctx);
|
||||
s_onUploadComplete[(int)AssetType.Texture] = static (e, ctx) => e.OnTextureUploadComplete(ctx);
|
||||
s_onReleaseResource[(int)AssetType.Texture] = static (e) =>
|
||||
{
|
||||
var handle = e.GetStorage<Handle<GPUTexture>>();
|
||||
e._resourceDatabase.ReleaseResource(handle.AsResource());
|
||||
};
|
||||
}
|
||||
|
||||
private static TextureFormat GetTextureFormat(uint depth, uint colorComponents)
|
||||
{
|
||||
return colorComponents switch
|
||||
{
|
||||
1 => depth switch
|
||||
{
|
||||
1 => depth switch
|
||||
{
|
||||
8 => TextureFormat.R8_UNorm,
|
||||
16 => TextureFormat.R16_UNorm,
|
||||
32 => TextureFormat.R32_UInt,
|
||||
_ => TextureFormat.Unknown,
|
||||
},
|
||||
2 => depth switch
|
||||
{
|
||||
8 => TextureFormat.R8G8_UNorm,
|
||||
16 => TextureFormat.R16G16_UNorm,
|
||||
32 => TextureFormat.R32G32_Float,
|
||||
_ => TextureFormat.Unknown,
|
||||
},
|
||||
3 or 4 => depth switch
|
||||
{
|
||||
8 => TextureFormat.R8G8B8A8_UNorm,
|
||||
16 => TextureFormat.R16G16B16A16_Float,
|
||||
32 => TextureFormat.R32G32B32A32_Float,
|
||||
_ => TextureFormat.Unknown,
|
||||
},
|
||||
8 => TextureFormat.R8_UNorm,
|
||||
16 => TextureFormat.R16_UNorm,
|
||||
32 => TextureFormat.R32_UInt,
|
||||
_ => TextureFormat.Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
private unsafe Result RecordTextureUpload(ICommandBuffer commandBuffer)
|
||||
{
|
||||
var pData = (byte*)_rawData.GetUnsafePtr();
|
||||
var reader = new BufferReader(pData, _rawData.Size);
|
||||
|
||||
var header = reader.Read<TextureContentHeader>();
|
||||
|
||||
var textureDesc = new TextureDesc
|
||||
},
|
||||
2 => depth switch
|
||||
{
|
||||
Width = header.width,
|
||||
Height = header.height,
|
||||
MipLevels = header.mipLevels,
|
||||
Slice = 1,
|
||||
Format = GetTextureFormat(header.depth, header.colorComponents),
|
||||
Dimension = (TextureDimension)header.dimension,
|
||||
Usage = TextureUsage.ShaderResource,
|
||||
};
|
||||
|
||||
var newHandle = RenderingUtility.CreateTexture(
|
||||
ResourceManager,
|
||||
ResourceDatabase,
|
||||
ResourceAllocator,
|
||||
commandBuffer,
|
||||
reader.CurrentAddress,
|
||||
reader.RemainingBytes,
|
||||
in textureDesc);
|
||||
|
||||
if (newHandle.IsInvalid)
|
||||
8 => TextureFormat.R8G8_UNorm,
|
||||
16 => TextureFormat.R16G16_UNorm,
|
||||
32 => TextureFormat.R32G32_Float,
|
||||
_ => TextureFormat.Unknown,
|
||||
},
|
||||
3 or 4 => depth switch
|
||||
{
|
||||
return Result.Failure("Failed to create GPU texture.");
|
||||
}
|
||||
|
||||
var oldHandle = GetStorage<Handle<GPUTexture>>();
|
||||
SetStorage((oldHandle, newHandle));
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
private void OnTextureUploadComplete()
|
||||
{
|
||||
var (oldHandle, newHandle) = GetStorage<(Handle<GPUTexture>, Handle<GPUTexture>)>();
|
||||
|
||||
ResourceDatabase.Swap(oldHandle.AsResource(), newHandle.AsResource());
|
||||
ResourceDatabase.ReleaseResource(newHandle.AsResource()); // releases old fallback slot
|
||||
|
||||
SetStorage((oldHandle, Handle<GPUTexture>.Invalid)); // Old handle is now the new handle, and the old fallback slot is released. Use Invalid handle to clear second slot.
|
||||
|
||||
_rawData.Dispose();
|
||||
_rawData = default;
|
||||
}
|
||||
8 => TextureFormat.R8G8B8A8_UNorm,
|
||||
16 => TextureFormat.R16G16B16A16_Float,
|
||||
32 => TextureFormat.R32G32B32A32_Float,
|
||||
_ => TextureFormat.Unknown,
|
||||
},
|
||||
_ => TextureFormat.Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
private Handle<GPUTexture> AllocateTextureHandle()
|
||||
private unsafe Result ParseTextureData()
|
||||
{
|
||||
// This will create a new slot in the database, but not allocation any GPU resource.
|
||||
// Everything in the slot will have the same value as the fallback texture, expect the slot will be marked as shared.
|
||||
return _resourceDatabase.CreateShared(_fallbackTexture.AsResource()).AsTexture();
|
||||
var pData = (byte*)_rawData.GetUnsafePtr();
|
||||
Logger.DebugAssert(pData != null);
|
||||
|
||||
var reader = new BufferReader(pData, _rawData.Size);
|
||||
|
||||
var header = reader.Read<TextureContentHeader>();
|
||||
var textureDesc = new TextureDesc
|
||||
{
|
||||
Width = header.width,
|
||||
Height = header.height,
|
||||
MipLevels = header.mipLevels,
|
||||
Slice = 1,
|
||||
Format = GetTextureFormat(header.depth, header.colorComponents),
|
||||
Dimension = (TextureDimension)header.dimension,
|
||||
Usage = TextureUsage.ShaderResource,
|
||||
};
|
||||
|
||||
// Will the gc be fine here?
|
||||
var textureData = new TextureData
|
||||
{
|
||||
desc = textureDesc,
|
||||
header = header,
|
||||
pData = reader.CurrentAddress,
|
||||
dataSize = reader.RemainingBytes,
|
||||
};
|
||||
|
||||
_parsedObject = textureData;
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
private unsafe Result RecordTextureUpload(ResourceStreamingContext context)
|
||||
{
|
||||
var textureData = _parsedObject as TextureData;
|
||||
Logger.DebugAssert(textureData != null);
|
||||
|
||||
var newHandle = RenderingUtility.CreateTexture(
|
||||
context.ResourceManager,
|
||||
context.ResourceDatabase,
|
||||
context.ResourceAllocator,
|
||||
context.CopyPipeline.GetCommandBuffer(),
|
||||
textureData.pData,
|
||||
textureData.dataSize,
|
||||
in textureData.desc);
|
||||
|
||||
if (newHandle.IsInvalid)
|
||||
{
|
||||
return Result.Failure("Failed to create GPU texture.");
|
||||
}
|
||||
|
||||
var oldHandle = GetStorage<Handle<GPUTexture>>();
|
||||
SetStorage((oldHandle, newHandle));
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
private void OnTextureUploadComplete(ResourceStreamingContext context)
|
||||
{
|
||||
var (oldHandle, newHandle) = GetStorage<(Handle<GPUTexture>, Handle<GPUTexture>)>();
|
||||
var actualHandle = context.ResourceDatabase.Replace(oldHandle.AsResource(), newHandle.AsResource());
|
||||
|
||||
context.GraphicsCommandBuffer.Barrier(BarrierDesc.Texture(oldHandle.AsResource(), BarrierSync.AllShading, BarrierAccess.ShaderResource, BarrierLayout.ShaderResource));
|
||||
|
||||
SetStorage((actualHandle, Handle<GPUTexture>.Invalid));
|
||||
|
||||
_rawData.Dispose();
|
||||
_parsedObject = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal partial class AssetManager
|
||||
{
|
||||
public Handle<GPUTexture> ResolveTexture(Guid assetID)
|
||||
{
|
||||
if (assetID == Guid.Empty)
|
||||
@@ -111,4 +153,19 @@ public partial class AssetManager
|
||||
|
||||
return entry.GetStorage<Handle<GPUTexture>>();
|
||||
}
|
||||
|
||||
public int ReleaseTexture(Guid assetID)
|
||||
{
|
||||
if (assetID == Guid.Empty)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!_entries.TryGetValue(assetID, out var entry) || entry.AssetType != AssetType.Texture)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return entry.Release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Utilities;
|
||||
using Ghost.Graphics;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Services;
|
||||
using Misaki.HighPerformance.Jobs;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Engine;
|
||||
|
||||
@@ -25,20 +26,7 @@ public enum AssetState : byte
|
||||
Failed = 6,
|
||||
}
|
||||
|
||||
public enum AssetType : byte
|
||||
{
|
||||
Texture = 0,
|
||||
Mesh = 1,
|
||||
Material = 2,
|
||||
Audio = 3,
|
||||
Scene = 4,
|
||||
Video = 5,
|
||||
Json = 6,
|
||||
|
||||
Unknown = 255,
|
||||
}
|
||||
|
||||
internal interface IContentProvider
|
||||
public interface IContentProvider
|
||||
{
|
||||
bool HasAsset(Guid guid);
|
||||
|
||||
@@ -49,258 +37,249 @@ internal interface IContentProvider
|
||||
AssetType GetAssetType(Guid guid);
|
||||
}
|
||||
|
||||
// TODO: Support DirectStorage.
|
||||
public partial class AssetManager : IDisposable
|
||||
internal partial class AssetEntry
|
||||
{
|
||||
private unsafe partial class AssetEntry : IDisposable
|
||||
private static readonly Action<AssetEntry>[] s_onCreation = new Action<AssetEntry>[(int)AssetType.Unknown + 1];
|
||||
private static readonly Func<AssetEntry, Result>[] s_onParseRawData = new Func<AssetEntry, Result>[(int)AssetType.Unknown + 1];
|
||||
private static readonly Action<AssetEntry, ResourceStreamingContext>[] s_onRecordUpload = new Action<AssetEntry, ResourceStreamingContext>[(int)AssetType.Unknown + 1];
|
||||
private static readonly Action<AssetEntry, ResourceStreamingContext>[] s_onUploadComplete = new Action<AssetEntry, ResourceStreamingContext>[(int)AssetType.Unknown + 1];
|
||||
private static readonly Action<AssetEntry>[] s_onReleaseResource = new Action<AssetEntry>[(int)AssetType.Unknown + 1];
|
||||
|
||||
static AssetEntry()
|
||||
{
|
||||
public struct __storage
|
||||
{
|
||||
public fixed byte data[64];
|
||||
}
|
||||
RegisterTextureCallback();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly AssetManager _assetManager;
|
||||
|
||||
private Guid _assetId;
|
||||
private __storage _storage;
|
||||
private MemoryBlock _rawData;
|
||||
|
||||
private JobHandle _loadJobHandle;
|
||||
private AssetType _assetType;
|
||||
private int _refCount;
|
||||
private int _state;
|
||||
|
||||
private ResourceManager ResourceManager => _assetManager._resourceManager;
|
||||
private IResourceDatabase ResourceDatabase => _assetManager._resourceDatabase;
|
||||
private IResourceAllocator ResourceAllocator => _assetManager._resourceAllocator;
|
||||
|
||||
public Guid AssetId => _assetId;
|
||||
public MemoryBlock RawData => _rawData;
|
||||
public JobHandle LoadJobHandle => _loadJobHandle;
|
||||
public AssetType AssetType => _assetType;
|
||||
public int RefCount => Volatile.Read(ref _refCount);
|
||||
|
||||
public AssetState State
|
||||
{
|
||||
get => (AssetState)Volatile.Read(ref _state);
|
||||
set => Volatile.Write(ref _state, (int)value);
|
||||
}
|
||||
|
||||
public AssetEntry(AssetManager manager, Guid assetId, AssetType assetType)
|
||||
{
|
||||
_assetManager = manager;
|
||||
|
||||
_assetId = assetId;
|
||||
_assetType = assetType;
|
||||
_refCount = 1;
|
||||
|
||||
switch (assetType)
|
||||
{
|
||||
case AssetType.Texture:
|
||||
SetStorage(manager.AllocateTextureHandle());
|
||||
break;
|
||||
case AssetType.Mesh:
|
||||
break;
|
||||
case AssetType.Material:
|
||||
break;
|
||||
case AssetType.Audio:
|
||||
break;
|
||||
case AssetType.Scene:
|
||||
break;
|
||||
case AssetType.Video:
|
||||
break;
|
||||
case AssetType.Json:
|
||||
break;
|
||||
case AssetType.Unknown:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetStorage<T>(T asset)
|
||||
where T : unmanaged
|
||||
{
|
||||
Unsafe.WriteUnaligned(ref _storage.data[0], asset);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T GetStorage<T>()
|
||||
where T : unmanaged
|
||||
{
|
||||
return Unsafe.ReadUnaligned<T>(ref _storage.data[0]);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetRawData([OwnershipTransfer] ref MemoryBlock data)
|
||||
{
|
||||
_rawData = data;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetLoadJobHandle(JobHandle handle)
|
||||
{
|
||||
_loadJobHandle = handle;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddRef()
|
||||
{
|
||||
Interlocked.Increment(ref _refCount);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int Release()
|
||||
{
|
||||
var newRefCount = Interlocked.Decrement(ref _refCount);
|
||||
Debug.Assert(newRefCount >= 0, "Reference count should not be negative");
|
||||
|
||||
if (newRefCount == 0)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
return newRefCount;
|
||||
}
|
||||
|
||||
public void OnRecordUploadCommands(ICommandBuffer commandBuffer)
|
||||
{
|
||||
switch (_assetType)
|
||||
{
|
||||
case AssetType.Texture:
|
||||
RecordTextureUpload(commandBuffer);
|
||||
break;
|
||||
case AssetType.Mesh:
|
||||
break;
|
||||
case AssetType.Material:
|
||||
break;
|
||||
case AssetType.Audio:
|
||||
break;
|
||||
case AssetType.Scene:
|
||||
break;
|
||||
case AssetType.Video:
|
||||
break;
|
||||
case AssetType.Json:
|
||||
break;
|
||||
case AssetType.Unknown:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnUploadComplete()
|
||||
{
|
||||
switch (_assetType)
|
||||
{
|
||||
case AssetType.Texture:
|
||||
OnTextureUploadComplete();
|
||||
break;
|
||||
case AssetType.Mesh:
|
||||
break;
|
||||
case AssetType.Material:
|
||||
break;
|
||||
case AssetType.Audio:
|
||||
break;
|
||||
case AssetType.Scene:
|
||||
break;
|
||||
case AssetType.Video:
|
||||
break;
|
||||
case AssetType.Json:
|
||||
break;
|
||||
case AssetType.Unknown:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Volatile.Write(ref _state, (int)AssetState.Ready);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
var handle = GetStorage<Handle<GPUTexture>>();
|
||||
ResourceDatabase.ReleaseResource(handle.AsResource());
|
||||
|
||||
_assetManager.RemoveEntry(_assetId);
|
||||
}
|
||||
internal unsafe partial class AssetEntry
|
||||
{
|
||||
private struct Storage
|
||||
{
|
||||
public fixed byte data[64];
|
||||
}
|
||||
|
||||
private struct LoadAssetJob : IJob
|
||||
{
|
||||
public Guid assetID;
|
||||
public AssetType assetType;
|
||||
|
||||
private static Result LoadRawData(IContentProvider contentProvider, AssetEntry entry)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var stream = contentProvider.OpenRead(entry.AssetId).GetValueOrThrow();
|
||||
|
||||
var data = new MemoryBlock((nuint)stream.Length, MemoryUtility.AlignOf<IntPtr>(), AllocationHandle.Persistent);
|
||||
|
||||
// C# built-in collections use int for indexing, so we need to ensure that the buffer size does not exceed int.MaxValue
|
||||
var maxChunkSize = (int)Math.Min(0x7fffffffu, data.Size);
|
||||
var offset = 0u;
|
||||
|
||||
while (offset < data.Size)
|
||||
{
|
||||
using var mem = NativeMemoryManager<byte>.FromMemoryBlock(data, (int)offset, maxChunkSize);
|
||||
stream.ReadExactly(mem.Memory.Span);
|
||||
offset += (uint)mem.Memory.Length;
|
||||
}
|
||||
|
||||
entry.SetRawData(ref data);
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Failure(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public void Execute(ref readonly JobExecutionContext ctx)
|
||||
{
|
||||
var assetManager = ctx.State as AssetManager;
|
||||
|
||||
Debug.Assert(assetManager is not null);
|
||||
Debug.Assert(assetManager._contentProvider.GetAssetType(assetID) == assetType);
|
||||
|
||||
if (!assetManager._entries.TryGetValue(assetID, out var entry))
|
||||
{
|
||||
Logger.Error($"Asset entry not found for {assetID}");
|
||||
return;
|
||||
}
|
||||
|
||||
entry.State = AssetState.Loading;
|
||||
|
||||
var result = LoadRawData(assetManager._contentProvider, entry);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
entry.State = AssetState.Failed;
|
||||
Logger.Error($"Failed to load asset {assetID}: {result.Message}");
|
||||
return;
|
||||
}
|
||||
|
||||
entry.State = AssetState.Loaded;
|
||||
}
|
||||
}
|
||||
|
||||
private const int _MAX_UPLOADS_PER_FRAME = 8;
|
||||
|
||||
private readonly IContentProvider _contentProvider;
|
||||
|
||||
private readonly ResourceManager _resourceManager;
|
||||
private readonly IResourceAllocator _resourceAllocator;
|
||||
private readonly AssetManager _assetManager;
|
||||
private readonly IResourceDatabase _resourceDatabase;
|
||||
private readonly AsyncCopyPipeline _copyPipeline; // Upload via copy queue.
|
||||
|
||||
private readonly Guid _assetId;
|
||||
private readonly AssetType _assetType;
|
||||
private readonly Guid[] _dependencies;
|
||||
|
||||
private Storage _storage;
|
||||
private MemoryBlock _rawData;
|
||||
private object? _parsedObject;
|
||||
|
||||
private JobHandle _loadJobHandle;
|
||||
private int _refCount;
|
||||
private int _state;
|
||||
|
||||
private bool _pendingReimport;
|
||||
|
||||
public Guid AssetId => _assetId;
|
||||
public MemoryBlock RawData => _rawData;
|
||||
public JobHandle LoadJobHandle => _loadJobHandle;
|
||||
public AssetType AssetType => _assetType;
|
||||
public ReadOnlySpan<Guid> Dependencies => _dependencies;
|
||||
public int RefCount => Volatile.Read(ref _refCount);
|
||||
|
||||
public ref int StateValue => ref _state;
|
||||
public AssetState State
|
||||
{
|
||||
get => (AssetState)Volatile.Read(ref _state);
|
||||
set => Volatile.Write(ref _state, (int)value);
|
||||
}
|
||||
|
||||
public AssetEntry(AssetManager manager, IResourceDatabase resourceDatabase, Guid assetId, AssetType assetType, Guid[] dependencies)
|
||||
{
|
||||
_assetManager = manager;
|
||||
_resourceDatabase = resourceDatabase;
|
||||
|
||||
_assetId = assetId;
|
||||
_assetType = assetType;
|
||||
_dependencies = dependencies;
|
||||
|
||||
s_onCreation[(int)_assetType]?.Invoke(this);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetStorage<T>(T asset)
|
||||
where T : unmanaged
|
||||
{
|
||||
Unsafe.WriteUnaligned(ref _storage.data[0], asset);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T GetStorage<T>()
|
||||
where T : unmanaged
|
||||
{
|
||||
return Unsafe.ReadUnaligned<T>(ref _storage.data[0]);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetRawData([OwnershipTransfer] ref MemoryBlock data)
|
||||
{
|
||||
_rawData = data;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetLoadJobHandle(JobHandle handle)
|
||||
{
|
||||
_loadJobHandle = handle;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetPendingReimport()
|
||||
{
|
||||
Volatile.Write(ref _pendingReimport, true);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddRef()
|
||||
{
|
||||
Interlocked.Increment(ref _refCount);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int Release()
|
||||
{
|
||||
Logger.DebugAssert(State == AssetState.Ready);
|
||||
|
||||
var newRefCount = Interlocked.Decrement(ref _refCount);
|
||||
Logger.DebugAssert(newRefCount >= 0, "Reference count should not be negative");
|
||||
|
||||
if (newRefCount == 0)
|
||||
{
|
||||
_assetManager.RemoveEntry(_assetId);
|
||||
OnReleaseResource();
|
||||
|
||||
foreach (var dep in _dependencies)
|
||||
{
|
||||
if (_assetManager.TryGetEntry(dep, out var entry))
|
||||
{
|
||||
entry.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newRefCount;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Result OnParseRawData()
|
||||
{
|
||||
return s_onParseRawData[(int)_assetType]?.Invoke(this) ?? Result.Failure("Unsupported asset type.");
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void OnRecordUploadCommands(ResourceStreamingContext context)
|
||||
{
|
||||
s_onRecordUpload[(int)_assetType]?.Invoke(this, context);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void OnUploadComplete(ResourceStreamingContext context)
|
||||
{
|
||||
s_onUploadComplete[(int)_assetType]?.Invoke(this, context);
|
||||
Volatile.Write(ref _state, (int)AssetState.Ready);
|
||||
|
||||
if (Interlocked.CompareExchange(ref _pendingReimport, false, true))
|
||||
{
|
||||
_assetManager.InvalidateAsset(_assetId); // re-queue
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void OnReleaseResource()
|
||||
{
|
||||
s_onReleaseResource[(int)_assetType]?.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
internal struct LoadAssetJob : IJob
|
||||
{
|
||||
public Guid assetID;
|
||||
public AssetType assetType;
|
||||
public GCHandle assetManagerHandle;
|
||||
|
||||
private static Result LoadRawData(IContentProvider contentProvider, AssetEntry entry)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var stream = contentProvider.OpenRead(entry.AssetId).GetValueOrThrow();
|
||||
|
||||
var data = new MemoryBlock((nuint)stream.Length, MemoryUtility.AlignOf<IntPtr>(), AllocationHandle.Persistent);
|
||||
|
||||
// C# built-in collections use int for indexing, so we need to ensure that the buffer size does not exceed int.MaxValue
|
||||
var maxChunkSize = (int)Math.Min(0x7fffffffu, data.Size);
|
||||
var offset = 0u;
|
||||
|
||||
while (offset < data.Size)
|
||||
{
|
||||
using var mem = NativeMemoryManager<byte>.FromMemoryBlock(data, (int)offset, maxChunkSize);
|
||||
stream.ReadExactly(mem.Memory.Span);
|
||||
offset += (uint)mem.Memory.Length;
|
||||
}
|
||||
|
||||
entry.SetRawData(ref data);
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.Failure(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly void Execute(ref readonly JobExecutionContext ctx)
|
||||
{
|
||||
var assetManager = assetManagerHandle.Target as AssetManager;
|
||||
|
||||
Logger.DebugAssert(assetManager is not null);
|
||||
|
||||
if (!assetManager.TryGetEntry(assetID, out var entry))
|
||||
{
|
||||
Logger.Error($"Asset entry not found for {assetID}");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.DebugAssert(entry.AssetType == assetType);
|
||||
Logger.DebugAssert(entry.State == AssetState.Scheduled);
|
||||
|
||||
entry.State = AssetState.Loading;
|
||||
|
||||
var result = LoadRawData(assetManager.ContentProvider, entry);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
entry.State = AssetState.Failed;
|
||||
Logger.Error($"Failed to load asset {assetID}: {result.Message}");
|
||||
return;
|
||||
}
|
||||
|
||||
result = entry.OnParseRawData();
|
||||
if (result.IsFailure)
|
||||
{
|
||||
entry.State = AssetState.Failed;
|
||||
Logger.Error($"Failed to parse asset {assetID}: {result.Message}");
|
||||
return;
|
||||
}
|
||||
|
||||
entry.State = AssetState.Loaded;
|
||||
|
||||
assetManager.StreamingProcessor.EnqueueForUpload(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Support DirectStorage.
|
||||
internal partial class AssetManager : IDisposable
|
||||
{
|
||||
private readonly IResourceDatabase _resourceDatabase;
|
||||
private readonly IContentProvider _contentProvider;
|
||||
private readonly ResourceStreamingProcessor _streamingProcessor;
|
||||
private readonly JobScheduler _jobScheduler;
|
||||
private readonly ConcurrentDictionary<Guid, AssetEntry> _entries;
|
||||
private readonly ConcurrentQueue<AssetEntry> _pendingFinalize;
|
||||
|
||||
private ulong _pendingCopyFenceValue;
|
||||
private readonly ConcurrentDictionary<Guid, AssetEntry> _entries;
|
||||
|
||||
private GCHandle _selfHandle;
|
||||
|
||||
// TODO
|
||||
private Handle<GPUTexture> _fallbackTexture;
|
||||
@@ -308,57 +287,56 @@ public partial class AssetManager : IDisposable
|
||||
private Handle<Mesh> _fallbackMesh;
|
||||
private Handle<Material> _fallbackMaterial;
|
||||
|
||||
internal AssetManager(IContentProvider contentProvider, ResourceManager resourceManager, IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase, AsyncCopyPipeline uploadBatch)
|
||||
public IContentProvider ContentProvider => _contentProvider;
|
||||
public ResourceStreamingProcessor StreamingProcessor => _streamingProcessor;
|
||||
|
||||
public Handle<GPUTexture> FallbackTexture => _fallbackTexture;
|
||||
|
||||
internal AssetManager(IResourceDatabase resourceDatabase, IContentProvider contentProvider, ResourceStreamingProcessor streamingProcessor, JobScheduler jobScheduler)
|
||||
{
|
||||
_contentProvider = contentProvider;
|
||||
_resourceManager = resourceManager;
|
||||
_resourceAllocator = resourceAllocator;
|
||||
_resourceDatabase = resourceDatabase;
|
||||
_copyPipeline = uploadBatch;
|
||||
_contentProvider = contentProvider;
|
||||
_streamingProcessor = streamingProcessor;
|
||||
_jobScheduler = jobScheduler;
|
||||
|
||||
var desc = new JobSchedulerDesc
|
||||
{
|
||||
ThreadCount = Environment.ProcessorCount < 8 ? 1 : 2,
|
||||
ThreadPriority = ThreadPriority.BelowNormal,
|
||||
State = this,
|
||||
};
|
||||
|
||||
_jobScheduler = new JobScheduler(in desc);
|
||||
_entries = new ConcurrentDictionary<Guid, AssetEntry>();
|
||||
_pendingFinalize = new ConcurrentQueue<AssetEntry>();
|
||||
_selfHandle = GCHandle.Alloc(this, GCHandleType.Normal);
|
||||
}
|
||||
|
||||
internal bool TryGetEntry(Guid guid, [NotNullWhen(true)] out AssetEntry? entry)
|
||||
{
|
||||
return _entries.TryGetValue(guid, out entry);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool RemoveEntry(Guid guid)
|
||||
internal bool RemoveEntry(Guid guid)
|
||||
{
|
||||
return _entries.TryRemove(guid, out _);
|
||||
return _entries.TryRemove(guid, out var entry);
|
||||
}
|
||||
|
||||
private void EnsureScheduled(AssetEntry entry)
|
||||
{
|
||||
if ((int)entry.State >= (int)AssetState.Scheduled)
|
||||
if (Interlocked.CompareExchange(ref entry.StateValue, (int)AssetState.Scheduled, (int)AssetState.Unloaded) != (int)AssetState.Unloaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Resolve dependencies (in-memory manifest/catalog lookup — instant)
|
||||
var deps = _contentProvider.GetDependencies(entry.AssetId);
|
||||
|
||||
// TODO: Can this be jobified? If the dependency tree is not deep, it should be fine to do it in main thread, otherwise we might need to schedule a job to do it.
|
||||
var dependency = JobHandle.Invalid;
|
||||
if (deps.Length > 0)
|
||||
if (entry.Dependencies.Length > 0)
|
||||
{
|
||||
// Avoid stack overflow for deep dependency tree like a whole scene.
|
||||
// Avoid stack overflow for deep dependency tree like a scene.
|
||||
|
||||
// Stack allocator here is fine, because it use virtual memory and has 32 mb capacity per thread.
|
||||
using var scope = AllocationManager.CreateStackScope();
|
||||
|
||||
using var list = new UnsafeList<Guid>(deps.Length * 2, scope.AllocationHandle);
|
||||
using var stack = new UnsafeStack<Guid>(deps.Length * 2, scope.AllocationHandle);
|
||||
using var visited = new UnsafeHashSet<Guid>(deps.Length * 2, scope.AllocationHandle);
|
||||
using var list = new UnsafeList<Guid>(entry.Dependencies.Length * 2, scope.AllocationHandle);
|
||||
using var stack = new UnsafeStack<Guid>(entry.Dependencies.Length * 2, scope.AllocationHandle);
|
||||
using var visited = new UnsafeHashSet<Guid>(entry.Dependencies.Length * 2, scope.AllocationHandle);
|
||||
|
||||
for (var i = 0; i < deps.Length; i++)
|
||||
for (var i = 0; i < entry.Dependencies.Length; i++)
|
||||
{
|
||||
stack.Push(deps[i]);
|
||||
stack.Push(entry.Dependencies[i]);
|
||||
}
|
||||
|
||||
while (stack.TryPop(out var guid))
|
||||
@@ -388,7 +366,7 @@ public partial class AssetManager : IDisposable
|
||||
{
|
||||
// This should create the entry and schedule the job on those assets does not have any dependency first.
|
||||
var handle = GetOrCreateEntry(list[i]).LoadJobHandle;
|
||||
Debug.Assert(handle.IsValid);
|
||||
Logger.DebugAssert(handle.IsValid);
|
||||
|
||||
depHandles.Add(handle);
|
||||
}
|
||||
@@ -400,85 +378,59 @@ public partial class AssetManager : IDisposable
|
||||
{
|
||||
assetID = entry.AssetId,
|
||||
assetType = entry.AssetType,
|
||||
assetManagerHandle = _selfHandle,
|
||||
};
|
||||
|
||||
entry.SetLoadJobHandle(_jobScheduler.Schedule(ref job, dependency));
|
||||
entry.SetLoadJobHandle(_jobScheduler.Schedule(ref job, dependency, JobPriority.Low)); // Use low priority to avoid blocking main thread critical tasks like rendering and physics.
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private AssetEntry GetOrCreateEntry(Guid guid)
|
||||
{
|
||||
return _entries.GetOrAdd(guid, static (id, self) =>
|
||||
var entry = _entries.GetOrAdd(guid, static (id, self) =>
|
||||
{
|
||||
var entry = new AssetEntry(self, id, self._contentProvider.GetAssetType(id))
|
||||
{
|
||||
State = AssetState.Scheduled
|
||||
};
|
||||
var type = self._contentProvider.GetAssetType(id);
|
||||
var deps = self._contentProvider.GetDependencies(id);
|
||||
|
||||
var entry = new AssetEntry(self, self._resourceDatabase, id, type, deps);
|
||||
|
||||
self.EnsureScheduled(entry);
|
||||
return entry;
|
||||
}, this);
|
||||
|
||||
entry.AddRef();
|
||||
return entry;
|
||||
}
|
||||
|
||||
// NOTE: Render thread only.
|
||||
internal void ProcessPendingUploads()
|
||||
public void InvalidateAsset(Guid guid)
|
||||
{
|
||||
// 1. If there's a pending copy batch from last frame, check its fence
|
||||
if (_pendingCopyFenceValue > 0 && _copyPipeline.CurrentFenceValue() >= _pendingCopyFenceValue)
|
||||
{
|
||||
while (_pendingFinalize.TryDequeue(out var item))
|
||||
{
|
||||
item.OnUploadComplete();
|
||||
}
|
||||
|
||||
_pendingCopyFenceValue = 0;
|
||||
}
|
||||
|
||||
if (_pendingCopyFenceValue > 0)
|
||||
if (!_entries.TryGetValue(guid, out var entry))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Collect entries that are in state == Loaded (I/O done, not yet uploaded)
|
||||
// Cap per frame to avoid stalling (e.g., max 8 textures per frame)
|
||||
_copyPipeline.Begin();
|
||||
|
||||
var cmdCopy = _copyPipeline.GetCommandBuffer();
|
||||
var uploadCount = 0;
|
||||
|
||||
foreach (var (guid, entry) in _entries)
|
||||
if (entry.State is AssetState.Loading or AssetState.Loaded or AssetState.Uploading)
|
||||
{
|
||||
if (entry.State != AssetState.Loaded)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (uploadCount >= _MAX_UPLOADS_PER_FRAME)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Record copy commands into cmdCopy
|
||||
entry.OnRecordUploadCommands(cmdCopy);
|
||||
entry.State = AssetState.Uploading;
|
||||
|
||||
_pendingFinalize.Enqueue(entry);
|
||||
uploadCount++;
|
||||
entry.SetPendingReimport();
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Submit the batch
|
||||
if (uploadCount > 0)
|
||||
{
|
||||
var result = _copyPipeline.End();
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
_pendingCopyFenceValue = _copyPipeline.SignaledFenceValue();
|
||||
}
|
||||
}
|
||||
// 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.
|
||||
entry.State = AssetState.Scheduled;
|
||||
EnsureScheduled(entry);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
foreach (var entry in _entries.Values)
|
||||
{
|
||||
entry.OnReleaseResource();
|
||||
}
|
||||
|
||||
_entries.Clear();
|
||||
_selfHandle.Free();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,29 +6,42 @@ namespace Ghost.Engine;
|
||||
|
||||
public sealed partial class EngineCore : IDisposable
|
||||
{
|
||||
private readonly IContentProvider _contentProvider;
|
||||
|
||||
private readonly JobScheduler _jobScheduler;
|
||||
private readonly ResourceStreamingProcessor _streamingProcessor;
|
||||
private readonly RenderSystem _renderSystem;
|
||||
private readonly AssetManager _assetManager;
|
||||
|
||||
public JobScheduler JobScheduler => _jobScheduler;
|
||||
public RenderSystem RenderSystem => _renderSystem;
|
||||
|
||||
public EngineCore()
|
||||
public EngineCore(IContentProvider contentProvider)
|
||||
{
|
||||
_jobScheduler = new JobScheduler(Environment.ProcessorCount - 2); // We -2 here, one for main thread, one for render thread
|
||||
_contentProvider = contentProvider;
|
||||
|
||||
var renderingConfig = new RenderSystemDesc
|
||||
var desc = new JobSchedulerDesc
|
||||
{
|
||||
ThreadCount = Environment.ProcessorCount - 2, // We -2 here, one for main thread, one for render thread
|
||||
ThreadPriority = ThreadPriority.Normal,
|
||||
};
|
||||
|
||||
_jobScheduler = new JobScheduler(in desc);
|
||||
_streamingProcessor = new ResourceStreamingProcessor();
|
||||
|
||||
var renderingDesc = new RenderSystemDesc
|
||||
{
|
||||
FrameBufferCount = 2,
|
||||
GraphicsAPI = GraphicsAPI.Direct3D12,
|
||||
InitialRenderPipelineSettings = new GhostRenderPipelineSettings(),
|
||||
ResourceStreamingProcessor = _streamingProcessor,
|
||||
ShaderCacheDirectory = "ShaderCache",
|
||||
};
|
||||
|
||||
_renderSystem = new RenderSystem(renderingConfig);
|
||||
_renderSystem = new RenderSystem(renderingDesc);
|
||||
_assetManager = new AssetManager(_renderSystem.GraphicsEngine.ResourceDatabase, _contentProvider, _streamingProcessor, _jobScheduler);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_assetManager.Dispose();
|
||||
_renderSystem.Dispose();
|
||||
_jobScheduler.Dispose();
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@ internal unsafe class GPUScene : IDisposable
|
||||
Dispose();
|
||||
}
|
||||
|
||||
// NOTE: This is not thread safe.
|
||||
public void ResizeIfNeeded(ICommandBuffer cmd)
|
||||
{
|
||||
if (_requiredResize == 0)
|
||||
@@ -107,6 +106,8 @@ internal unsafe class GPUScene : IDisposable
|
||||
|
||||
// Return the last index. We will swap the last instance data with the removed index on gpu to keep the buffer compact.
|
||||
var last = Interlocked.Decrement(ref _instanceCount);
|
||||
Logger.DebugAssert(last >= 0);
|
||||
|
||||
return last;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Core.Utilities;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Services;
|
||||
@@ -13,28 +14,47 @@ namespace Ghost.Engine.RenderPipeline;
|
||||
public partial struct UpdateGPUSceneShaderProperty
|
||||
{
|
||||
public uint gpuSceneBuffer;
|
||||
public uint addBuffer;
|
||||
public uint addCount;
|
||||
public uint updateBuffer;
|
||||
public uint updateCount;
|
||||
public uint removeBuffer;
|
||||
public uint removeCount;
|
||||
}
|
||||
|
||||
internal partial class GhostRenderPipeline
|
||||
{
|
||||
private static unsafe Handle<GPUBuffer> CreateAddInstanceBuffer(GhostRenderPayload ghostPayload, ResourceManager resourceManager, IResourceDatabase resourceDatabase, out int count)
|
||||
private struct UpdateInstanceData
|
||||
{
|
||||
public float4x4 localToWorld;
|
||||
public uint instanceID;
|
||||
public uint meshBuffer;
|
||||
public uint materialPalette;
|
||||
public uint renderingLayerMask;
|
||||
public uint shadowCastingMode;
|
||||
}
|
||||
|
||||
private struct RemoveInstanceData
|
||||
{
|
||||
public uint instanceID;
|
||||
public uint swapWithInstanceID;
|
||||
}
|
||||
|
||||
private static unsafe Handle<GPUBuffer> CreateUpdateInstanceBuffer(GhostRenderPayload ghostPayload, ResourceManager resourceManager, IResourceDatabase resourceDatabase, out int count)
|
||||
{
|
||||
// TODO: This should also include update requests like transform update, material update, etc.
|
||||
var totalUpdateCount = ghostPayload.AddRequest.Count; // + ghostPayload.UpdateRequest.Count;
|
||||
|
||||
if (!ghostPayload.AddRequest.IsEmpty)
|
||||
{
|
||||
var addDesc = new BufferDesc
|
||||
{
|
||||
Size = (nuint)ghostPayload.AddRequest.Count * MemoryUtility.SizeOf<AddInstanceData>(),
|
||||
Stride = (uint)MemoryUtility.SizeOf<AddInstanceData>(),
|
||||
Size = (nuint)ghostPayload.AddRequest.Count * MemoryUtility.SizeOf<UpdateInstanceData>(),
|
||||
Stride = (uint)MemoryUtility.SizeOf<UpdateInstanceData>(),
|
||||
Usage = BufferUsage.Structured | BufferUsage.ShaderResource,
|
||||
HeapType = HeapType.Upload
|
||||
};
|
||||
|
||||
var addBuffer = resourceManager.CreateTransientBuffer(in addDesc, "Add Instance Buffer");
|
||||
var pAddData = (AddInstanceData*)resourceDatabase.MapResource(addBuffer.AsResource(), 0, null);
|
||||
var pAddData = (UpdateInstanceData*)resourceDatabase.MapResource(addBuffer.AsResource(), 0, null);
|
||||
|
||||
var i = 0;
|
||||
while (ghostPayload.AddRequest.TryDequeue(out var addRequest))
|
||||
@@ -46,7 +66,7 @@ internal partial class GhostRenderPipeline
|
||||
continue;
|
||||
}
|
||||
|
||||
pAddData[i] = new AddInstanceData
|
||||
pAddData[i] = new UpdateInstanceData
|
||||
{
|
||||
localToWorld = addRequest.localToWorld,
|
||||
instanceID = addRequest.instanceId,
|
||||
@@ -106,14 +126,16 @@ internal partial class GhostRenderPipeline
|
||||
return default;
|
||||
}
|
||||
|
||||
public void UpdateGPUScene(RenderContext ctx, GhostRenderPayload payload)
|
||||
private void UpdateGPUScene(RenderContext ctx, GhostRenderPayload payload)
|
||||
{
|
||||
var addBuffer = CreateAddInstanceBuffer(payload, ctx.ResourceManager, ctx.ResourceDatabase, out var addCount);
|
||||
_gpuScene.ResizeIfNeeded(ctx.CommandBuffer);
|
||||
|
||||
var updateBuffer = CreateUpdateInstanceBuffer(payload, ctx.ResourceManager, ctx.ResourceDatabase, out var updateCount);
|
||||
var removeBuffer = CreateRemoveInstanceBuffer(payload, ctx.ResourceManager, ctx.ResourceDatabase, out var removeCount);
|
||||
|
||||
if (addCount <= 0 && removeCount <= 0)
|
||||
if (updateCount <= 0 && removeCount <= 0)
|
||||
{
|
||||
Logger.DebugAssert(addBuffer.IsInvalid && removeBuffer.IsInvalid, "Buffers should be invalid when there are no updates.");
|
||||
Logger.DebugAssert(updateBuffer.IsInvalid && removeBuffer.IsInvalid, "Buffers should be invalid when there are no updates.");
|
||||
return; // No updates needed
|
||||
}
|
||||
|
||||
@@ -126,8 +148,8 @@ internal partial class GhostRenderPipeline
|
||||
var property = new UpdateGPUSceneShaderProperty
|
||||
{
|
||||
gpuSceneBuffer = ctx.ResourceDatabase.GetBindlessIndex(_gpuScene.SceneBuffer.AsResource(), BindlessAccess.UnorderedAccess),
|
||||
addBuffer = ctx.ResourceDatabase.GetBindlessIndex(addBuffer.AsResource()),
|
||||
addCount = (uint)addCount,
|
||||
updateBuffer = ctx.ResourceDatabase.GetBindlessIndex(updateBuffer.AsResource()),
|
||||
updateCount = (uint)updateCount,
|
||||
removeBuffer = ctx.ResourceDatabase.GetBindlessIndex(removeBuffer.AsResource()),
|
||||
removeCount = (uint)removeCount
|
||||
};
|
||||
|
||||
@@ -2,28 +2,11 @@ using Ghost.Core;
|
||||
using Ghost.Graphics;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RenderGraphModule;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
|
||||
namespace Ghost.Engine.RenderPipeline;
|
||||
|
||||
internal partial class GhostRenderPipeline : IRenderPipeline
|
||||
{
|
||||
private struct AddInstanceData
|
||||
{
|
||||
public float4x4 localToWorld;
|
||||
public uint instanceID;
|
||||
public uint meshBuffer;
|
||||
public uint materialPalette;
|
||||
public uint renderingLayerMask;
|
||||
public uint shadowCastingMode;
|
||||
}
|
||||
|
||||
private struct RemoveInstanceData
|
||||
{
|
||||
public uint instanceID;
|
||||
public uint swapWithInstanceID;
|
||||
}
|
||||
|
||||
private readonly RenderSystem _renderSystem;
|
||||
|
||||
private readonly RenderGraph _renderGraph;
|
||||
@@ -39,7 +22,7 @@ internal partial class GhostRenderPipeline : IRenderPipeline
|
||||
_gpuScene = new GPUScene(renderSystem.GraphicsEngine.ResourceAllocator, renderSystem.GraphicsEngine.ResourceDatabase, 102_400u); // 102.4k objects should be enough for now
|
||||
}
|
||||
|
||||
public void Render(RenderContext ctx, int frameIndex, IRenderPayload payload)
|
||||
public void Render(RenderContext ctx, int frameIndex, IRenderPayload payload, ReadOnlySpan<byte> resourceUpdateCommands)
|
||||
{
|
||||
var ghostPayload = (GhostRenderPayload)payload;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Engine.Components;
|
||||
using Ghost.Graphics;
|
||||
using Ghost.Graphics.Core;
|
||||
@@ -26,16 +27,17 @@ internal sealed class GhostRenderPayload : IRenderPayload
|
||||
|
||||
private UnsafeList<RenderRequest> _renderRequests;
|
||||
|
||||
// TODO: Consider using a more efficient data structure for these queues, such as a lock-free ring buffer or a custom concurrent queue implementation.
|
||||
private readonly ConcurrentQueue<AddInstanceRequest> _addRequest;
|
||||
private readonly ConcurrentQueue<RemoveInstanceRequest> _removeRequest;
|
||||
|
||||
private uint _instanceCountBefore;
|
||||
private uint _instanceCount;
|
||||
|
||||
public ReadOnlySpan<RenderRequest> RenderRequests => _renderRequests;
|
||||
|
||||
public ConcurrentQueue<AddInstanceRequest> AddRequest => _addRequest;
|
||||
public ConcurrentQueue<RemoveInstanceRequest> RemoveRequest => _removeRequest;
|
||||
public uint InstanceCountBefore => _instanceCountBefore;
|
||||
public uint InstanceCount => _instanceCount;
|
||||
|
||||
public GhostRenderPayload(GhostRenderPipeline renderPipeline)
|
||||
@@ -70,10 +72,16 @@ internal sealed class GhostRenderPayload : IRenderPayload
|
||||
}
|
||||
}
|
||||
|
||||
public void BeginRecord()
|
||||
{
|
||||
_instanceCountBefore = _renderPipeline.GPUScene.InstanceCount;
|
||||
}
|
||||
|
||||
public void EndRecord()
|
||||
{
|
||||
// We capture the count here to prevent that main thread continues to add more requests for next frame while the render thread is still processing current frame's requests.
|
||||
_instanceCount = _renderPipeline.GPUScene.InstanceCount;
|
||||
Logger.DebugAssert(_instanceCount == _instanceCountBefore + (uint)_addRequest.Count - (uint)_removeRequest.Count);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
|
||||
81
src/Runtime/Ghost.Engine/ResourceStreamingProcessor.cs
Normal file
81
src/Runtime/Ghost.Engine/ResourceStreamingProcessor.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics;
|
||||
using SharpCompress.Common;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Ghost.Engine;
|
||||
|
||||
internal class ResourceStreamingProcessor : IResourceStreamingProcessor
|
||||
{
|
||||
private const int _MAX_UPLOADS_PER_FRAME = 8;
|
||||
|
||||
private readonly ConcurrentQueue<AssetEntry> _pendingUpload;
|
||||
private readonly ConcurrentQueue<AssetEntry> _pendingFinalize;
|
||||
|
||||
private ulong _pendingCopyFenceValue;
|
||||
|
||||
public ResourceStreamingProcessor()
|
||||
{
|
||||
_pendingUpload = new ConcurrentQueue<AssetEntry>();
|
||||
_pendingFinalize = new ConcurrentQueue<AssetEntry>();
|
||||
_pendingCopyFenceValue = 0;
|
||||
}
|
||||
|
||||
public void EnqueueForUpload(AssetEntry entry)
|
||||
{
|
||||
_pendingUpload.Enqueue(entry);
|
||||
}
|
||||
|
||||
public void ProcessPendingUploads(ResourceStreamingContext context)
|
||||
{
|
||||
// 1. If there's a pending copy batch from last frame, check its fence
|
||||
if (_pendingCopyFenceValue > 0 && context.CopyPipeline.CurrentFenceValue() >= _pendingCopyFenceValue)
|
||||
{
|
||||
while (_pendingFinalize.TryDequeue(out var item))
|
||||
{
|
||||
item.OnUploadComplete(context);
|
||||
}
|
||||
|
||||
_pendingCopyFenceValue = 0;
|
||||
}
|
||||
|
||||
if (_pendingCopyFenceValue > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Collect entries that are in state == Loaded (I/O done, not yet uploaded)
|
||||
// Cap per frame to avoid stalling (e.g., max 8 textures per frame)
|
||||
if (_pendingUpload.IsEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
context.CopyPipeline.Begin();
|
||||
|
||||
var uploadCount = 0;
|
||||
while (uploadCount < _MAX_UPLOADS_PER_FRAME && _pendingUpload.TryDequeue(out var entry))
|
||||
{
|
||||
if (entry.State != AssetState.Loaded)
|
||||
{
|
||||
Logger.Warning($"Asset {entry.AssetId} is in state {entry.State}, expected Loaded. Skipping upload.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Record copy commands into cmdCopy
|
||||
entry.OnRecordUploadCommands(context);
|
||||
entry.State = AssetState.Uploading;
|
||||
|
||||
_pendingFinalize.Enqueue(entry);
|
||||
uploadCount++;
|
||||
}
|
||||
|
||||
var result = context.CopyPipeline.End();
|
||||
|
||||
// 3. Submit the batch
|
||||
if (uploadCount > 0 && result.IsSuccess)
|
||||
{
|
||||
_pendingCopyFenceValue = context.CopyPipeline.SignaledFenceValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ internal sealed unsafe class ChunkDebugView
|
||||
|
||||
private static object[] GetItems(ref readonly Chunk chunk)
|
||||
{
|
||||
#if !(DEBUG || GHOST_EDITOR)
|
||||
#if !DEBUG
|
||||
return [];
|
||||
#else
|
||||
var pData = chunk.GetUnsafePtr();
|
||||
@@ -185,7 +185,7 @@ internal unsafe struct Archetype : IDisposable
|
||||
private int _maxComponentID;
|
||||
private int _entityIdsOffset;
|
||||
|
||||
// -1 means no cleanup component, 0 means haven't computed yet (since 0 is the empty archetype), positive value means the archetype id of the cleanup edge.
|
||||
// 0 means no cleanup component (since 0 is the empty archetype), -1 means haven't computed yet, positive value means the archetype id of the cleanup edge.
|
||||
internal int _cleanupEdge;
|
||||
|
||||
public readonly Identifier<Archetype> ID => _id;
|
||||
@@ -248,7 +248,7 @@ internal unsafe struct Archetype : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
if (cleanupCount == 0)
|
||||
if (cleanupCount > 0)
|
||||
{
|
||||
_cleanupEdge = -1;
|
||||
}
|
||||
|
||||
@@ -291,14 +291,15 @@ public unsafe partial class EntityManager : IDisposable
|
||||
|
||||
ref var archetype = ref _world.ComponentManager.GetArchetypeReference(location.archetypeID);
|
||||
|
||||
if (archetype._cleanupEdge < 0)
|
||||
// 0 means no cleanup component (since 0 is the empty archetype), -1 means haven't computed yet, positive value means the archetype id of the cleanup edge.
|
||||
if (archetype._cleanupEdge == 0)
|
||||
{
|
||||
return DestroyEntity_Internal(entity, location);
|
||||
}
|
||||
else
|
||||
{
|
||||
Identifier<Archetype> newArcID = default;
|
||||
if (archetype._cleanupEdge == 0)
|
||||
if (archetype._cleanupEdge < 0)
|
||||
{
|
||||
ref var signature = ref archetype._signature;
|
||||
|
||||
|
||||
@@ -7,11 +7,6 @@
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<IsAotCompatible>True</IsAotCompatible>
|
||||
<IsTrimmable>True</IsTrimmable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<IsAotCompatible>True</IsAotCompatible>
|
||||
<IsTrimmable>True</IsTrimmable>
|
||||
|
||||
@@ -65,7 +65,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
||||
_device = new D3D12RenderDevice();
|
||||
_descriptorAllocator = new D3D12DescriptorAllocator(_device);
|
||||
|
||||
_resourceDatabase = new D3D12ResourceDatabase(_descriptorAllocator);
|
||||
_resourceDatabase = new D3D12ResourceDatabase(_device, _descriptorAllocator);
|
||||
_pipelineLibrary = new D3D12PipelineLibrary(_device);
|
||||
_resourceAllocator = new D3D12ResourceAllocator(_device, _descriptorAllocator, _resourceDatabase, _pipelineLibrary);
|
||||
|
||||
|
||||
@@ -37,405 +37,6 @@ internal sealed unsafe partial class D3D12ResourceAllocator
|
||||
throw new InvalidOperationException($"ERROR: Texture size too large for DirectX 12 (width {width}, height {height})");
|
||||
}
|
||||
}
|
||||
|
||||
private static D3D12_SHADER_RESOURCE_VIEW_DESC CreateTextureSrvDesc(ID3D12Resource* pResource, uint mipLevels, uint arraySize, bool isCubeMap, TextureFormat originalFormat)
|
||||
{
|
||||
var resourceDesc = pResource->GetDesc();
|
||||
var srvDesc = new D3D12_SHADER_RESOURCE_VIEW_DESC
|
||||
{
|
||||
Format = resourceDesc.Format,
|
||||
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
|
||||
};
|
||||
|
||||
if (originalFormat == TextureFormat.D32_Float)
|
||||
{
|
||||
srvDesc.Format = DXGI_FORMAT_R32_FLOAT;
|
||||
}
|
||||
else if (originalFormat == TextureFormat.D24_UNorm_S8_UInt)
|
||||
{
|
||||
srvDesc.Format = DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
|
||||
}
|
||||
|
||||
switch (resourceDesc.Dimension)
|
||||
{
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
|
||||
if (resourceDesc.DepthOrArraySize > 1)
|
||||
{
|
||||
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1DARRAY;
|
||||
srvDesc.Texture1DArray = new D3D12_TEX1D_ARRAY_SRV
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
ArraySize = arraySize,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1D;
|
||||
srvDesc.Texture1D = new D3D12_TEX1D_SRV
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
|
||||
if (resourceDesc.DepthOrArraySize > 1)
|
||||
{
|
||||
if (isCubeMap)
|
||||
{
|
||||
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBEARRAY;
|
||||
srvDesc.TextureCubeArray = new D3D12_TEXCUBE_ARRAY_SRV
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
NumCubes = arraySize / 6,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
srvDesc.ViewDimension = resourceDesc.SampleDesc.Count > 1 ? D3D12_SRV_DIMENSION_TEXTURE2DMSARRAY : D3D12_SRV_DIMENSION_TEXTURE2DARRAY;
|
||||
srvDesc.Texture2DArray = new D3D12_TEX2D_ARRAY_SRV
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
ArraySize = arraySize,
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isCubeMap)
|
||||
{
|
||||
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE;
|
||||
srvDesc.TextureCube = new D3D12_TEXCUBE_SRV
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
srvDesc.ViewDimension = resourceDesc.SampleDesc.Count > 1 ? D3D12_SRV_DIMENSION_TEXTURE2DMS : D3D12_SRV_DIMENSION_TEXTURE2D;
|
||||
srvDesc.Texture2D = new D3D12_TEX2D_SRV
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
};
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE3D:
|
||||
srvDesc.Texture3D = new D3D12_TEX3D_SRV
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported texture dimension for SRV: {resourceDesc.Dimension}");
|
||||
}
|
||||
|
||||
return srvDesc;
|
||||
}
|
||||
|
||||
private static D3D12_SHADER_RESOURCE_VIEW_DESC CreateBufferSrvDesc(ID3D12Resource* pResource, uint stride, bool isRaw)
|
||||
{
|
||||
var resourceDesc = pResource->GetDesc();
|
||||
var srvDesc = new D3D12_SHADER_RESOURCE_VIEW_DESC
|
||||
{
|
||||
ViewDimension = D3D12_SRV_DIMENSION_BUFFER,
|
||||
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
|
||||
};
|
||||
|
||||
if (isRaw)
|
||||
{
|
||||
srvDesc.Format = DXGI_FORMAT_R32_TYPELESS;
|
||||
srvDesc.Buffer.FirstElement = 0;
|
||||
srvDesc.Buffer.NumElements = (uint)(resourceDesc.Width / 4u);
|
||||
srvDesc.Buffer.StructureByteStride = 0;
|
||||
srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_RAW;
|
||||
}
|
||||
else // Assumes Structured
|
||||
{
|
||||
srvDesc.Format = resourceDesc.Format;
|
||||
srvDesc.Buffer.FirstElement = 0;
|
||||
srvDesc.Buffer.NumElements = (uint)(resourceDesc.Width / stride);
|
||||
srvDesc.Buffer.StructureByteStride = stride;
|
||||
srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE;
|
||||
}
|
||||
|
||||
return srvDesc;
|
||||
}
|
||||
|
||||
private static D3D12_RENDER_TARGET_VIEW_DESC CreateRtvDesc(ID3D12Resource* pResource, uint mipSlice = 0, uint firstArraySlice = 0, uint planeSlice = 0)
|
||||
{
|
||||
var resourceDesc = pResource->GetDesc();
|
||||
var rtvDesc = new D3D12_RENDER_TARGET_VIEW_DESC();
|
||||
|
||||
switch (resourceDesc.Dimension)
|
||||
{
|
||||
case D3D12_RESOURCE_DIMENSION_BUFFER:
|
||||
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_BUFFER;
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
|
||||
rtvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_RTV_DIMENSION_TEXTURE1DARRAY : D3D12_RTV_DIMENSION_TEXTURE1D;
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
|
||||
if (resourceDesc.SampleDesc.Count > 1)
|
||||
{
|
||||
rtvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY : D3D12_RTV_DIMENSION_TEXTURE2DMS;
|
||||
}
|
||||
else
|
||||
{
|
||||
rtvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_RTV_DIMENSION_TEXTURE2DARRAY : D3D12_RTV_DIMENSION_TEXTURE2D;
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE3D:
|
||||
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE3D;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported texture dimension for SRV: {resourceDesc.Dimension}");
|
||||
}
|
||||
|
||||
rtvDesc.Format = resourceDesc.Format;
|
||||
|
||||
var isArray =
|
||||
rtvDesc.ViewDimension == D3D12_RTV_DIMENSION_TEXTURE2DARRAY ||
|
||||
rtvDesc.ViewDimension == D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY;
|
||||
|
||||
var arraySize = 1u;
|
||||
if (isArray)
|
||||
{
|
||||
arraySize = resourceDesc.ArraySize() - firstArraySlice;
|
||||
}
|
||||
|
||||
switch (rtvDesc.ViewDimension)
|
||||
{
|
||||
case D3D12_RTV_DIMENSION_BUFFER:
|
||||
rtvDesc.Buffer.FirstElement = firstArraySlice;
|
||||
rtvDesc.Buffer.NumElements = arraySize;
|
||||
break;
|
||||
|
||||
case D3D12_RTV_DIMENSION_TEXTURE1D:
|
||||
rtvDesc.Texture1D.MipSlice = mipSlice;
|
||||
break;
|
||||
|
||||
case D3D12_RTV_DIMENSION_TEXTURE1DARRAY:
|
||||
rtvDesc.Texture1DArray.MipSlice = mipSlice;
|
||||
rtvDesc.Texture1DArray.FirstArraySlice = firstArraySlice;
|
||||
rtvDesc.Texture1DArray.ArraySize = arraySize;
|
||||
break;
|
||||
|
||||
case D3D12_RTV_DIMENSION_TEXTURE2D:
|
||||
rtvDesc.Texture2D.MipSlice = mipSlice;
|
||||
rtvDesc.Texture2D.PlaneSlice = planeSlice;
|
||||
break;
|
||||
|
||||
case D3D12_RTV_DIMENSION_TEXTURE2DARRAY:
|
||||
rtvDesc.Texture2DArray.MipSlice = mipSlice;
|
||||
rtvDesc.Texture2DArray.FirstArraySlice = firstArraySlice;
|
||||
rtvDesc.Texture2DArray.ArraySize = arraySize;
|
||||
rtvDesc.Texture2DArray.PlaneSlice = planeSlice;
|
||||
break;
|
||||
|
||||
case D3D12_RTV_DIMENSION_TEXTURE2DMS:
|
||||
break;
|
||||
|
||||
case D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY:
|
||||
rtvDesc.Texture2DMSArray.FirstArraySlice = firstArraySlice;
|
||||
rtvDesc.Texture2DMSArray.ArraySize = arraySize;
|
||||
break;
|
||||
|
||||
case D3D12_RTV_DIMENSION_TEXTURE3D:
|
||||
rtvDesc.Texture3D.MipSlice = mipSlice;
|
||||
rtvDesc.Texture3D.FirstWSlice = firstArraySlice;
|
||||
rtvDesc.Texture3D.WSize = arraySize;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported RTV dimension: {rtvDesc.ViewDimension}");
|
||||
}
|
||||
|
||||
return rtvDesc;
|
||||
}
|
||||
|
||||
private static D3D12_DEPTH_STENCIL_VIEW_DESC CreateDsvDesc(ID3D12Resource* pResource, uint mipSlice = 0, uint firstArraySlice = 0, D3D12_DSV_FLAGS flags = D3D12_DSV_FLAG_NONE, TextureFormat originalFormat = TextureFormat.Unknown)
|
||||
{
|
||||
var resourceDesc = pResource->GetDesc();
|
||||
var dsvDesc = new D3D12_DEPTH_STENCIL_VIEW_DESC
|
||||
{
|
||||
Flags = flags,
|
||||
};
|
||||
|
||||
switch (resourceDesc.Dimension)
|
||||
{
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
|
||||
dsvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_DSV_DIMENSION_TEXTURE1DARRAY : D3D12_DSV_DIMENSION_TEXTURE1D;
|
||||
break;
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
|
||||
if (resourceDesc.SampleDesc.Count > 1)
|
||||
{
|
||||
dsvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY : D3D12_DSV_DIMENSION_TEXTURE2DMS;
|
||||
}
|
||||
else
|
||||
{
|
||||
dsvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_DSV_DIMENSION_TEXTURE2DARRAY : D3D12_DSV_DIMENSION_TEXTURE2D;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
dsvDesc.Format = originalFormat == TextureFormat.Unknown ? resourceDesc.Format : originalFormat.ToDXGIFormat();
|
||||
|
||||
var isArray =
|
||||
dsvDesc.ViewDimension == D3D12_DSV_DIMENSION_TEXTURE2DARRAY ||
|
||||
dsvDesc.ViewDimension == D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY;
|
||||
|
||||
var arraySize = 1u;
|
||||
if (isArray)
|
||||
{
|
||||
arraySize = resourceDesc.ArraySize() - firstArraySlice;
|
||||
}
|
||||
|
||||
switch (dsvDesc.ViewDimension)
|
||||
{
|
||||
case D3D12_DSV_DIMENSION_TEXTURE1D:
|
||||
dsvDesc.Texture1D.MipSlice = mipSlice;
|
||||
break;
|
||||
case D3D12_DSV_DIMENSION_TEXTURE1DARRAY:
|
||||
dsvDesc.Texture1DArray.MipSlice = mipSlice;
|
||||
dsvDesc.Texture1DArray.FirstArraySlice = firstArraySlice;
|
||||
dsvDesc.Texture1DArray.ArraySize = arraySize;
|
||||
break;
|
||||
case D3D12_DSV_DIMENSION_TEXTURE2D:
|
||||
dsvDesc.Texture2D.MipSlice = mipSlice;
|
||||
break;
|
||||
case D3D12_DSV_DIMENSION_TEXTURE2DARRAY:
|
||||
dsvDesc.Texture2DArray.MipSlice = mipSlice;
|
||||
dsvDesc.Texture2DArray.FirstArraySlice = firstArraySlice;
|
||||
dsvDesc.Texture2DArray.ArraySize = arraySize;
|
||||
break;
|
||||
case D3D12_DSV_DIMENSION_TEXTURE2DMS:
|
||||
break;
|
||||
case D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY:
|
||||
dsvDesc.Texture2DMSArray.FirstArraySlice = firstArraySlice;
|
||||
dsvDesc.Texture2DMSArray.ArraySize = arraySize;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return dsvDesc;
|
||||
}
|
||||
|
||||
private static D3D12_UNORDERED_ACCESS_VIEW_DESC CreateTextureUavDesc(ID3D12Resource* pResource, uint mipSlice = 0, uint firstArraySlice = 0, uint planeSlice = 0)
|
||||
{
|
||||
var resourceDesc = pResource->GetDesc();
|
||||
var uavDesc = new D3D12_UNORDERED_ACCESS_VIEW_DESC
|
||||
{
|
||||
Format = resourceDesc.Format
|
||||
};
|
||||
|
||||
switch (resourceDesc.Dimension)
|
||||
{
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
|
||||
if (resourceDesc.DepthOrArraySize > 1)
|
||||
{
|
||||
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE1DARRAY;
|
||||
uavDesc.Texture1DArray = new D3D12_TEX1D_ARRAY_UAV
|
||||
{
|
||||
MipSlice = mipSlice,
|
||||
FirstArraySlice = firstArraySlice,
|
||||
ArraySize = resourceDesc.ArraySize() - firstArraySlice
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE1D;
|
||||
uavDesc.Texture1D = new D3D12_TEX1D_UAV
|
||||
{
|
||||
MipSlice = mipSlice
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
|
||||
if (resourceDesc.DepthOrArraySize > 1)
|
||||
{
|
||||
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY;
|
||||
uavDesc.Texture2DArray = new D3D12_TEX2D_ARRAY_UAV
|
||||
{
|
||||
MipSlice = mipSlice,
|
||||
FirstArraySlice = firstArraySlice,
|
||||
ArraySize = resourceDesc.ArraySize() - firstArraySlice,
|
||||
PlaneSlice = planeSlice
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
|
||||
uavDesc.Texture2D = new D3D12_TEX2D_UAV
|
||||
{
|
||||
MipSlice = mipSlice,
|
||||
PlaneSlice = planeSlice
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE3D:
|
||||
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE3D;
|
||||
uavDesc.Texture3D = new D3D12_TEX3D_UAV
|
||||
{
|
||||
MipSlice = mipSlice,
|
||||
FirstWSlice = firstArraySlice,
|
||||
WSize = resourceDesc.Depth() - firstArraySlice
|
||||
};
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_BUFFER:
|
||||
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER;
|
||||
uavDesc.Buffer = new D3D12_BUFFER_UAV
|
||||
{
|
||||
FirstElement = 0,
|
||||
NumElements = (uint)(resourceDesc.Width / 4), // Assuming R32_TYPELESS RAW
|
||||
StructureByteStride = 0,
|
||||
Flags = D3D12_BUFFER_UAV_FLAG_RAW
|
||||
};
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported texture dimension for UAV: {resourceDesc.Dimension}");
|
||||
}
|
||||
|
||||
return uavDesc;
|
||||
}
|
||||
|
||||
private static D3D12_UNORDERED_ACCESS_VIEW_DESC CreateBufferUavDesc(ID3D12Resource* pResource, uint stride, bool isRaw)
|
||||
{
|
||||
var resourceDesc = pResource->GetDesc();
|
||||
var uavDesc = new D3D12_UNORDERED_ACCESS_VIEW_DESC
|
||||
{
|
||||
ViewDimension = D3D12_UAV_DIMENSION_BUFFER,
|
||||
};
|
||||
|
||||
if (isRaw)
|
||||
{
|
||||
uavDesc.Format = DXGI_FORMAT_R32_TYPELESS;
|
||||
uavDesc.Buffer.FirstElement = 0;
|
||||
uavDesc.Buffer.NumElements = (uint)(resourceDesc.Width / 4u);
|
||||
uavDesc.Buffer.StructureByteStride = 0;
|
||||
uavDesc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_RAW;
|
||||
}
|
||||
else // Assumes Structured
|
||||
{
|
||||
uavDesc.Format = resourceDesc.Format;
|
||||
uavDesc.Buffer.FirstElement = 0;
|
||||
uavDesc.Buffer.NumElements = (uint)(resourceDesc.Width / stride);
|
||||
uavDesc.Buffer.StructureByteStride = stride;
|
||||
uavDesc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_NONE;
|
||||
}
|
||||
|
||||
return uavDesc;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
||||
@@ -606,47 +207,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
||||
return Handle<GPUTexture>.Invalid;
|
||||
}
|
||||
|
||||
var resourceDescriptor = ResourceViewGroup.Invalid;
|
||||
if (desc.Usage.HasFlag(TextureUsage.ShaderResource))
|
||||
{
|
||||
resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav();
|
||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv);
|
||||
|
||||
var isCubeMap = desc.Dimension == TextureDimension.TextureCube || desc.Dimension == TextureDimension.TextureCubeArray;
|
||||
var srvDesc = CreateTextureSrvDesc(pResource, resourceDesc.MipLevels, resourceDesc.DepthOrArraySize, isCubeMap, desc.Format);
|
||||
|
||||
_device.NativeObject.Get()->CreateShaderResourceView(pResource, &srvDesc, cpuHandle);
|
||||
_descriptorAllocator.CopyToShaderVisible(resourceDescriptor.srv);
|
||||
}
|
||||
|
||||
if (desc.Usage.HasFlag(TextureUsage.RenderTarget))
|
||||
{
|
||||
resourceDescriptor.rtv = _descriptorAllocator.AllocateRTV();
|
||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.rtv);
|
||||
var rtvDesc = CreateRtvDesc(pResource);
|
||||
|
||||
_device.NativeObject.Get()->CreateRenderTargetView(pResource, &rtvDesc, cpuHandle);
|
||||
}
|
||||
|
||||
if (desc.Usage.HasFlag(TextureUsage.DepthStencil))
|
||||
{
|
||||
resourceDescriptor.dsv = _descriptorAllocator.AllocateDSV();
|
||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.dsv);
|
||||
var dsvDesc = CreateDsvDesc(pResource, 0, 0, D3D12_DSV_FLAG_NONE, desc.Format);
|
||||
|
||||
_device.NativeObject.Get()->CreateDepthStencilView(pResource, &dsvDesc, cpuHandle);
|
||||
}
|
||||
|
||||
if (desc.Usage.HasFlag(TextureUsage.UnorderedAccess))
|
||||
{
|
||||
resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav();
|
||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.uav);
|
||||
var uavDesc = CreateTextureUavDesc(pResource);
|
||||
|
||||
_device.NativeObject.Get()->CreateUnorderedAccessView(pResource, null, &uavDesc, cpuHandle);
|
||||
_descriptorAllocator.CopyToShaderVisible(resourceDescriptor.uav);
|
||||
}
|
||||
|
||||
var resourceDescriptor = D3D12Utility.CreateResourceDescriptor(_device, _descriptorAllocator, ResourceDesc.Texture(desc), pResource);
|
||||
var barrierData = new ResourceBarrierData
|
||||
{
|
||||
layout = BarrierLayout.Common,
|
||||
@@ -707,42 +268,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
|
||||
return Handle<GPUBuffer>.Invalid;
|
||||
}
|
||||
|
||||
var resourceDescriptor = ResourceViewGroup.Invalid;
|
||||
|
||||
if (desc.Usage.HasFlag(BufferUsage.Constant))
|
||||
{
|
||||
resourceDescriptor.cbv = _descriptorAllocator.AllocateCbvSrvUav();
|
||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.cbv);
|
||||
var cbvDesc = new D3D12_CONSTANT_BUFFER_VIEW_DESC
|
||||
{
|
||||
BufferLocation = pResource->GetGPUVirtualAddress(),
|
||||
SizeInBytes = (uint)resourceDesc.Width,
|
||||
};
|
||||
|
||||
_device.NativeObject.Get()->CreateConstantBufferView(&cbvDesc, cpuHandle);
|
||||
_descriptorAllocator.CopyToShaderVisible(resourceDescriptor.cbv);
|
||||
}
|
||||
|
||||
if (desc.Usage.HasFlag(BufferUsage.ShaderResource))
|
||||
{
|
||||
resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav();
|
||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv);
|
||||
var srvDesc = CreateBufferSrvDesc(pResource, desc.Stride, isRaw);
|
||||
|
||||
_device.NativeObject.Get()->CreateShaderResourceView(pResource, &srvDesc, cpuHandle);
|
||||
_descriptorAllocator.CopyToShaderVisible(resourceDescriptor.srv);
|
||||
}
|
||||
|
||||
if (desc.Usage.HasFlag(BufferUsage.UnorderedAccess))
|
||||
{
|
||||
resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav();
|
||||
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.uav);
|
||||
var uavDesc = CreateBufferUavDesc(pResource, desc.Stride, isRaw);
|
||||
|
||||
_device.NativeObject.Get()->CreateUnorderedAccessView(pResource, null, &uavDesc, cpuHandle);
|
||||
_descriptorAllocator.CopyToShaderVisible(resourceDescriptor.uav);
|
||||
}
|
||||
|
||||
var resourceDescriptor = D3D12Utility.CreateResourceDescriptor(_device, _descriptorAllocator, ResourceDesc.Buffer(desc), pResource);
|
||||
var barrierData = new ResourceBarrierData
|
||||
{
|
||||
layout = BarrierLayout.Undefined,
|
||||
|
||||
@@ -104,6 +104,7 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
||||
}
|
||||
}
|
||||
|
||||
private readonly D3D12RenderDevice _device;
|
||||
private readonly D3D12DescriptorAllocator _descriptorAllocator;
|
||||
|
||||
private UnsafeSlotMap<ResourceRecord> _resources;
|
||||
@@ -121,8 +122,9 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
||||
|
||||
public ResourceBarrierData globalBarrier;
|
||||
|
||||
public D3D12ResourceDatabase(D3D12DescriptorAllocator descriptorAllocator)
|
||||
public D3D12ResourceDatabase(D3D12RenderDevice device, D3D12DescriptorAllocator descriptorAllocator)
|
||||
{
|
||||
_device = device;
|
||||
_descriptorAllocator = descriptorAllocator;
|
||||
|
||||
_resources = new UnsafeSlotMap<ResourceRecord>(64, AllocationHandle.Persistent, AllocationOption.Clear);
|
||||
@@ -367,19 +369,56 @@ internal unsafe class D3D12ResourceDatabase : IResourceDatabase
|
||||
{
|
||||
ref var recordA = ref _resources.GetElementReferenceAt(handleA.ID, handleA.Generation, out var existA);
|
||||
ref var recordB = ref _resources.GetElementReferenceAt(handleB.ID, handleB.Generation, out var existB);
|
||||
|
||||
if (!existA || !existB)
|
||||
{
|
||||
return Error.NotFound;
|
||||
}
|
||||
|
||||
// ViewGroups are pinned to their slots — save before swap
|
||||
var viewA = recordA.viewGroup;
|
||||
var viewB = recordB.viewGroup;
|
||||
|
||||
var temp = recordA;
|
||||
recordA = recordB;
|
||||
recordB = temp;
|
||||
|
||||
// Restore viewGroups to their original slots
|
||||
recordA.viewGroup = viewA;
|
||||
recordB.viewGroup = viewB;
|
||||
|
||||
D3D12Utility.CreateResourceDescriptor(_device, _descriptorAllocator, recordA.desc, recordA.ResourcePtr, viewA);
|
||||
D3D12Utility.CreateResourceDescriptor(_device, _descriptorAllocator, recordB.desc, recordB.ResourcePtr, viewB);
|
||||
|
||||
return Error.None;
|
||||
}
|
||||
|
||||
public Handle<GPUResource> Replace(Handle<GPUResource> dst, Handle<GPUResource> src)
|
||||
{
|
||||
ref var recordDst = ref _resources.GetElementReferenceAt(dst.ID, dst.Generation, out var existDst);
|
||||
ref var recordSrc = ref _resources.GetElementReferenceAt(src.ID, src.Generation, out var existSrc);
|
||||
if (!existDst || !existSrc)
|
||||
{
|
||||
return Handle<GPUResource>.Invalid;
|
||||
}
|
||||
|
||||
var dstView = recordDst.viewGroup;
|
||||
var srcView = recordSrc.viewGroup;
|
||||
|
||||
var temp = recordDst;
|
||||
recordDst = recordSrc;
|
||||
recordSrc = temp;
|
||||
|
||||
// Pin viewGroups back
|
||||
recordDst.viewGroup = dstView;
|
||||
recordSrc.viewGroup = srcView;
|
||||
|
||||
// Update dst's descriptor to point to the new resource
|
||||
D3D12Utility.CreateResourceDescriptor(_device, _descriptorAllocator, recordDst.desc, recordDst.ResourcePtr, dstView);
|
||||
ReleaseResource(src);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
public Handle<GPUResource> CreateShared(Handle<GPUResource> src)
|
||||
{
|
||||
if (src.IsInvalid)
|
||||
|
||||
@@ -486,7 +486,7 @@ internal static unsafe class D3D12Utility
|
||||
public static D3D12_RESOURCE_DESC ToD3D12ResourceDesc(this in TextureDesc desc)
|
||||
{
|
||||
var dxgiFormat = desc.Format.ToDXGIFormat();
|
||||
|
||||
|
||||
if (desc.Usage.HasFlag(TextureUsage.DepthStencil) && desc.Usage.HasFlag(TextureUsage.ShaderResource))
|
||||
{
|
||||
if (dxgiFormat == DXGI_FORMAT_D32_FLOAT)
|
||||
@@ -795,4 +795,496 @@ internal static unsafe class D3D12Utility
|
||||
}
|
||||
|
||||
public static D3D12_DEPTH_STENCILOP_DESC D3D12_DEPTH_STENCILOP_DESC_DEFAULT => D3D12_DEPTH_STENCILOP_DESC_CREATE(D3D12_STENCIL_OP_KEEP, D3D12_STENCIL_OP_KEEP, D3D12_STENCIL_OP_KEEP, D3D12_COMPARISON_FUNC_ALWAYS);
|
||||
|
||||
|
||||
public static D3D12_SHADER_RESOURCE_VIEW_DESC CreateTextureSrvDesc(ID3D12Resource* pResource, uint mipLevels, uint arraySize, bool isCubeMap, TextureFormat originalFormat)
|
||||
{
|
||||
var resourceDesc = pResource->GetDesc();
|
||||
var srvDesc = new D3D12_SHADER_RESOURCE_VIEW_DESC
|
||||
{
|
||||
Format = resourceDesc.Format,
|
||||
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
|
||||
};
|
||||
|
||||
if (originalFormat == TextureFormat.D32_Float)
|
||||
{
|
||||
srvDesc.Format = DXGI_FORMAT_R32_FLOAT;
|
||||
}
|
||||
else if (originalFormat == TextureFormat.D24_UNorm_S8_UInt)
|
||||
{
|
||||
srvDesc.Format = DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
|
||||
}
|
||||
|
||||
switch (resourceDesc.Dimension)
|
||||
{
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
|
||||
if (resourceDesc.DepthOrArraySize > 1)
|
||||
{
|
||||
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1DARRAY;
|
||||
srvDesc.Texture1DArray = new D3D12_TEX1D_ARRAY_SRV
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
ArraySize = arraySize,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1D;
|
||||
srvDesc.Texture1D = new D3D12_TEX1D_SRV
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
|
||||
if (resourceDesc.DepthOrArraySize > 1)
|
||||
{
|
||||
if (isCubeMap)
|
||||
{
|
||||
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBEARRAY;
|
||||
srvDesc.TextureCubeArray = new D3D12_TEXCUBE_ARRAY_SRV
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
NumCubes = arraySize / 6,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
srvDesc.ViewDimension = resourceDesc.SampleDesc.Count > 1 ? D3D12_SRV_DIMENSION_TEXTURE2DMSARRAY : D3D12_SRV_DIMENSION_TEXTURE2DARRAY;
|
||||
srvDesc.Texture2DArray = new D3D12_TEX2D_ARRAY_SRV
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
ArraySize = arraySize,
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isCubeMap)
|
||||
{
|
||||
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE;
|
||||
srvDesc.TextureCube = new D3D12_TEXCUBE_SRV
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
srvDesc.ViewDimension = resourceDesc.SampleDesc.Count > 1 ? D3D12_SRV_DIMENSION_TEXTURE2DMS : D3D12_SRV_DIMENSION_TEXTURE2D;
|
||||
srvDesc.Texture2D = new D3D12_TEX2D_SRV
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
};
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE3D:
|
||||
srvDesc.Texture3D = new D3D12_TEX3D_SRV
|
||||
{
|
||||
MipLevels = mipLevels,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported texture dimension for SRV: {resourceDesc.Dimension}");
|
||||
}
|
||||
|
||||
return srvDesc;
|
||||
}
|
||||
|
||||
public static D3D12_SHADER_RESOURCE_VIEW_DESC CreateBufferSrvDesc(ID3D12Resource* pResource, uint stride, bool isRaw)
|
||||
{
|
||||
var resourceDesc = pResource->GetDesc();
|
||||
var srvDesc = new D3D12_SHADER_RESOURCE_VIEW_DESC
|
||||
{
|
||||
ViewDimension = D3D12_SRV_DIMENSION_BUFFER,
|
||||
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
|
||||
};
|
||||
|
||||
if (isRaw)
|
||||
{
|
||||
srvDesc.Format = DXGI_FORMAT_R32_TYPELESS;
|
||||
srvDesc.Buffer.FirstElement = 0;
|
||||
srvDesc.Buffer.NumElements = (uint)(resourceDesc.Width / 4u);
|
||||
srvDesc.Buffer.StructureByteStride = 0;
|
||||
srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_RAW;
|
||||
}
|
||||
else // Assumes Structured
|
||||
{
|
||||
srvDesc.Format = resourceDesc.Format;
|
||||
srvDesc.Buffer.FirstElement = 0;
|
||||
srvDesc.Buffer.NumElements = (uint)(resourceDesc.Width / stride);
|
||||
srvDesc.Buffer.StructureByteStride = stride;
|
||||
srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE;
|
||||
}
|
||||
|
||||
return srvDesc;
|
||||
}
|
||||
|
||||
public static D3D12_RENDER_TARGET_VIEW_DESC CreateRtvDesc(ID3D12Resource* pResource, uint mipSlice = 0, uint firstArraySlice = 0, uint planeSlice = 0)
|
||||
{
|
||||
var resourceDesc = pResource->GetDesc();
|
||||
var rtvDesc = new D3D12_RENDER_TARGET_VIEW_DESC();
|
||||
|
||||
switch (resourceDesc.Dimension)
|
||||
{
|
||||
case D3D12_RESOURCE_DIMENSION_BUFFER:
|
||||
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_BUFFER;
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
|
||||
rtvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_RTV_DIMENSION_TEXTURE1DARRAY : D3D12_RTV_DIMENSION_TEXTURE1D;
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
|
||||
if (resourceDesc.SampleDesc.Count > 1)
|
||||
{
|
||||
rtvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY : D3D12_RTV_DIMENSION_TEXTURE2DMS;
|
||||
}
|
||||
else
|
||||
{
|
||||
rtvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_RTV_DIMENSION_TEXTURE2DARRAY : D3D12_RTV_DIMENSION_TEXTURE2D;
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE3D:
|
||||
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE3D;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported texture dimension for SRV: {resourceDesc.Dimension}");
|
||||
}
|
||||
|
||||
rtvDesc.Format = resourceDesc.Format;
|
||||
|
||||
var isArray =
|
||||
rtvDesc.ViewDimension == D3D12_RTV_DIMENSION_TEXTURE2DARRAY ||
|
||||
rtvDesc.ViewDimension == D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY;
|
||||
|
||||
var arraySize = 1u;
|
||||
if (isArray)
|
||||
{
|
||||
arraySize = resourceDesc.ArraySize() - firstArraySlice;
|
||||
}
|
||||
|
||||
switch (rtvDesc.ViewDimension)
|
||||
{
|
||||
case D3D12_RTV_DIMENSION_BUFFER:
|
||||
rtvDesc.Buffer.FirstElement = firstArraySlice;
|
||||
rtvDesc.Buffer.NumElements = arraySize;
|
||||
break;
|
||||
|
||||
case D3D12_RTV_DIMENSION_TEXTURE1D:
|
||||
rtvDesc.Texture1D.MipSlice = mipSlice;
|
||||
break;
|
||||
|
||||
case D3D12_RTV_DIMENSION_TEXTURE1DARRAY:
|
||||
rtvDesc.Texture1DArray.MipSlice = mipSlice;
|
||||
rtvDesc.Texture1DArray.FirstArraySlice = firstArraySlice;
|
||||
rtvDesc.Texture1DArray.ArraySize = arraySize;
|
||||
break;
|
||||
|
||||
case D3D12_RTV_DIMENSION_TEXTURE2D:
|
||||
rtvDesc.Texture2D.MipSlice = mipSlice;
|
||||
rtvDesc.Texture2D.PlaneSlice = planeSlice;
|
||||
break;
|
||||
|
||||
case D3D12_RTV_DIMENSION_TEXTURE2DARRAY:
|
||||
rtvDesc.Texture2DArray.MipSlice = mipSlice;
|
||||
rtvDesc.Texture2DArray.FirstArraySlice = firstArraySlice;
|
||||
rtvDesc.Texture2DArray.ArraySize = arraySize;
|
||||
rtvDesc.Texture2DArray.PlaneSlice = planeSlice;
|
||||
break;
|
||||
|
||||
case D3D12_RTV_DIMENSION_TEXTURE2DMS:
|
||||
break;
|
||||
|
||||
case D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY:
|
||||
rtvDesc.Texture2DMSArray.FirstArraySlice = firstArraySlice;
|
||||
rtvDesc.Texture2DMSArray.ArraySize = arraySize;
|
||||
break;
|
||||
|
||||
case D3D12_RTV_DIMENSION_TEXTURE3D:
|
||||
rtvDesc.Texture3D.MipSlice = mipSlice;
|
||||
rtvDesc.Texture3D.FirstWSlice = firstArraySlice;
|
||||
rtvDesc.Texture3D.WSize = arraySize;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported RTV dimension: {rtvDesc.ViewDimension}");
|
||||
}
|
||||
|
||||
return rtvDesc;
|
||||
}
|
||||
|
||||
public static D3D12_DEPTH_STENCIL_VIEW_DESC CreateDsvDesc(ID3D12Resource* pResource, uint mipSlice = 0, uint firstArraySlice = 0, D3D12_DSV_FLAGS flags = D3D12_DSV_FLAG_NONE, TextureFormat originalFormat = TextureFormat.Unknown)
|
||||
{
|
||||
var resourceDesc = pResource->GetDesc();
|
||||
var dsvDesc = new D3D12_DEPTH_STENCIL_VIEW_DESC
|
||||
{
|
||||
Flags = flags,
|
||||
};
|
||||
|
||||
switch (resourceDesc.Dimension)
|
||||
{
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
|
||||
dsvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_DSV_DIMENSION_TEXTURE1DARRAY : D3D12_DSV_DIMENSION_TEXTURE1D;
|
||||
break;
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
|
||||
if (resourceDesc.SampleDesc.Count > 1)
|
||||
{
|
||||
dsvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY : D3D12_DSV_DIMENSION_TEXTURE2DMS;
|
||||
}
|
||||
else
|
||||
{
|
||||
dsvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_DSV_DIMENSION_TEXTURE2DARRAY : D3D12_DSV_DIMENSION_TEXTURE2D;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
dsvDesc.Format = originalFormat == TextureFormat.Unknown ? resourceDesc.Format : originalFormat.ToDXGIFormat();
|
||||
|
||||
var isArray =
|
||||
dsvDesc.ViewDimension == D3D12_DSV_DIMENSION_TEXTURE2DARRAY ||
|
||||
dsvDesc.ViewDimension == D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY;
|
||||
|
||||
var arraySize = 1u;
|
||||
if (isArray)
|
||||
{
|
||||
arraySize = resourceDesc.ArraySize() - firstArraySlice;
|
||||
}
|
||||
|
||||
switch (dsvDesc.ViewDimension)
|
||||
{
|
||||
case D3D12_DSV_DIMENSION_TEXTURE1D:
|
||||
dsvDesc.Texture1D.MipSlice = mipSlice;
|
||||
break;
|
||||
case D3D12_DSV_DIMENSION_TEXTURE1DARRAY:
|
||||
dsvDesc.Texture1DArray.MipSlice = mipSlice;
|
||||
dsvDesc.Texture1DArray.FirstArraySlice = firstArraySlice;
|
||||
dsvDesc.Texture1DArray.ArraySize = arraySize;
|
||||
break;
|
||||
case D3D12_DSV_DIMENSION_TEXTURE2D:
|
||||
dsvDesc.Texture2D.MipSlice = mipSlice;
|
||||
break;
|
||||
case D3D12_DSV_DIMENSION_TEXTURE2DARRAY:
|
||||
dsvDesc.Texture2DArray.MipSlice = mipSlice;
|
||||
dsvDesc.Texture2DArray.FirstArraySlice = firstArraySlice;
|
||||
dsvDesc.Texture2DArray.ArraySize = arraySize;
|
||||
break;
|
||||
case D3D12_DSV_DIMENSION_TEXTURE2DMS:
|
||||
break;
|
||||
case D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY:
|
||||
dsvDesc.Texture2DMSArray.FirstArraySlice = firstArraySlice;
|
||||
dsvDesc.Texture2DMSArray.ArraySize = arraySize;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return dsvDesc;
|
||||
}
|
||||
|
||||
public static D3D12_UNORDERED_ACCESS_VIEW_DESC CreateTextureUavDesc(ID3D12Resource* pResource, uint mipSlice = 0, uint firstArraySlice = 0, uint planeSlice = 0)
|
||||
{
|
||||
var resourceDesc = pResource->GetDesc();
|
||||
var uavDesc = new D3D12_UNORDERED_ACCESS_VIEW_DESC
|
||||
{
|
||||
Format = resourceDesc.Format
|
||||
};
|
||||
|
||||
switch (resourceDesc.Dimension)
|
||||
{
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
|
||||
if (resourceDesc.DepthOrArraySize > 1)
|
||||
{
|
||||
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE1DARRAY;
|
||||
uavDesc.Texture1DArray = new D3D12_TEX1D_ARRAY_UAV
|
||||
{
|
||||
MipSlice = mipSlice,
|
||||
FirstArraySlice = firstArraySlice,
|
||||
ArraySize = resourceDesc.ArraySize() - firstArraySlice
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE1D;
|
||||
uavDesc.Texture1D = new D3D12_TEX1D_UAV
|
||||
{
|
||||
MipSlice = mipSlice
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
|
||||
if (resourceDesc.DepthOrArraySize > 1)
|
||||
{
|
||||
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY;
|
||||
uavDesc.Texture2DArray = new D3D12_TEX2D_ARRAY_UAV
|
||||
{
|
||||
MipSlice = mipSlice,
|
||||
FirstArraySlice = firstArraySlice,
|
||||
ArraySize = resourceDesc.ArraySize() - firstArraySlice,
|
||||
PlaneSlice = planeSlice
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
|
||||
uavDesc.Texture2D = new D3D12_TEX2D_UAV
|
||||
{
|
||||
MipSlice = mipSlice,
|
||||
PlaneSlice = planeSlice
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_TEXTURE3D:
|
||||
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE3D;
|
||||
uavDesc.Texture3D = new D3D12_TEX3D_UAV
|
||||
{
|
||||
MipSlice = mipSlice,
|
||||
FirstWSlice = firstArraySlice,
|
||||
WSize = resourceDesc.Depth() - firstArraySlice
|
||||
};
|
||||
break;
|
||||
|
||||
case D3D12_RESOURCE_DIMENSION_BUFFER:
|
||||
uavDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER;
|
||||
uavDesc.Buffer = new D3D12_BUFFER_UAV
|
||||
{
|
||||
FirstElement = 0,
|
||||
NumElements = (uint)(resourceDesc.Width / 4), // Assuming R32_TYPELESS RAW
|
||||
StructureByteStride = 0,
|
||||
Flags = D3D12_BUFFER_UAV_FLAG_RAW
|
||||
};
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported texture dimension for UAV: {resourceDesc.Dimension}");
|
||||
}
|
||||
|
||||
return uavDesc;
|
||||
}
|
||||
|
||||
public static D3D12_UNORDERED_ACCESS_VIEW_DESC CreateBufferUavDesc(ID3D12Resource* pResource, uint stride, bool isRaw)
|
||||
{
|
||||
var resourceDesc = pResource->GetDesc();
|
||||
var uavDesc = new D3D12_UNORDERED_ACCESS_VIEW_DESC
|
||||
{
|
||||
ViewDimension = D3D12_UAV_DIMENSION_BUFFER,
|
||||
};
|
||||
|
||||
if (isRaw)
|
||||
{
|
||||
uavDesc.Format = DXGI_FORMAT_R32_TYPELESS;
|
||||
uavDesc.Buffer.FirstElement = 0;
|
||||
uavDesc.Buffer.NumElements = (uint)(resourceDesc.Width / 4u);
|
||||
uavDesc.Buffer.StructureByteStride = 0;
|
||||
uavDesc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_RAW;
|
||||
}
|
||||
else // Assumes Structured
|
||||
{
|
||||
uavDesc.Format = resourceDesc.Format;
|
||||
uavDesc.Buffer.FirstElement = 0;
|
||||
uavDesc.Buffer.NumElements = (uint)(resourceDesc.Width / stride);
|
||||
uavDesc.Buffer.StructureByteStride = stride;
|
||||
uavDesc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_NONE;
|
||||
}
|
||||
|
||||
return uavDesc;
|
||||
}
|
||||
|
||||
public static ResourceViewGroup CreateResourceDescriptor(D3D12RenderDevice device, D3D12DescriptorAllocator descriptorAllocator, in ResourceDesc desc, ID3D12Resource* pResource, ResourceViewGroup originalGroup = default)
|
||||
{
|
||||
var resourceDescriptor = new ResourceViewGroup();
|
||||
var resourceDesc = pResource->GetDesc();
|
||||
|
||||
if (desc.Type == ResourceType.Texture)
|
||||
{
|
||||
ref readonly var textureDesc = ref desc.TextureDescriptor;
|
||||
|
||||
if (textureDesc.Usage.HasFlag(TextureUsage.ShaderResource))
|
||||
{
|
||||
resourceDescriptor.srv = originalGroup.srv.IsValid ? originalGroup.srv : descriptorAllocator.AllocateCbvSrvUav();
|
||||
var cpuHandle = descriptorAllocator.GetCpuHandle(resourceDescriptor.srv);
|
||||
|
||||
var isCubeMap = textureDesc.Dimension == TextureDimension.TextureCube || textureDesc.Dimension == TextureDimension.TextureCubeArray;
|
||||
var srvDesc = CreateTextureSrvDesc(pResource, resourceDesc.MipLevels, resourceDesc.DepthOrArraySize, isCubeMap, textureDesc.Format);
|
||||
|
||||
device.NativeObject.Get()->CreateShaderResourceView(pResource, &srvDesc, cpuHandle);
|
||||
descriptorAllocator.CopyToShaderVisible(resourceDescriptor.srv);
|
||||
}
|
||||
|
||||
if (textureDesc.Usage.HasFlag(TextureUsage.RenderTarget))
|
||||
{
|
||||
resourceDescriptor.rtv = originalGroup.rtv.IsValid ? originalGroup.rtv : descriptorAllocator.AllocateRTV();
|
||||
var cpuHandle = descriptorAllocator.GetCpuHandle(resourceDescriptor.rtv);
|
||||
var rtvDesc = CreateRtvDesc(pResource);
|
||||
|
||||
device.NativeObject.Get()->CreateRenderTargetView(pResource, &rtvDesc, cpuHandle);
|
||||
}
|
||||
|
||||
if (textureDesc.Usage.HasFlag(TextureUsage.DepthStencil))
|
||||
{
|
||||
resourceDescriptor.dsv = originalGroup.dsv.IsValid ? originalGroup.dsv : descriptorAllocator.AllocateDSV();
|
||||
var cpuHandle = descriptorAllocator.GetCpuHandle(resourceDescriptor.dsv);
|
||||
var dsvDesc = CreateDsvDesc(pResource, 0, 0, D3D12_DSV_FLAG_NONE, textureDesc.Format);
|
||||
|
||||
device.NativeObject.Get()->CreateDepthStencilView(pResource, &dsvDesc, cpuHandle);
|
||||
}
|
||||
|
||||
if (textureDesc.Usage.HasFlag(TextureUsage.UnorderedAccess))
|
||||
{
|
||||
resourceDescriptor.uav = originalGroup.uav.IsValid ? originalGroup.uav : descriptorAllocator.AllocateCbvSrvUav();
|
||||
var cpuHandle = descriptorAllocator.GetCpuHandle(resourceDescriptor.uav);
|
||||
var uavDesc = CreateTextureUavDesc(pResource);
|
||||
|
||||
device.NativeObject.Get()->CreateUnorderedAccessView(pResource, null, &uavDesc, cpuHandle);
|
||||
descriptorAllocator.CopyToShaderVisible(resourceDescriptor.uav);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ref readonly var bufferDesc = ref desc.BufferDescriptor;
|
||||
var isRaw = bufferDesc.Usage.HasFlag(BufferUsage.Raw);
|
||||
|
||||
if (bufferDesc.Usage.HasFlag(BufferUsage.Constant))
|
||||
{
|
||||
resourceDescriptor.cbv = originalGroup.cbv.IsValid ? originalGroup.cbv : descriptorAllocator.AllocateCbvSrvUav();
|
||||
var cpuHandle = descriptorAllocator.GetCpuHandle(resourceDescriptor.cbv);
|
||||
var cbvDesc = new D3D12_CONSTANT_BUFFER_VIEW_DESC
|
||||
{
|
||||
BufferLocation = pResource->GetGPUVirtualAddress(),
|
||||
SizeInBytes = (uint)resourceDesc.Width,
|
||||
};
|
||||
|
||||
device.NativeObject.Get()->CreateConstantBufferView(&cbvDesc, cpuHandle);
|
||||
descriptorAllocator.CopyToShaderVisible(resourceDescriptor.cbv);
|
||||
}
|
||||
|
||||
if (bufferDesc.Usage.HasFlag(BufferUsage.ShaderResource))
|
||||
{
|
||||
resourceDescriptor.srv = originalGroup.srv.IsValid ? originalGroup.srv : descriptorAllocator.AllocateCbvSrvUav();
|
||||
var cpuHandle = descriptorAllocator.GetCpuHandle(resourceDescriptor.srv);
|
||||
var srvDesc = CreateBufferSrvDesc(pResource, bufferDesc.Stride, isRaw);
|
||||
|
||||
device.NativeObject.Get()->CreateShaderResourceView(pResource, &srvDesc, cpuHandle);
|
||||
descriptorAllocator.CopyToShaderVisible(resourceDescriptor.srv);
|
||||
}
|
||||
|
||||
if (bufferDesc.Usage.HasFlag(BufferUsage.UnorderedAccess))
|
||||
{
|
||||
resourceDescriptor.uav = originalGroup.uav.IsValid ? originalGroup.uav : descriptorAllocator.AllocateCbvSrvUav();
|
||||
var cpuHandle = descriptorAllocator.GetCpuHandle(resourceDescriptor.uav);
|
||||
var uavDesc = CreateBufferUavDesc(pResource, bufferDesc.Stride, isRaw);
|
||||
|
||||
device.NativeObject.Get()->CreateUnorderedAccessView(pResource, null, &uavDesc, cpuHandle);
|
||||
descriptorAllocator.CopyToShaderVisible(resourceDescriptor.uav);
|
||||
}
|
||||
}
|
||||
|
||||
return resourceDescriptor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +122,14 @@ public unsafe interface IResourceDatabase : IDisposable
|
||||
/// <returns>An Error indicating the success or failure of the swap operation.</returns>
|
||||
Error Swap(Handle<GPUResource> handleA, Handle<GPUResource> handleB);
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the GPU resource associated with the destination handle with the resource associated with the source handle, and release the original resource of the destination handle.
|
||||
/// </summary>
|
||||
/// <param name="dst">The handle to the destination resource.</param>
|
||||
/// <param name="src">The handle to the source resource.</param>
|
||||
/// <returns>The handle to the replaced resource.</returns>
|
||||
Handle<GPUResource> Replace(Handle<GPUResource> dst, Handle<GPUResource> src);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GPU resource that is a share of the specified source resource, including all its properties and data.
|
||||
/// The new resource will have the same description and content as the source resource, but will be a distinct entity in the resource database with its own handle.
|
||||
|
||||
@@ -12,24 +12,34 @@ namespace Ghost.Graphics.Core;
|
||||
// TODO: Temporary rendering context for heap creation and data upload. We will refactor it later when we have a better understanding of the engine architecture.
|
||||
public readonly unsafe ref struct RenderContext
|
||||
{
|
||||
private readonly ResourceManager _resourceManager;
|
||||
private readonly ShaderLibrary _shaderLibrary;
|
||||
private readonly IGraphicsEngine _engine;
|
||||
private readonly ICommandBuffer _commandBuffer;
|
||||
|
||||
public ICommandBuffer CommandBuffer => _commandBuffer;
|
||||
|
||||
public ResourceManager ResourceManager => _resourceManager;
|
||||
public IResourceAllocator ResourceAllocator => _engine.ResourceAllocator;
|
||||
public IResourceDatabase ResourceDatabase => _engine.ResourceDatabase;
|
||||
public IPipelineLibrary PipelineLibrary => _engine.PipelineLibrary;
|
||||
|
||||
internal RenderContext(ResourceManager resourceManager, ShaderLibrary shaderLibrary, IGraphicsEngine engine, ICommandBuffer cmd)
|
||||
public required ICommandBuffer CommandBuffer
|
||||
{
|
||||
_resourceManager = resourceManager;
|
||||
_shaderLibrary = shaderLibrary;
|
||||
_engine = engine;
|
||||
_commandBuffer = cmd;
|
||||
get; init;
|
||||
}
|
||||
|
||||
public required ResourceManager ResourceManager
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public required IResourceAllocator ResourceAllocator
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public required IResourceDatabase ResourceDatabase
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public required IPipelineLibrary PipelineLibrary
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
internal ShaderLibrary ShaderLibrary
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
private void TransitionBarrier(Handle<GPUResource> resource, bool isTexture, BarrierLayout newLayout, BarrierAccess newAccess, BarrierSync newSync)
|
||||
@@ -44,13 +54,13 @@ public readonly unsafe ref struct RenderContext
|
||||
desc = BarrierDesc.Buffer(resource, newSync, newAccess);
|
||||
}
|
||||
|
||||
_commandBuffer.Barrier(desc);
|
||||
CommandBuffer.Barrier(desc);
|
||||
}
|
||||
|
||||
public void UploadBuffer<T>(Handle<GPUBuffer> buffer, params ReadOnlySpan<T> data)
|
||||
where T : unmanaged
|
||||
{
|
||||
var r = _engine.ResourceDatabase.GetResourceDescription(buffer.AsResource());
|
||||
var r = ResourceDatabase.GetResourceDescription(buffer.AsResource());
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return;
|
||||
@@ -65,9 +75,9 @@ public readonly unsafe ref struct RenderContext
|
||||
{
|
||||
fixed (T* pData = data)
|
||||
{
|
||||
var mappedData = _engine.ResourceDatabase.MapResource(buffer.AsResource(), 0, null);
|
||||
var mappedData = ResourceDatabase.MapResource(buffer.AsResource(), 0, null);
|
||||
MemoryUtility.MemCpy(mappedData, pData, sizeInBytes);
|
||||
_engine.ResourceDatabase.UnmapResource(buffer.AsResource(), 0, null);
|
||||
ResourceDatabase.UnmapResource(buffer.AsResource(), 0, null);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -79,7 +89,7 @@ public readonly unsafe ref struct RenderContext
|
||||
HeapType = HeapType.Upload,
|
||||
};
|
||||
|
||||
var uploadHandle = _resourceManager.CreateTransientBuffer(in uploadDesc);
|
||||
var uploadHandle = ResourceManager.CreateTransientBuffer(in uploadDesc);
|
||||
if (uploadHandle.IsInvalid)
|
||||
{
|
||||
throw new OutOfMemoryException("Failed to create upload buffer for buffer data.");
|
||||
@@ -87,19 +97,19 @@ public readonly unsafe ref struct RenderContext
|
||||
|
||||
fixed (T* pData = data)
|
||||
{
|
||||
var mappedData = _engine.ResourceDatabase.MapResource(uploadHandle.AsResource(), 0, null);
|
||||
var mappedData = ResourceDatabase.MapResource(uploadHandle.AsResource(), 0, null);
|
||||
MemoryUtility.MemCpy(mappedData, pData, sizeInBytes);
|
||||
_engine.ResourceDatabase.UnmapResource(uploadHandle.AsResource(), 0, null);
|
||||
ResourceDatabase.UnmapResource(uploadHandle.AsResource(), 0, null);
|
||||
}
|
||||
|
||||
_commandBuffer.CopyBuffer(buffer, uploadHandle, 0, 0, sizeInBytes);
|
||||
CommandBuffer.CopyBuffer(buffer, uploadHandle, 0, 0, sizeInBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices, bool staticMesh)
|
||||
{
|
||||
var mesh = _resourceManager.CreateMesh(vertices, indices);
|
||||
var r = _resourceManager.GetMeshReference(mesh);
|
||||
var mesh = ResourceManager.CreateMesh(vertices, indices);
|
||||
var r = ResourceManager.GetMeshReference(mesh);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return mesh;
|
||||
@@ -146,7 +156,7 @@ public readonly unsafe ref struct RenderContext
|
||||
/// <param name="markMeshStatic">Whether to mark the mesh as static. If it's true, the cpu buffer of the mesh will not be avaliable any more</param>
|
||||
public void UploadMesh(Handle<Mesh> mesh, bool markMeshStatic)
|
||||
{
|
||||
var r = _resourceManager.GetMeshReference(mesh);
|
||||
var r = ResourceManager.GetMeshReference(mesh);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return;
|
||||
@@ -173,7 +183,7 @@ public readonly unsafe ref struct RenderContext
|
||||
|
||||
public void UploadMeshlets(Handle<Mesh> mesh)
|
||||
{
|
||||
var r = _resourceManager.GetMeshReference(mesh);
|
||||
var r = ResourceManager.GetMeshReference(mesh);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return;
|
||||
@@ -208,9 +218,9 @@ public readonly unsafe ref struct RenderContext
|
||||
HeapType = HeapType.Default,
|
||||
};
|
||||
|
||||
meshRef.MeshLetBuffer = _engine.ResourceAllocator.CreateBuffer(in meshletDesc, "Meshlets");
|
||||
meshRef.MeshletVerticesBuffer = _engine.ResourceAllocator.CreateBuffer(in verticesDesc, "MeshletVertices");
|
||||
meshRef.MeshletTrianglesBuffer = _engine.ResourceAllocator.CreateBuffer(in trianglesDesc, "MeshletTriangles");
|
||||
meshRef.MeshLetBuffer = ResourceAllocator.CreateBuffer(in meshletDesc, "Meshlets");
|
||||
meshRef.MeshletVerticesBuffer = ResourceAllocator.CreateBuffer(in verticesDesc, "MeshletVertices");
|
||||
meshRef.MeshletTrianglesBuffer = ResourceAllocator.CreateBuffer(in trianglesDesc, "MeshletTriangles");
|
||||
|
||||
TransitionBarrier(meshRef.MeshLetBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||
TransitionBarrier(meshRef.MeshletVerticesBuffer.AsResource(), false, BarrierLayout.Undefined, BarrierAccess.CopyDest, BarrierSync.Copy);
|
||||
@@ -227,7 +237,7 @@ public readonly unsafe ref struct RenderContext
|
||||
|
||||
public void UpdateObjectData(Handle<Mesh> mesh)
|
||||
{
|
||||
var r = _resourceManager.GetMeshReference(mesh);
|
||||
var r = ResourceManager.GetMeshReference(mesh);
|
||||
if (r.IsFailure)
|
||||
{
|
||||
return;
|
||||
@@ -238,11 +248,11 @@ public readonly unsafe ref struct RenderContext
|
||||
{
|
||||
worldBoundsMin = meshData.BoundingBox.Min,
|
||||
worldBoundsMax = meshData.BoundingBox.Max,
|
||||
vertexBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.VertexBuffer.AsResource()),
|
||||
indexBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.IndexBuffer.AsResource()),
|
||||
meshletBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.MeshLetBuffer.AsResource()),
|
||||
meshletVerticesBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.MeshletVerticesBuffer.AsResource()),
|
||||
meshletTrianglesBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.MeshletTrianglesBuffer.AsResource()),
|
||||
vertexBuffer = ResourceDatabase.GetBindlessIndex(meshData.VertexBuffer.AsResource()),
|
||||
indexBuffer = ResourceDatabase.GetBindlessIndex(meshData.IndexBuffer.AsResource()),
|
||||
meshletBuffer = ResourceDatabase.GetBindlessIndex(meshData.MeshLetBuffer.AsResource()),
|
||||
meshletVerticesBuffer = ResourceDatabase.GetBindlessIndex(meshData.MeshletVerticesBuffer.AsResource()),
|
||||
meshletTrianglesBuffer = ResourceDatabase.GetBindlessIndex(meshData.MeshletTrianglesBuffer.AsResource()),
|
||||
};
|
||||
|
||||
var bufferHandle = meshData.MeshDataBuffer.AsResource();
|
||||
@@ -275,7 +285,7 @@ public readonly unsafe ref struct RenderContext
|
||||
HeapType = HeapType.Upload,
|
||||
};
|
||||
|
||||
var uploadHandle = _resourceManager.CreateTransientBuffer(in uploadDesc);
|
||||
var uploadHandle = ResourceManager.CreateTransientBuffer(in uploadDesc);
|
||||
if (uploadHandle.IsInvalid)
|
||||
{
|
||||
throw new OutOfMemoryException("Failed to create upload buffer for texture data.");
|
||||
@@ -292,7 +302,7 @@ public readonly unsafe ref struct RenderContext
|
||||
slicePitch = slicePitch
|
||||
};
|
||||
|
||||
_commandBuffer.UpdateSubResources(texture.AsResource(), uploadHandle.AsResource(), subresourceData);
|
||||
CommandBuffer.UpdateSubResources(texture.AsResource(), uploadHandle.AsResource(), subresourceData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,7 +315,7 @@ public readonly unsafe ref struct RenderContext
|
||||
var variantKey = RHIUtility.CreateShaderVariantKey(entryHash, in keywordSet);
|
||||
|
||||
// TODO: Refactor this into a helper method.
|
||||
var (compiledHash, error) = _shaderLibrary.GetCompiledHash(variantKey);
|
||||
var (compiledHash, error) = ShaderLibrary.GetCompiledHash(variantKey);
|
||||
if (error.IsFailure)
|
||||
{
|
||||
// TODO: Fallback to an error material.
|
||||
@@ -318,7 +328,7 @@ public readonly unsafe ref struct RenderContext
|
||||
if (!PipelineLibrary.HasPipelineStateObject(pipelineKey))
|
||||
{
|
||||
using var scope = AllocationManager.CreateStackScope();
|
||||
var compiledCacheResult = _shaderLibrary.GetCompiledCache(shader.UniqueID, entryIndex, scope.AllocationHandle);
|
||||
var compiledCacheResult = ShaderLibrary.GetCompiledCache(shader.UniqueID, entryIndex, scope.AllocationHandle);
|
||||
if (compiledCacheResult.IsFailure)
|
||||
{
|
||||
// TODO: Fallback to a checkerboard shader.
|
||||
@@ -341,7 +351,7 @@ public readonly unsafe ref struct RenderContext
|
||||
PipelineLibrary.CreateComputePipeline(in psoDes).GetValueOrThrow();
|
||||
}
|
||||
|
||||
_commandBuffer.SetPipelineState(pipelineKey);
|
||||
CommandBuffer.SetPipelineState(pipelineKey);
|
||||
|
||||
|
||||
var propertySpan = MemoryMarshal.AsBytes(new ReadOnlySpan<T>(in property));
|
||||
@@ -374,7 +384,7 @@ public readonly unsafe ref struct RenderContext
|
||||
propertyBuffer = ResourceDatabase.GetBindlessIndex(properyBuffer.AsResource()),
|
||||
};
|
||||
|
||||
_commandBuffer.SetGraphicsRoot32Constants(0, pushConstant.AsUInts());
|
||||
_commandBuffer.DispatchCompute(threadGroupCount.x, threadGroupCount.y, threadGroupCount.z);
|
||||
CommandBuffer.SetGraphicsRoot32Constants(0, pushConstant.AsUInts());
|
||||
CommandBuffer.DispatchCompute(threadGroupCount.x, threadGroupCount.y, threadGroupCount.z);
|
||||
}
|
||||
}
|
||||
|
||||
42
src/Runtime/Ghost.Graphics/IResourceStreamingProcessor.cs
Normal file
42
src/Runtime/Ghost.Graphics/IResourceStreamingProcessor.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Ghost.Graphics.Services;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ghost.Graphics;
|
||||
|
||||
internal ref struct ResourceStreamingContext
|
||||
{
|
||||
public required AsyncCopyPipeline CopyPipeline
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public required ResourceManager ResourceManager
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public required IResourceDatabase ResourceDatabase
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public required IResourceAllocator ResourceAllocator
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public required ICommandBuffer GraphicsCommandBuffer
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
}
|
||||
|
||||
internal interface IResourceStreamingProcessor
|
||||
{
|
||||
void ProcessPendingUploads(ResourceStreamingContext context);
|
||||
}
|
||||
@@ -31,6 +31,11 @@ internal readonly struct RenderSystemDesc
|
||||
get; init;
|
||||
}
|
||||
|
||||
public required IResourceStreamingProcessor ResourceStreamingProcessor
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
|
||||
public required string ShaderCacheDirectory
|
||||
{
|
||||
get; init;
|
||||
@@ -85,12 +90,16 @@ public class RenderSystem : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private readonly RenderSystemDesc _config;
|
||||
private readonly IResourceStreamingProcessor _streamingProcessor;
|
||||
|
||||
private IRenderPipelineSettings _renderPipelineSettings;
|
||||
private IRenderPipeline _renderPipeline;
|
||||
|
||||
private readonly IGraphicsEngine _graphicsEngine;
|
||||
private readonly ResourceManager _resourceManager;
|
||||
private readonly SwapChainManager _swapChainManager;
|
||||
private readonly ShaderLibrary _shaderLibrary;
|
||||
private readonly AsyncCopyPipeline _asyncCopyPipeline;
|
||||
|
||||
private readonly IFence _fence;
|
||||
private readonly FrameResource[] _frameResources;
|
||||
@@ -99,9 +108,6 @@ public class RenderSystem : IDisposable
|
||||
|
||||
private readonly ConcurrentDictionary<ISwapChain, uint2> _resizeRequest;
|
||||
|
||||
private IRenderPipelineSettings _renderPipelineSettings;
|
||||
private IRenderPipeline _renderPipeline;
|
||||
|
||||
private ulong _cpuFenceValue;
|
||||
private ulong _submittedFenceValue;
|
||||
|
||||
@@ -113,13 +119,14 @@ public class RenderSystem : IDisposable
|
||||
public IGraphicsEngine GraphicsEngine => _graphicsEngine;
|
||||
public ResourceManager ResourceManager => _resourceManager;
|
||||
public SwapChainManager SwapChainManager => _swapChainManager;
|
||||
public AsyncCopyPipeline AsyncCopyPipeline => _asyncCopyPipeline;
|
||||
|
||||
public bool IsRunning => _isRunning;
|
||||
|
||||
public ulong CPUFenceValue => _cpuFenceValue;
|
||||
public ulong SubmittedFenceValue => _submittedFenceValue;
|
||||
|
||||
public uint MaxFrameLatency => _config.FrameBufferCount;
|
||||
public int MaxFrameLatency => _frameResources.Length;
|
||||
|
||||
public IRenderPipelineSettings RenderPipelineSettings
|
||||
{
|
||||
@@ -152,8 +159,6 @@ public class RenderSystem : IDisposable
|
||||
|
||||
internal RenderSystem(RenderSystemDesc desc)
|
||||
{
|
||||
_config = desc;
|
||||
|
||||
var engineDesc = new GraphicsEngineDesc
|
||||
{
|
||||
FrameBufferCount = desc.FrameBufferCount
|
||||
@@ -178,9 +183,8 @@ public class RenderSystem : IDisposable
|
||||
throw new NotSupportedException($"The specified graphics API '{desc.GraphicsAPI}' is not supported.");
|
||||
}
|
||||
|
||||
_resourceManager = new ResourceManager(_graphicsEngine.Device, _graphicsEngine.ResourceAllocator, _graphicsEngine.ResourceDatabase);
|
||||
_swapChainManager = new SwapChainManager(_graphicsEngine);
|
||||
_shaderLibrary = new ShaderLibrary(desc.ShaderCompilationBridge, desc.ShaderCacheDirectory);
|
||||
_streamingProcessor = desc.ResourceStreamingProcessor;
|
||||
_renderPipelineSettings = desc.InitialRenderPipelineSettings;
|
||||
|
||||
_fence = _graphicsEngine.CreateFence(0);
|
||||
// Create frame resources for synchronization
|
||||
@@ -195,6 +199,17 @@ public class RenderSystem : IDisposable
|
||||
};
|
||||
}
|
||||
|
||||
_renderPipeline = _renderPipelineSettings.CreatePipeline(this);
|
||||
for (var i = 0; i < _frameResources.Length; i++)
|
||||
{
|
||||
_frameResources[i].RenderPayload = _renderPipelineSettings.CreatePayload(this, _renderPipeline);
|
||||
}
|
||||
|
||||
_resourceManager = new ResourceManager(_graphicsEngine.Device, _graphicsEngine.ResourceAllocator, _graphicsEngine.ResourceDatabase);
|
||||
_swapChainManager = new SwapChainManager(_graphicsEngine);
|
||||
_shaderLibrary = new ShaderLibrary(desc.ShaderCompilationBridge, desc.ShaderCacheDirectory);
|
||||
_asyncCopyPipeline = new AsyncCopyPipeline(_graphicsEngine);
|
||||
|
||||
_renderThread = new Thread(RenderLoop)
|
||||
{
|
||||
IsBackground = true,
|
||||
@@ -205,14 +220,6 @@ public class RenderSystem : IDisposable
|
||||
_shutdownEvent = new AutoResetEvent(false);
|
||||
_resizeRequest = new ConcurrentDictionary<ISwapChain, uint2>();
|
||||
|
||||
_renderPipelineSettings = _config.InitialRenderPipelineSettings;
|
||||
|
||||
_renderPipeline = _renderPipelineSettings.CreatePipeline(this);
|
||||
for (var i = 0; i < _frameResources.Length; i++)
|
||||
{
|
||||
_frameResources[i].RenderPayload = _renderPipelineSettings.CreatePayload(this, _renderPipeline);
|
||||
}
|
||||
|
||||
_isRunning = false;
|
||||
_disposed = false;
|
||||
}
|
||||
@@ -239,7 +246,7 @@ public class RenderSystem : IDisposable
|
||||
|
||||
while (_isRunning)
|
||||
{
|
||||
var frameIndex = (int)(_submittedFenceValue % _config.FrameBufferCount);
|
||||
var frameIndex = (int)(_submittedFenceValue % (ulong)_frameResources.Length);
|
||||
ref var frameResource = ref _frameResources[frameIndex];
|
||||
|
||||
try
|
||||
@@ -297,10 +304,27 @@ public class RenderSystem : IDisposable
|
||||
try
|
||||
{
|
||||
cmd.Begin(frameResource.CommandAllocator);
|
||||
|
||||
var streamingContext = new ResourceStreamingContext
|
||||
{
|
||||
CopyPipeline = _asyncCopyPipeline,
|
||||
GraphicsCommandBuffer = cmd,
|
||||
ResourceAllocator = _graphicsEngine.ResourceAllocator,
|
||||
ResourceDatabase = _graphicsEngine.ResourceDatabase,
|
||||
ResourceManager = _resourceManager
|
||||
};
|
||||
_streamingProcessor.ProcessPendingUploads(streamingContext);
|
||||
|
||||
var ctx = new RenderContext(_resourceManager, _shaderLibrary, _graphicsEngine, cmd);
|
||||
|
||||
_renderPipeline.Render(ctx, frameIndex, frameResource.RenderPayload);
|
||||
var renderContext = new RenderContext
|
||||
{
|
||||
CommandBuffer = cmd,
|
||||
PipelineLibrary = _graphicsEngine.PipelineLibrary,
|
||||
ResourceAllocator = _graphicsEngine.ResourceAllocator,
|
||||
ResourceDatabase = _graphicsEngine.ResourceDatabase,
|
||||
ResourceManager = _resourceManager,
|
||||
ShaderLibrary = _shaderLibrary,
|
||||
};
|
||||
_renderPipeline.Render(renderContext, frameIndex, frameResource.RenderPayload);
|
||||
_swapChainManager.TransitionToPresent(cmd);
|
||||
|
||||
// End recording commands and submit
|
||||
@@ -369,7 +393,7 @@ public class RenderSystem : IDisposable
|
||||
{
|
||||
Logger.DebugAssert(!_disposed, "Cannot signal CPU ready on a disposed RenderSystem.");
|
||||
|
||||
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
||||
var eventIndex = (int)(_cpuFenceValue % (ulong)_frameResources.Length);
|
||||
ref var frameResource = ref _frameResources[eventIndex];
|
||||
|
||||
frameResource.CpuReadyEvent.Set();
|
||||
@@ -386,14 +410,14 @@ public class RenderSystem : IDisposable
|
||||
{
|
||||
Logger.DebugAssert(!_disposed, "Cannot acquire CPU frame on a disposed RenderSystem.");
|
||||
|
||||
var requiredGpuFence = _cpuFenceValue < _config.FrameBufferCount ? 0 : _cpuFenceValue - _config.FrameBufferCount + 1;
|
||||
var requiredGpuFence = _cpuFenceValue < (ulong)_frameResources.Length ? 0 : _cpuFenceValue - (ulong)_frameResources.Length + 1;
|
||||
|
||||
if (requiredGpuFence > 0 && _fence.CompletedValue < requiredGpuFence)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
||||
var eventIndex = (int)(_cpuFenceValue % (ulong)_frameResources.Length);
|
||||
ref var frameResource = ref _frameResources[eventIndex];
|
||||
|
||||
return true;
|
||||
@@ -409,7 +433,7 @@ public class RenderSystem : IDisposable
|
||||
return true;
|
||||
}
|
||||
|
||||
var eventIndex = (int)((submittedFenceValue - 1) % _config.FrameBufferCount);
|
||||
var eventIndex = (int)((submittedFenceValue - 1) % (ulong)_frameResources.Length);
|
||||
return _frameResources[eventIndex].GpuReadyEvent.WaitOne(timeOut);
|
||||
}
|
||||
|
||||
@@ -429,7 +453,7 @@ public class RenderSystem : IDisposable
|
||||
{
|
||||
Logger.DebugAssert(!_disposed, "Cannot get current frame payload from a disposed RenderSystem.");
|
||||
|
||||
var eventIndex = (int)(_cpuFenceValue % _config.FrameBufferCount);
|
||||
var eventIndex = (int)(_cpuFenceValue % (ulong)_frameResources.Length);
|
||||
ref var frameResource = ref _frameResources[eventIndex];
|
||||
|
||||
return frameResource.RenderPayload;
|
||||
|
||||
@@ -4,7 +4,6 @@ using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ghost.Graphics.Services;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user