Refactor core systems and improve resource management

- Updated dependencies, including `Misaki.HighPerformance` and `TerraFX.Interop`.
- Refactored `Result` struct for better error handling and chaining.
- Removed `Ptr<T>` struct as it was no longer necessary.
- Enhanced `Win32Utility` with `Attach` and `Dispose` methods.
- Improved `ProjectService` and `AppStateMachine` with `Result` integration.
- Refactored `IShaderCompiler` to support SPIR-V cross-compilation and pass-level compilation.
- Standardized Direct3D12 resource management with `UniquePtr` and added `D3D12Object` base class.
- Improved shader reflection validation and pipeline creation in `D3D12PipelineLibrary`.
- Updated `SDLCompiler` for better error handling during shader generation.
- Enhanced logging, debugging, and code readability across the codebase.
- Performed general code cleanup, including unused namespace removal and naming consistency.
This commit is contained in:
2025-11-23 15:02:37 +09:00
parent 5c4e1a3350
commit dfe786a2aa
46 changed files with 1193 additions and 733 deletions

View File

@@ -20,10 +20,10 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Misaki.HighPerformance" Version="1.0.0" /> <PackageReference Include="Misaki.HighPerformance" Version="1.0.0" />
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.1.0" /> <PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.1.0" />
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.1.2" /> <PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.2.0" />
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.2.6" /> <PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.2.6" />
<PackageReference Include="System.IO.Hashing" Version="9.0.10" /> <PackageReference Include="System.IO.Hashing" Version="10.0.0" />
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.2" /> <PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.5" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,27 +0,0 @@
using System.Runtime.CompilerServices;
namespace Ghost.Core;
public unsafe readonly struct Ptr<T>
where T : unmanaged
{
public readonly T* value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Ptr(T* value)
{
this.value = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator T*(Ptr<T> ptr)
{
return ptr.value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Ptr<T>(T* value)
{
return new Ptr<T>(value);
}
}

View File

@@ -1,15 +1,22 @@
using System.Runtime.CompilerServices;
using TerraFX.Interop.Windows;
namespace Ghost.Core; namespace Ghost.Core;
public readonly struct Result public readonly struct Result
{ {
public readonly bool success; private readonly bool _isSuccess;
private readonly string? _message;
public readonly string? message; public readonly bool IsSuccess => _isSuccess;
public readonly bool IsFailure => !_isSuccess;
public readonly string? Message => _message;
public Result(bool success, string? message = null) public Result(bool success, string? message = null)
{ {
this.success = success; _isSuccess = success;
this.message = message; _message = message;
} }
public static Result Success() public static Result Success()
@@ -17,7 +24,7 @@ public readonly struct Result
return new Result(true); return new Result(true);
} }
public static Result Fail(string? message) public static Result Failure(string? message = null)
{ {
return new Result(false, message); return new Result(false, message);
} }
@@ -27,23 +34,48 @@ public readonly struct Result
return Result<T>.Success(value); return Result<T>.Success(value);
} }
public override string ToString() => success ? "OK" : $"Error: {message}"; public static Result<T> Failure<T>(string? message = null)
{
return Result<T>.Failure(message);
}
public static implicit operator bool(Result result) => result.success; public static Result<T, S> Create<T, S>(T value, S status)
{
return new Result<T, S>(value, status);
}
public override string ToString() => IsSuccess ? "OK" : $"Error: {Message}";
public static implicit operator bool(Result result) => result.IsSuccess;
} }
public readonly struct Result<T> public readonly struct Result<T>
{ {
public readonly bool success; private readonly bool _isSuccess;
public readonly T value; private readonly T _value;
private readonly string? _message;
public readonly string? message; public readonly bool IsSuccess => _isSuccess;
public readonly bool IsFailure => !_isSuccess;
public readonly T Value => _value;
public readonly string? Message => _message;
public Result(bool success, T value, string? message = null) public Result(bool success, T value, string? message = null)
{ {
this.success = success; _isSuccess = success;
this.value = value; _value = value;
this.message = message; _message = message;
}
public ref readonly T GetValueRef()
{
if (!IsSuccess)
{
throw new InvalidOperationException("Cannot get value from a failed Result.");
}
return ref Unsafe.AsRef(in _value);
} }
public static Result<T> Success(T value) public static Result<T> Success(T value)
@@ -51,26 +83,52 @@ public readonly struct Result<T>
return new Result<T>(true, value); return new Result<T>(true, value);
} }
public static Result<T> Fail(string? message) public static Result<T> Failure(string? message = null)
{ {
return new Result<T>(false, default!, message); return new Result<T>(false, default!, message);
} }
public override string ToString() => success ? $"OK: {value}" : $"Error: {message}"; public override string ToString() => IsSuccess ? $"OK: {Value}" : $"Error: {Message}";
public static implicit operator Result<T>(T? data) => data is not null ? Success(data) : Fail(null); public static implicit operator Result<T>(T? data) => data is not null ? Success(data) : Failure(null);
public static implicit operator Result<T>(Result result) => result.success ? Success(default!) : Fail(result.message); public static implicit operator Result<T>(Result result) => result.IsSuccess ? Success(default!) : Failure(result.Message);
public static implicit operator bool(Result<T> result) => result.IsSuccess;
public static implicit operator Result(Result<T> result) => result.IsSuccess ? Result.Success() : Result.Failure(result.Message);
}
public enum ResultStatus : byte
{
Success,
NotFound,
InvalidArgument,
InvalidState,
InternalError,
PermissionDenied,
NotSupported,
OutOfMemory,
Timeout,
Cancelled,
UnknownError
} }
public readonly struct Result<T, S> public readonly struct Result<T, S>
{ {
public readonly T value; private readonly T _value;
public readonly S status; private readonly S _status;
public T Value => _value;
public S Status => _status;
public Result(T value, S status) public Result(T value, S status)
{ {
this.value = value; _value = value;
this.status = status; _status = status;
}
public ref readonly T GetValueRef()
{
return ref Unsafe.AsRef(in _value);
} }
public static Result<T, S> Create(T value, S status) public static Result<T, S> Create(T value, S status)
@@ -78,26 +136,71 @@ public readonly struct Result<T, S>
return new Result<T, S>(value, status); return new Result<T, S>(value, status);
} }
public override string ToString() => $"Value: {value}, Status: {status}"; public override string ToString() => $"Value: {_value}, Status: {_status}";
} }
public static class ResultExtensions public static class ResultExtensions
{ {
public static void ThrowIfFailed(this Result result) public static void ThrowIfFailed(this Result result)
{ {
if (!result.success) if (!result.IsSuccess)
{ {
throw new InvalidOperationException($"Operation failed: {result.message}"); throw new InvalidOperationException($"Operation failed: {result.Message}");
} }
} }
public static T GetValueOrThrow<T>(this Result<T> result) public static T GetValueOrThrow<T>(this Result<T> result)
{ {
if (!result.success) if (!result.IsSuccess)
{ {
throw new InvalidOperationException($"Operation failed: {result.message}"); throw new InvalidOperationException($"Operation failed: {result.Message}");
} }
return result.value; return result.Value;
}
public static T? GetValueOrDefault<T>(this Result<T> result, T? defaultValue = default)
{
return result.IsSuccess ? result.Value : defaultValue;
}
public static Result OnSuccess(this Result result, Action action)
{
if (result.IsSuccess)
{
action();
}
return result;
}
public static Result<T> OnSuccess<T>(this Result<T> result, Action<T> action)
{
if (result.IsSuccess)
{
action(result.Value);
}
return result;
}
public static Result OnFailed(this Result result, Action<string?> action)
{
if (result.IsFailure)
{
action(result.Message);
}
return result;
}
public static Result<T> OnFailed<T>(this Result<T> result, Action<string?> action)
{
if (result.IsFailure)
{
action(result.Message);
}
return result;
} }
} }

View File

@@ -42,6 +42,32 @@ internal static unsafe partial class Win32Utility
return new IID_PPV(Windows.__uuidof<T>(), comPtr.PPV()); return new IID_PPV(Windows.__uuidof<T>(), comPtr.PPV());
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Attach<T>(ref this UniquePtr<T> uPtr, T* other)
where T : unmanaged, IUnknown.Interface
{
var ptr = uPtr.Get();
if (ptr != null)
{
var refCount = ptr->Release();
Debug.Assert((refCount != 0) || (ptr != other));
}
uPtr = new UniquePtr<T>(other);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Dispose<T>(ref this UniquePtr<T> uPtr)
where T : unmanaged, IUnknown.Interface
{
T* ptr = uPtr.Get();
if (ptr != null)
{
uPtr = default;
MemoryLeakException.ThrowIfRefCountNonZero(ptr->Release());
}
}
[Conditional("DEBUG")] [Conditional("DEBUG")]
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Assert(this HRESULT hr) public static void Assert(this HRESULT hr)
@@ -50,9 +76,14 @@ internal static unsafe partial class Win32Utility
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ThrowIfFailed(this HRESULT hr) public static Result ToResult(this HRESULT hr, [CallerArgumentExpression(nameof(hr))] string? op = null)
{ {
Windows.ThrowIfFailed(hr); if (hr.SUCCEEDED)
{
return Result.Success();
}
return Result.Failure($"{op} failed with code {hr}");
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -102,8 +133,6 @@ internal static unsafe partial class Win32Utility
extension(MemoryLeakException) extension(MemoryLeakException)
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("DEBUG")]
[Conditional("GHOST_EDITOR")]
public static void ThrowIfRefCountNonZero(uint count) public static void ThrowIfRefCountNonZero(uint count)
{ {
if (count != 0) if (count != 0)

View File

@@ -75,26 +75,26 @@ internal partial class ProjectService
{ {
if (string.IsNullOrWhiteSpace(projectDirectory) || !Directory.Exists(projectDirectory)) if (string.IsNullOrWhiteSpace(projectDirectory) || !Directory.Exists(projectDirectory))
{ {
return Result<ProjectMetadataInfo>.Fail("Project directory is invalid or does not exist."); return Result<ProjectMetadataInfo>.Failure("Project directory is invalid or does not exist.");
} }
var projectAssetsPath = Path.Combine(projectDirectory, ASSETS_FOLDER); var projectAssetsPath = Path.Combine(projectDirectory, ASSETS_FOLDER);
var projectConfigPath = Path.Combine(projectDirectory, CONFIG_FOLDER); var projectConfigPath = Path.Combine(projectDirectory, CONFIG_FOLDER);
if (!Directory.Exists(projectAssetsPath) || !Directory.Exists(projectConfigPath)) if (!Directory.Exists(projectAssetsPath) || !Directory.Exists(projectConfigPath))
{ {
return Result<ProjectMetadataInfo>.Fail("Project folder structure is invalid."); return Result<ProjectMetadataInfo>.Failure("Project folder structure is invalid.");
} }
var metadataPath = Directory.GetFiles(projectDirectory, $"*.{ProjectMetadata.PROJECT_EXTENSION}", SearchOption.TopDirectoryOnly).FirstOrDefault(); var metadataPath = Directory.GetFiles(projectDirectory, $"*.{ProjectMetadata.PROJECT_EXTENSION}", SearchOption.TopDirectoryOnly).FirstOrDefault();
if (string.IsNullOrWhiteSpace(metadataPath) || !File.Exists(metadataPath)) if (string.IsNullOrWhiteSpace(metadataPath) || !File.Exists(metadataPath))
{ {
return Result<ProjectMetadataInfo>.Fail("Project metadata file not found."); return Result<ProjectMetadataInfo>.Failure("Project metadata file not found.");
} }
var metadata = await LoadMetadataAsync(metadataPath); var metadata = await LoadMetadataAsync(metadataPath);
if (metadata == null) if (metadata == null)
{ {
return Result<ProjectMetadataInfo>.Fail("Project metadata file is corrupted or invalid."); return Result<ProjectMetadataInfo>.Failure("Project metadata file is corrupted or invalid.");
} }
return new ProjectMetadataInfo(metadataPath, metadata); return new ProjectMetadataInfo(metadataPath, metadata);
@@ -187,7 +187,7 @@ internal partial class ProjectService
// Check if folder is empty // Check if folder is empty
if (Directory.EnumerateFiles(projectPath, "*", SearchOption.AllDirectories).Any()) if (Directory.EnumerateFiles(projectPath, "*", SearchOption.AllDirectories).Any())
{ {
return new(false, default, "Directory is not empty"); return Result.Failure("Directory is not empty");
} }
} }
@@ -197,28 +197,28 @@ internal partial class ProjectService
await SetupRequestFolderAsync(projectPath, templatePath); await SetupRequestFolderAsync(projectPath, templatePath);
var info = await AddProjectAsync(projectName, metadataPath); var info = await AddProjectAsync(projectName, metadataPath);
return new(true, new(metadataPath, metadata)); return Result.Success(new ProjectMetadataInfo(metadataPath, metadata));
} }
catch (Exception e) catch (Exception e)
{ {
return Result<ProjectMetadataInfo>.Fail($"Failed to create project: {e.Message}"); return Result.Failure($"Failed to create project: {e.Message}");
} }
} }
public async Task<Result<ProjectMetadataInfo>> AddProjectFromDirectoryAsync(string projectDirectory) public async Task<Result<ProjectMetadataInfo>> AddProjectFromDirectoryAsync(string projectDirectory)
{ {
var result = await ValidateProjectDirectoryAsync(projectDirectory); var result = await ValidateProjectDirectoryAsync(projectDirectory);
if (!result.success) if (result.IsFailure)
{ {
return result; return result;
} }
if (await HasProjectAsync(result.value.Path)) if (await HasProjectAsync(result.Value.Path))
{ {
return Result<ProjectMetadataInfo>.Fail("Project already exists."); return Result.Failure("Project already exists.");
} }
await AddProjectAsync(result.value.Metadata.Name, result.value.Path); await AddProjectAsync(result.Value.Metadata.Name, result.Value.Path);
return result; return result;
} }

View File

@@ -1,3 +1,5 @@
using Ghost.Core;
namespace Ghost.Editor.Core.AppState; namespace Ghost.Editor.Core.AppState;
internal partial class AppStateMachine : IDisposable, IAsyncDisposable internal partial class AppStateMachine : IDisposable, IAsyncDisposable
@@ -5,52 +7,91 @@ internal partial class AppStateMachine : IDisposable, IAsyncDisposable
private Dictionary<StateKey, Lazy<IAppState>> _states = new(); private Dictionary<StateKey, Lazy<IAppState>> _states = new();
private IAppState? _current; private IAppState? _current;
private bool _disposed;
public void RegisterState(StateKey key, Func<IAppState> stateFactory) public void RegisterState(StateKey key, Func<IAppState> stateFactory)
{ {
_states[key] = new(stateFactory); _states[key] = new(stateFactory);
} }
public async Task TransitionToAsync(StateKey stateKey, object? parameter = null) public async Task<Result> TransitionToAsync(StateKey stateKey, object? parameter = null)
{ {
var previous = _current; var previous = _current;
if (!_states.TryGetValue(stateKey, out var next)) if (!_states.TryGetValue(stateKey, out var next))
{ {
throw new InvalidOperationException($"State '{stateKey}' is not registered."); return Result.Failure($"State '{stateKey}' not found.");
}
Result result;
if (previous != null)
{
result = await previous.OnExitingAsync();
if (result.IsFailure)
{
return result;
}
}
result = await next.Value.OnEnteringAsync(parameter);
if (result.IsFailure)
{
if (previous != null)
{
await previous.OnEnteredAsync(parameter);
}
return result;
} }
if (previous != null) if (previous != null)
{ {
await previous.OnExitingAsync(); result = await previous.OnExitedAsync();
if (result.IsFailure)
{
await next.Value.OnExitedAsync();
await previous.OnEnteredAsync(parameter);
return result;
}
} }
await next.Value.OnEnteringAsync(parameter); result = await next.Value.OnEnteredAsync(parameter);
if (result.IsFailure)
{
await next.Value.OnExitedAsync();
if (previous != null) if (previous != null)
{ {
await previous.OnExitedAsync(); await previous.OnEnteredAsync(parameter);
} }
await next.Value.OnEnteredAsync(parameter); return result;
}
_current = next.Value; _current = next.Value;
return Result.Success();
} }
public void Dispose() public void Dispose()
{ {
_states.Clear(); DisposeAsync().AsTask().Wait();
_current?.OnExitingAsync().GetAwaiter().GetResult();
_current?.OnExitedAsync().GetAwaiter().GetResult();
_current = null;
} }
public async ValueTask DisposeAsync() public async ValueTask DisposeAsync()
{ {
if (_disposed)
{
return;
}
_states.Clear(); _states.Clear();
if (_current != null) if (_current != null)
{ {
await _current.OnExitingAsync(); await _current.OnExitingAsync();
await _current.OnExitedAsync(); await _current.OnExitedAsync();
} }
_current = null;
_disposed = true;
} }
} }

View File

@@ -1,3 +1,5 @@
using Ghost.Core;
namespace Ghost.Editor.Core.AppState; namespace Ghost.Editor.Core.AppState;
internal interface IAppState internal interface IAppState
@@ -5,22 +7,22 @@ internal interface IAppState
/// <summary> /// <summary>
/// Called when exiting the state. /// Called when exiting the state.
/// </summary> /// </summary>
public Task OnExitingAsync(); public Task<Result> OnExitingAsync();
/// <summary> /// <summary>
/// Called when entering the state, right after OnEnteringAsync. /// Called when entering the state, right after OnEnteringAsync.
/// <paramref name="parameter">can be used to pass data into the state, such as a project to load.</summary> /// <paramref name="parameter">can be used to pass data into the state, such as a project to load.</summary>
/// </summary> /// </summary>
public Task OnEnteringAsync(object? parameter); public Task<Result> OnEnteringAsync(object? parameter);
/// <summary> /// <summary>
/// Called when exiting the state, specifically for pose transitions. /// Called when exiting the state, specifically for pose transitions.
/// </summary> /// </summary>
public Task OnExitedAsync(); public Task<Result> OnExitedAsync();
/// <summary> /// <summary>
/// Called when entered the state, specifically after the state has been fully initialized and is ready for interaction. /// Called when entered the state, specifically after the state has been fully initialized and is ready for interaction.
/// </summary> /// </summary>
/// <param name="parameter">can be used to pass data into the state, such as a project to load.</param> /// <param name="parameter">can be used to pass data into the state, such as a project to load.</param>
public Task OnEnteredAsync(object? parameter); public Task<Result> OnEnteredAsync(object? parameter);
} }

View File

@@ -1,6 +1,5 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Editor.Core.Utilities; using Ghost.Editor.Core.Utilities;
using Ghost.Engine.Services;
using System.Reflection; using System.Reflection;
using System.Text.Json; using System.Text.Json;
@@ -8,7 +7,7 @@ namespace Ghost.Editor.Core.AssetHandle;
public static partial class AssetDatabase public static partial class AssetDatabase
{ {
private static readonly Dictionary<string, Type> _importerTypeLookup = new(); private static readonly Dictionary<string, Type> s_importerTypeLookup = new();
private static void InitializeMetaData() private static void InitializeMetaData()
{ {
@@ -23,7 +22,7 @@ public static partial class AssetDatabase
var attribute = type.GetCustomAttribute<AssetImporterAttribute>()!; var attribute = type.GetCustomAttribute<AssetImporterAttribute>()!;
foreach (var extension in attribute.SupportedExtensions) foreach (var extension in attribute.SupportedExtensions)
{ {
_importerTypeLookup[extension] = type; s_importerTypeLookup[extension] = type;
} }
} }
@@ -36,12 +35,12 @@ public static partial class AssetDatabase
{ {
if (Directory.Exists(assetPath)) if (Directory.Exists(assetPath))
{ {
return Result<string>.Fail("Folder does not have meta data"); return Result<string>.Failure("Folder does not have meta data");
} }
if (Path.GetExtension(assetPath).Equals(".meta", StringComparison.OrdinalIgnoreCase)) if (Path.GetExtension(assetPath).Equals(".meta", StringComparison.OrdinalIgnoreCase))
{ {
return Result<string>.Fail("Asset path cannot be a meta file"); return Result<string>.Failure("Asset path cannot be a meta file");
} }
return Result<string>.Success(assetPath + ".meta"); return Result<string>.Success(assetPath + ".meta");
@@ -51,7 +50,7 @@ public static partial class AssetDatabase
{ {
var extension = Path.GetExtension(assetPath); var extension = Path.GetExtension(assetPath);
if (_importerTypeLookup.TryGetValue(extension, out var importerType)) if (s_importerTypeLookup.TryGetValue(extension, out var importerType))
{ {
var settingsType = importerType.BaseType?.GetGenericArguments()[0]; var settingsType = importerType.BaseType?.GetGenericArguments()[0];
if (settingsType == null || !typeof(ImporterSettings).IsAssignableFrom(settingsType)) if (settingsType == null || !typeof(ImporterSettings).IsAssignableFrom(settingsType))
@@ -79,28 +78,27 @@ public static partial class AssetDatabase
} }
} }
internal static void GenerateMetaFile(string assetPath) internal static Result GenerateMetaFile(string assetPath)
{ {
var metaFileResult = GetMetaFilePath(assetPath); var metaFileResult = GetMetaFilePath(assetPath);
if (!metaFileResult.success) if (!metaFileResult.IsSuccess)
{ {
Logger.LogError(metaFileResult.message ?? string.Empty); return metaFileResult;
return;
} }
if (File.Exists(metaFileResult.value)) if (File.Exists(metaFileResult.Value))
{ {
var existingMeta = JsonSerializer.Deserialize<AssetMeta>(File.ReadAllText(metaFileResult.value)); var existingMeta = JsonSerializer.Deserialize<AssetMeta>(File.ReadAllText(metaFileResult.Value));
if (existingMeta != null && _assetPathLookup.TryGetValue(existingMeta.Guid, out var path)) if (existingMeta != null && s_assetPathLookup.TryGetValue(existingMeta.Guid, out var path))
{ {
if (assetPath != path) if (assetPath != path)
{ {
existingMeta.Guid = Guid.NewGuid(); existingMeta.Guid = Guid.NewGuid();
WriteMetaFile(metaFileResult.value, existingMeta); WriteMetaFile(metaFileResult.Value, existingMeta);
} }
} }
return; return Result.Success();
} }
var defaultSettings = GetDefaultSettingsForAsset(assetPath); var defaultSettings = GetDefaultSettingsForAsset(assetPath);
@@ -110,7 +108,9 @@ public static partial class AssetDatabase
Settings = defaultSettings Settings = defaultSettings
}; };
WriteMetaFile(metaFileResult.value, metaData); WriteMetaFile(metaFileResult.Value, metaData);
return Result.Success();
} }
private static void OnAssetCreated(object sender, FileSystemEventArgs e) private static void OnAssetCreated(object sender, FileSystemEventArgs e)
@@ -121,19 +121,19 @@ public static partial class AssetDatabase
private static void OnAssetDeleted(object sender, FileSystemEventArgs e) private static void OnAssetDeleted(object sender, FileSystemEventArgs e)
{ {
var metaFileResult = GetMetaFilePath(e.FullPath); var metaFileResult = GetMetaFilePath(e.FullPath);
if (metaFileResult.success && File.Exists(metaFileResult.value)) if (metaFileResult.IsSuccess && File.Exists(metaFileResult.Value))
{ {
try try
{ {
var meta = JsonSerializer.Deserialize<AssetMeta>(File.ReadAllText(metaFileResult.value)); var meta = JsonSerializer.Deserialize<AssetMeta>(File.ReadAllText(metaFileResult.Value));
if (meta != null if (meta != null
&& _assetPathLookup.TryGetValue(meta.Guid, out var path) && s_assetPathLookup.TryGetValue(meta.Guid, out var path)
&& path == e.FullPath) && path == e.FullPath)
{ {
_assetPathLookup.Remove(meta.Guid); s_assetPathLookup.Remove(meta.Guid);
} }
File.Delete(metaFileResult.value); File.Delete(metaFileResult.Value);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -6,7 +6,7 @@ public static partial class AssetDatabase
{ {
private static FileSystemWatcher? _watcher; private static FileSystemWatcher? _watcher;
private static readonly Dictionary<Guid, string> _assetPathLookup = new(); private static readonly Dictionary<Guid, string> s_assetPathLookup = new();
public static DirectoryInfo? AssetsDirectory public static DirectoryInfo? AssetsDirectory
{ {

View File

@@ -56,11 +56,11 @@ public partial class ValueControl<T> : Control
} }
/// <summary> /// <summary>
/// Sets the value without notifying the change event. /// Sets the _value without notifying the change event.
/// </summary> /// </summary>
/// <param name="value">The new value to set.</param> /// <param name="value">The new _value to set.</param>
/// <remarks>This method only suppresses the change event notification, not the <see cref="ValueChanged(T, T)"/> method. /// <remarks>This method only suppresses the change event notification, not the <see cref="ValueChanged(T, T)"/> method.
/// Useful when you need to change the value programmatically without triggering the change event.</remarks> /// Useful when you need to change the _value programmatically without triggering the change event.</remarks>
public void SetValueWithoutNotifying(T value) public void SetValueWithoutNotifying(T value)
{ {
_suppressChangedEvent = true; _suppressChangedEvent = true;

View File

@@ -73,17 +73,17 @@ internal partial class CreateProjectViewModel(INotificationService notificationS
} }
var result = await projectService.CreateProjectAsync(ProjectName, ProjectLocation, EngineData.s_engineVersion, SelectedTemplate.Value.directory); var result = await projectService.CreateProjectAsync(ProjectName, ProjectLocation, EngineData.s_engineVersion, SelectedTemplate.Value.directory);
if (!result.success) if (result.IsFailure)
{ {
notificationService.ShowNotification(result.message, MessageType.Error); notificationService.ShowNotification(result.Message, MessageType.Error);
return; return;
} }
try try
{ {
await stateService.TransitionToAsync(StateKey.EngineEditor, result.value); await stateService.TransitionToAsync(StateKey.EngineEditor, result.Value);
} }
catch (System.Exception e) catch (Exception e)
{ {
notificationService.ShowNotification($"Failed to load project: {e.Message}", MessageType.Error); notificationService.ShowNotification($"Failed to load project: {e.Message}", MessageType.Error);
} }

View File

@@ -66,14 +66,14 @@ internal partial class OpenProjectViewModel(ProjectService projectService, INoti
if (rootFolder != null) if (rootFolder != null)
{ {
var result = await projectService.AddProjectFromDirectoryAsync(rootFolder.Path); var result = await projectService.AddProjectFromDirectoryAsync(rootFolder.Path);
if (result.success) if (result.IsSuccess)
{ {
projects.Add(result.value); projects.Add(result.Value);
goto CloseDropPanel; goto CloseDropPanel;
} }
else else
{ {
errorMessage = result.message; errorMessage = result.Message;
} }
} }
} }

View File

@@ -7,7 +7,7 @@ public abstract class ScriptComponent : IComponentData
internal World _world = null!; internal World _world = null!;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether this script component is enabled. /// Gets or sets a Value indicating whether this script component is enabled.
/// </summary> /// </summary>
public bool Enable public bool Enable
{ {

View File

@@ -108,7 +108,7 @@ public readonly struct EntityManager : IDisposable
/// </summary> /// </summary>
/// <typeparam name="T">The type of the component to set.</typeparam> /// <typeparam name="T">The type of the component to set.</typeparam>
/// <param name="entity">The entity for which the component is to be add.</param> /// <param name="entity">The entity for which the component is to be add.</param>
/// <param name="component">The component value to add.</param> /// <param name="component">The component Value to add.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void AddComponent<T>(Entity entity, T component) public readonly void AddComponent<T>(Entity entity, T component)
where T : unmanaged, IComponentData where T : unmanaged, IComponentData
@@ -145,7 +145,7 @@ public readonly struct EntityManager : IDisposable
/// Sets a component of the specified type for the given <see cref="Entity"/>. /// Sets a component of the specified type for the given <see cref="Entity"/>.
/// </summary> /// </summary>
/// <param name="entity">The entity for which the component is to be set.</param> /// <param name="entity">The entity for which the component is to be set.</param>
/// <param name="component">The component value to set.</param> /// <param name="component">The component Value to set.</param>
/// <param name="type">The type of the component to set.</param> /// <param name="type">The type of the component to set.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetComponent(Entity entity, IComponentData component, Type type) public readonly void SetComponent(Entity entity, IComponentData component, Type type)
@@ -169,7 +169,7 @@ public readonly struct EntityManager : IDisposable
/// </summary> /// </summary>
/// <typeparam name="T">The type of the component to set.</typeparam> /// <typeparam name="T">The type of the component to set.</typeparam>
/// <param name="entity">The entity for which the component is to be set.</param> /// <param name="entity">The entity for which the component is to be set.</param>
/// <param name="component">The component value to set.</param> /// <param name="component">The component Value to set.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetComponent<T>(Entity entity, in T component) public readonly void SetComponent<T>(Entity entity, in T component)
where T : unmanaged, IComponentData where T : unmanaged, IComponentData

View File

@@ -40,11 +40,11 @@ public struct QueryFilter : IDisposable
if (!allMask.IsCreated) if (!allMask.IsCreated)
{ {
allMask = new UnsafeBitSet(mask.Length, Allocator.Stack, AllocationOption.None); allMask = new UnsafeBitSet(mask.Count, Allocator.Stack, AllocationOption.None);
allMask.SetAll(); allMask.SetAll();
} }
allMask.AndOperation(mask); allMask.And(mask);
} }
foreach (var typeHandle in _any) foreach (var typeHandle in _any)
@@ -53,10 +53,10 @@ public struct QueryFilter : IDisposable
if (!anyMask.IsCreated) if (!anyMask.IsCreated)
{ {
anyMask = new UnsafeBitSet(mask.Length, Allocator.Stack); anyMask = new UnsafeBitSet(mask.Count, Allocator.Stack);
} }
anyMask.OrOperation(mask); anyMask.And(mask);
} }
foreach (var typeHandle in _absent) foreach (var typeHandle in _absent)
@@ -65,25 +65,25 @@ public struct QueryFilter : IDisposable
if (!absentMask.IsCreated) if (!absentMask.IsCreated)
{ {
absentMask = new UnsafeBitSet(mask.Length, Allocator.Stack); absentMask = new UnsafeBitSet(mask.Count, Allocator.Stack);
} }
absentMask.OrOperation(mask); absentMask.Or(mask);
} }
if (allMask.IsCreated) if (allMask.IsCreated)
{ {
result.AndOperation(allMask); result.And(allMask);
} }
if (anyMask.IsCreated) if (anyMask.IsCreated)
{ {
result.AndOperation(anyMask); result.And(anyMask);
} }
if (absentMask.IsCreated) if (absentMask.IsCreated)
{ {
result.AndOperation(~absentMask); result.And(~absentMask);
} }
} }

View File

@@ -1,12 +1,15 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
namespace Ghost.Graphics.RHI; namespace Ghost.Graphics.Contracts;
public struct CompileResult : IDisposable public struct CompileResult : IDisposable
{ {
public UnsafeArray<byte> bytecode; public UnsafeArray<byte> bytecode;
public ShaderReflectionData reflectionData;
public readonly bool IsCreated => bytecode.IsCreated; public readonly bool IsCreated => bytecode.IsCreated;
@@ -16,6 +19,20 @@ public struct CompileResult : IDisposable
} }
} }
public struct GraphicsCompiledResult : IDisposable
{
public CompileResult tsResult;
public CompileResult msResult;
public CompileResult psResult;
public void Dispose()
{
tsResult.Dispose();
msResult.Dispose();
psResult.Dispose();
}
}
public ref struct CompilerConfig public ref struct CompilerConfig
{ {
public ReadOnlySpan<string> defines; public ReadOnlySpan<string> defines;
@@ -49,7 +66,8 @@ public enum CompilerOption
None = 0, None = 0,
KeepDebugInfo = 1 << 0, KeepDebugInfo = 1 << 0,
KeepReflections = 1 << 1, KeepReflections = 1 << 1,
WarnAsError = 1 << 2 WarnAsError = 1 << 2,
SpirvCrossCompile = 1 << 3
} }
public enum ShaderStage public enum ShaderStage
@@ -125,6 +143,7 @@ public readonly struct ShaderReflectionData
public unsafe interface IShaderCompiler public unsafe interface IShaderCompiler
{ {
Result<CompileResult> Compile(ref readonly CompilerConfig config, Allocator allocator, void** ppReflection); Result<CompileResult> Compile(ref readonly CompilerConfig config, Allocator allocator);
Result<ShaderReflectionData> PerformDXCReflection<T>(T* pReflectionBlob) where T : unmanaged; Result<GraphicsCompiledResult> CompilePass(IPassDescriptor descriptor);
Result<GraphicsCompiledResult> LoadCompiledCache(ShaderPassKey key);
} }

View File

@@ -126,7 +126,7 @@ public ref struct MaterialAccessor
/// Sets a float property in the material's constant buffer. /// Sets a float property in the material's constant buffer.
/// </summary> /// </summary>
/// <param name="propertyName">The name of the property to set.</param> /// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param> /// <param name="value">The Value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetFloat(string propertyName, in float value) public readonly void SetFloat(string propertyName, in float value)
{ {
@@ -137,7 +137,7 @@ public ref struct MaterialAccessor
/// Sets a uint property in the material's constant buffer (useful for texture indices). /// Sets a uint property in the material's constant buffer (useful for texture indices).
/// </summary> /// </summary>
/// <param name="propertyName">The name of the property to set.</param> /// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param> /// <param name="value">The Value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetUInt(string propertyName, in uint value) public readonly void SetUInt(string propertyName, in uint value)
{ {
@@ -148,7 +148,7 @@ public ref struct MaterialAccessor
/// Sets a Vector property in the material's constant buffer. /// Sets a Vector property in the material's constant buffer.
/// </summary> /// </summary>
/// <param name="propertyName">The name of the property to set.</param> /// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param> /// <param name="value">The Value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetVector(string propertyName, in float4 value) public readonly void SetVector(string propertyName, in float4 value)
{ {
@@ -159,7 +159,7 @@ public ref struct MaterialAccessor
/// Sets a Matrix property in the material's constant buffer. /// Sets a Matrix property in the material's constant buffer.
/// </summary> /// </summary>
/// <param name="propertyName">The name of the property to set.</param> /// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param> /// <param name="value">The Value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetMatrix(string propertyName, in float4x4 value) public readonly void SetMatrix(string propertyName, in float4x4 value)
{ {

View File

@@ -1,4 +1,5 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
@@ -17,6 +18,7 @@ public unsafe readonly ref struct RenderingContext
public ICommandBuffer CopyCommandBuffer => _copyCmd; public ICommandBuffer CopyCommandBuffer => _copyCmd;
public ICommandBuffer ComputeCommandBuffer => _computeCmd; public ICommandBuffer ComputeCommandBuffer => _computeCmd;
public IShaderCompiler ShaderCompiler => _engine.ShaderCompiler;
public IResourceAllocator ResourceAllocator => _engine.ResourceAllocator; public IResourceAllocator ResourceAllocator => _engine.ResourceAllocator;
public IResourceDatabase ResourceDatabase => _engine.ResourceDatabase; public IResourceDatabase ResourceDatabase => _engine.ResourceDatabase;
public IPipelineLibrary PipelineLibrary => _engine.PipelineLibrary; public IPipelineLibrary PipelineLibrary => _engine.PipelineLibrary;

View File

@@ -14,9 +14,9 @@ using static TerraFX.Aliases.DXGI_Alias;
namespace Ghost.Graphics.D3D12; namespace Ghost.Graphics.D3D12;
internal unsafe class D3D12CommandBuffer : D3D12RHIObject<ID3D12GraphicsCommandList10>, ICommandBuffer internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList10>, ICommandBuffer
{ {
private ComPtr<ID3D12CommandAllocator> _allocator; private UniquePtr<ID3D12CommandAllocator> _allocator;
private readonly D3D12PipelineLibrary _pipelineLibrary; private readonly D3D12PipelineLibrary _pipelineLibrary;
private readonly D3D12ResourceDatabase _resourceDatabase; private readonly D3D12ResourceDatabase _resourceDatabase;
@@ -282,8 +282,15 @@ internal unsafe class D3D12CommandBuffer : D3D12RHIObject<ID3D12GraphicsCommandL
ThrowIfNotRecording(); ThrowIfNotRecording();
IncrementCommandCount(); IncrementCommandCount();
var shaderPipeline = _pipelineLibrary.GetGraphicsPSO(pipelineKey).GetValueOrThrow(); var psor = _pipelineLibrary.GetGraphicsPSO(pipelineKey);
nativeObject.Get()->SetPipelineState(shaderPipeline.value); if (psor.Status != ResultStatus.Success)
{
#if DEBUG || GHOST_EDITOR
Logger.LogError($"Failed to get graphics pipeline state object for key {pipelineKey}: {psor.Status}");
#endif
}
nativeObject.Get()->SetPipelineState(psor.Value);
} }
public void SetConstantBufferView(uint slot, Handle<GraphicsBuffer> buffer) public void SetConstantBufferView(uint slot, Handle<GraphicsBuffer> buffer)
@@ -479,7 +486,7 @@ internal unsafe class D3D12CommandBuffer : D3D12RHIObject<ID3D12GraphicsCommandL
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
if (IsDisposed) if (Disposed)
{ {
return; return;
} }
@@ -489,7 +496,7 @@ internal unsafe class D3D12CommandBuffer : D3D12RHIObject<ID3D12GraphicsCommandL
throw new InvalidOperationException("Command buffer is still recording"); throw new InvalidOperationException("Command buffer is still recording");
} }
MemoryLeakException.ThrowIfRefCountNonZero(_allocator.Reset()); _allocator.Dispose();
_commandCount = 0; _commandCount = 0;
base.Dispose(disposing); base.Dispose(disposing);

View File

@@ -1,6 +1,7 @@
using Ghost.Core.Utilities; using Ghost.Core.Utilities;
using Ghost.Graphics.D3D12.Utilities; using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using TerraFX.Interop.DirectX; using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows; using TerraFX.Interop.Windows;
@@ -9,19 +10,18 @@ namespace Ghost.Graphics.D3D12;
/// <summary> /// <summary>
/// D3D12 implementation of command queue interface /// D3D12 implementation of command queue interface
/// </summary> /// </summary>
internal unsafe class D3D12CommandQueue : ICommandQueue internal unsafe class D3D12CommandQueue : D3D12Object<ID3D12CommandQueue>, ICommandQueue
{ {
private ComPtr<ID3D12CommandQueue> _queue; private UniquePtr<ID3D12Fence1> _fence;
private ComPtr<ID3D12Fence1> _fence;
private readonly AutoResetEvent _fenceEvent; private readonly AutoResetEvent _fenceEvent;
private ulong _fenceValue; private ulong _fenceValue;
private bool _disposed;
public CommandQueueType Type public CommandQueueType Type
{ {
get; get;
} }
public ID3D12CommandQueue* NativeQueue => _queue.Get();
public ID3D12CommandQueue* NativeQueue => nativeObject.Get();
public D3D12CommandQueue(ID3D12Device14* pDevice, CommandQueueType type) public D3D12CommandQueue(ID3D12Device14* pDevice, CommandQueueType type)
{ {
@@ -41,17 +41,25 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
ThrowIfFailed(pDevice->CreateCommandQueue(&queueDesc, __uuidof(pQueue), (void**)&pQueue)); ThrowIfFailed(pDevice->CreateCommandQueue(&queueDesc, __uuidof(pQueue), (void**)&pQueue));
ThrowIfFailed(pDevice->CreateFence(0, D3D12_FENCE_FLAGS.D3D12_FENCE_FLAG_NONE, __uuidof(pFence), (void**)&pFence)); ThrowIfFailed(pDevice->CreateFence(0, D3D12_FENCE_FLAGS.D3D12_FENCE_FLAG_NONE, __uuidof(pFence), (void**)&pFence));
_queue.Attach(pQueue); nativeObject.Attach(pQueue);
_fence.Attach(pFence); _fence.Attach(pFence);
} }
~D3D12CommandQueue() private static D3D12_COMMAND_LIST_TYPE ConvertCommandQueueType(CommandQueueType type)
{ {
Dispose(); return type switch
{
CommandQueueType.Graphics => D3D12_COMMAND_LIST_TYPE.D3D12_COMMAND_LIST_TYPE_DIRECT,
CommandQueueType.Compute => D3D12_COMMAND_LIST_TYPE.D3D12_COMMAND_LIST_TYPE_COMPUTE,
CommandQueueType.Copy => D3D12_COMMAND_LIST_TYPE.D3D12_COMMAND_LIST_TYPE_COPY,
_ => throw new ArgumentException($"Unknown command queue type: {type}")
};
} }
public void Submit(ICommandBuffer commandBuffer) public void Submit(ICommandBuffer commandBuffer)
{ {
ThrowIfDisposed();
if (commandBuffer.IsEmpty) if (commandBuffer.IsEmpty)
{ {
return; return;
@@ -61,7 +69,7 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
{ {
var commandList = d3d12CommandBuffer.NativeCommandList; var commandList = d3d12CommandBuffer.NativeCommandList;
var commandListPtr = (ID3D12CommandList*)commandList; var commandListPtr = (ID3D12CommandList*)commandList;
_queue.Get()->ExecuteCommandLists(1, &commandListPtr); nativeObject.Get()->ExecuteCommandLists(1, &commandListPtr);
} }
else else
{ {
@@ -71,6 +79,8 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
public void Submit(params ReadOnlySpan<ICommandBuffer> commandBuffers) public void Submit(params ReadOnlySpan<ICommandBuffer> commandBuffers)
{ {
ThrowIfDisposed();
Span<int> executableIndices = stackalloc int[commandBuffers.Length]; Span<int> executableIndices = stackalloc int[commandBuffers.Length];
executableIndices.Fill(-1); executableIndices.Fill(-1);
@@ -107,18 +117,22 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
currentIndex++; currentIndex++;
} }
_queue.Get()->ExecuteCommandLists((uint)currentIndex, ppCommandLists); nativeObject.Get()->ExecuteCommandLists((uint)currentIndex, ppCommandLists);
} }
public ulong Signal(ulong value) public ulong Signal(ulong value)
{ {
ThrowIfDisposed();
_fenceValue = value; _fenceValue = value;
_queue.Get()->Signal((ID3D12Fence*)_fence.Get(), _fenceValue); nativeObject.Get()->Signal((ID3D12Fence*)_fence.Get(), _fenceValue);
return _fenceValue; return _fenceValue;
} }
public void WaitForValue(ulong value) public void WaitForValue(ulong value)
{ {
ThrowIfDisposed();
if (_fence.Get()->GetCompletedValue() < value) if (_fence.Get()->GetCompletedValue() < value)
{ {
var handle = new HANDLE((void*)_fenceEvent.SafeWaitHandle.DangerousGetHandle()); var handle = new HANDLE((void*)_fenceEvent.SafeWaitHandle.DangerousGetHandle());
@@ -131,39 +145,28 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
public ulong GetCompletedValue() public ulong GetCompletedValue()
{ {
ThrowIfDisposed();
return _fence.Get()->GetCompletedValue(); return _fence.Get()->GetCompletedValue();
} }
public void WaitIdle() public void WaitIdle()
{ {
ThrowIfDisposed();
var fenceValue = Signal(Interlocked.Increment(ref _fenceValue)); var fenceValue = Signal(Interlocked.Increment(ref _fenceValue));
WaitForValue(fenceValue); WaitForValue(fenceValue);
} }
private static D3D12_COMMAND_LIST_TYPE ConvertCommandQueueType(CommandQueueType type) protected override void Dispose(bool disposing)
{ {
return type switch if (Disposed)
{
CommandQueueType.Graphics => D3D12_COMMAND_LIST_TYPE.D3D12_COMMAND_LIST_TYPE_DIRECT,
CommandQueueType.Compute => D3D12_COMMAND_LIST_TYPE.D3D12_COMMAND_LIST_TYPE_COMPUTE,
CommandQueueType.Copy => D3D12_COMMAND_LIST_TYPE.D3D12_COMMAND_LIST_TYPE_COPY,
_ => throw new ArgumentException($"Unknown command queue type: {type}")
};
}
public void Dispose()
{
if (_disposed)
{ {
return; return;
} }
_fenceEvent?.Dispose(); _fenceEvent?.Dispose();
_fence.Dispose(); _fence.Dispose();
_queue.Dispose();
_disposed = true; base.Dispose(disposing);
GC.SuppressFinalize(this);
} }
} }

View File

@@ -1,3 +1,5 @@
using Ghost.Core.Utilities;
using Misaki.HighPerformance.LowLevel;
using TerraFX.Interop.DirectX; using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows; using TerraFX.Interop.Windows;
@@ -7,9 +9,9 @@ namespace Ghost.Graphics.D3D12;
internal unsafe class D3D12DebugLayer internal unsafe class D3D12DebugLayer
{ {
private readonly ComPtr<ID3D12Debug6> _d3d12Debug; private UniquePtr<ID3D12Debug6> _d3d12Debug;
private readonly ComPtr<IDXGIDebug1> _dxgiDebug; private UniquePtr<IDXGIDebug1> _dxgiDebug;
private readonly ComPtr<IDXGIInfoQueue> _dxgiInfoQueue; private UniquePtr<IDXGIInfoQueue> _dxgiInfoQueue;
public D3D12DebugLayer() public D3D12DebugLayer()
{ {

View File

@@ -1,8 +1,9 @@
using Ghost.Graphics.Contracts;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Utilities;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Ghost.Graphics.RHI;
namespace Ghost.Graphics.D3D12; namespace Ghost.Graphics.D3D12;
internal unsafe class D3D12GraphicsEngine : IGraphicsEngine internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
@@ -12,6 +13,7 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
#endif #endif
private readonly D3D12RenderDevice _device; private readonly D3D12RenderDevice _device;
private readonly DxcShaderCompiler _shaderCompiler;
private readonly D3D12DescriptorAllocator _descriptorAllocator; private readonly D3D12DescriptorAllocator _descriptorAllocator;
private readonly D3D12ResourceDatabase _resourceDatabase; private readonly D3D12ResourceDatabase _resourceDatabase;
private readonly D3D12PipelineLibrary _pipelineLibrary; private readonly D3D12PipelineLibrary _pipelineLibrary;
@@ -23,6 +25,7 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
private bool _disposed; private bool _disposed;
public IRenderDevice Device => _device; public IRenderDevice Device => _device;
public IShaderCompiler ShaderCompiler => _shaderCompiler;
public IPipelineLibrary PipelineLibrary => _pipelineLibrary; public IPipelineLibrary PipelineLibrary => _pipelineLibrary;
public IResourceDatabase ResourceDatabase => _resourceDatabase; public IResourceDatabase ResourceDatabase => _resourceDatabase;
public IResourceAllocator ResourceAllocator => _resourceAllocator; public IResourceAllocator ResourceAllocator => _resourceAllocator;
@@ -34,6 +37,7 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
_debugLayer = new(); _debugLayer = new();
#endif #endif
_device = new(); _device = new();
_shaderCompiler = new();
_descriptorAllocator = new(_device); _descriptorAllocator = new(_device);
_resourceDatabase = new(_descriptorAllocator); _resourceDatabase = new(_descriptorAllocator);

View File

@@ -0,0 +1,132 @@
using Ghost.Core.Utilities;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using System.Runtime.CompilerServices;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
namespace Ghost.Graphics.D3D12;
internal abstract class D3D12RHIObject : IRHIObject
{
public string Name
{
get; set;
} = string.Empty;
}
internal abstract unsafe class D3D12Object<T> : IRHIObject, IDisposable
where T : unmanaged, ID3D12Object.Interface
{
private bool _disposed;
private string _name = string.Empty;
protected UniquePtr<T> nativeObject;
protected bool Disposed => _disposed;
public string Name
{
get => _name;
set
{
if (_name == value)
{
return;
}
_name = value;
if (nativeObject.Get() != null)
{
nativeObject.Get()->SetName(value);
}
}
}
~D3D12Object()
{
Dispose(false);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, this);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
nativeObject.Dispose();
_disposed = true;
}
}
internal abstract class IUnknownObject<T> : IRHIObject, IDisposable
where T : unmanaged, IUnknown.Interface
{
private bool _disposed;
private string _name = string.Empty;
protected UniquePtr<T> nativeObject;
protected bool Disposed => _disposed;
public string Name
{
get => _name;
set
{
if (_name == value)
{
return;
}
_name = value;
}
}
~IUnknownObject()
{
Dispose(false);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, this);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
nativeObject.Dispose();
_disposed = true;
}
}

View File

@@ -1,9 +1,11 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Core.Graphics; using Ghost.Core.Graphics;
using Ghost.Core.Utilities; using Ghost.Core.Utilities;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.Core; using Ghost.Graphics.Core;
using Ghost.Graphics.D3D12.Utilities; using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.LowLevel.Utilities;
@@ -17,25 +19,10 @@ using static TerraFX.Aliases.D3D12_Alias;
namespace Ghost.Graphics.D3D12; namespace Ghost.Graphics.D3D12;
internal struct D3D12GraphicsCompiledResult : IDisposable
{
public CompileResult tsResult;
public CompileResult msResult;
public CompileResult psResult;
public CBufferInfo cbufferInfo;
public void Dispose()
{
tsResult.Dispose();
msResult.Dispose();
psResult.Dispose();
}
}
internal struct D3D12PipelineState : IDisposable internal struct D3D12PipelineState : IDisposable
{ {
public D3DX12_MESH_SHADER_PIPELINE_STATE_DESC psoDesc; public D3DX12_MESH_SHADER_PIPELINE_STATE_DESC psoDesc;
public ComPtr<ID3D12PipelineState> pso; public UniquePtr<ID3D12PipelineState> pso;
public ShaderPassKey shaderPass; public ShaderPassKey shaderPass;
public void Dispose() public void Dispose()
@@ -49,12 +36,11 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
private readonly D3D12RenderDevice _device; private readonly D3D12RenderDevice _device;
private readonly D3D12ResourceDatabase _resourceDatabase; private readonly D3D12ResourceDatabase _resourceDatabase;
private ComPtr<ID3D12PipelineLibrary1> _library; private UniquePtr<ID3D12PipelineLibrary1> _library;
private ComPtr<ID3D12RootSignature> _defaultRootSignature; private UniquePtr<ID3D12RootSignature> _defaultRootSignature;
private readonly Dictionary<GraphicsPipelineKey, D3D12PipelineState> _pipelineCache; private readonly Dictionary<GraphicsPipelineKey, D3D12PipelineState> _pipelineCache;
// NOTE: This is just a temporary cache for compiled shader code. We will implement a proper disk cache later. private readonly Dictionary<ShaderPassKey, CBufferInfo> _cbufferInfoCache;
private readonly Dictionary<ShaderPassKey, D3D12GraphicsCompiledResult> _compiledResults;
public ID3D12RootSignature* DefaultRootSignature => _defaultRootSignature.Get(); public ID3D12RootSignature* DefaultRootSignature => _defaultRootSignature.Get();
@@ -63,13 +49,13 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
_device = device; _device = device;
_resourceDatabase = resourceDatabase; _resourceDatabase = resourceDatabase;
_pipelineCache = new(); _pipelineCache = new Dictionary<GraphicsPipelineKey, D3D12PipelineState>();
_compiledResults = new(); _cbufferInfoCache = new Dictionary<ShaderPassKey, CBufferInfo>();
CreateDefaultRootSignature(); CreateDefaultRootSignature();
} }
private void CreateDefaultRootSignature() private Result CreateDefaultRootSignature()
{ {
_defaultRootSignature = default; _defaultRootSignature = default;
@@ -154,22 +140,39 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
Desc_1_1 = rootSignatureDesc Desc_1_1 = rootSignatureDesc
}; };
using ComPtr<ID3DBlob> signature = default; ID3DBlob* pSignature = default;
using ComPtr<ID3DBlob> error = default; ID3DBlob* pError = default;
var serializeResult = D3D12SerializeVersionedRootSignature(&versionedDesc, signature.GetAddressOf(), error.GetAddressOf()); try
{
var serializeResult = D3D12SerializeVersionedRootSignature(&versionedDesc, &pSignature, &pError);
if (serializeResult.FAILED) if (serializeResult.FAILED)
{ {
var errorMsg = error.Get() != null ? Marshal.PtrToStringUTF8((nint)error.Get()->GetBufferPointer()) : "Unknown error"; var errorMsg = pError != null ? Marshal.PtrToStringUTF8((nint)pError->GetBufferPointer()) : "Unknown error";
throw new InvalidOperationException($"Failed to serialize default root signature: {errorMsg}"); return Result.Failure($"Failed to serialize default root signature: {errorMsg}");
} }
ID3D12RootSignature* pRootSignature = default; ID3D12RootSignature* pRootSignature = default;
ThrowIfFailed(_device.NativeDevice->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(), ThrowIfFailed(_device.NativeDevice->CreateRootSignature(0, pSignature->GetBufferPointer(), pSignature->GetBufferSize(),
__uuidof(pRootSignature), (void**)&pRootSignature)); __uuidof(pRootSignature), (void**)&pRootSignature));
_defaultRootSignature.Attach(pRootSignature); _defaultRootSignature.Attach(pRootSignature);
} }
finally
{
if (pSignature != null)
{
pSignature->Release();
}
if (pError != null)
{
pError->Release();
}
}
return Result.Success();
}
public void InitializeLibrary(string? filePath) public void InitializeLibrary(string? filePath)
{ {
@@ -208,20 +211,20 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
fs.Write(buffer.AsSpan()); fs.Write(buffer.AsSpan());
} }
private static Result<CBufferInfo> ValidateReflectionData(FullPassDescriptor descriptor, ShaderReflectionData reflectionData) private static Result<CBufferInfo> ValidateReflectionData(ShaderReflectionData reflectionData)
{ {
CBufferInfo cbufferInfo = default; CBufferInfo cbufferInfo;
foreach (var info in reflectionData.ResourcesBindings) foreach (var info in reflectionData.ResourcesBindings)
{ {
if (info.BindPoint > 3) if (info.BindPoint > 3)
{ {
return Result.Fail($"Resource binding point {info.BindPoint} is out of range. Only binding points 0-3 are supported in the current root signature."); return Result.Failure($"Resource binding point {info.BindPoint} is out of range. Only binding points 0-3 are supported in the current root signature.");
} }
if (info.Type != D3D_SHADER_INPUT_TYPE.D3D_SIT_CBUFFER) if (info.Type != ShaderInputType.ConstantBuffer)
{ {
return Result.Fail($"Resource binding type {info.Type} is not supported. Only constant buffers are supported in the current root signature."); return Result.Failure($"Resource binding type {info.Type} is not supported. Only constant buffers are supported in the current root signature.");
} }
if (info.BindPoint == RootSignatureLayout.PER_MATERIAL_BUFFER_SLOT) if (info.BindPoint == RootSignatureLayout.PER_MATERIAL_BUFFER_SLOT)
@@ -239,112 +242,11 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
} }
} }
return Result.Fail("Per-material constant buffer not found in shader reflection data."); return Result.Failure("Per-material constant buffer not found in shader reflection data.");
// TODO: Validate Cbuffer sizes and bindings. // TODO: Validate Cbuffer sizes and bindings.
} }
private static D3D12GraphicsCompiledResult CompileAndValidateFullPass(FullPassDescriptor descriptor)
{
static CompileResult CompileAndValidate(ref CompilerConfig config, FullPassDescriptor descriptor)
{
IDxcBlob* reflectionBlob = default;
CBufferInfo cbufferInfo = default;
try
{
// TODO: This does not include generated code. This will cause a root signature mismatch.
var result = D3D12ShaderCompiler.Compile(ref config, Allocator.Persistent, (void**)&reflectionBlob).GetValueOrThrow();
if (reflectionBlob != null)
{
var reflection = D3D12ShaderCompiler.PerformDXCReflection(reflectionBlob).GetValueOrThrow();
cbufferInfo = ValidateReflectionData(descriptor, reflection).GetValueOrThrow();
}
return result;
}
finally
{
if (reflectionBlob != null)
{
reflectionBlob->Release();
}
}
}
CompileResult tsResult = default;
var tsEntry = descriptor.taskShader;
if (tsEntry.IsCreated)
{
var config = new CompilerConfig
{
defines = descriptor.defines.AsSpan(),
include = descriptor.generatedCodePath,
shaderPath = tsEntry.shader,
entryPoint = tsEntry.entry,
stage = ShaderStage.TaskShader,
tier = CompilerTier.Tier0,
optimizeLevel = CompilerOptimizeLevel.O3,
options = CompilerOption.KeepReflections,
};
tsResult = CompileAndValidate(ref config, descriptor);
}
CompileResult msResult;
var msEntry = descriptor.meshShader;
if (msEntry.IsCreated)
{
var config = new CompilerConfig
{
defines = descriptor.defines.AsSpan(),
include = descriptor.generatedCodePath,
shaderPath = msEntry.shader,
entryPoint = msEntry.entry,
stage = ShaderStage.MeshShader,
tier = CompilerTier.Tier0,
optimizeLevel = CompilerOptimizeLevel.O3,
options = CompilerOption.KeepReflections,
};
msResult = CompileAndValidate(ref config, descriptor);
}
else
{
throw new InvalidOperationException("Mesh shader expected.");
}
CompileResult psResult;
var psEntry = descriptor.pixelShader;
if (psEntry.IsCreated)
{
var config = new CompilerConfig
{
defines = descriptor.defines.AsSpan(),
include = descriptor.generatedCodePath,
shaderPath = psEntry.shader,
entryPoint = psEntry.entry,
stage = ShaderStage.PixelShader,
tier = CompilerTier.Tier0,
optimizeLevel = CompilerOptimizeLevel.O3,
options = CompilerOption.KeepReflections,
};
psResult = CompileAndValidate(ref config, descriptor);
}
else
{
throw new InvalidOperationException("Pixel shader expected.");
}
return new D3D12GraphicsCompiledResult
{
tsResult = tsResult,
msResult = msResult,
psResult = psResult
};
}
private static D3D12_COMPARISON_FUNC ToD3DCompare(ZTestOptions z) => z switch private static D3D12_COMPARISON_FUNC ToD3DCompare(ZTestOptions z) => z switch
{ {
ZTestOptions.Disabled => D3D12_COMPARISON_FUNC_ALWAYS, ZTestOptions.Disabled => D3D12_COMPARISON_FUNC_ALWAYS,
@@ -366,15 +268,70 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
return D3D12Utility.D3D12_DEPTH_STENCIL_DESC_CREATE(depthEnabled, writeEnabled, cmp); return D3D12Utility.D3D12_DEPTH_STENCIL_DESC_CREATE(depthEnabled, writeEnabled, cmp);
} }
private bool TryGetCompiledCache(ShaderPassKey passKey, out D3D12GraphicsCompiledResult compiled) public Result<GraphicsPipelineKey> CompilePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled)
{ {
return _compiledResults.TryGetValue(passKey, out compiled); static Result<CBufferInfo> ValidatePassReflectionData(ref readonly GraphicsCompiledResult compiled)
{
var msr = ValidateReflectionData(compiled.msResult.reflectionData);
if (msr.IsFailure)
{
return Result.Failure("Validation of mesh shader reflection data failed: " + msr.Message);
} }
private GraphicsPipelineKey CompilePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly D3D12GraphicsCompiledResult compiled) var psr = ValidateReflectionData(compiled.psResult.reflectionData);
if (psr.IsFailure)
{ {
return Result.Failure("Validation of pixel shader reflection data failed: " + psr.Message);
}
if (msr.Value != psr.Value)
{
return Result.Failure("Mesh shader and pixel shader constant buffer layouts do not match.");
}
if (compiled.tsResult.IsCreated)
{
var tsr = ValidateReflectionData(compiled.tsResult.reflectionData);
if (tsr.IsFailure)
{
return Result.Failure("Validation of task shader reflection data failed: " + tsr.Message);
}
if (tsr.Value != msr.Value)
{
return Result.Failure("Task shader and mesh shader constant buffer layouts do not match.");
}
}
return psr.Value;
}
var hash = new GraphicsPipelineHash
{
Id = descriptor.PassId,
RtvCount = (uint)descriptor.RtvFormats.Length,
DsvFormat = descriptor.DsvFormat,
};
var rtvCount = (uint)Math.Min(descriptor.RtvFormats.Length, D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT); var rtvCount = (uint)Math.Min(descriptor.RtvFormats.Length, D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT);
for (var i = 0; i < rtvCount && i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; i++)
{
hash.RtvFormats[i] = descriptor.RtvFormats[i];
}
var key = hash.GetKey();
if (!_pipelineCache.ContainsKey(key))
{
var result = ValidatePassReflectionData(in compiled);
if (result.IsFailure)
{
return Result.Failure(result.Message);
}
_cbufferInfoCache[descriptor.PassId] = result.Value;
var desc = new D3DX12_MESH_SHADER_PIPELINE_STATE_DESC var desc = new D3DX12_MESH_SHADER_PIPELINE_STATE_DESC
{ {
pRootSignature = _defaultRootSignature.Get(), pRootSignature = _defaultRootSignature.Get(),
@@ -412,26 +369,12 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
desc.AS = new D3D12_SHADER_BYTECODE(compiled.tsResult.bytecode.GetUnsafePtr(), (nuint)compiled.tsResult.bytecode.Count); desc.AS = new D3D12_SHADER_BYTECODE(compiled.tsResult.bytecode.GetUnsafePtr(), (nuint)compiled.tsResult.bytecode.Count);
} }
var hash = new GraphicsPipelineHash
{
Id = descriptor.PassId,
RtvCount = (uint)descriptor.RtvFormats.Length,
DsvFormat = descriptor.DsvFormat,
};
for (var i = 0; i < rtvCount && i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; i++) for (var i = 0; i < rtvCount && i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; i++)
{ {
desc.RTVFormats[i] = descriptor.RtvFormats[i].ToDXGIFormat(); desc.RTVFormats[i] = descriptor.RtvFormats[i].ToDXGIFormat();
desc.BlendState.RenderTarget[i].RenderTargetWriteMask = (byte)(descriptor.ColorMask & 0x0F); desc.BlendState.RenderTarget[i].RenderTargetWriteMask = (byte)(descriptor.ColorMask & 0x0F);
hash.RtvFormats[i] = descriptor.RtvFormats[i];
} }
var key = hash.GetKey();
ref var existing = ref CollectionsMarshal.GetValueRefOrAddDefault(_pipelineCache, key, out var exists);
if (!exists)
{
existing.psoDesc = desc;
var meshStream = new CD3DX12_PIPELINE_MESH_STATE_STREAM(in desc); var meshStream = new CD3DX12_PIPELINE_MESH_STATE_STREAM(in desc);
var streamDesc = new D3D12_PIPELINE_STATE_STREAM_DESC var streamDesc = new D3D12_PIPELINE_STATE_STREAM_DESC
{ {
@@ -443,7 +386,11 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
var pKeyStr = stackalloc char[GraphicsPipelineKey.KEY_STRING_LENGTH]; var pKeyStr = stackalloc char[GraphicsPipelineKey.KEY_STRING_LENGTH];
var keySpan = new Span<char>(pKeyStr, GraphicsPipelineKey.KEY_STRING_LENGTH); var keySpan = new Span<char>(pKeyStr, GraphicsPipelineKey.KEY_STRING_LENGTH);
key.GetString(keySpan).ThrowIfFailed(); var kr = key.GetString(keySpan);
if (kr.IsFailure)
{
return kr;
}
var hr = _library.Get()->LoadPipeline(pKeyStr, &streamDesc, __uuidof(pPipelineState), (void**)&pPipelineState); var hr = _library.Get()->LoadPipeline(pKeyStr, &streamDesc, __uuidof(pPipelineState), (void**)&pPipelineState);
if (hr == E.E_INVALIDARG) if (hr == E.E_INVALIDARG)
@@ -457,77 +404,35 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
ThrowIfFailed(hr); ThrowIfFailed(hr);
} }
existing.pso.Attach(pPipelineState); D3D12PipelineState pso = default;
pso.shaderPass = descriptor.PassId;
pso.psoDesc = desc;
pso.pso.Attach(pPipelineState);
_pipelineCache[key] = pso;
} }
return key; return key;
} }
public GraphicsPipelineKey CompilePassPSO(IPassDescriptor descriptor, ReadOnlySpan<TextureFormat> rtvs, TextureFormat dsv) public Result<CBufferInfo, ResultStatus> GetCBufferInfo(ShaderPassKey passId)
{ {
GraphicsPipelineKey key = default; if (_cbufferInfoCache.TryGetValue(passId, out var cbufferInfo))
var passKey = new ShaderPassKey(descriptor.Identifier);
var hasCompiledCache = TryGetCompiledCache(passKey, out var compiled);
switch (descriptor)
{ {
case FullPassDescriptor fullPass: return Result.Create(cbufferInfo, ResultStatus.Success);
if (!hasCompiledCache)
{
compiled = CompileAndValidateFullPass(fullPass);
} }
var psoDes = new GraphicsPSODescriptor return Result.Create(default(CBufferInfo), ResultStatus.NotFound);
{
PassId = new ShaderPassKey(fullPass.Identifier),
ZTest = fullPass.localPipeline.zTest,
ZWrite = fullPass.localPipeline.zWrite,
Cull = fullPass.localPipeline.cull,
Blend = fullPass.localPipeline.blend,
ColorMask = fullPass.localPipeline.colorMask,
RtvFormats = rtvs,
DsvFormat = dsv,
};
key = CompilePSO(in psoDes, in compiled);
break;
// Do we need to support other pass types?
case FallbackPassDescriptor:
if (!hasCompiledCache)
{
throw new ArgumentException("FallbackPassDescriptor is not supported for PSO compilation. There may be some inheritance dependency issues.");
} }
break; public Result<SharedPtr<ID3D12PipelineState>, ResultStatus> GetGraphicsPSO(GraphicsPipelineKey key)
default:
break;
}
return key;
}
public Result<Ptr<ID3D12PipelineState>> GetGraphicsPSO(GraphicsPipelineKey key)
{ {
if (_pipelineCache.TryGetValue(key, out var cacheEntry)) if (_pipelineCache.TryGetValue(key, out var cacheEntry))
{ {
return new Ptr<ID3D12PipelineState>(cacheEntry.pso.Get()); return Result.Create(new SharedPtr<ID3D12PipelineState>(cacheEntry.pso.Get()), ResultStatus.Success);
} }
return Result.Fail("Pipeline state not found in cache."); return Result.Create(default(SharedPtr<ID3D12PipelineState>), ResultStatus.NotFound);
}
public Result<CBufferInfo> GetCBufferInfo(ShaderPassKey key)
{
if (_compiledResults.TryGetValue(key, out var compiled))
{
return compiled.cbufferInfo;
}
return Result.Fail("Compiled shader not found in cache.");
} }
public void Dispose() public void Dispose()

View File

@@ -1,67 +0,0 @@
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using System.Runtime.CompilerServices;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
namespace Ghost.Graphics.D3D12;
internal abstract unsafe class D3D12RHIObject<T> : IRHIObject, IDisposable
where T : unmanaged, ID3D12Object.Interface
{
private bool _disposed;
private string _name = string.Empty;
protected ComPtr<T> nativeObject;
protected bool IsDisposed => _disposed;
public string Name
{
get => _name;
set
{
if (_name == value)
{
return;
}
_name = value;
if (nativeObject.Get() != null)
{
nativeObject.Get()->SetName(value);
}
}
}
~D3D12RHIObject()
{
Dispose(false);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, this);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
MemoryLeakException.ThrowIfRefCountNonZero(nativeObject.Reset());
_disposed = true;
}
}

View File

@@ -1,9 +1,10 @@
using Ghost.Core.Utilities; using Ghost.Core.Utilities;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using System.Runtime.Versioning; using Misaki.HighPerformance.LowLevel;
using TerraFX.Interop.DirectX; using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows; using TerraFX.Interop.Windows;
using static TerraFX.Aliases.D3D_Alias; using static TerraFX.Aliases.D3D_Alias;
using static TerraFX.Aliases.D3D12_Alias;
using static TerraFX.Aliases.DXGI_Alias; using static TerraFX.Aliases.DXGI_Alias;
namespace Ghost.Graphics.D3D12; namespace Ghost.Graphics.D3D12;
@@ -11,33 +12,30 @@ namespace Ghost.Graphics.D3D12;
/// <summary> /// <summary>
/// D3D12 implementation of the render device interface /// D3D12 implementation of the render device interface
/// </summary> /// </summary>
internal unsafe class D3D12RenderDevice : IRenderDevice internal unsafe class D3D12RenderDevice : D3D12Object<ID3D12Device14>, IRenderDevice
{ {
private ComPtr<IDXGIFactory7> _dxgiFactory; private UniquePtr<IDXGIFactory7> _dxgiFactory;
private ComPtr<ID3D12Device14> _device; private UniquePtr<IDXGIAdapter1> _adapter;
private ComPtr<IDXGIAdapter1> _adapter;
private readonly D3D12CommandQueue _graphicsQueue; private readonly D3D12CommandQueue _graphicsQueue;
private readonly D3D12CommandQueue _computeQueue; private readonly D3D12CommandQueue _computeQueue;
private readonly D3D12CommandQueue _copyQueue; private readonly D3D12CommandQueue _copyQueue;
private bool _disposed;
public ICommandQueue GraphicsQueue => _graphicsQueue; public ICommandQueue GraphicsQueue => _graphicsQueue;
public ICommandQueue ComputeQueue => _computeQueue; public ICommandQueue ComputeQueue => _computeQueue;
public ICommandQueue CopyQueue => _copyQueue; public ICommandQueue CopyQueue => _copyQueue;
public ID3D12Device14* NativeDevice => _device.Get();
public IDXGIFactory7* DXGIFactory => _dxgiFactory.Get(); public IDXGIFactory7* DXGIFactory => _dxgiFactory.Get();
public ID3D12Device14* NativeDevice => nativeObject.Get();
public IDXGIAdapter1* Adapter => _adapter.Get(); public IDXGIAdapter1* Adapter => _adapter.Get();
public D3D12RenderDevice() public D3D12RenderDevice()
{ {
InitializeDevice(); InitializeDevice();
_graphicsQueue = new D3D12CommandQueue(_device.Get(), CommandQueueType.Graphics); _graphicsQueue = new D3D12CommandQueue(nativeObject.Get(), CommandQueueType.Graphics);
_computeQueue = new D3D12CommandQueue(_device.Get(), CommandQueueType.Compute); _computeQueue = new D3D12CommandQueue(nativeObject.Get(), CommandQueueType.Compute);
_copyQueue = new D3D12CommandQueue(_device.Get(), CommandQueueType.Copy); _copyQueue = new D3D12CommandQueue(nativeObject.Get(), CommandQueueType.Copy);
} }
~D3D12RenderDevice() ~D3D12RenderDevice()
@@ -60,7 +58,7 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
IDXGIAdapter1* pAdapter = default; IDXGIAdapter1* pAdapter = default;
for (uint adapterIndex = 0; for (uint adapterIndex = 0;
_dxgiFactory.Get()->EnumAdapterByGpuPreference(adapterIndex, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, __uuidof<IDXGIAdapter1>(), (void**)&pAdapter).SUCCEEDED; _dxgiFactory.Get()->EnumAdapterByGpuPreference(adapterIndex, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, __uuidof(pAdapter), (void**)&pAdapter).SUCCEEDED;
adapterIndex++) adapterIndex++)
{ {
DXGI_ADAPTER_DESC1 desc = default; DXGI_ADAPTER_DESC1 desc = default;
@@ -84,30 +82,77 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
if (pDevice == null) if (pDevice == null)
{ {
pAdapter->Release(); // Dispose the last adapter we tried. If the operation succeeded, we would have moved it. pAdapter->Release(); // Dispose the last adapter we tried.
throw new PlatformNotSupportedException("Cannot create ID3D12Device with feature level 12.0"); throw new PlatformNotSupportedException("Cannot create ID3D12Device with feature level 12.0");
} }
_device.Attach(pDevice); nativeObject.Attach(pDevice);
} }
public void Dispose() public FeatureSupport GetFeatureSupport()
{ {
if (_disposed) ThrowIfDisposed();
FeatureSupport support = FeatureSupport.None;
D3D12_FEATURE_DATA_D3D12_OPTIONS options = default;
if (nativeObject.Get()->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &options, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS)).SUCCEEDED)
{
if (options.ResourceBindingTier == D3D12_RESOURCE_BINDING_TIER_3)
{
support |= FeatureSupport.BindlessResources;
}
}
D3D12_FEATURE_DATA_D3D12_OPTIONS5 options5 = default;
if (nativeObject.Get()->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS5, &options5, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS5)).SUCCEEDED)
{
if (options5.RaytracingTier != D3D12_RAYTRACING_TIER_NOT_SUPPORTED)
{
support |= FeatureSupport.RayTracing;
}
}
D3D12_FEATURE_DATA_D3D12_OPTIONS6 options6 = default;
if (nativeObject.Get()->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS6, &options6, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS6)).SUCCEEDED)
{
if (options6.VariableShadingRateTier != D3D12_VARIABLE_SHADING_RATE_TIER_NOT_SUPPORTED)
{
support |= FeatureSupport.VariableRateShading;
}
}
D3D12_FEATURE_DATA_D3D12_OPTIONS7 options7 = default;
if (nativeObject.Get()->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS7, &options7, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS7)).SUCCEEDED)
{
if (options7.MeshShaderTier != D3D12_MESH_SHADER_TIER_NOT_SUPPORTED)
{
support |= FeatureSupport.MeshShaders;
}
if (options7.SamplerFeedbackTier != D3D12_SAMPLER_FEEDBACK_TIER_NOT_SUPPORTED)
{
support |= FeatureSupport.SamplerFeedback;
}
}
return support;
}
protected override void Dispose(bool disposing)
{
if (Disposed)
{ {
return; return;
} }
_graphicsQueue?.Dispose(); _graphicsQueue.Dispose();
_computeQueue?.Dispose(); _computeQueue.Dispose();
_copyQueue?.Dispose(); _copyQueue.Dispose();
_device.Reset();
_dxgiFactory.Dispose(); _dxgiFactory.Dispose();
_adapter.Dispose(); _adapter.Dispose();
_disposed = true; base.Dispose(disposing);
GC.SuppressFinalize(this);
} }
} }

View File

@@ -589,9 +589,9 @@ internal sealed unsafe partial class D3D12ResourceAllocator
// TODO: Dedicated pool for copy, render graph, and persistent resources // TODO: Dedicated pool for copy, render graph, and persistent resources
// TODO: Thread safety for resource allocator // TODO: Thread safety for resource allocator
// A common solution is to use ticket. Each allocation request create a ticket and put it into a thread-safe queue. A dedicated thread process the queue and fulfill the requests. // A common solution is to use ticket. Each pAllocation request create a ticket and put it into a thread-safe queue. A dedicated thread process the queue and fulfill the requests.
internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator, IDisposable internal sealed unsafe partial class D3D12ResourceAllocator : IUnknownObject<D3D12MA_Allocator>, IResourceAllocator
{ {
private readonly IFenceSynchronizer _fenceSynchronizer; private readonly IFenceSynchronizer _fenceSynchronizer;
private readonly D3D12RenderDevice _device; private readonly D3D12RenderDevice _device;
@@ -599,11 +599,8 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
private readonly D3D12ResourceDatabase _resourceDatabase; private readonly D3D12ResourceDatabase _resourceDatabase;
private readonly D3D12PipelineLibrary _pipelineLibrary; private readonly D3D12PipelineLibrary _pipelineLibrary;
private ComPtr<D3D12MA_Allocator> _allocator;
private UnsafeQueue<Handle<GPUResource>> _temResources; private UnsafeQueue<Handle<GPUResource>> _temResources;
private bool _disposed;
public D3D12ResourceAllocator( public D3D12ResourceAllocator(
IFenceSynchronizer fenceSynchronizer, IFenceSynchronizer fenceSynchronizer,
D3D12RenderDevice device, D3D12RenderDevice device,
@@ -620,7 +617,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
D3D12MA_Allocator* pAllocator = default; D3D12MA_Allocator* pAllocator = default;
ThrowIfFailed(D3D12MA_CreateAllocator(&desc, &pAllocator)); ThrowIfFailed(D3D12MA_CreateAllocator(&desc, &pAllocator));
_allocator.Attach(pAllocator); nativeObject.Attach(pAllocator);
_fenceSynchronizer = fenceSynchronizer; _fenceSynchronizer = fenceSynchronizer;
_device = device; _device = device;
@@ -637,7 +634,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private Handle<GPUResource> TrackResource(ComPtr<D3D12MA_Allocation> allocation, D3D12_RESOURCE_STATES state, ResourceViewGroup resourceDescriptor, ResourceDesc desc, bool isTemp) private Handle<GPUResource> TrackResource(D3D12MA_Allocation* allocation, D3D12_RESOURCE_STATES state, ResourceViewGroup resourceDescriptor, ResourceDesc desc, bool isTemp)
{ {
var handle = _resourceDatabase.AddResource(allocation, _fenceSynchronizer.CPUFenceValue, D3D12StatesToRHIState(state), resourceDescriptor, desc); var handle = _resourceDatabase.AddResource(allocation, _fenceSynchronizer.CPUFenceValue, D3D12StatesToRHIState(state), resourceDescriptor, desc);
@@ -651,6 +648,8 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
public Handle<Texture> CreateTexture(ref readonly TextureDesc desc, bool isTemp = false) public Handle<Texture> CreateTexture(ref readonly TextureDesc desc, bool isTemp = false)
{ {
ThrowIfDisposed();
CheckTexture2DSize(desc.Width, desc.Height); CheckTexture2DSize(desc.Width, desc.Height);
var d3d12Format = ConvertTextureFormat(desc.Format); var d3d12Format = ConvertTextureFormat(desc.Format);
@@ -706,8 +705,8 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
var initialState = DetermineInitialTextureState(desc.Usage); var initialState = DetermineInitialTextureState(desc.Usage);
ComPtr<D3D12MA_Allocation> allocation = default; D3D12MA_Allocation* pAllocation = default;
ThrowIfFailed(_allocator.Get()->CreateResource(&allocationDesc, &resourceDesc, initialState, null, allocation.GetAddressOf(), Win32Utility.IID_NULL, null)); ThrowIfFailed(nativeObject.Get()->CreateResource(&allocationDesc, &resourceDesc, initialState, null, &pAllocation, Win32Utility.IID_NULL, null));
var resourceDescriptor = ResourceViewGroup.Invalid; var resourceDescriptor = ResourceViewGroup.Invalid;
if (desc.Usage.HasFlag(TextureUsage.ShaderResource)) if (desc.Usage.HasFlag(TextureUsage.ShaderResource))
@@ -716,51 +715,54 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv); var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv);
var isCubeMap = desc.Dimension == TextureDimension.TextureCube || desc.Dimension == TextureDimension.TextureCubeArray; var isCubeMap = desc.Dimension == TextureDimension.TextureCube || desc.Dimension == TextureDimension.TextureCubeArray;
var srvDesc = CreateTextureSrvDesc(allocation.Get()->GetResource(), mipLevels, desc.Slice, isCubeMap); var srvDesc = CreateTextureSrvDesc(pAllocation->GetResource(), mipLevels, desc.Slice, isCubeMap);
_device.NativeDevice->CreateShaderResourceView(allocation.Get()->GetResource(), &srvDesc, cpuHandle); _device.NativeDevice->CreateShaderResourceView(pAllocation->GetResource(), &srvDesc, cpuHandle);
} }
if (desc.Usage.HasFlag(TextureUsage.RenderTarget)) if (desc.Usage.HasFlag(TextureUsage.RenderTarget))
{ {
resourceDescriptor.rtv = _descriptorAllocator.AllocateRTV(isTemp); resourceDescriptor.rtv = _descriptorAllocator.AllocateRTV(isTemp);
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.rtv); var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.rtv);
var rtvDesc = CreateRtvDesc(allocation.Get()->GetResource()); var rtvDesc = CreateRtvDesc(pAllocation->GetResource());
_device.NativeDevice->CreateRenderTargetView(allocation.Get()->GetResource(), &rtvDesc, cpuHandle); _device.NativeDevice->CreateRenderTargetView(pAllocation->GetResource(), &rtvDesc, cpuHandle);
} }
if (desc.Usage.HasFlag(TextureUsage.DepthStencil)) if (desc.Usage.HasFlag(TextureUsage.DepthStencil))
{ {
resourceDescriptor.dsv = _descriptorAllocator.AllocateDSV(isTemp); resourceDescriptor.dsv = _descriptorAllocator.AllocateDSV(isTemp);
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.dsv); var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.dsv);
var dsvDesc = CreateDsvDesc(allocation.Get()->GetResource()); var dsvDesc = CreateDsvDesc(pAllocation->GetResource());
_device.NativeDevice->CreateDepthStencilView(allocation.Get()->GetResource(), &dsvDesc, cpuHandle); _device.NativeDevice->CreateDepthStencilView(pAllocation->GetResource(), &dsvDesc, cpuHandle);
} }
if (desc.Usage.HasFlag(TextureUsage.UnorderedAccess)) if (desc.Usage.HasFlag(TextureUsage.UnorderedAccess))
{ {
resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav(isTemp); resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav(isTemp);
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.uav); var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.uav);
var uavDesc = CreateTextureUavDesc(allocation.Get()->GetResource()); var uavDesc = CreateTextureUavDesc(pAllocation->GetResource());
_device.NativeDevice->CreateUnorderedAccessView(allocation.Get()->GetResource(), null, &uavDesc, cpuHandle); _device.NativeDevice->CreateUnorderedAccessView(pAllocation->GetResource(), null, &uavDesc, cpuHandle);
} }
var handle = TrackResource(allocation, initialState, resourceDescriptor, ResourceDesc.Texture(desc), isTemp); var handle = TrackResource(pAllocation, initialState, resourceDescriptor, ResourceDesc.Texture(desc), isTemp);
return handle.AsTexture(); return handle.AsTexture();
} }
public Handle<Texture> CreateRenderTarget(ref readonly RenderTargetDesc desc, bool isTemp = false) public Handle<Texture> CreateRenderTarget(ref readonly RenderTargetDesc desc, bool isTemp = false)
{ {
ThrowIfDisposed();
var textureDesc = desc.ToTextureDescripton(); var textureDesc = desc.ToTextureDescripton();
return CreateTexture(ref textureDesc, isTemp); return CreateTexture(ref textureDesc, isTemp);
} }
public Handle<GraphicsBuffer> CreateBuffer(ref readonly BufferDesc desc, bool isTemp = false) public Handle<GraphicsBuffer> CreateBuffer(ref readonly BufferDesc desc, bool isTemp = false)
{ {
ThrowIfDisposed();
CheckBufferSize(desc.Size); CheckBufferSize(desc.Size);
var resourceDescription = D3D12_RESOURCE_DESC.Buffer(desc.Size, ConvertBufferUsage(desc.Usage)); var resourceDescription = D3D12_RESOURCE_DESC.Buffer(desc.Size, ConvertBufferUsage(desc.Usage));
@@ -778,11 +780,11 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
var initialState = DetermineInitialBufferState(desc.Usage, desc.MemoryType); var initialState = DetermineInitialBufferState(desc.Usage, desc.MemoryType);
ComPtr<D3D12MA_Allocation> allocation = default; D3D12MA_Allocation* pAllocation = default;
ThrowIfFailed(_allocator.Get()->CreateResource(&allocationDesc, &resourceDescription, initialState, null, allocation.GetAddressOf(), Win32Utility.IID_NULL, null)); ThrowIfFailed(nativeObject.Get()->CreateResource(&allocationDesc, &resourceDescription, initialState, null, &pAllocation, Win32Utility.IID_NULL, null));
var resourceDescriptor = ResourceViewGroup.Invalid; var resourceDescriptor = ResourceViewGroup.Invalid;
var pResource = allocation.Get()->GetResource(); var pResource = pAllocation->GetResource();
if (desc.Usage.HasFlag(BufferUsage.Constant)) if (desc.Usage.HasFlag(BufferUsage.Constant))
{ {
@@ -803,7 +805,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
{ {
resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav(isTemp); resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav(isTemp);
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv); var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv);
var srvDesc = CreateBufferSrvDesc(allocation.Get()->GetResource(), desc.Stride, isRaw); var srvDesc = CreateBufferSrvDesc(pAllocation->GetResource(), desc.Stride, isRaw);
_device.NativeDevice->CreateShaderResourceView(pResource, &srvDesc, cpuHandle); _device.NativeDevice->CreateShaderResourceView(pResource, &srvDesc, cpuHandle);
} }
@@ -812,17 +814,19 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
{ {
resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav(isTemp); resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav(isTemp);
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.uav); var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.uav);
var uavDesc = CreateBufferUavDesc(allocation.Get()->GetResource(), desc.Stride, isRaw); var uavDesc = CreateBufferUavDesc(pAllocation->GetResource(), desc.Stride, isRaw);
_device.NativeDevice->CreateUnorderedAccessView(pResource, null, &uavDesc, cpuHandle); _device.NativeDevice->CreateUnorderedAccessView(pResource, null, &uavDesc, cpuHandle);
} }
var handle = TrackResource(allocation, initialState, resourceDescriptor, ResourceDesc.Buffer(desc), isTemp); var handle = TrackResource(pAllocation, initialState, resourceDescriptor, ResourceDesc.Buffer(desc), isTemp);
return handle.AsGraphicsBuffer(); return handle.AsGraphicsBuffer();
} }
public Handle<GraphicsBuffer> CreateUploadBuffer(ulong size, bool isTemp = true) public Handle<GraphicsBuffer> CreateUploadBuffer(ulong size, bool isTemp = true)
{ {
ThrowIfDisposed();
var desc = new BufferDesc var desc = new BufferDesc
{ {
Size = size, Size = size,
@@ -835,6 +839,8 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices) public Handle<Mesh> CreateMesh(UnsafeList<Vertex> vertices, UnsafeList<uint> indices)
{ {
ThrowIfDisposed();
var vertexBufferDesc = new BufferDesc var vertexBufferDesc = new BufferDesc
{ {
Size = (uint)(vertices.Count * Unsafe.SizeOf<Vertex>()), Size = (uint)(vertices.Count * Unsafe.SizeOf<Vertex>()),
@@ -867,14 +873,18 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
public Handle<Material> CreateMaterial(Identifier<Shader> shader) public Handle<Material> CreateMaterial(Identifier<Shader> shader)
{ {
ThrowIfDisposed();
var material = new Material(); var material = new Material();
material.SetShader(shader, this, _resourceDatabase); material.SetShader(shader, this, _resourceDatabase);
return _resourceDatabase.AddMaterial(ref material); return _resourceDatabase.AddMaterial(ref material);
} }
public Identifier<Shader> CreateShader(ShaderDescriptor descriptor) public Identifier<Shader> CreateGraphicsShader(ShaderDescriptor descriptor)
{ {
ThrowIfDisposed();
var shader = new Shader(descriptor); var shader = new Shader(descriptor);
foreach (var pass in descriptor.passes) foreach (var pass in descriptor.passes)
{ {
@@ -883,9 +893,14 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
continue; continue;
} }
var passKey = new ShaderPassKey(fullPass.uniqueIdentifier); var passKey = new ShaderPassKey(fullPass.Identifier);
var cbufferInfo = _pipelineLibrary.GetCBufferInfo(passKey).GetValueOrThrow(); var cbr = _pipelineLibrary.GetCBufferInfo(passKey);
_resourceDatabase.AddShaderPass(new ShaderPassKey(fullPass.uniqueIdentifier), new ShaderPass(cbufferInfo)); if (cbr.Status != ResultStatus.Success)
{
return Identifier<Shader>.Invalid;
}
_resourceDatabase.AddShaderPass(passKey, new ShaderPass(cbr.Value));
} }
return _resourceDatabase.AddShader(shader); return _resourceDatabase.AddShader(shader);
@@ -893,6 +908,8 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
public void ReleaseTempResources() public void ReleaseTempResources()
{ {
ThrowIfDisposed();
while (_temResources.Count > 0) while (_temResources.Count > 0)
{ {
var handle = _temResources.Peek(); var handle = _temResources.Peek();
@@ -916,9 +933,9 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
} }
} }
public void Dispose() protected override void Dispose(bool disposing)
{ {
if (_disposed) if (Disposed)
{ {
return; return;
} }
@@ -936,9 +953,7 @@ internal sealed unsafe partial class D3D12ResourceAllocator : IResourceAllocator
} }
_temResources.Dispose(); _temResources.Dispose();
_allocator.Dispose();
_disposed = true; base.Dispose(disposing);
GC.SuppressFinalize(this);
} }
} }

View File

@@ -2,6 +2,7 @@ using Ghost.Core;
using Ghost.Graphics.Core; using Ghost.Graphics.Core;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Collections; using Misaki.HighPerformance.Collections;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using System.Diagnostics; using System.Diagnostics;
@@ -19,16 +20,16 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
public struct ResourceUnion public struct ResourceUnion
{ {
[FieldOffset(0)] [FieldOffset(0)]
public ComPtr<D3D12MA_Allocation> allocation; public UniquePtr<D3D12MA_Allocation> allocation;
[FieldOffset(0)] [FieldOffset(0)]
public ComPtr<ID3D12Resource> resource; public UniquePtr<ID3D12Resource> resource;
public ResourceUnion(ComPtr<D3D12MA_Allocation> allocation) public ResourceUnion(D3D12MA_Allocation* allocation)
{ {
this.allocation = allocation; this.allocation = allocation;
} }
public ResourceUnion(ComPtr<ID3D12Resource> resource) public ResourceUnion(ID3D12Resource* resource)
{ {
this.resource = resource; this.resource = resource;
} }
@@ -44,7 +45,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
public readonly bool Allocated => isExternal ? resourceUnion.resource.Get() != null : resourceUnion.allocation.Get() != null; public readonly bool Allocated => isExternal ? resourceUnion.resource.Get() != null : resourceUnion.allocation.Get() != null;
public readonly ID3D12Resource* ResourcePtr => isExternal ? resourceUnion.resource.Get() : resourceUnion.allocation.Get()->GetResource(); public readonly ID3D12Resource* ResourcePtr => isExternal ? resourceUnion.resource.Get() : resourceUnion.allocation.Get()->GetResource();
public ResourceRecord(ComPtr<D3D12MA_Allocation> allocation, uint cpuFenceValue, ResourceState state, ResourceViewGroup resourceDescriptor, ResourceDesc desc) public ResourceRecord(D3D12MA_Allocation* allocation, uint cpuFenceValue, ResourceState state, ResourceViewGroup resourceDescriptor, ResourceDesc desc)
{ {
this.resourceUnion = new ResourceUnion(allocation); this.resourceUnion = new ResourceUnion(allocation);
this.isExternal = false; this.isExternal = false;
@@ -55,7 +56,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
this.desc = desc; this.desc = desc;
} }
public ResourceRecord(ComPtr<ID3D12Resource> resource, ResourceState state) public ResourceRecord(ID3D12Resource* resource, ResourceState state)
{ {
this.resourceUnion = new ResourceUnion(resource); this.resourceUnion = new ResourceUnion(resource);
this.isExternal = true; this.isExternal = true;
@@ -63,7 +64,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
this.viewGroup = default; this.viewGroup = default;
this.cpuFenceValue = ~0u; this.cpuFenceValue = ~0u;
this.state = state; this.state = state;
this.desc = ResourceDesc.FromD3D12(resource.Get()->GetDesc()); this.desc = ResourceDesc.FromD3D12(resource->GetDesc());
} }
public uint Release(D3D12DescriptorAllocator descriptorAllocator) public uint Release(D3D12DescriptorAllocator descriptorAllocator)
@@ -73,11 +74,11 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
{ {
if (isExternal) if (isExternal)
{ {
refCount = resourceUnion.resource.Reset(); refCount = resourceUnion.resource.Get()->Release();
} }
else else
{ {
refCount = resourceUnion.allocation.Reset(); refCount = resourceUnion.allocation.Get()->Release();
} }
resourceUnion = default; resourceUnion = default;
@@ -131,11 +132,11 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
resource = default!; resource = default!;
} }
public Handle<GPUResource> ImportExternalResource(ComPtr<ID3D12Resource> resource, ResourceState initialState, string? name = null) public unsafe Handle<GPUResource> ImportExternalResource(ID3D12Resource* pResource, ResourceState initialState, string? name = null)
{ {
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
var id = _resources.Add(new ResourceRecord(resource, initialState), out var generation); var id = _resources.Add(new ResourceRecord(pResource, initialState), out var generation);
var handle = new Handle<GPUResource>(id, generation); var handle = new Handle<GPUResource>(id, generation);
#if DEBUG || GHOST_EDITOR #if DEBUG || GHOST_EDITOR
@@ -148,7 +149,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
return handle; return handle;
} }
public Handle<GPUResource> AddResource(ComPtr<D3D12MA_Allocation> allocation, uint cpuFenceValue, ResourceState initialState, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string? name = null) public unsafe Handle<GPUResource> AddResource(D3D12MA_Allocation* allocation, uint cpuFenceValue, ResourceState initialState, ResourceViewGroup resourceDescriptor, ResourceDesc desc, string? name = null)
{ {
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);

View File

@@ -17,13 +17,11 @@ namespace Ghost.Graphics.D3D12;
/// <summary> /// <summary>
/// D3D12 implementation of swap chain interface /// D3D12 implementation of swap chain interface
/// </summary> /// </summary>
internal unsafe class D3D12SwapChain : ISwapChain internal unsafe class D3D12SwapChain : IUnknownObject<IDXGISwapChain4>, ISwapChain
{ {
private readonly D3D12ResourceDatabase _resourceDatabase; private readonly D3D12ResourceDatabase _resourceDatabase;
private ComPtr<IDXGISwapChain4> _swapChain;
private UnsafeArray<Handle<Texture>> _backBuffers; private UnsafeArray<Handle<Texture>> _backBuffers;
private bool _disposed;
public uint Width public uint Width
{ {
@@ -109,37 +107,42 @@ internal unsafe class D3D12SwapChain : ISwapChain
pTempSwapChain->QueryInterface(__uuidof(pSwapChain), (void**)&pSwapChain); pTempSwapChain->QueryInterface(__uuidof(pSwapChain), (void**)&pSwapChain);
pTempSwapChain->Release(); pTempSwapChain->Release();
_swapChain.Attach(pSwapChain); nativeObject.Attach(pSwapChain);
} }
private void CreateBackBuffers() private void CreateBackBuffers()
{ {
for (uint i = 0; i < BufferCount; i++) for (uint i = 0; i < BufferCount; i++)
{ {
ComPtr<ID3D12Resource> backBuffer = default; ID3D12Resource* pBackBuffer = default;
_swapChain.Get()->GetBuffer(i, backBuffer.IID(), backBuffer.PPV()); nativeObject.Get()->GetBuffer(i, __uuidof(pBackBuffer), (void**)&pBackBuffer);
backBuffer.Get()->SetName($"SwapChain_BackBuffer_{i}"); pBackBuffer->SetName($"SwapChain_BackBuffer_{i}");
_backBuffers[i] = _resourceDatabase.ImportExternalResource(backBuffer.Move(), ResourceState.Present).AsTexture(); _backBuffers[i] = _resourceDatabase.ImportExternalResource(pBackBuffer, ResourceState.Present).AsTexture();
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Handle<Texture> GetCurrentBackBuffer() public Handle<Texture> GetCurrentBackBuffer()
{ {
return _backBuffers[_swapChain.Get()->GetCurrentBackBufferIndex()]; ThrowIfDisposed();
return _backBuffers[nativeObject.Get()->GetCurrentBackBufferIndex()];
} }
public void Present(bool vsync = true) public void Present(bool vsync = true)
{ {
ThrowIfDisposed();
var presentFlags = 0u; var presentFlags = 0u;
var syncInterval = vsync ? 1u : 0u; var syncInterval = vsync ? 1u : 0u;
ThrowIfFailed(_swapChain.Get()->Present(syncInterval, presentFlags)); ThrowIfFailed(nativeObject.Get()->Present(syncInterval, presentFlags));
} }
public void Resize(uint width, uint height) public void Resize(uint width, uint height)
{ {
ThrowIfDisposed();
if (Width == width && Height == height) if (Width == width && Height == height)
{ {
return; return;
@@ -152,7 +155,7 @@ internal unsafe class D3D12SwapChain : ISwapChain
} }
// Resize the swap chain // Resize the swap chain
if (_swapChain.Get()->ResizeBuffers(BufferCount, width, height, DXGI_FORMAT_B8G8R8A8_UNORM, (uint)DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING).FAILED) if (nativeObject.Get()->ResizeBuffers(BufferCount, width, height, DXGI_FORMAT_B8G8R8A8_UNORM, (uint)DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING).FAILED)
{ {
throw new InvalidOperationException("Failed to resize swap chain buffers."); throw new InvalidOperationException("Failed to resize swap chain buffers.");
} }
@@ -164,9 +167,9 @@ internal unsafe class D3D12SwapChain : ISwapChain
CreateBackBuffers(); CreateBackBuffers();
} }
public void Dispose() protected override void Dispose(bool disposing)
{ {
if (_disposed) if (Disposed)
{ {
return; return;
} }
@@ -176,8 +179,8 @@ internal unsafe class D3D12SwapChain : ISwapChain
_resourceDatabase.ReleaseResource(_backBuffers[i].AsResource()); _resourceDatabase.ReleaseResource(_backBuffers[i].AsResource());
} }
_swapChain.Dispose();
_backBuffers.Dispose(); _backBuffers.Dispose();
_disposed = true;
base.Dispose(disposing);
} }
} }

View File

@@ -1,10 +1,10 @@
using Ghost.Core.Utilities; using Ghost.Core.Utilities;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.LowLevel.Utilities;
using System.Diagnostics; using System.Diagnostics;
using System.Numerics; using System.Numerics;
using TerraFX.Interop.DirectX; using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using static TerraFX.Aliases.D3D12_Alias; using static TerraFX.Aliases.D3D12_Alias;
@@ -16,8 +16,8 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
private readonly D3D12RenderDevice _device; private readonly D3D12RenderDevice _device;
private ComPtr<ID3D12DescriptorHeap> _heap; private UniquePtr<ID3D12DescriptorHeap> _heap;
private ComPtr<ID3D12DescriptorHeap> _shaderVisibleHeap; private UniquePtr<ID3D12DescriptorHeap> _shaderVisibleHeap;
private D3D12_CPU_DESCRIPTOR_HANDLE _startCpuHandle; private D3D12_CPU_DESCRIPTOR_HANDLE _startCpuHandle;
private D3D12_CPU_DESCRIPTOR_HANDLE _startCpuHandleShaderVisible; private D3D12_CPU_DESCRIPTOR_HANDLE _startCpuHandleShaderVisible;
private D3D12_GPU_DESCRIPTOR_HANDLE _startGpuHandleShaderVisible; private D3D12_GPU_DESCRIPTOR_HANDLE _startGpuHandleShaderVisible;
@@ -323,18 +323,25 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
var oldSize = NumDescriptors; var oldSize = NumDescriptors;
var newSize = (int)BitOperations.RoundUpToPowerOf2((uint)minRequiredSize); var newSize = (int)BitOperations.RoundUpToPowerOf2((uint)minRequiredSize);
using var oldHeap = _heap; var oldHeap = _heap.Detach();
try
{
if (!AllocateResources(newSize)) if (!AllocateResources(newSize))
{ {
return false; return false;
} }
_device.NativeDevice->CopyDescriptorsSimple((uint)oldSize, _startCpuHandle, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType); _device.NativeDevice->CopyDescriptorsSimple((uint)oldSize, _startCpuHandle, oldHeap->GetCPUDescriptorHandleForHeapStart(), HeapType);
if (_shaderVisibleHeap.Get() != null) if (_shaderVisibleHeap.Get() != null)
{ {
_device.NativeDevice->CopyDescriptorsSimple((uint)oldSize, _startCpuHandleShaderVisible, oldHeap.Get()->GetCPUDescriptorHandleForHeapStart(), HeapType); _device.NativeDevice->CopyDescriptorsSimple((uint)oldSize, _startCpuHandleShaderVisible, oldHeap->GetCPUDescriptorHandleForHeapStart(), HeapType);
}
}
finally
{
oldHeap->Release();
} }
return true; return true;

View File

@@ -2784,7 +2784,6 @@ public static partial class D3D12_Alias
public const D3D12_BARRIER_LAYOUT D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_SHADER_RESOURCE = D3D12_BARRIER_LAYOUT.D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_SHADER_RESOURCE; public const D3D12_BARRIER_LAYOUT D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_SHADER_RESOURCE = D3D12_BARRIER_LAYOUT.D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_SHADER_RESOURCE;
public const D3D12_BARRIER_LAYOUT D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_COPY_SOURCE = D3D12_BARRIER_LAYOUT.D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_COPY_SOURCE; public const D3D12_BARRIER_LAYOUT D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_COPY_SOURCE = D3D12_BARRIER_LAYOUT.D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_COPY_SOURCE;
public const D3D12_BARRIER_LAYOUT D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_COPY_DEST = D3D12_BARRIER_LAYOUT.D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_COPY_DEST; public const D3D12_BARRIER_LAYOUT D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_COPY_DEST = D3D12_BARRIER_LAYOUT.D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_COPY_DEST;
public const D3D12_BARRIER_LAYOUT D3D12_BARRIER_LAYOUT_VIDEO_QUEUE_COMMON = D3D12_BARRIER_LAYOUT.D3D12_BARRIER_LAYOUT_VIDEO_QUEUE_COMMON;
public const D3D12_BARRIER_LAYOUT D3D12_BARRIER_LAYOUT_UNDEFINED = D3D12_BARRIER_LAYOUT.D3D12_BARRIER_LAYOUT_UNDEFINED; public const D3D12_BARRIER_LAYOUT D3D12_BARRIER_LAYOUT_UNDEFINED = D3D12_BARRIER_LAYOUT.D3D12_BARRIER_LAYOUT_UNDEFINED;
} }

View File

@@ -31,8 +31,12 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Misaki.HighPerformance.Analyzer" Version="1.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Misaki.HighPerformance.Image" Version="1.0.0" /> <PackageReference Include="Misaki.HighPerformance.Image" Version="1.0.0" />
<PackageReference Include="TerraFX.Interop.D3D12MemoryAllocator" Version="2.0.1.5" /> <PackageReference Include="TerraFX.Interop.D3D12MemoryAllocator" Version="3.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -59,7 +59,7 @@ public readonly struct GraphicsPipelineKey
{ {
if (!value.TryFormat(destination, out _, "X16")) if (!value.TryFormat(destination, out _, "X16"))
{ {
return Result.Fail("Failed to format GraphicsPipelineKey to string."); return Result.Failure("Failed to format GraphicsPipelineKey to string.");
} }
destination[16] = '\0'; destination[16] = '\0';
@@ -166,7 +166,7 @@ public ref struct GraphicsPSODescriptor
} }
} }
public readonly struct CBufferPropertyInfo public readonly record struct CBufferPropertyInfo
{ {
public string Name public string Name
{ {
@@ -184,7 +184,7 @@ public readonly struct CBufferPropertyInfo
} }
} }
public readonly struct CBufferInfo public readonly record struct CBufferInfo
{ {
public string Name public string Name
{ {

View File

@@ -167,7 +167,7 @@ public interface ICommandBuffer : IDisposable
/// <summary> /// <summary>
/// Uploads the specified data to the buffer represented by the given handle. /// Uploads the specified data to the buffer represented by the given handle.
/// </summary> /// </summary>
/// <typeparam name="T">The unmanaged value type of the elements to upload to the buffer.</typeparam> /// <typeparam name="T">The unmanaged Value type of the elements to upload to the buffer.</typeparam>
/// <param name="buffer">A handle to the buffer that will receive the uploaded data.</param> /// <param name="buffer">A handle to the buffer that will receive the uploaded data.</param>
/// <param name="data">A read-only span containing the data to upload to the buffer. The span must contain elements of type /// <param name="data">A read-only span containing the data to upload to the buffer. The span must contain elements of type
/// <typeparamref name="T"/>.</param> /// <typeparamref name="T"/>.</param>

View File

@@ -26,22 +26,22 @@ public interface ICommandQueue : IDisposable
public void Submit(params ReadOnlySpan<ICommandBuffer> commandBuffers); public void Submit(params ReadOnlySpan<ICommandBuffer> commandBuffers);
/// <summary> /// <summary>
/// Signals a fence with the specified value /// Signals a fence with the specified Value
/// </summary> /// </summary>
/// <param name="value">Value to signal</param> /// <param name="value">Value to signal</param>
/// <returns>The fence value that was signaled</returns> /// <returns>The fence Value that was signaled</returns>
public ulong Signal(ulong value); public ulong Signal(ulong value);
/// <summary> /// <summary>
/// Waits for the fence to reach the specified value /// Waits for the fence to reach the specified Value
/// </summary> /// </summary>
/// <param name="value">Value to wait for</param> /// <param name="value">Value to wait for</param>
public void WaitForValue(ulong value); public void WaitForValue(ulong value);
/// <summary> /// <summary>
/// Gets the last completed fence value /// Gets the last completed fence Value
/// </summary> /// </summary>
/// <returns>Last completed fence value</returns> /// <returns>Last completed fence Value</returns>
public ulong GetCompletedValue(); public ulong GetCompletedValue();
/// <summary> /// <summary>

View File

@@ -1,3 +1,5 @@
using Ghost.Graphics.Contracts;
namespace Ghost.Graphics.RHI; namespace Ghost.Graphics.RHI;
public interface IGraphicsEngine : IDisposable public interface IGraphicsEngine : IDisposable
@@ -7,6 +9,11 @@ public interface IGraphicsEngine : IDisposable
get; get;
} }
IShaderCompiler ShaderCompiler
{
get;
}
IPipelineLibrary PipelineLibrary IPipelineLibrary PipelineLibrary
{ {
get; get;

View File

@@ -1,4 +1,5 @@
using Ghost.Core.Graphics; using Ghost.Core;
using Ghost.Graphics.Contracts;
namespace Ghost.Graphics.RHI; namespace Ghost.Graphics.RHI;
@@ -21,5 +22,6 @@ public interface IPipelineLibrary
/// <param name="filePath">File path. If null, load default library.</param> /// <param name="filePath">File path. If null, load default library.</param>
void InitializeLibrary(string? filePath); void InitializeLibrary(string? filePath);
void SaveLibraryToDisk(string filePath); void SaveLibraryToDisk(string filePath);
GraphicsPipelineKey CompilePassPSO(IPassDescriptor descriptor, ReadOnlySpan<TextureFormat> rtvs, TextureFormat dsv); Result<GraphicsPipelineKey> CompilePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled);
Result<CBufferInfo, ResultStatus> GetCBufferInfo(ShaderPassKey passId);
} }

View File

@@ -1,5 +1,16 @@
namespace Ghost.Graphics.RHI; namespace Ghost.Graphics.RHI;
[Flags]
public enum FeatureSupport
{
None = 0,
RayTracing = 1 << 0,
VariableRateShading = 1 << 1,
MeshShaders = 1 << 2,
SamplerFeedback = 1 << 3,
BindlessResources = 1 << 4,
}
/// <summary> /// <summary>
/// D3D12-native render device interface for creating graphics resources /// D3D12-native render device interface for creating graphics resources
/// </summary> /// </summary>
@@ -28,4 +39,6 @@ public interface IRenderDevice : IDisposable
{ {
get; get;
} }
public FeatureSupport GetFeatureSupport();
} }

View File

@@ -48,5 +48,5 @@ public interface IResourceAllocator
/// </summary> /// </summary>
/// <returns>An <see cref="Identifier{Shader}"/> representing the newly created shader.</returns> /// <returns>An <see cref="Identifier{Shader}"/> representing the newly created shader.</returns>
/// <param name="descriptor">The viewGroup containing the shader's properties and passes.</param> /// <param name="descriptor">The viewGroup containing the shader's properties and passes.</param>
public Identifier<Shader> CreateShader(ShaderDescriptor descriptor); public Identifier<Shader> CreateGraphicsShader(ShaderDescriptor descriptor);
} }

View File

@@ -35,11 +35,11 @@ public interface IResourceDatabase
/// Retrieves the current state of the specified resource. /// Retrieves the current state of the specified resource.
/// </summary> /// </summary>
/// <param name="handle">The handle that uniquely identifies the resource whose state is to be retrieved.</param> /// <param name="handle">The handle that uniquely identifies the resource whose state is to be retrieved.</param>
/// <returns>A ResourceState value representing the current state of the resource associated with the specified handle.</returns> /// <returns>A ResourceState Value representing the current state of the resource associated with the specified handle.</returns>
ResourceState GetResourceState(Handle<GPUResource> handle); ResourceState GetResourceState(Handle<GPUResource> handle);
/// <summary> /// <summary>
/// Sets the state of the specified resource handle to the given value. /// Sets the state of the specified resource handle to the given Value.
/// </summary> /// </summary>
/// <param name="handle">The handle that identifies the resource whose state will be updated.</param> /// <param name="handle">The handle that identifies the resource whose state will be updated.</param>
/// <param name="state">The new state to assign to the resource represented by <paramref name="handle"/>.</param> /// <param name="state">The new state to assign to the resource represented by <paramref name="handle"/>.</param>

View File

@@ -1,10 +1,13 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Graphics.Core; using Ghost.Core.Graphics;
using Ghost.Graphics.Contracts; using Ghost.Graphics.Contracts;
using Ghost.Graphics.Core;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Ghost.Graphics.Utilities; using Ghost.Graphics.Utilities;
using Ghost.SDL.Compiler; using Ghost.SDL.Compiler;
using Misaki.HighPerformance.Image; using Misaki.HighPerformance.Image;
using Misaki.HighPerformance.Utilities;
using TerraFX.Interop.Windows;
namespace Ghost.Graphics.RenderPasses; namespace Ghost.Graphics.RenderPasses;
@@ -30,14 +33,36 @@ internal unsafe class MeshRenderPass : IRenderPass
{ {
var shaderDescriptor = SDLCompiler.CompileShader("F:/csharp/GhostEngine/Ghost.Graphics/test.gshader", "C:/Users/Misaki/Downloads/Archive").GetValueOrThrow(); var shaderDescriptor = SDLCompiler.CompileShader("F:/csharp/GhostEngine/Ghost.Graphics/test.gshader", "C:/Users/Misaki/Downloads/Archive").GetValueOrThrow();
var key = ctx.PipelineLibrary.CompilePassPSO(shaderDescriptor.passes[0], [TextureFormat.B8G8R8A8_UNorm], TextureFormat.Unknown); foreach (var pass in shaderDescriptor.passes)
{
var compileResult = ctx.ShaderCompiler.CompilePass(pass);
if (compileResult.IsFailure || pass is not FullPassDescriptor fullPass)
{
continue;
}
var psoDes = new GraphicsPSODescriptor
{
PassId = new ShaderPassKey(fullPass.Identifier),
ZTest = fullPass.localPipeline.zTest,
ZWrite = fullPass.localPipeline.zWrite,
Cull = fullPass.localPipeline.cull,
Blend = fullPass.localPipeline.blend,
ColorMask = fullPass.localPipeline.colorMask,
RtvFormats = [TextureFormat.B8G8R8A8_UNorm],
DsvFormat = TextureFormat.Unknown,
};
ctx.PipelineLibrary.CompilePSO(in psoDes, in compileResult.GetValueRef()).GetValueOrThrow();
}
MeshBuilder.CreateCube(0.75f, default, out var vertices, out var indices); MeshBuilder.CreateCube(0.75f, default, out var vertices, out var indices);
_mesh = ctx.CreateMesh(vertices, indices); _mesh = ctx.CreateMesh(vertices, indices);
ctx.UploadMesh(_mesh, true); ctx.UploadMesh(_mesh, true);
_shader = ctx.ResourceAllocator.CreateShader(shaderDescriptor); _shader = ctx.ResourceAllocator.CreateGraphicsShader(shaderDescriptor);
_material = ctx.ResourceAllocator.CreateMaterial(_shader); _material = ctx.ResourceAllocator.CreateMaterial(_shader);
var imageResults = new ImageResult[_textureFiles.Length]; var imageResults = new ImageResult[_textureFiles.Length];

View File

@@ -1,16 +1,21 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Core.Utilities;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Utilities;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using TerraFX.Interop.DirectX; using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows; using TerraFX.Interop.Windows;
using static TerraFX.Interop.DirectX.DXC; using static TerraFX.Interop.DirectX.DXC;
namespace Ghost.Graphics.D3D12; namespace Ghost.Graphics.Utilities;
internal partial class D3D12ShaderCompiler internal sealed partial class DxcShaderCompiler
{ {
private static string GetProfileString(ShaderStage stage, CompilerTier version) private static string GetProfileString(ShaderStage stage, CompilerTier version)
{ {
@@ -84,6 +89,11 @@ internal partial class D3D12ShaderCompiler
argsArray.Add(DXC_ARG_WARNINGS_ARE_ERRORS); argsArray.Add(DXC_ARG_WARNINGS_ARE_ERRORS);
} }
if (config.options.HasFlag(CompilerOption.SpirvCrossCompile))
{
argsArray.Add("-spirv");
}
return argsArray; return argsArray;
} }
@@ -105,12 +115,16 @@ internal partial class D3D12ShaderCompiler
} }
} }
internal unsafe partial class D3D12ShaderCompiler : IShaderCompiler internal sealed unsafe partial class DxcShaderCompiler : IShaderCompiler, IDisposable
{ {
private ComPtr<IDxcCompiler3> _compiler; private UniquePtr<IDxcCompiler3> _compiler;
private ComPtr<IDxcUtils> _utils; private UniquePtr<IDxcUtils> _utils;
// NOTE: This is just a temporary cache for compiled shader code. We will implement a proper disk cache later.
private readonly Dictionary<ShaderPassKey, GraphicsCompiledResult> _compiledResults;
public D3D12ShaderCompiler() private bool _disposed;
public DxcShaderCompiler()
{ {
// Initialize DXC _compiler.Get() and _utils.Get() // Initialize DXC _compiler.Get() and _utils.Get()
var dxccID = CLSID.CLSID_DxcCompiler; var dxccID = CLSID.CLSID_DxcCompiler;
@@ -123,121 +137,19 @@ internal unsafe partial class D3D12ShaderCompiler : IShaderCompiler
_compiler.Attach(pCompiler); _compiler.Attach(pCompiler);
_utils.Attach(pUtils); _utils.Attach(pUtils);
_compiledResults = new();
} }
public Result<CompileResult> Compile(ref readonly CompilerConfig config, Allocator allocator, void** ppReflection) ~DxcShaderCompiler()
{ {
// NOTE: Should we cache the _compiler.Get() and _utils.Get() instances for better performance? Dispose();
IDxcIncludeHandler* pIncludeHandler = default;
try
{
// Create DXC _compiler.Get() and _utils.Get()
var dxccID = CLSID.CLSID_DxcCompiler;
var dxcuID = CLSID.CLSID_DxcUtils;
ThrowIfFailed(_utils.Get()->CreateDefaultIncludeHandler(&pIncludeHandler));
// Create source blob
using ComPtr<IDxcBlobEncoding> sourceBlob = default;
fixed (char* pPath = config.shaderPath)
{
if (_utils.Get()->LoadFile(pPath, null, sourceBlob.GetAddressOf()).FAILED)
{
return Result.Fail($"Failed to load shader file: {config.shaderPath}");
}
} }
var argsArray = GetCompilerArguments(in config); private Result<ShaderReflectionData> PerformDXCReflection(IDxcBlob* pReflectionBlob)
var argPtrs = stackalloc char*[argsArray.Count];
for (var i = 0; i < argsArray.Count; i++)
{ {
argPtrs[i] = (char*)Marshal.StringToHGlobalUni(argsArray[i]);
}
IDxcResult* pResult = default;
try
{
// Compile shader
var buffer = new DxcBuffer
{
Ptr = sourceBlob.Get()->GetBufferPointer(),
Size = sourceBlob.Get()->GetBufferSize(),
Encoding = DXC_CP_UTF8
};
ThrowIfFailed(_compiler.Get()->Compile(&buffer, argPtrs, (uint)argsArray.Count, pIncludeHandler, __uuidof(pResult), (void**)&pResult));
// Check compilation pResult
HRESULT hrStatus;
pResult->GetStatus(&hrStatus);
if (hrStatus.FAILED)
{
// Get error messages
using ComPtr<IDxcBlobEncoding> errorBlob = default;
pResult->GetErrorBuffer(errorBlob.GetAddressOf());
if (errorBlob.Get() != null)
{
var errorMessage = Marshal.PtrToStringUTF8((IntPtr)errorBlob.Get()->GetBufferPointer());
return Result.Fail($"DXC shader compilation failed:\n{errorMessage}");
}
else
{
return Result.Fail("DXC shader compilation failed with unknown error.");
}
}
// Get compiled bytecode
using ComPtr<IDxcBlob> bytecodeBlob = default;
ThrowIfFailed(pResult->GetResult(bytecodeBlob.GetAddressOf()));
// Get pReflection data using DXC API
if (ppReflection != null)
{
ThrowIfFailed(pResult->GetOutput(DXC_OUT_KIND.DXC_OUT_REFLECTION, __uuidof<IDxcBlob>(), ppReflection, null));
}
var bytecodeSize = bytecodeBlob.Get()->GetBufferSize();
var bytecode = new UnsafeArray<byte>((int)bytecodeSize, allocator);
NativeMemory.Copy(bytecodeBlob.Get()->GetBufferPointer(), bytecode.GetUnsafePtr(), bytecodeSize);
return new CompileResult
{
bytecode = bytecode,
};
}
finally
{
for (var i = 0; i < argsArray.Count; i++)
{
Marshal.FreeHGlobal((nint)argPtrs[i]);
}
pResult->Release();
}
}
finally
{
pIncludeHandler->Release();
}
}
// TODO: Since we are using fixed root signature layout, the pReflection pass should only validate the layout, not generate it.
// TODO: Ideally this should return a structured pReflection data instead of populating raw lists/dictionaries.
public Result<ShaderReflectionData> PerformDXCReflection<T>(T* pReflectionBlob)
where T : unmanaged
{
if (typeof(T) != typeof(IDxcBlob))
{
return Result<ShaderReflectionData>.Fail("Unsupported reflection type. Only IDxcBlob is supported.");
}
ID3D12ShaderReflection* pReflection = default; ID3D12ShaderReflection* pReflection = default;
IDxcBlob* pDxcReflectionBlob = (IDxcBlob*)pReflectionBlob; var pDxcReflectionBlob = (IDxcBlob*)pReflectionBlob;
try try
{ {
@@ -267,7 +179,7 @@ internal unsafe partial class D3D12ShaderCompiler : IShaderCompiler
var resourceName = Marshal.PtrToStringUTF8((IntPtr)bindDesc.Name); var resourceName = Marshal.PtrToStringUTF8((IntPtr)bindDesc.Name);
if (resourceName == null) if (resourceName == null)
{ {
return Result.Fail("Failed to get resource name from reflection data."); return Result.Failure("Failed to get resource name from reflection data.");
} }
var info = new ResourceBindingInfo var info = new ResourceBindingInfo
@@ -329,4 +241,262 @@ internal unsafe partial class D3D12ShaderCompiler : IShaderCompiler
pReflection->Release(); pReflection->Release();
} }
} }
public Result<CompileResult> Compile(ref readonly CompilerConfig config, Allocator allocator)
{
ObjectDisposedException.ThrowIf(_disposed, this);
IDxcIncludeHandler* pIncludeHandler = default;
IDxcBlobEncoding* pSourceBlob = default;
try
{
// Create DXC _compiler.Get() and _utils.Get()
var dxccID = CLSID.CLSID_DxcCompiler;
var dxcuID = CLSID.CLSID_DxcUtils;
ThrowIfFailed(_utils.Get()->CreateDefaultIncludeHandler(&pIncludeHandler));
// Create source blob
fixed (char* pPath = config.shaderPath)
{
if (_utils.Get()->LoadFile(pPath, null, &pSourceBlob).FAILED)
{
return Result.Failure($"Failed to load shader file: {config.shaderPath}");
}
}
var argsArray = GetCompilerArguments(in config);
var argPtrs = stackalloc char*[argsArray.Count];
for (var i = 0; i < argsArray.Count; i++)
{
argPtrs[i] = (char*)Marshal.StringToHGlobalUni(argsArray[i]);
}
IDxcResult* pResult = default;
try
{
// Compile shader
var buffer = new DxcBuffer
{
Ptr = pSourceBlob->GetBufferPointer(),
Size = pSourceBlob->GetBufferSize(),
Encoding = DXC_CP_UTF8
};
ThrowIfFailed(_compiler.Get()->Compile(&buffer, argPtrs, (uint)argsArray.Count, pIncludeHandler, __uuidof(pResult), (void**)&pResult));
// Check compilation pResult
HRESULT hrStatus;
pResult->GetStatus(&hrStatus);
if (hrStatus.FAILED)
{
// Get error messages
IDxcBlobEncoding* pErrorBlob = default;
pResult->GetErrorBuffer(&pErrorBlob);
if (pErrorBlob != null)
{
var errorMessage = Marshal.PtrToStringUTF8((IntPtr)pErrorBlob->GetBufferPointer());
pErrorBlob->Release();
return Result.Failure($"DXC shader compilation failed:\n{errorMessage}");
}
else
{
return Result.Failure("DXC shader compilation failed with unknown error.");
}
}
// Get compiled bytecode
IDxcBlob* pBytecodeBlob = default;
try
{
ThrowIfFailed(pResult->GetResult(&pBytecodeBlob));
ShaderReflectionData reflection = default;
if (config.options.HasFlag(CompilerOption.KeepReflections))
{
IDxcBlob* pReflection = default;
if ((pResult->GetOutput(DXC_OUT_KIND.DXC_OUT_REFLECTION, __uuidof<IDxcBlob>(), (void**)&pReflection, null).SUCCEEDED))
{
reflection = PerformDXCReflection(pReflection).GetValueOrDefault();
}
if (pReflection != null)
{
pReflection->Release();
}
}
var bytecodeSize = pBytecodeBlob->GetBufferSize();
var bytecode = new UnsafeArray<byte>((int)bytecodeSize, allocator);
NativeMemory.Copy(pBytecodeBlob->GetBufferPointer(), bytecode.GetUnsafePtr(), bytecodeSize);
return new CompileResult
{
bytecode = bytecode,
reflectionData = reflection,
};
}
finally
{
if (pBytecodeBlob != null)
{
pBytecodeBlob->Release();
}
}
}
finally
{
for (var i = 0; i < argsArray.Count; i++)
{
Marshal.FreeHGlobal((nint)argPtrs[i]);
}
if (pResult != null)
{
pResult->Release();
}
}
}
finally
{
if (pIncludeHandler != null)
{
pIncludeHandler->Release();
}
}
}
public Result<GraphicsCompiledResult> CompilePass(IPassDescriptor descriptor)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (descriptor is not FullPassDescriptor fullDescriptor)
{
return Result.Failure("FullPassDescriptor expected.");
}
CompileResult tsResult = default;
var tsEntry = fullDescriptor.taskShader;
if (tsEntry.IsCreated)
{
var config = new CompilerConfig
{
defines = fullDescriptor.defines.AsSpan(),
include = fullDescriptor.generatedCodePath,
shaderPath = tsEntry.shader,
entryPoint = tsEntry.entry,
stage = ShaderStage.TaskShader,
tier = CompilerTier.Tier0,
optimizeLevel = CompilerOptimizeLevel.O3,
options = CompilerOption.KeepReflections,
};
var result = Compile(ref config, Allocator.Persistent);
if (result.IsFailure)
{
return Result.Failure(result.Message);
}
tsResult = result.Value;
}
CompileResult msResult;
var msEntry = fullDescriptor.meshShader;
if (msEntry.IsCreated)
{
var config = new CompilerConfig
{
defines = fullDescriptor.defines.AsSpan(),
include = fullDescriptor.generatedCodePath,
shaderPath = msEntry.shader,
entryPoint = msEntry.entry,
stage = ShaderStage.MeshShader,
tier = CompilerTier.Tier0,
optimizeLevel = CompilerOptimizeLevel.O3,
options = CompilerOption.KeepReflections,
};
var result = Compile(ref config, Allocator.Persistent);
if (result.IsFailure)
{
return Result.Failure(result.Message);
}
msResult = result.Value;
}
else
{
return Result.Failure("Mesh shader expected.");
}
CompileResult psResult;
var psEntry = fullDescriptor.pixelShader;
if (psEntry.IsCreated)
{
var config = new CompilerConfig
{
defines = fullDescriptor.defines.AsSpan(),
include = fullDescriptor.generatedCodePath,
shaderPath = psEntry.shader,
entryPoint = psEntry.entry,
stage = ShaderStage.PixelShader,
tier = CompilerTier.Tier0,
optimizeLevel = CompilerOptimizeLevel.O3,
options = CompilerOption.KeepReflections,
};
var result = Compile(ref config, Allocator.Persistent);
if (result.IsFailure)
{
return Result.Failure(result.Message);
}
psResult = result.Value;
}
else
{
return Result.Failure("Pixel shader expected.");
}
return new GraphicsCompiledResult
{
tsResult = tsResult,
msResult = msResult,
psResult = psResult,
};
}
public Result<GraphicsCompiledResult> LoadCompiledCache(ShaderPassKey key)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (_compiledResults.TryGetValue(key, out var compiledResult))
{
return compiledResult;
}
else
{
return Result.Failure("Key not found.");
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
_compiler.Dispose();
_utils.Dispose();
_disposed = true;
GC.SuppressFinalize(this);
}
} }

View File

@@ -30,7 +30,11 @@ if (model == null)
} }
var descriptor = SDLCompiler.ResolveShader(model); var descriptor = SDLCompiler.ResolveShader(model);
SDLCompiler.GenerateShader(descriptor, "C:/Users/Misaki/Downloads/Archive");
foreach (var pass in descriptor.passes)
{
SDLCompiler.GeneratePass(pass, "C:/Users/Misaki/Downloads/Archive");
}
Console.WriteLine("Shader compiled successfully:"); Console.WriteLine("Shader compiled successfully:");

View File

@@ -442,7 +442,7 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
return false; return false;
} }
// Build default value if we have a builder (textures currently null / TODO) // Build default Value if we have a builder (textures currently null / TODO)
if (info.Builder != null) if (info.Builder != null)
{ {
try try

View File

@@ -247,11 +247,15 @@ internal static class SDLCompiler
errorMessages.AppendLine(error.ToString()); errorMessages.AppendLine(error.ToString());
} }
return Result.Fail("Failed to compile shader due to errors:\n" + errorMessages.ToString()); return Result.Failure("Failed to compile shader due to errors:\n" + errorMessages.ToString());
} }
var desc = ResolveShader(model); var desc = ResolveShader(model);
var globalPropPath = GenerateGlobalProperties(desc.globalProperties, generatedOutputDirectory); var globalPropResult = GenerateGlobalProperties(desc.globalProperties, generatedOutputDirectory);
if (globalPropResult.IsFailure)
{
return Result.Failure("Failed to generate global properties: " + globalPropResult.Message);
}
foreach (var pass in desc.passes) foreach (var pass in desc.passes)
{ {
@@ -261,15 +265,21 @@ internal static class SDLCompiler
} }
fullPass.includes ??= new List<string>(); fullPass.includes ??= new List<string>();
fullPass.includes.Add(globalPropPath); fullPass.includes.Add(globalPropResult.Value);
fullPass.generatedCodePath = GeneratePass(fullPass, generatedOutputDirectory); var generatedResult = GeneratePass(fullPass, generatedOutputDirectory);
if (generatedResult.IsFailure)
{
return Result.Failure("Failed to generate pass files: " + generatedResult.Message);
}
fullPass.generatedCodePath = generatedResult.Value;
} }
return desc; return desc;
} }
catch (Exception ex) catch (Exception ex)
{ {
return Result.Fail("Failed to generate shader files: " + ex.Message); return Result.Failure("Failed to generate shader files: " + ex.Message);
} }
} }
@@ -303,16 +313,16 @@ internal static class SDLCompiler
}; };
} }
public static string GeneratePass(IPassDescriptor descriptor, string targetDirectory) public static Result<string> GeneratePass(IPassDescriptor descriptor, string targetDirectory)
{ {
if (descriptor is not FullPassDescriptor fullPass) if (descriptor is not FullPassDescriptor fullPass)
{ {
throw new NotSupportedException("Only full pass descriptors are supported for compilation."); return Result.Failure("Only full pass descriptors are supported for compilation.");
} }
if (!Directory.Exists(targetDirectory)) if (!Directory.Exists(targetDirectory))
{ {
throw new ArgumentException("Target directory does not exist.", nameof(targetDirectory)); return Result.Failure("Target directory does not exist.");
} }
var outputFileName = fullPass.uniqueIdentifier.Replace(' ', '_'); var outputFileName = fullPass.uniqueIdentifier.Replace(' ', '_');
@@ -369,11 +379,11 @@ struct PerMaterialData
return outputFilePath; return outputFilePath;
} }
public static string GenerateGlobalProperties(List<PropertyDescriptor> globalProperties, string targetDirectory) public static Result<string> GenerateGlobalProperties(List<PropertyDescriptor> globalProperties, string targetDirectory)
{ {
if (!Directory.Exists(targetDirectory)) if (!Directory.Exists(targetDirectory))
{ {
throw new ArgumentException("Target directory does not exist.", nameof(targetDirectory)); return Result.Failure("Target directory does not exist.");
} }
var globalFilePath = Path.Combine(targetDirectory, _GLOBAL_PROPERTY_FILE_NAME); var globalFilePath = Path.Combine(targetDirectory, _GLOBAL_PROPERTY_FILE_NAME);

View File

@@ -73,7 +73,7 @@ internal static partial class ShaderStructGenerator
var name = $"{CamelCaseToUnderscoreRegex().Replace(enumName, "_$1")}_{names[i]}"; var name = $"{CamelCaseToUnderscoreRegex().Replace(enumName, "_$1")}_{names[i]}";
var value = values.GetValue(i); var value = values.GetValue(i);
// sb.Append(@$" // sb.Append(@$"
//{name} = {value},"); //{name} = {Value},");
sb.Append(@$" sb.Append(@$"
#define {name.ToUpperInvariant()} {value}"); // Use #define for capability. Enum is only support for newer HLSL versions. #define {name.ToUpperInvariant()} {value}"); // Use #define for capability. Enum is only support for newer HLSL versions.
} }