Refactor AppState and rendering pipeline components

Changed the `AppStateMachine` to implement `IDisposable` and `IAsyncDisposable` for better resource management.
Changed the `IAppState` interface to include asynchronous methods for state transitions.
Changed the `App` class to start the host asynchronously and added an `OnClosed` method for proper shutdown.
Changed the `EditorState` class to ensure the window closes correctly when exiting the state.
Changed the `LandingState` class to improve window activation and deactivation management.
Changed the `HostHelper` class to register `LandingWindow` and `EngineEditorWindow` as singletons for better performance.
Changed the `ScenePage` class to utilize a new interface for swap chain management.
Changed the `OpenProjectPage` and `CreateProjectPage` classes to enhance navigation handling.
Changed the `ConsoleViewModel` to improve log update handling with a new context structure.
Changed the `OpenProjectViewModel` to clear project lists when navigating away.
Changed the `EngineCore` class to start the graphics pipeline asynchronously.
Changed the `Logger` class to use a new context structure for log changes.
Added the `ICommandBuffer`, `IGraphicsDevice`, and `IRenderView` interfaces to enhance the rendering pipeline.
Changed the `DX12CommandBuffer`, `DX12GraphicsDevice`, and `DX12RenderView` classes for improved resource management and rendering efficiency.
Refactored the `Mesh` class to use a new `Vertex` structure for simplified vertex management.
Added the `TextureUtility` class for texture management utilities, including mip count calculation.
Changed the `launchSettings.json` to include a new profile for the graphics project with native debugging enabled.
Changed the `MeshBuilder` class to utilize the new `Vertex` structure for vertex creation.
This commit is contained in:
2025-06-29 11:38:29 +09:00
parent 4110c166cf
commit 8fd1222780
34 changed files with 1479 additions and 269 deletions

View File

@@ -1,6 +1,6 @@
namespace Ghost.Editor.Core.AppState; namespace Ghost.Editor.Core.AppState;
internal class AppStateMachine internal partial class AppStateMachine : IDisposable, IAsyncDisposable
{ {
private Dictionary<StateKey, Lazy<IAppState>> s_states = new(); private Dictionary<StateKey, Lazy<IAppState>> s_states = new();
private IAppState? s_current; private IAppState? s_current;
@@ -13,22 +13,43 @@ internal class AppStateMachine
public async Task TransitionToAsync(StateKey stateKey, object? parameter = null) public async Task TransitionToAsync(StateKey stateKey, object? parameter = null)
{ {
var previous = s_current; var previous = s_current;
var next = s_states[stateKey].Value; if (!s_states.TryGetValue(stateKey, out var next))
{
throw new InvalidOperationException($"State '{stateKey}' is not registered.");
}
if (previous != null) if (previous != null)
{ {
await previous.OnExitingAsync(); await previous.OnExitingAsync();
} }
await next.OnEnteringAsync(parameter); await next.Value.OnEnteringAsync(parameter);
if (previous != null) if (previous != null)
{ {
await previous.OnExitedAsync(); await previous.OnExitedAsync();
} }
await next.OnEnteredAsync(parameter); await next.Value.OnEnteredAsync(parameter);
s_current = next; s_current = next.Value;
}
public void Dispose()
{
s_states.Clear();
s_current?.OnExitingAsync().GetAwaiter().GetResult();
s_current?.OnExitedAsync().GetAwaiter().GetResult();
}
public async ValueTask DisposeAsync()
{
s_states.Clear();
if (s_current != null)
{
await s_current.OnExitingAsync();
await s_current.OnExitedAsync();
}
} }
} }

View File

@@ -1,6 +1,4 @@
using System.Threading.Tasks; namespace Ghost.Editor.Core.AppState;
namespace Ghost.Editor.Core.AppState;
internal interface IAppState internal interface IAppState
{ {

View File

@@ -27,7 +27,10 @@ public partial class App : Application
{ {
if (Current is App app) if (Current is App app)
{ {
// HACK: As far as I can tell, there is no proper application shutdown event in WinUI 3.
app._window?.Closed -= app.OnClosed;
app._window = value; app._window = value;
app._window?.Closed += app.OnClosed;
} }
} }
} }
@@ -86,7 +89,7 @@ public partial class App : Application
{ {
base.OnLaunched(args); base.OnLaunched(args);
Host.Start(); await Host.StartAsync();
ActivationHandler.Handle(args); ActivationHandler.Handle(args);
var stateMachine = GetService<AppStateMachine>(); var stateMachine = GetService<AppStateMachine>();
@@ -96,6 +99,12 @@ public partial class App : Application
await stateMachine.TransitionToAsync(StateKey.Landing); await stateMachine.TransitionToAsync(StateKey.Landing);
} }
private void OnClosed(object? sender, WindowEventArgs args)
{
Host.StopAsync().GetAwaiter().GetResult();
Host.Dispose();
}
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{ {
Logger.LogError(e.Exception); Logger.LogError(e.Exception);

View File

@@ -17,6 +17,7 @@ internal class EditorState : IAppState
{ {
App.Window = null; App.Window = null;
} }
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -44,11 +45,6 @@ internal class EditorState : IAppState
await _engineCore.ShutDownAsync(); await _engineCore.ShutDownAsync();
} }
if (App.Window == _window)
{
App.Window = null;
}
_window?.Close(); _window?.Close();
_window = null; _window = null;
} }

View File

@@ -1,6 +1,4 @@
using Ghost.Editor; using Ghost.Editor.View.Windows;
using Ghost.Editor.View.Windows;
using System.Threading.Tasks;
namespace Ghost.Editor.Core.AppState; namespace Ghost.Editor.Core.AppState;
@@ -14,27 +12,25 @@ internal class LandingState : IAppState
{ {
App.Window = null; App.Window = null;
} }
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task OnEnteringAsync(object? parameter) public Task OnEnteringAsync(object? parameter)
{ {
_window = App.GetService<LandingWindow>(); _window = App.GetService<LandingWindow>();
_window.Activate();
App.Window = _window; App.Window = _window;
_window.Activate();
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task OnExitedAsync() public Task OnExitedAsync()
{ {
if (App.Window == _window)
{
App.Window = null;
}
_window?.Close(); _window?.Close();
_window = null; _window = null;
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@@ -1,7 +1,8 @@
{ {
"profiles": { "profiles": {
"Ghost.Editor (Package)": { "Ghost.Editor (Package)": {
"commandName": "MsixPackage" "commandName": "MsixPackage",
"nativeDebugging": false
}, },
"Ghost.Editor (Unpackaged)": { "Ghost.Editor (Unpackaged)": {
"commandName": "Project" "commandName": "Project"

View File

@@ -15,7 +15,7 @@ internal static partial class HostHelper
{ {
public static void AddLandingScope(HostBuilderContext context, IServiceCollection services) public static void AddLandingScope(HostBuilderContext context, IServiceCollection services)
{ {
services.AddTransient<LandingWindow>(); services.AddSingleton<LandingWindow>();
services.AddTransient<CreateProjectPage>(); services.AddTransient<CreateProjectPage>();
services.AddTransient<CreateProjectViewModel>(); services.AddTransient<CreateProjectViewModel>();
@@ -30,8 +30,8 @@ internal static partial class HostHelper
{ {
services.AddSingleton<EngineCore>(); services.AddSingleton<EngineCore>();
services.AddTransient<EngineEditorWindow>(); services.AddSingleton<EngineEditorWindow>();
services.AddTransient<EngineEditorViewModel>(); services.AddSingleton<EngineEditorViewModel>();
services.AddTransient<ScenePage>(); services.AddTransient<ScenePage>();

View File

@@ -2,8 +2,7 @@ using Ghost.Editor.Controls.Internal;
using Ghost.Graphics; using Ghost.Graphics;
using Ghost.Graphics.Contracts; using Ghost.Graphics.Contracts;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media; using Vortice.WinUI;
using SharpGen.Runtime;
using WinRT; using WinRT;
namespace Ghost.Editor.View.Pages.EngineEditor; namespace Ghost.Editor.View.Pages.EngineEditor;
@@ -11,6 +10,7 @@ namespace Ghost.Editor.View.Pages.EngineEditor;
internal sealed partial class ScenePage : NavigationTabPage internal sealed partial class ScenePage : NavigationTabPage
{ {
private IRenderView? _renderer; private IRenderView? _renderer;
private ISwapChainPanelNative2? _swapChainPanelNative;
public ScenePage() public ScenePage()
{ {
@@ -28,19 +28,19 @@ internal sealed partial class ScenePage : NavigationTabPage
private void SwapChainPanel_Loaded(object sender, RoutedEventArgs e) private void SwapChainPanel_Loaded(object sender, RoutedEventArgs e)
{ {
var guid = typeof(Vortice.WinUI.ISwapChainPanelNative).GUID; var guid = typeof(ISwapChainPanelNative2).GUID;
Result result = ((IWinRTObject)SwapChainPanel).NativeObject.TryAs(guid, out var swapChainPanelNativeHandle); ((IWinRTObject)SwapChainPanel).NativeObject.TryAs(guid, out var swapChainPanelNativeHandle);
result.CheckError();
var swapChainPanelNative = new Vortice.WinUI.ISwapChainPanelNative(swapChainPanelNativeHandle); _swapChainPanelNative = new ISwapChainPanelNative2(swapChainPanelNativeHandle);
_renderer = GraphicsPipeline.GraphicsDevice.CreateRenderView(new(swapChainPanelNative, (uint)SwapChainPanel.ActualWidth, (uint)SwapChainPanel.ActualHeight)); _renderer = GraphicsPipeline.GraphicsDevice.CreateRenderView(new(_swapChainPanelNative, (uint)SwapChainPanel.ActualWidth, (uint)SwapChainPanel.ActualHeight));
CompositionTarget.Rendering += OnRendering; //CompositionTarget.Rendering += OnRendering;
} }
private void SwapChainPanel_Unloaded(object sender, RoutedEventArgs e) private void SwapChainPanel_Unloaded(object sender, RoutedEventArgs e)
{ {
CompositionTarget.Rendering -= OnRendering; //CompositionTarget.Rendering -= OnRendering;
_swapChainPanelNative?.Dispose();
_renderer?.Dispose(); _renderer?.Dispose();
} }
@@ -48,7 +48,7 @@ internal sealed partial class ScenePage : NavigationTabPage
{ {
if (e.NewSize.Width > 8.0 && e.NewSize.Height > 8.0) if (e.NewSize.Width > 8.0 && e.NewSize.Height > 8.0)
{ {
_renderer?.Resize((uint)e.NewSize.Width, (uint)e.NewSize.Height); _renderer?.RequestResize((uint)e.NewSize.Width, (uint)e.NewSize.Height);
} }
} }
} }

View File

@@ -23,4 +23,10 @@ internal sealed partial class CreateProjectPage : Page
base.OnNavigatedTo(e); base.OnNavigatedTo(e);
ViewModel.OnNavigatedTo(e.Parameter); ViewModel.OnNavigatedTo(e.Parameter);
} }
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
ViewModel.OnNavigatedFrom();
}
} }

View File

@@ -1,5 +1,5 @@
using Ghost.Editor.ViewModels.Pages.Landing; using Ghost.Data.Models;
using Ghost.Data.Models; using Ghost.Editor.ViewModels.Pages.Landing;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation; using Microsoft.UI.Xaml.Navigation;
@@ -26,7 +26,7 @@ internal sealed partial class OpenProjectPage : Page
ViewModel.OnNavigatedTo(e.Parameter); ViewModel.OnNavigatedTo(e.Parameter);
} }
override protected void OnNavigatedFrom(NavigationEventArgs e) protected override void OnNavigatedFrom(NavigationEventArgs e)
{ {
base.OnNavigatedFrom(e); base.OnNavigatedFrom(e);
ViewModel.OnNavigatedFrom(); ViewModel.OnNavigatedFrom();
@@ -65,7 +65,8 @@ internal sealed partial class OpenProjectPage : Page
{ {
if (e.ClickedItem is ProjectMetadataInfo project) if (e.ClickedItem is ProjectMetadataInfo project)
{ {
await ViewModel.LoadProject(project); await Task.Yield();
await ViewModel.OpenProjectAsync(project);
} }
} }
} }

View File

@@ -3,7 +3,6 @@ using Ghost.Editor.Core.Notifications;
using Ghost.Editor.Core.Progress; using Ghost.Editor.Core.Progress;
using Ghost.Editor.ViewModels.Windows; using Ghost.Editor.ViewModels.Windows;
using Ghost.Engine.Resources; using Ghost.Engine.Resources;
using Microsoft.Extensions.DependencyInjection;
using WinUIEx; using WinUIEx;
// To learn more about WinUI, the WinUI project structure, // To learn more about WinUI, the WinUI project structure,
@@ -15,8 +14,6 @@ namespace Ghost.Editor.View.Windows;
/// </summary> /// </summary>
internal sealed partial class EngineEditorWindow : WindowEx internal sealed partial class EngineEditorWindow : WindowEx
{ {
private IServiceScope? _editorScope;
private readonly NotificationService _notificationService; private readonly NotificationService _notificationService;
private readonly ProgressService _progressService; private readonly ProgressService _progressService;
@@ -45,17 +42,12 @@ internal sealed partial class EngineEditorWindow : WindowEx
{ {
Bindings.Update(); Bindings.Update();
_editorScope?.Dispose();
_editorScope = App.CreateScope();
_notificationService.SetReference(InfoBar, NotificationQueue); _notificationService.SetReference(InfoBar, NotificationQueue);
_progressService.SetReference(ProgressBarContainer); _progressService.SetReference(ProgressBarContainer);
} }
private void WindowEx_Closed(object sender, Microsoft.UI.Xaml.WindowEventArgs args) private void WindowEx_Closed(object sender, Microsoft.UI.Xaml.WindowEventArgs args)
{ {
_editorScope?.Dispose();
_notificationService.ClearReference(); _notificationService.ClearReference();
_progressService.ClearReference(); _progressService.ClearReference();
} }

View File

@@ -2,7 +2,6 @@
using Ghost.Editor.Core.Notifications; using Ghost.Editor.Core.Notifications;
using Ghost.Editor.View.Pages.Landing; using Ghost.Editor.View.Pages.Landing;
using Ghost.Engine.Resources; using Ghost.Engine.Resources;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation; using Microsoft.UI.Xaml.Media.Animation;
using WinUIEx; using WinUIEx;
@@ -11,8 +10,6 @@ namespace Ghost.Editor.View.Windows;
internal sealed partial class LandingWindow : WindowEx internal sealed partial class LandingWindow : WindowEx
{ {
private IServiceScope? _landingScope;
private readonly NotificationService _notificationService; private readonly NotificationService _notificationService;
private int _previousSelectedIndex; private int _previousSelectedIndex;
@@ -34,14 +31,11 @@ internal sealed partial class LandingWindow : WindowEx
private void WindowEx_Activated(object sender, Microsoft.UI.Xaml.WindowActivatedEventArgs args) private void WindowEx_Activated(object sender, Microsoft.UI.Xaml.WindowActivatedEventArgs args)
{ {
_landingScope?.Dispose();
_landingScope = App.CreateScope();
_notificationService.SetReference(InfoBar, NotificationQueue); _notificationService.SetReference(InfoBar, NotificationQueue);
} }
private void WindowEx_Closed(object sender, Microsoft.UI.Xaml.WindowEventArgs args) private void WindowEx_Closed(object sender, Microsoft.UI.Xaml.WindowEventArgs args)
{ {
_landingScope?.Dispose();
_notificationService.ClearReference(); _notificationService.ClearReference();
} }
@@ -58,7 +52,7 @@ internal sealed partial class LandingWindow : WindowEx
var slideNavigationTransitionEffect = currentSelectedIndex - _previousSelectedIndex > 0 ? var slideNavigationTransitionEffect = currentSelectedIndex - _previousSelectedIndex > 0 ?
SlideNavigationTransitionEffect.FromRight : SlideNavigationTransitionEffect.FromLeft; SlideNavigationTransitionEffect.FromRight : SlideNavigationTransitionEffect.FromLeft;
ContentFrame.Navigate(pageType, _landingScope, new SlideNavigationTransitionInfo() { Effect = slideNavigationTransitionEffect }); ContentFrame.Navigate(pageType, null, new SlideNavigationTransitionInfo() { Effect = slideNavigationTransitionEffect });
_previousSelectedIndex = currentSelectedIndex; _previousSelectedIndex = currentSelectedIndex;
} }

View File

@@ -59,17 +59,17 @@ internal partial class ConsoleViewModel : ObservableObject
Logger.OnLogsUpdate -= UpdateLogs; Logger.OnLogsUpdate -= UpdateLogs;
} }
private void UpdateLogs(LogChangeType updateType) private void UpdateLogs(LogChangeContext ctx)
{ {
switch (updateType) switch (ctx.changeType)
{ {
case LogChangeType.LogAdded: case LogChangeType.LogAdded:
Logs.Add(Logger.Logs[^1]); Logs.Add(Logger.Logs[ctx.index]);
break; break;
case LogChangeType.LogRemoved: case LogChangeType.LogRemoved:
if (Logs.Count > 0) if (Logs.Count > 0)
{ {
Logs.RemoveAt(0); Logs.RemoveAt(ctx.index);
} }
break; break;
case LogChangeType.LogsCleared: case LogChangeType.LogsCleared:

View File

@@ -36,7 +36,6 @@ internal partial class OpenProjectViewModel(ProjectService projectService, INoti
public async void OnNavigatedTo(object? parameter) public async void OnNavigatedTo(object? parameter)
{ {
projects.Clear();
await foreach (var projectInfo in projectService.GetAllProjectAsync()) await foreach (var projectInfo in projectService.GetAllProjectAsync())
{ {
var metadata = await ProjectService.LoadMetadataAsync(projectInfo.MetadataPath); var metadata = await ProjectService.LoadMetadataAsync(projectInfo.MetadataPath);
@@ -54,6 +53,7 @@ internal partial class OpenProjectViewModel(ProjectService projectService, INoti
public void OnNavigatedFrom() public void OnNavigatedFrom()
{ {
projects.Clear();
} }
public async Task ContentDrop(DataPackageView dataView) public async Task ContentDrop(DataPackageView dataView)
@@ -89,7 +89,7 @@ internal partial class OpenProjectViewModel(ProjectService projectService, INoti
UpdateEmptyPlaceHolderVisibility(); UpdateEmptyPlaceHolderVisibility();
} }
public async Task LoadProject(ProjectMetadataInfo project) public async Task OpenProjectAsync(ProjectMetadataInfo project)
{ {
try try
{ {

View File

@@ -11,6 +11,7 @@ internal class EngineCore
{ {
ActivationHandler.Handle(args); ActivationHandler.Handle(args);
GraphicsPipeline.Initialize(GraphicsAPI.DX12); GraphicsPipeline.Initialize(GraphicsAPI.DX12);
GraphicsPipeline.Start();
Logger.LogInfo("Engine started successfully."); Logger.LogInfo("Engine started successfully.");

View File

@@ -10,6 +10,12 @@ internal enum LogChangeType
LogsCleared LogsCleared
} }
internal readonly struct LogChangeContext(LogChangeType type, int index)
{
public readonly LogChangeType changeType = type;
public readonly int index = index;
}
public static class Logger public static class Logger
{ {
@@ -18,31 +24,30 @@ public static class Logger
private static readonly List<LogMessage> _logs = new(); private static readonly List<LogMessage> _logs = new();
internal static IReadOnlyList<LogMessage> Logs => _logs; internal static IReadOnlyList<LogMessage> Logs => _logs;
internal static event Action<LogChangeType>? OnLogsUpdate; internal static event Action<LogChangeContext>? OnLogsUpdate;
internal static bool HasStackTrace internal static bool HasStackTrace
{ {
get; set; get; set;
} }
private static void LogInternal(LogLevel level, string? message, int skipFrame) private static StackTrace? CaptureStackTrace()
{
return HasStackTrace ? new StackTrace(1, true) : null;
}
private static void LogInternal(LogLevel level, object? message, StackTrace? stackTrace)
{ {
if (_logs.Count >= _MAX_LOGS) if (_logs.Count >= _MAX_LOGS)
{ {
_logs.RemoveAt(0); _logs.RemoveAt(0);
OnLogsUpdate?.Invoke(LogChangeType.LogRemoved); OnLogsUpdate?.Invoke(new(LogChangeType.LogRemoved, 0));
} }
StackTrace? stackTrace = null; var logMessage = new LogMessage(level, message?.ToString(), stackTrace?.ToString());
if (HasStackTrace)
{
stackTrace = new StackTrace(skipFrame, true);
}
var logMessage = new LogMessage(level, message, stackTrace?.ToString());
_logs.Add(logMessage); _logs.Add(logMessage);
OnLogsUpdate?.Invoke(LogChangeType.LogAdded); OnLogsUpdate?.Invoke(new(LogChangeType.LogAdded, _logs.Count - 1));
} }
private static void LogExceptionInternal(Exception ex) private static void LogExceptionInternal(Exception ex)
@@ -50,33 +55,33 @@ public static class Logger
if (_logs.Count >= _MAX_LOGS) if (_logs.Count >= _MAX_LOGS)
{ {
_logs.RemoveAt(0); _logs.RemoveAt(0);
OnLogsUpdate?.Invoke(LogChangeType.LogRemoved); OnLogsUpdate?.Invoke(new(LogChangeType.LogRemoved, 0));
} }
var logMessage = new LogMessage(LogLevel.Error, ex.Message, ex.StackTrace); var logMessage = new LogMessage(LogLevel.Error, ex.Message, ex.StackTrace);
_logs.Add(logMessage); _logs.Add(logMessage);
OnLogsUpdate?.Invoke(LogChangeType.LogAdded); OnLogsUpdate?.Invoke(new(LogChangeType.LogAdded, _logs.Count - 1));
} }
public static void Log(LogLevel level, string? message) public static void Log(LogLevel level, object? message)
{ {
LogInternal(level, message, 2); LogInternal(level, message, CaptureStackTrace());
} }
public static void LogInfo(string? message) public static void LogInfo(object? message)
{ {
LogInternal(LogLevel.Info, message, 3); LogInternal(LogLevel.Info, message, CaptureStackTrace());
} }
public static void LogWarning(string? message) public static void LogWarning(object? message)
{ {
LogInternal(LogLevel.Warning, message, 3); LogInternal(LogLevel.Warning, message, CaptureStackTrace());
} }
public static void LogError(string? message) public static void LogError(object? message)
{ {
LogInternal(LogLevel.Error, message, 3); LogInternal(LogLevel.Error, message, CaptureStackTrace());
} }
public static void LogError(Exception ex) public static void LogError(Exception ex)
@@ -84,17 +89,17 @@ public static class Logger
LogExceptionInternal(ex); LogExceptionInternal(ex);
} }
public static void Assert(bool condition, string? message = null) public static void Assert(bool condition, object? message = null)
{ {
if (!condition) if (!condition)
{ {
LogInternal(LogLevel.Error, message ?? "Assertion failed", 3); LogInternal(LogLevel.Error, message ?? "Assertion failed", CaptureStackTrace());
} }
} }
internal static void Clear() internal static void Clear()
{ {
_logs.Clear(); _logs.Clear();
OnLogsUpdate?.Invoke(LogChangeType.LogsCleared); OnLogsUpdate?.Invoke(new(LogChangeType.LogsCleared, -1));
} }
} }

View File

@@ -0,0 +1,5 @@
namespace Ghost.Graphics.Contracts;
public interface ICommandBuffer
{
}

View File

@@ -2,10 +2,10 @@
namespace Ghost.Graphics.Contracts; namespace Ghost.Graphics.Contracts;
internal interface IGraphicsDevice : IDisposable public interface IGraphicsDevice : IDisposable
{ {
public static abstract IGraphicsDevice Create(); public static abstract IGraphicsDevice Create();
public IRenderView CreateRenderView(in SwapChainSurface swapChainSurface); public IRenderView CreateRenderView(in SwapChainPresenter swapChainSurface);
public void OnRender(); public void OnRender();
} }

View File

@@ -1,11 +1,42 @@
namespace Ghost.Graphics.Contracts; namespace Ghost.Graphics.Contracts;
/// <summary>
/// Defines the contract for a render view in the graphics pipeline.
/// </summary>
internal interface IRenderView : IDisposable internal interface IRenderView : IDisposable
{ {
public void Resize(uint width, uint height); /// <summary>
public void Render(); /// Requests a resize of the render view.
/// </summary>
/// <param name="width">The new width of the render view.</param>
/// <param name="height">The new height of the render view.</param>
/// <remarks>This only submits a resize request without executing it. May overwrite last request if next request issued before next frame.</remarks>
public void RequestResize(uint width, uint height);
/// <summary>
/// Executes any pending resize operations for the current context.
/// </summary>
public void ExecutePendingResize();
/// <summary>
/// Begins a render operation.
/// </summary>
/// <returns>An ICommandBuffer instance to manage render commands.</returns>
public ICommandBuffer BeginRender();
/// <summary>
/// Renders the current content to the output target.
/// </summary>
public void Render();
/// <summary>
/// Ends the current rendering operation and finalizes any pending rendering tasks.
/// </summary>
public void EndRender();
/// <summary>
/// Waits for the next frame to be ready for rendering.
/// </summary>
public void WaitNextFrame(); public void WaitNextFrame();
/// <summary>
/// Waits for the rendering operations to complete and the GPU to be idle.
/// </summary>
public void WaitIdle(); public void WaitIdle();
public void Flush();
} }

View File

@@ -0,0 +1,14 @@
using Ghost.Graphics.Contracts;
using Vortice.Direct3D12;
namespace Ghost.Graphics.DX12;
internal class DX12CommandBuffer : ICommandBuffer
{
private ID3D12GraphicsCommandList10 _commandList;
public DX12CommandBuffer(ID3D12GraphicsCommandList10 commandList)
{
_commandList = commandList;
}
}

View File

@@ -8,29 +8,29 @@ namespace Ghost.Graphics.DX12;
internal class DX12DebugLayer : IDebugLayer internal class DX12DebugLayer : IDebugLayer
{ {
#if DEBUG
private readonly ID3D12Debug6 _d3d12Debug; private readonly ID3D12Debug6 _d3d12Debug;
private readonly IDXGIDebug1 _dxgiDebug; private readonly IDXGIDebug1 _dxgiDebug;
#endif private readonly IDXGIInfoQueue? _dxgiInfoQueue;
public DX12DebugLayer() public DX12DebugLayer()
{ {
#if DEBUG
_d3d12Debug = D3D12.D3D12GetDebugInterface<ID3D12Debug6>(); _d3d12Debug = D3D12.D3D12GetDebugInterface<ID3D12Debug6>();
_d3d12Debug.EnableDebugLayer(); _d3d12Debug.EnableDebugLayer();
_dxgiDebug = DXGI.DXGIGetDebugInterface1<IDXGIDebug1>(); _dxgiDebug = DXGI.DXGIGetDebugInterface1<IDXGIDebug1>();
_dxgiDebug.EnableLeakTrackingForThread(); _dxgiDebug.EnableLeakTrackingForThread();
#endif
_dxgiInfoQueue = DXGI.DXGIGetDebugInterface1<IDXGIInfoQueue>();
_dxgiInfoQueue.SetBreakOnSeverity(DXGI.DebugAll, InfoQueueMessageSeverity.Error, true);
_dxgiInfoQueue.SetBreakOnSeverity(DXGI.DebugAll, InfoQueueMessageSeverity.Corruption, true);
} }
public void Dispose() public void Dispose()
{ {
#if DEBUG
_dxgiDebug.ReportLiveObjects(DXGI.DebugAll, ReportLiveObjectFlags.Detail | ReportLiveObjectFlags.IgnoreInternal); _dxgiDebug.ReportLiveObjects(DXGI.DebugAll, ReportLiveObjectFlags.Detail | ReportLiveObjectFlags.IgnoreInternal);
_d3d12Debug?.Dispose(); _d3d12Debug.Dispose();
_dxgiDebug?.Dispose(); _dxgiDebug.Dispose();
#endif _dxgiInfoQueue?.Dispose();
} }
} }

View File

@@ -13,11 +13,14 @@ internal class DX12GraphicsDevice : IGraphicsDevice
private readonly ID3D12CommandQueue _commandQueue; private readonly ID3D12CommandQueue _commandQueue;
private readonly List<IRenderView> _renderViews = new(); private readonly List<IRenderView> _renderViews = new();
private readonly Lock _lock = new();
#if DEBUG #if DEBUG
private readonly IDebugLayer _debugLayer; private readonly DX12DebugLayer _debugLayer;
#endif #endif
private bool _disposed;
public ID3D12Device14 Device => _device; public ID3D12Device14 Device => _device;
public IDXGIFactory7 DXGIFactory => _dxgiFactory; public IDXGIFactory7 DXGIFactory => _dxgiFactory;
public ID3D12CommandQueue CommandQueue => _commandQueue; public ID3D12CommandQueue CommandQueue => _commandQueue;
@@ -61,6 +64,8 @@ internal class DX12GraphicsDevice : IGraphicsDevice
adapter.Dispose(); adapter.Dispose();
break; break;
} }
adapter.Dispose();
} }
if (d3d12Device == null) if (d3d12Device == null)
@@ -83,35 +88,52 @@ internal class DX12GraphicsDevice : IGraphicsDevice
queue = _device.CreateCommandQueue(queueDesc); queue = _device.CreateCommandQueue(queueDesc);
} }
public IRenderView CreateRenderView(in SwapChainSurface swapChainSurface) public IRenderView CreateRenderView(in SwapChainPresenter swapChainSurface)
{ {
var renderView = new DX12RenderView(this, swapChainSurface); var renderView = new DX12RenderView(this, swapChainSurface);
lock (_lock)
{
_renderViews.Add(renderView); _renderViews.Add(renderView);
}
return renderView; return renderView;
} }
public void OnRender() public void OnRender()
{
lock (_lock)
{ {
foreach (var renderView in _renderViews) foreach (var renderView in _renderViews)
{ {
renderView.ExecutePendingResize();
renderView.BeginRender();
renderView.Render(); renderView.Render();
renderView.EndRender();
}
} }
} }
public void Dispose() public void Dispose()
{ {
if (_disposed)
{
return;
}
foreach (var renderView in _renderViews) foreach (var renderView in _renderViews)
{ {
renderView.Dispose(); renderView.Dispose();
} }
_renderViews.Clear(); _renderViews.Clear();
_commandQueue?.Dispose(); _commandQueue.Release();
_device?.Dispose(); _device.Release();
_dxgiFactory?.Dispose(); _dxgiFactory.Release();
#if DEBUG #if DEBUG
_debugLayer.Dispose(); _debugLayer.Dispose();
#endif #endif
_disposed = true;
} }
} }

View File

@@ -1,7 +1,6 @@
using Ghost.Graphics.Contracts; using Ghost.Graphics.Contracts;
using Ghost.Graphics.Data; using Ghost.Graphics.Data;
using Ghost.Graphics.DX12.Utilities; using Ghost.Graphics.DX12.Utilities;
using System.Runtime.CompilerServices;
using Vortice.Direct3D12; using Vortice.Direct3D12;
using Vortice.DXGI; using Vortice.DXGI;
@@ -13,6 +12,7 @@ internal class DX12RenderView : IRenderView
private const int _DEPTH_STENCIL_VIEW_HEAP_SIZE = 256; private const int _DEPTH_STENCIL_VIEW_HEAP_SIZE = 256;
private readonly DX12GraphicsDevice _graphicsDevice; private readonly DX12GraphicsDevice _graphicsDevice;
private readonly SwapChainPresenter _swapChainPresenter;
private readonly IDXGISwapChain4 _swapChain; private readonly IDXGISwapChain4 _swapChain;
private readonly ID3D12Resource[] _renderTargets; private readonly ID3D12Resource[] _renderTargets;
@@ -20,7 +20,7 @@ internal class DX12RenderView : IRenderView
private uint _backBufferIndex; private uint _backBufferIndex;
private readonly ID3D12CommandAllocator[] _commandAllocators; private readonly ID3D12CommandAllocator[] _commandAllocators;
private readonly ID3D12GraphicsCommandList7 _commandList; private readonly ID3D12GraphicsCommandList10 _commandList;
private readonly ID3D12Fence1 _fence; private readonly ID3D12Fence1 _fence;
private readonly AutoResetEvent _fenceEvent; private readonly AutoResetEvent _fenceEvent;
@@ -28,9 +28,19 @@ internal class DX12RenderView : IRenderView
private readonly D3D12DescriptorAllocator _rtvHeap; private readonly D3D12DescriptorAllocator _rtvHeap;
public DX12RenderView(DX12GraphicsDevice pipelineContext, in SwapChainSurface swapChainSurface) private readonly ICommandBuffer _commandBuffer;
private readonly Lock _lock = new();
private uint _pendingWidth;
private uint _pendingHeight;
private bool _resizeRequested;
private bool _disposed;
public DX12RenderView(DX12GraphicsDevice graphicsDevice, in SwapChainPresenter swapChainSurface)
{ {
_graphicsDevice = pipelineContext; _graphicsDevice = graphicsDevice;
_swapChainPresenter = swapChainSurface;
_rtvHeap = new(_graphicsDevice.Device, DescriptorHeapType.RenderTargetView, _RENDER_TARGET_VIEW_HEAP_SIZE); _rtvHeap = new(_graphicsDevice.Device, DescriptorHeapType.RenderTargetView, _RENDER_TARGET_VIEW_HEAP_SIZE);
@@ -39,21 +49,23 @@ internal class DX12RenderView : IRenderView
_fenceValues = new ulong[GraphicsPipeline.FRAME_COUNT]; _fenceValues = new ulong[GraphicsPipeline.FRAME_COUNT];
_renderTargetDescriptorIndexes = new uint[GraphicsPipeline.FRAME_COUNT]; _renderTargetDescriptorIndexes = new uint[GraphicsPipeline.FRAME_COUNT];
InitializeSwapChain(swapChainSurface, out _swapChain); InitializeSwapChain(out _swapChain);
InitializeCommandObjects(out _commandAllocators, out _commandList, out _fence); InitializeCommandObjects(out _commandAllocators, out _commandList, out _fence);
CreateRenderTargets(); CreateRenderTargets();
_commandBuffer = new DX12CommandBuffer(_commandList);
} }
private void InitializeSwapChain(in SwapChainSurface swapChainSurface, out IDXGISwapChain4 swapChain) private void InitializeSwapChain(out IDXGISwapChain4 swapChain)
{ {
var swapChainDesc = new SwapChainDescription1 var swapChainDesc = new SwapChainDescription1
{ {
Width = swapChainSurface.Width, Width = _swapChainPresenter.Width,
Height = swapChainSurface.Height, Height = _swapChainPresenter.Height,
Format = Format.B8G8R8A8_UNorm, Format = Format.B8G8R8A8_UNorm,
Stereo = false, Stereo = false,
SampleDescription = new SampleDescription(1, 0), SampleDescription = new SampleDescription(1, 0),
BufferUsage = Usage.RenderTargetOutput, BufferUsage = Usage.Backbuffer | Usage.RenderTargetOutput,
BufferCount = GraphicsPipeline.FRAME_COUNT, BufferCount = GraphicsPipeline.FRAME_COUNT,
Scaling = Scaling.Stretch, Scaling = Scaling.Stretch,
SwapEffect = SwapEffect.FlipDiscard, SwapEffect = SwapEffect.FlipDiscard,
@@ -61,36 +73,37 @@ internal class DX12RenderView : IRenderView
Flags = SwapChainFlags.AllowTearing Flags = SwapChainFlags.AllowTearing
}; };
// NOTE: Not going to need it for now, this is for standalone applications. switch (_swapChainPresenter.Type)
{
case SwapChainPresenter.TargetType.Composition:
var swapChain1 = _graphicsDevice.DXGIFactory.CreateSwapChainForComposition(_graphicsDevice.CommandQueue, swapChainDesc);
swapChain = swapChain1.QueryInterface<IDXGISwapChain4>();
swapChain1.Dispose();
_backBufferIndex = swapChain.CurrentBackBufferIndex;
_swapChainPresenter.SwapChainPanelNative!.SetSwapChain(swapChain);
break;
case SwapChainPresenter.TargetType.Hwnd:
var swapChainFullscreenDesc = new SwapChainFullscreenDescription var swapChainFullscreenDesc = new SwapChainFullscreenDescription
{ {
Windowed = true, Windowed = true,
}; };
switch (swapChainSurface.Type)
{
case SwapChainSurface.TargetType.Composition:
var swapChain1 = _graphicsDevice.DXGIFactory.CreateSwapChainForComposition(_graphicsDevice.CommandQueue, swapChainDesc);
swapChain = swapChain1.QueryInterface<IDXGISwapChain4>();
_backBufferIndex = swapChain.CurrentBackBufferIndex;
swapChainSurface.SwapChainPanelNative!.SetSwapChain(swapChain);
break;
case SwapChainSurface.TargetType.Hwnd:
var swapChain2 = _graphicsDevice.DXGIFactory.CreateSwapChainForHwnd( var swapChain2 = _graphicsDevice.DXGIFactory.CreateSwapChainForHwnd(
_graphicsDevice.CommandQueue, _graphicsDevice.CommandQueue,
swapChainSurface.Hwnd, _swapChainPresenter.Hwnd,
swapChainDesc, swapChainDesc,
swapChainFullscreenDesc, swapChainFullscreenDesc,
null); null);
swapChain = swapChain2.QueryInterface<IDXGISwapChain4>(); swapChain = swapChain2.QueryInterface<IDXGISwapChain4>();
swapChain2.Dispose();
break; break;
default: default:
throw new ArgumentException("Unsupported swap chain surface type."); throw new ArgumentException("Unsupported swap chain surface type.");
} }
} }
private void InitializeCommandObjects(out ID3D12CommandAllocator[] commandAllocator, out ID3D12GraphicsCommandList7 commandList, out ID3D12Fence1 fence) private void InitializeCommandObjects(out ID3D12CommandAllocator[] commandAllocator, out ID3D12GraphicsCommandList10 commandList, out ID3D12Fence1 fence)
{ {
commandAllocator = new ID3D12CommandAllocator[GraphicsPipeline.FRAME_COUNT]; commandAllocator = new ID3D12CommandAllocator[GraphicsPipeline.FRAME_COUNT];
for (var i = 0; i < GraphicsPipeline.FRAME_COUNT; i++) for (var i = 0; i < GraphicsPipeline.FRAME_COUNT; i++)
@@ -98,10 +111,10 @@ internal class DX12RenderView : IRenderView
commandAllocator[i] = _graphicsDevice.Device.CreateCommandAllocator(CommandListType.Direct); commandAllocator[i] = _graphicsDevice.Device.CreateCommandAllocator(CommandListType.Direct);
} }
commandList = _graphicsDevice.Device.CreateCommandList<ID3D12GraphicsCommandList7>(CommandListType.Direct, commandAllocator[0], null!); commandList = _graphicsDevice.Device.CreateCommandList<ID3D12GraphicsCommandList10>(CommandListType.Direct, commandAllocator[0], null!);
commandList.Close();
fence = _graphicsDevice.Device.CreateFence<ID3D12Fence1>(_fenceValues[_backBufferIndex], FenceFlags.None); fence = _graphicsDevice.Device.CreateFence<ID3D12Fence1>(_fenceValues[_backBufferIndex], FenceFlags.None);
_commandList.Close();
_fenceValues[_backBufferIndex]++; _fenceValues[_backBufferIndex]++;
} }
@@ -118,32 +131,80 @@ internal class DX12RenderView : IRenderView
} }
} }
public void Resize(uint width, uint height) public void RequestResize(uint width, uint height)
{ {
lock (_lock)
{
if (_pendingWidth == width && _pendingHeight == height)
{
return;
}
_resizeRequested = true;
_pendingWidth = width;
_pendingHeight = height;
}
}
public void ExecutePendingResize()
{
if (!_resizeRequested)
{
return;
}
uint newWidth;
uint newHeight;
lock (_lock)
{
newWidth = _pendingWidth;
newHeight = _pendingHeight;
_resizeRequested = false;
}
WaitIdle(); WaitIdle();
for (var i = 0; i < GraphicsPipeline.FRAME_COUNT; i++) for (var i = 0; i < GraphicsPipeline.FRAME_COUNT; i++)
{
if (_renderTargets[i] is not null)
{ {
_renderTargets[i].Dispose(); _renderTargets[i].Dispose();
_rtvHeap.ReleaseDescriptor(_renderTargetDescriptorIndexes[i]); _rtvHeap.ReleaseDescriptor(_renderTargetDescriptorIndexes[i]);
}
_fenceValues[i] = _fenceValues[_backBufferIndex]; _fenceValues[i] = _fenceValues[_backBufferIndex];
} }
_swapChain.ResizeBuffers(GraphicsPipeline.FRAME_COUNT, width, height, Format.B8G8R8A8_UNorm, SwapChainFlags.AllowTearing).CheckError(); _swapChain.ResizeBuffers(GraphicsPipeline.FRAME_COUNT, newWidth, newHeight, Format.B8G8R8A8_UNorm, SwapChainFlags.AllowTearing).CheckError();
CreateRenderTargets(); CreateRenderTargets();
_backBufferIndex = _swapChain.CurrentBackBufferIndex; _backBufferIndex = _swapChain.CurrentBackBufferIndex;
} }
public void Render() public ICommandBuffer BeginRender()
{ {
_backBufferIndex = _swapChain.CurrentBackBufferIndex;
var commandAllocator = _commandAllocators[_backBufferIndex]; var commandAllocator = _commandAllocators[_backBufferIndex];
commandAllocator.Reset(); commandAllocator.Reset();
_commandList.Reset(commandAllocator, null); _commandList.Reset(commandAllocator, null);
_commandList.ResourceBarrierTransition(_renderTargets[_backBufferIndex], ResourceStates.Present, ResourceStates.RenderTarget);
return _commandBuffer;
}
public void Render()
{
}
public void EndRender()
{
_commandList.ResourceBarrierTransition(_renderTargets[_backBufferIndex], ResourceStates.RenderTarget, ResourceStates.Present);
_commandList.Close(); _commandList.Close();
_graphicsDevice.CommandQueue.ExecuteCommandLists([_commandList]);
_graphicsDevice.CommandQueue.ExecuteCommandLists(new[] { _commandList });
_swapChain.Present(1, PresentFlags.None).CheckError(); _swapChain.Present(1, PresentFlags.None).CheckError();
@@ -159,7 +220,6 @@ internal class DX12RenderView : IRenderView
return; return;
} }
_backBufferIndex = _swapChain.CurrentBackBufferIndex;
if (_fence.CompletedValue < _fenceValues[_backBufferIndex] if (_fence.CompletedValue < _fenceValues[_backBufferIndex]
&& _fence.SetEventOnCompletion(_fenceValues[_backBufferIndex], _fenceEvent.SafeWaitHandle.DangerousGetHandle()).Success) && _fence.SetEventOnCompletion(_fenceValues[_backBufferIndex], _fenceEvent.SafeWaitHandle.DangerousGetHandle()).Success)
{ {
@@ -172,38 +232,36 @@ internal class DX12RenderView : IRenderView
public void WaitIdle() public void WaitIdle()
{ {
var fenceValue = _fenceValues[_backBufferIndex]; var fenceValue = _fenceValues[_backBufferIndex];
if (_graphicsDevice.CommandQueue.Signal(_fence, fenceValue).Failure if (_graphicsDevice.CommandQueue.Signal(_fence, fenceValue).Success
|| _fence.SetEventOnCompletion(fenceValue, _fenceEvent.SafeWaitHandle.DangerousGetHandle()).Failure) && _fence.SetEventOnCompletion(fenceValue, _fenceEvent.SafeWaitHandle.DangerousGetHandle()).Success)
{ {
return;
}
_fenceEvent.WaitOne(); _fenceEvent.WaitOne();
_fenceValues[_backBufferIndex]++; _fenceValues[_backBufferIndex]++;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Flush()
{
for (var i = 0; i < GraphicsPipeline.FRAME_COUNT; i++)
{
WaitIdle();
}
} }
public void Dispose() public void Dispose()
{ {
Flush(); if (_disposed)
{
return;
}
WaitIdle();
_swapChainPresenter.SwapChainPanelNative?.SetSwapChain(null);
foreach (var commandAllocator in _commandAllocators) foreach (var commandAllocator in _commandAllocators)
{ {
commandAllocator.Dispose(); commandAllocator.Dispose();
} }
_commandAllocators.AsSpan().Clear();
foreach (var renderTarget in _renderTargets) foreach (var renderTarget in _renderTargets)
{ {
renderTarget.Dispose(); renderTarget.Dispose();
} }
_renderTargets.AsSpan().Clear();
_swapChain.Dispose(); _swapChain.Dispose();
_commandList.Dispose(); _commandList.Dispose();
@@ -215,5 +273,7 @@ internal class DX12RenderView : IRenderView
_backBufferIndex = 0; _backBufferIndex = 0;
_fenceValues.AsSpan().Clear(); _fenceValues.AsSpan().Clear();
_disposed = true;
} }
} }

View File

@@ -10,7 +10,7 @@ internal class D3D12DescriptorAllocator : IDisposable
private const DescriptorIndex _INVALID_DESCRIPTOR_INDEX = ~0u; private const DescriptorIndex _INVALID_DESCRIPTOR_INDEX = ~0u;
private readonly ID3D12Device _device; private readonly ID3D12Device _device;
private readonly Lock _mutex = new(); private readonly Lock _lock = new();
private ID3D12DescriptorHeap? _heap; private ID3D12DescriptorHeap? _heap;
private ID3D12DescriptorHeap? _shaderVisibleHeap; private ID3D12DescriptorHeap? _shaderVisibleHeap;
@@ -56,14 +56,15 @@ internal class D3D12DescriptorAllocator : IDisposable
ShaderVisible = type == DescriptorHeapType.ConstantBufferViewShaderResourceViewUnorderedAccessView || type == DescriptorHeapType.Sampler; ShaderVisible = type == DescriptorHeapType.ConstantBufferViewShaderResourceViewUnorderedAccessView || type == DescriptorHeapType.Sampler;
Stride = device.GetDescriptorHandleIncrementSize(type); Stride = device.GetDescriptorHandleIncrementSize(type);
Debug.Assert(AllocateResources(numDescriptors)); var success = AllocateResources(numDescriptors);
Debug.Assert(success);
} }
public DescriptorIndex AllocateDescriptor() => AllocateDescriptors(1); public DescriptorIndex AllocateDescriptor() => AllocateDescriptors(1);
public DescriptorIndex AllocateDescriptors(uint count) public DescriptorIndex AllocateDescriptors(uint count)
{ {
lock (_mutex) lock (_lock)
{ {
DescriptorIndex foundIndex = 0; DescriptorIndex foundIndex = 0;
uint freeCount = 0; uint freeCount = 0;
@@ -120,7 +121,7 @@ internal class D3D12DescriptorAllocator : IDisposable
return; return;
} }
lock (_mutex) lock (_lock)
{ {
for (var index = baseIndex; index < baseIndex + count; index++) for (var index = baseIndex; index < baseIndex + count; index++)
{ {

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,234 @@
using Ghost.Graphics.Utilities;
using System.Runtime.CompilerServices;
using Vortice.Direct3D12;
using Vortice.DXGI;
namespace Ghost.Graphics.DX12.Utilities;
internal unsafe class D3D12ResourceUtils
{
private const ResourceStates _INITIALCOPYTARGETSTATE = ResourceStates.Common;
private const ResourceStates _INITIALREADTARGETSTATE = ResourceStates.Common;
private const ResourceStates _INITIALUAVTARGETSTATE = ResourceStates.Common;
public static ID3D12Resource CreateStaticBuffer<T>(
ID3D12Device device,
D3D12ResourceUploadBatch resourceUpload,
T[] data, ResourceStates afterState,
ResourceFlags flags = ResourceFlags.None)
where T : unmanaged
{
Span<T> span = data;
return CreateStaticBuffer(device, resourceUpload, span, afterState, flags);
}
public static ID3D12Resource CreateStaticBuffer<T>(
ID3D12Device device,
D3D12ResourceUploadBatch resourceUpload,
Span<T> data,
ResourceStates afterState,
ResourceFlags flags = ResourceFlags.None)
where T : unmanaged
{
var sizeInBytes = (uint)(sizeof(T) * data.Length);
var c_maxBytes = D3D12.RequestResourceSizeInMegaBytesExpressionATerm * 1024u * 1024u;
if (sizeInBytes > c_maxBytes)
{
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
}
var buffer = device.CreateCommittedResource(
HeapType.Default,
HeapFlags.None,
ResourceDescription.Buffer(sizeInBytes, flags),
_INITIALCOPYTARGETSTATE
);
fixed (T* dataPtr = data)
{
SubresourceData initData = new()
{
pData = dataPtr,
};
resourceUpload.Upload(buffer, 0, &initData, 1);
resourceUpload.Transition(buffer, ResourceStates.CopyDest, afterState);
return buffer;
}
}
public static ID3D12Resource CreateUploadBuffer<T>(
ID3D12Device device,
T[] data,
ResourceFlags flags = ResourceFlags.None)
where T : unmanaged
{
var sizeInBytes = (uint)(sizeof(T) * data.Length);
fixed (T* dataPtr = data)
{
return CreateUploadBuffer(device, sizeInBytes, dataPtr, flags);
}
}
public static ID3D12Resource CreateUploadBuffer<T>(
ID3D12Device device,
Span<T> data,
ResourceFlags flags = ResourceFlags.None)
where T : unmanaged
{
var sizeInBytes = (uint)(sizeof(T) * data.Length);
fixed (T* dataPtr = data)
{
return CreateUploadBuffer(device, sizeInBytes, dataPtr, flags);
}
}
public static ID3D12Resource CreateUploadBuffer(
ID3D12Device device,
uint sizeInBytes,
void* data = default,
ResourceFlags flags = ResourceFlags.None)
{
var c_maxBytes = D3D12.RequestResourceSizeInMegaBytesExpressionATerm * 1024u * 1024u;
if (sizeInBytes > c_maxBytes)
{
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
}
var buffer = device.CreateCommittedResource(
HeapType.Upload,
HeapFlags.None,
ResourceDescription.Buffer(sizeInBytes, flags),
ResourceStates.GenericRead
);
if (data is not null)
{
void* mappedPtr = default;
buffer.Map(0, null, &mappedPtr).CheckError();
Unsafe.CopyBlock(data, mappedPtr, sizeInBytes);
buffer.Unmap(0, null);
}
return buffer;
}
public static ID3D12Resource CreateReadbackBuffer(
ID3D12Device device,
uint sizeInBytes,
ResourceFlags flags = ResourceFlags.None)
{
var c_maxBytes = D3D12.RequestResourceSizeInMegaBytesExpressionATerm * 1024u * 1024u;
if (sizeInBytes > c_maxBytes)
{
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
}
var buffer = device.CreateCommittedResource(
HeapType.Readback,
HeapFlags.None,
ResourceDescription.Buffer(sizeInBytes, flags),
_INITIALREADTARGETSTATE
);
return buffer;
}
public static ID3D12Resource CreateCPUDestinationBuffer(
ID3D12Device device,
uint sizeInBytes,
ResourceFlags flags = ResourceFlags.None)
{
var c_maxBytes = D3D12.RequestResourceSizeInMegaBytesExpressionATerm * 1024u * 1024u;
if (sizeInBytes > c_maxBytes)
{
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {sizeInBytes})");
}
var buffer = device.CreateCommittedResource(
HeapType.Default,
HeapFlags.None,
ResourceDescription.Buffer(sizeInBytes, flags),
ResourceStates.CopyDest
);
return buffer;
}
public static ID3D12Resource CreateUAVBuffer(ID3D12Device device, uint bufferSize,
ResourceStates initialState = ResourceStates.Common,
ResourceFlags flags = ResourceFlags.None)
{
var c_maxBytes = D3D12.RequestResourceSizeInMegaBytesExpressionATerm * 1024u * 1024u;
if (bufferSize > c_maxBytes)
{
throw new InvalidOperationException($"ERROR: Resource size too large for DirectX 12 (size {bufferSize})");
}
var buffer = device.CreateCommittedResource(
HeapType.Default,
HeapFlags.None,
ResourceDescription.Buffer(bufferSize, ResourceFlags.AllowUnorderedAccess | flags),
_INITIALCOPYTARGETSTATE
);
return buffer;
}
public static ID3D12Resource CreateTexture2D<T>(
ID3D12Device device,
D3D12ResourceUploadBatch resourceUpload,
uint width, uint height, Format format,
Span<T> data,
bool generateMips = false,
ResourceStates afterState = ResourceStates.PixelShaderResource,
ResourceFlags flags = ResourceFlags.None)
where T : unmanaged
{
if ((width > D3D12.RequestTexture2DUOrVDimension) || (height > D3D12.RequestTexture2DUOrVDimension))
{
throw new InvalidOperationException($"ERROR: Resource dimensions too large for DirectX 12 (2D: size {width} by {height})");
}
ushort mipLevels = 1;
if (generateMips)
{
generateMips = resourceUpload.IsSupportedForGenerateMips(format);
if (generateMips)
{
mipLevels = (ushort)TextureUtility.CountMips(width, height);
}
}
var texture = device.CreateCommittedResource(
HeapType.Default,
HeapFlags.None,
ResourceDescription.Texture2D(format, width, height, 1, mipLevels, 1, 0, flags),
_INITIALCOPYTARGETSTATE
);
fixed (T* dataPtr = data)
{
FormatHelper.GetSurfaceInfo(format, width, height, out var rowPitch, out var slicePitch);
SubresourceData initData = new()
{
pData = dataPtr,
RowPitch = (nint)rowPitch,
SlicePitch = (nint)slicePitch
};
resourceUpload.Upload(texture, 0, &initData, 1);
resourceUpload.Transition(texture, ResourceStates.CopyDest, afterState);
if (generateMips)
{
resourceUpload.GenerateMips(texture);
}
return texture;
}
}
}

View File

@@ -1,26 +1,31 @@
using Misaki.HighPerformance.Unsafe.Collections; using Ghost.Graphics.DX12.Utilities;
using Misaki.HighPerformance.Unsafe.Collections;
using Misaki.HighPerformance.Unsafe.Helpers; using Misaki.HighPerformance.Unsafe.Helpers;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using Vortice.Direct3D12;
using Vortice.DXGI;
using Vortice.Mathematics;
namespace Ghost.Graphics.Data; namespace Ghost.Graphics.Data;
public sealed class Mesh(int initialVertexCapacity = 256, int initialIndexCapacity = 512) : IDisposable public sealed class Mesh(int initialVertexCapacity = 256, int initialIndexCapacity = 512) : IDisposable
{ {
private UnsafeList<Vector3> _vertices = new(initialVertexCapacity, Allocator.Persistent); private UnsafeList<Vertex> _vertices = new(initialVertexCapacity, Allocator.Persistent);
private UnsafeList<Vector3> _normals = new(initialVertexCapacity, Allocator.Persistent);
private UnsafeList<Vector4> _tangents = new(initialVertexCapacity, Allocator.Persistent);
private UnsafeList<Color32> _colors = new(initialVertexCapacity, Allocator.Persistent);
private UnsafeList<Vector2> _uvs = new(initialVertexCapacity, Allocator.Persistent);
private UnsafeList<int> _indices = new(initialIndexCapacity, Allocator.Persistent); private UnsafeList<int> _indices = new(initialIndexCapacity, Allocator.Persistent);
public Span<Vector3> Vertices => _vertices.AsSpan(); private BoundingBox _bounds;
public Span<Vector3> Normals => _normals.AsSpan();
public Span<Vector4> Tangents => _tangents.AsSpan();
public Span<Color32> Colors => _colors.AsSpan();
public Span<Vector2> UVs => _uvs.AsSpan();
public Span<int> Indices => _indices.AsSpan();
private ID3D12Resource? _vertexBuffer;
private ID3D12Resource? _indexBuffer;
private VertexBufferView _vertexBufferView;
private IndexBufferView _indexBufferView;
public Span<Vertex> Vertices => _vertices.AsSpan();
public Span<int> Indices => _indices.AsSpan();
public BoundingBox Bounds => _bounds;
public int VertexCount => _vertices.Count; public int VertexCount => _vertices.Count;
public int IndexCount => _indices.Count;
~Mesh() ~Mesh()
{ {
@@ -30,21 +35,11 @@ public sealed class Mesh(int initialVertexCapacity = 256, int initialIndexCapaci
/// <summary> /// <summary>
/// Adds a vertex to the mesh with the specified attributes. /// Adds a vertex to the mesh with the specified attributes.
/// </summary> /// </summary>
/// <remarks>This method adds the vertex attributes to their respective collections, allowing the mesh /// <param name="vertex">The data to add</param>
/// to be constructed with detailed vertex data. Ensure that all parameters are provided with valid values to [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// avoid incomplete or incorrect mesh data.</remarks> public void AddVertex(Vertex vertex)
/// <param name="position">The position of the vertex in 3D space.</param>
/// <param name="normal">The normal vector at the vertex.</param>
/// <param name="tangent">The tangent vector at the vertex.</param>
/// <param name="color">The color of the vertex.</param>
/// <param name="uv">The UV coordinates of the vertex.</param>
public void AddVertex(Vector3 position, Vector3 normal, Vector4 tangent, Color32 color, Vector2 uv)
{ {
_vertices.Add(position); _vertices.Add(vertex);
_normals.Add(normal);
_tangents.Add(tangent);
_colors.Add(color);
_uvs.Add(uv);
} }
/// <summary> /// <summary>
@@ -66,13 +61,12 @@ public sealed class Mesh(int initialVertexCapacity = 256, int initialIndexCapaci
_indices.Add(index2); _indices.Add(index2);
} }
/// <summary>
/// Reduces the memory usage of the internal collections by resizing them to match their current element count.
/// </summary>
public void TrimExcess() public void TrimExcess()
{ {
_vertices.Resize(_vertices.Count); _vertices.Resize(_vertices.Count);
_normals.Resize(_normals.Count);
_tangents.Resize(_tangents.Count);
_colors.Resize(_colors.Count);
_uvs.Resize(_uvs.Count);
_indices.Resize(_indices.Count); _indices.Resize(_indices.Count);
} }
@@ -90,16 +84,6 @@ public sealed class Mesh(int initialVertexCapacity = 256, int initialIndexCapaci
return; return;
} }
if (!_normals.IsCreated)
{
_normals = new(_vertices.Count, Allocator.Persistent);
}
else
{
_normals.Clear();
_normals.Resize(_vertices.Count);
}
for (var i = 0; i < _indices.Count; i += 3) for (var i = 0; i < _indices.Count; i += 3)
{ {
var i0 = _indices[i]; var i0 = _indices[i];
@@ -110,18 +94,18 @@ public sealed class Mesh(int initialVertexCapacity = 256, int initialIndexCapaci
var v1 = _vertices[i1]; var v1 = _vertices[i1];
var v2 = _vertices[i2]; var v2 = _vertices[i2];
var edge1 = v1 - v0; var edge1 = v1.Position - v0.Position;
var edge2 = v2 - v0; var edge2 = v2.Position - v0.Position;
var faceNormal = Vector3.Cross(edge1, edge2); var faceNormal = Vector3.Cross(edge1.AsVector3(), edge2.AsVector3());
_normals[i0] += faceNormal; _vertices[i0].Normal += faceNormal.AsVector4();
_normals[i1] += faceNormal; _vertices[i1].Normal += faceNormal.AsVector4();
_normals[i2] += faceNormal; _vertices[i2].Normal += faceNormal.AsVector4();
} }
for (var i = 0; i < _normals.Count; i++) for (var i = 0; i < _vertices.Count; i++)
{ {
_normals[i] = Vector3.Normalize(_normals[i]); _vertices[i].Normal = Vector4.Normalize(_vertices[i].Normal);
} }
} }
@@ -133,29 +117,7 @@ public sealed class Mesh(int initialVertexCapacity = 256, int initialIndexCapaci
/// </remarks> /// </remarks>
public void ComputeTangents() public void ComputeTangents()
{ {
if (!_vertices.IsCreated || _vertices.Count < 3 var bitangents = new Vector4[_vertices.Count];
|| !_indices.IsCreated || _indices.Count < 3
|| !_uvs.IsCreated || _uvs.Count < _vertices.Count)
{
return;
}
if (!_normals.IsCreated || _normals.Count != _vertices.Count)
{
ComputeNormal();
}
if (!_tangents.IsCreated)
{
_tangents = new(_vertices.Count, Allocator.Persistent);
}
else
{
_tangents.Clear();
_tangents.Resize(_vertices.Count);
}
var bitangents = new Vector3[_vertices.Count];
for (var i = 0; i < _indices.Count; i += 3) for (var i = 0; i < _indices.Count; i += 3)
{ {
@@ -166,12 +128,13 @@ public sealed class Mesh(int initialVertexCapacity = 256, int initialIndexCapaci
var v0 = _vertices[i0]; var v0 = _vertices[i0];
var v1 = _vertices[i1]; var v1 = _vertices[i1];
var v2 = _vertices[i2]; var v2 = _vertices[i2];
var uv0 = _uvs[i0];
var uv1 = _uvs[i1];
var uv2 = _uvs[i2];
var deltaPos1 = v1 - v0; var uv0 = _vertices[i0].UV;
var deltaPos2 = v2 - v0; var uv1 = _vertices[i1].UV;
var uv2 = _vertices[i2].UV;
var deltaPos1 = v1.Position - v0.Position;
var deltaPos2 = v2.Position - v0.Position;
var deltaUV1 = uv1 - uv0; var deltaUV1 = uv1 - uv0;
var deltaUV2 = uv2 - uv0; var deltaUV2 = uv2 - uv0;
@@ -182,8 +145,8 @@ public sealed class Mesh(int initialVertexCapacity = 256, int initialIndexCapaci
for (var j = 0; j < 3; j++) for (var j = 0; j < 3; j++)
{ {
var idx = _indices[i + j]; var idx = _indices[i + j];
var t = _tangents[idx]; var t = _vertices[idx].Tangent;
_tangents[idx] = new Vector4( _vertices[idx].Tangent = new Vector4(
t.X + tangent.X, t.X + tangent.X,
t.Y + tangent.Y, t.Y + tangent.Y,
t.Z + tangent.Z, t.Z + tangent.Z,
@@ -196,38 +159,119 @@ public sealed class Mesh(int initialVertexCapacity = 256, int initialIndexCapaci
for (var i = 0; i < _vertices.Count; i++) for (var i = 0; i < _vertices.Count; i++)
{ {
var n = _normals![i]; var n = _vertices[i].Normal;
var t = _tangents[i]; var t = _vertices[i].Tangent;
var t3 = new Vector3(t.X, t.Y, t.Z); var n3 = n.AsVector3();
var t3 = t.AsVector3();
var proj = n * Vector3.Dot(n, t3); var proj = n3 * Vector3.Dot(n3, t3);
t3 = Vector3.Normalize(t3 - proj); t3 = Vector3.Normalize(t3 - proj);
var b = bitangents[i]; var b = bitangents[i];
var w = Vector3.Dot(Vector3.Cross(n, t3), b) < 0.0f ? -1.0f : 1.0f; var w = Vector3.Dot(Vector3.Cross(n3, t3), b.AsVector3()) < 0.0f ? -1.0f : 1.0f;
_tangents[i] = new Vector4(t3.X, t3.Y, t3.Z, w); _vertices[i].Tangent = new Vector4(t3.X, t3.Y, t3.Z, w);
} }
} }
/// <summary>
/// Computes the bounding box of the mesh based on its vertices.
/// </summary>
public void ComputeBounds()
{
if (_vertices.Count == 0)
{
_bounds = BoundingBox.Zero;
return;
}
var min = new Vector3(float.MaxValue);
var max = new Vector3(float.MinValue);
foreach (var vertex in _vertices)
{
var pos = vertex.Position.AsVector3();
min = Vector3.Min(min, pos);
max = Vector3.Max(max, pos);
}
_bounds = new BoundingBox(min, max);
}
/// <summary>
/// Uploads the mesh data to GPU resources immediately.
/// </summary>
/// <param name="device">The Direct3D 12 device.</param>
/// <param name="commandList">The Direct3D 12 command list to record the upload commands.</param>
public unsafe void UploadMeshData(ID3D12Device device, ID3D12GraphicsCommandList commandList)
{
if (VertexCount == 0 || IndexCount == 0)
{
return;
}
_vertexBuffer?.Dispose();
_indexBuffer?.Dispose();
var vertexBufferSize = (uint)(VertexCount * sizeof(Vertex));
var indexBufferSize = (uint)(IndexCount * sizeof(int));
_vertexBuffer = D3D12ResourceUtils.CreateCPUDestinationBuffer(device, vertexBufferSize);
_indexBuffer = D3D12ResourceUtils.CreateCPUDestinationBuffer(device, indexBufferSize);
using var vertexUploadBuffer = D3D12ResourceUtils.CreateUploadBuffer(device, vertexBufferSize);
using var indexUploadBuffer = D3D12ResourceUtils.CreateUploadBuffer(device, indexBufferSize);
void* vertexData;
vertexUploadBuffer.Map(0, null, &vertexData);
Unsafe.CopyBlock(vertexData, _vertices.GetUnsafePtr(), vertexBufferSize);
vertexUploadBuffer.Unmap(0);
void* indexData;
indexUploadBuffer.Map(0, null, &indexData);
Unsafe.CopyBlock(indexData, _indices.GetUnsafePtr(), indexBufferSize);
indexUploadBuffer.Unmap(0);
commandList.CopyBufferRegion(_vertexBuffer, 0, vertexUploadBuffer, 0, vertexBufferSize);
commandList.CopyBufferRegion(_indexBuffer, 0, indexUploadBuffer, 0, indexBufferSize);
_vertexBufferView = new VertexBufferView
{
BufferLocation = _vertexBuffer.GPUVirtualAddress,
SizeInBytes = vertexBufferSize,
StrideInBytes = (uint)sizeof(Vertex)
};
_indexBufferView = new IndexBufferView
{
BufferLocation = _indexBuffer.GPUVirtualAddress,
SizeInBytes = indexBufferSize,
Format = Format.R32_UInt
};
}
private void DisposeGpuResources()
{
_vertexBuffer?.Release();
_vertexBuffer = null;
_indexBuffer?.Release();
_indexBuffer = null;
}
/// <summary>
/// Clears all vertex and index data and releases associated GPU resources.
/// </summar>
public void Clear() public void Clear()
{ {
_vertices.Clear(); _vertices.Clear();
_normals.Clear();
_tangents.Clear();
_colors.Clear();
_uvs.Clear();
_indices.Clear(); _indices.Clear();
DisposeGpuResources();
} }
public void Dispose() public void Dispose()
{ {
_vertices.Dispose(); _vertices.Dispose();
_normals.Dispose();
_tangents.Dispose();
_colors.Dispose();
_uvs.Dispose();
_indices.Dispose(); _indices.Dispose();
DisposeGpuResources();
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }

View File

@@ -0,0 +1,25 @@
namespace Ghost.Graphics.Data;
internal abstract unsafe class ResourceView
{
public GraphicsResource Resource
{
get;
}
internal void* CpuDescriptorHandle
{
get;
}
protected ResourceView(GraphicsResource resource, void* descriptor)
{
Resource = resource;
CpuDescriptorHandle = descriptor;
}
protected ResourceView(GraphicsResource resource, IntPtr descriptor) :
this(resource, (void*)descriptor)
{
}
}

View File

@@ -1,6 +1,6 @@
namespace Ghost.Graphics.Data; namespace Ghost.Graphics.Data;
internal readonly struct SwapChainSurface internal readonly struct SwapChainPresenter
{ {
public enum TargetType public enum TargetType
{ {
@@ -33,7 +33,7 @@ internal readonly struct SwapChainSurface
get; get;
} }
public SwapChainSurface(Vortice.WinUI.ISwapChainPanelNative swapChainPanelNative, uint width, uint height) public SwapChainPresenter(Vortice.WinUI.ISwapChainPanelNative swapChainPanelNative, uint width, uint height)
{ {
Type = TargetType.Composition; Type = TargetType.Composition;
SwapChainPanelNative = swapChainPanelNative; SwapChainPanelNative = swapChainPanelNative;
@@ -42,7 +42,7 @@ internal readonly struct SwapChainSurface
Height = height; Height = height;
} }
public SwapChainSurface(IntPtr hwnd, uint width, uint height) public SwapChainPresenter(IntPtr hwnd, uint width, uint height)
{ {
Type = TargetType.Hwnd; Type = TargetType.Hwnd;
SwapChainPanelNative = null; SwapChainPanelNative = null;

View File

@@ -0,0 +1,36 @@
using System.Numerics;
namespace Ghost.Graphics.Data;
public struct Vertex(Vector4 position, Vector4 normal, Vector4 tangent, Color32 color, Vector4 uv)
{
public Vector4 Position
{
get;
set;
} = position;
public Vector4 Normal
{
get;
set;
} = normal;
public Vector4 Tangent
{
get;
set;
} = tangent;
public Color32 Color
{
get;
set;
} = color;
public Vector4 UV
{
get;
set;
} = uv;
}

View File

@@ -3,9 +3,9 @@ using Ghost.Graphics.Data;
namespace Ghost.Graphics; namespace Ghost.Graphics;
internal static class GraphicsPipeline public static class GraphicsPipeline
{ {
public const int FRAME_COUNT = 2; internal const int FRAME_COUNT = 2;
private static IGraphicsDevice? _graphicsDevice; private static IGraphicsDevice? _graphicsDevice;
private static Thread? _renderThread; private static Thread? _renderThread;
@@ -24,7 +24,7 @@ internal static class GraphicsPipeline
} }
} }
public static void Initialize(GraphicsAPI api) internal static void Initialize(GraphicsAPI api)
{ {
_graphicsDevice = api switch _graphicsDevice = api switch
{ {
@@ -43,7 +43,7 @@ internal static class GraphicsPipeline
} }
} }
public static void Start() internal static void Start()
{ {
if (_isRunning) if (_isRunning)
{ {
@@ -59,13 +59,13 @@ internal static class GraphicsPipeline
_renderThread.Start(); _renderThread.Start();
} }
public static void Stop() internal static void Stop()
{ {
_isRunning = false; _isRunning = false;
_renderThread?.Join(); _renderThread?.Join();
} }
public static void Shutdown() internal static void Shutdown()
{ {
Stop(); Stop();

View File

@@ -0,0 +1,8 @@
{
"profiles": {
"Ghost.Graphics": {
"commandName": "Project",
"nativeDebugging": true
}
}
}

View File

@@ -36,7 +36,15 @@ public static class MeshBuilder
var baseIndex = mesh.VertexCount; var baseIndex = mesh.VertexCount;
for (var i = 0; i < 4; i++) for (var i = 0; i < 4; i++)
{ {
mesh.AddVertex(corners[face[i]], Vector3.Zero, Vector4.Zero, color, uvs[i]); var vertex = new Vertex
{
Position = corners[face[i]].AsVector4(),
Normal = Vector4.Zero,
Tangent = Vector4.Zero,
Color = color,
UV = uvs[i].AsVector4()
};
mesh.AddVertex(new(corners[face[i]].AsVector4(), Vector4.Zero, Vector4.Zero, color, uvs[i].AsVector4()));
} }
mesh.AddTriangle(baseIndex + 0, baseIndex + 1, baseIndex + 2); mesh.AddTriangle(baseIndex + 0, baseIndex + 1, baseIndex + 2);
@@ -57,10 +65,10 @@ public static class MeshBuilder
var hd = depth * 0.5f; var hd = depth * 0.5f;
var mesh = new Mesh(4, 6); var mesh = new Mesh(4, 6);
mesh.AddVertex(new(-hw, 0, -hd), Vector3.Zero, Vector4.Zero, color, new(0, 0)); mesh.AddVertex(new(new(-hw, 0.0f, -hd, 0.0f), Vector4.Zero, Vector4.Zero, color, new(0.0f)));
mesh.AddVertex(new(hw, 0, -hd), Vector3.Zero, Vector4.Zero, color, new(1, 0)); mesh.AddVertex(new(new(hw, 0.0f, -hd, 0.0f), Vector4.Zero, Vector4.Zero, color, new(1.0f, 0.0f, 0.0f, 0.0f)));
mesh.AddVertex(new(hw, 0, hd), Vector3.Zero, Vector4.Zero, color, new(1, 1)); mesh.AddVertex(new(new(hw, 0.0f, hd, 0.0f), Vector4.Zero, Vector4.Zero, color, new(1.0f, 1.0f, 0.0f, 0.0f)));
mesh.AddVertex(new(-hw, 0, hd), Vector3.Zero, Vector4.Zero, color, new(0, 1)); mesh.AddVertex(new(new(-hw, 0.0f, hd, 0.0f), Vector4.Zero, Vector4.Zero, color, new(0.0f, 1.0f, 0.0f, 0.0f)));
mesh.AddTriangle(0, 1, 2); mesh.AddTriangle(0, 1, 2);
mesh.AddTriangle(0, 2, 3); mesh.AddTriangle(0, 2, 3);
@@ -95,12 +103,14 @@ public static class MeshBuilder
var z = sinPhi * sinTheta; var z = sinPhi * sinTheta;
mesh.AddVertex( mesh.AddVertex(
position: new Vector3(x, y, z) * radius, new()
normal: Vector3.Zero, {
tangent: Vector4.Zero, Position = new Vector4(x, y, z, 0.0f) * radius,
color: color, Normal = Vector4.Zero,
uv: new Vector2((float)lon / longitudeSegments, (float)lat / latitudeSegments) Tangent = Vector4.Zero,
); Color = color,
UV = new Vector4((float)lon / longitudeSegments, (float)lat / latitudeSegments, 0.0f, 0.0f)
});
} }
} }

View File

@@ -0,0 +1,21 @@
namespace Ghost.Graphics.Utilities;
public class TextureUtility
{
public static uint CountMips(uint width, uint height)
{
if (width == 0 || height == 0)
{
return 0;
}
uint count = 1;
while (width > 1 || height > 1)
{
width >>= 1;
height >>= 1;
count++;
}
return count;
}
}