Refactor and enhance codebase for maintainability

Refactored and reorganized the codebase to improve readability, performance, and maintainability. Introduced new interfaces and structs for better resource management, updated project configuration files, and refactored shader and graphics pipeline management. Improved error handling, code formatting, and removed unused code and namespaces. Updated DLL references and method signatures for consistency and maintainability.
This commit is contained in:
2025-10-22 18:46:39 +09:00
parent 6d1b510ac1
commit d2d9f5feb7
80 changed files with 2836 additions and 2198 deletions

View File

@@ -0,0 +1,6 @@
namespace Ghost.Core.Contracts;
internal interface IReleasable
{
void InternalRelease();
}

View File

@@ -7,7 +7,18 @@
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
<DefineConstants>$(DefineConstants);PLATEFORME_WIN64</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
<DefineConstants>$(DefineConstants);PLATEFORME_WIN64</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.IO.Hashing" Version="9.0.10" />
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.2" />
</ItemGroup>
@@ -17,9 +28,5 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Folder Include="Contracts\" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,5 @@
namespace Ghost.Core.Graphics;
public enum KeywordType
{
Static,
@@ -20,6 +21,8 @@ public struct ShaderEntryPoint
{
public string entry;
public string shader;
public readonly bool IsCreated => !string.IsNullOrEmpty(entry) && !string.IsNullOrEmpty(shader);
}
public struct KeywordsGroup
@@ -52,27 +55,11 @@ public interface IPassDescriptor
{
get;
}
}
public class FullPassDescriptor : IPassDescriptor
public string Name
{
public string uniqueIdentifier = string.Empty;
public ShaderEntryPoint vertexShader;
public ShaderEntryPoint pixelShader;
public List<string>? defines;
public List<string>? includes;
public List<KeywordsGroup>? keywords;
public List<PropertyDescriptor>? properties;
public PipelineDescriptor localPipeline;
public string Identifier => uniqueIdentifier;
get;
}
public class FallbackPassDescriptor : IPassDescriptor
{
public string fallbackPassIdentifier = string.Empty;
public string Identifier => fallbackPassIdentifier;
}
public struct PropertyDescriptor
@@ -82,6 +69,33 @@ public struct PropertyDescriptor
public object? defaultValue;
}
public class FullPassDescriptor : IPassDescriptor
{
public string uniqueIdentifier = string.Empty;
public string name = string.Empty;
public ShaderEntryPoint taskShader;
public ShaderEntryPoint meshShader;
public ShaderEntryPoint pixelShader;
public List<string>? defines;
public List<string>? includes;
public List<KeywordsGroup>? keywords;
public List<PropertyDescriptor>? properties;
public PipelineDescriptor localPipeline;
public string Identifier => uniqueIdentifier;
public string Name => name;
}
public class FallbackPassDescriptor : IPassDescriptor
{
public string fallbackPassIdentifier = string.Empty;
public string name = string.Empty;
public string Identifier => fallbackPassIdentifier;
public string Name => name;
}
public class ShaderDescriptor
{
public string name = string.Empty;

View File

@@ -7,7 +7,6 @@ public readonly struct Handle<T>
where T : IHandleType
{
public readonly int id;
public readonly int generation;
public Handle(int id, int generation)
@@ -22,7 +21,7 @@ public readonly struct Handle<T>
public readonly override int GetHashCode()
{
return id.GetHashCode();
return id + (generation << 16);
}
public readonly override bool Equals(object? obj)

View File

@@ -9,7 +9,7 @@ public enum LogLevel
Error
}
internal struct LogMessage
internal readonly struct LogMessage
{
public LogLevel Level
{
@@ -58,10 +58,9 @@ internal interface ILogger
}
public void Log(string message, LogLevel level);
public void Log(Exception exception);
public void Assert(bool condition, string message);
public void Clear();
}
// TODO: Add file logging.
@@ -98,6 +97,14 @@ internal class LoggerImplementation : ILogger
}
}
}
public void Clear()
{
lock (_lock)
{
_logs.Clear();
}
}
}
public static class Logger
@@ -149,4 +156,14 @@ public static class Logger
{
s_logger.Log(ex);
}
public static void Assert(bool condition, string message)
{
s_logger.Assert(condition, message);
}
public static void Clear()
{
s_logger.Clear();
}
}

View File

@@ -17,20 +17,14 @@ public readonly struct Result
return new Result(true);
}
public static Result Failure(string? message)
public static Result Fail(string? message)
{
return new Result(false, message);
}
public void ThrowIfFailed()
{
if (!success)
{
throw new InvalidOperationException($"Operation failed: {message}");
}
}
public override string ToString() => success ? "OK" : $"Error: {message}";
public static implicit operator Result(bool success) => success ? Success() : Fail(null);
}
public readonly struct Result<T>
@@ -52,18 +46,33 @@ public readonly struct Result<T>
return new Result<T>(true, data);
}
public static Result<T> Failure(string? message)
public static Result<T> Fail(string? message)
{
return new Result<T>(false, default!, message);
}
public void ThrowIfFailed()
public override string ToString() => success ? $"OK: {value}" : $"Error: {message}";
public static implicit operator Result<T>(T? data) => data is not null ? Success(data) : Fail(null);
}
public static class ResultExtensions
{
if (!success)
public static void ThrowIfFailed(this Result result)
{
throw new InvalidOperationException($"Operation failed: {message}");
if (!result.success)
{
throw new InvalidOperationException($"Operation failed: {result.message}");
}
}
public override string ToString() => success ? $"OK: {value}" : $"Error: {message}";
public static T GetValueOrThrow<T>(this Result<T> result)
{
if (!result.success)
{
throw new InvalidOperationException($"Operation failed: {result.message}");
}
return result.value;
}
}

View File

@@ -0,0 +1,12 @@
using Ghost.Core.Contracts;
namespace Ghost.Core.Utilities;
internal static class InternalResource
{
public static void Release<T>(ref T? resource)
where T : IReleasable
{
resource?.InternalRelease();
}
}

View File

@@ -1,10 +1,22 @@
using System.Runtime.CompilerServices;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using TerraFX.Interop.Windows;
namespace Ghost.Graphics.D3D12.Utilities;
namespace Ghost.Core.Utilities;
#if PLATEFORME_WIN64
[SupportedOSPlatform("windows10.0.19041.0")]
internal unsafe static class Win32Utility
{
public static Guid* IID_NULL => (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in IID.IID_NULL));
[Conditional("DEBUG")]
public static void Assert(this HRESULT hr)
{
Debug.Assert(hr.SUCCEEDED);
}
public static void ThrowIfFailed(this HRESULT hr)
{
Windows.ThrowIfFailed(hr);
@@ -22,11 +34,11 @@ internal unsafe static class Win32Utility
return (void**)comPtr.ReleaseAndGetAddressOf();
}
public static ComPtr<T> Move<T>(this ComPtr<T> comPtr)
public static ComPtr<T> Move<T>(ref this ComPtr<T> comPtr)
where T : unmanaged, IUnknown.Interface
{
ComPtr<T> copy = default;
Unsafe.AsRef(in comPtr).Swap(ref copy);
var copy = default(ComPtr<T>);
comPtr.Swap(ref copy);
return copy;
}
@@ -36,3 +48,4 @@ internal unsafe static class Win32Utility
return (flags & Unsafe.As<T, uint>(ref flag)) != 0;
}
}
#endif

View File

@@ -75,29 +75,29 @@ internal partial class ProjectService
{
if (string.IsNullOrWhiteSpace(projectDirectory) || !Directory.Exists(projectDirectory))
{
return Result<ProjectMetadataInfo>.Failure("Project directory is invalid or does not exist.");
return Result<ProjectMetadataInfo>.Fail("Project directory is invalid or does not exist.");
}
var projectAssetsPath = Path.Combine(projectDirectory, ASSETS_FOLDER);
var projectConfigPath = Path.Combine(projectDirectory, CONFIG_FOLDER);
if (!Directory.Exists(projectAssetsPath) || !Directory.Exists(projectConfigPath))
{
return Result<ProjectMetadataInfo>.Failure("Project folder structure is invalid.");
return Result<ProjectMetadataInfo>.Fail("Project folder structure is invalid.");
}
var metadataPath = Directory.GetFiles(projectDirectory, $"*.{ProjectMetadata.PROJECT_EXTENSION}", SearchOption.TopDirectoryOnly).FirstOrDefault();
if (string.IsNullOrWhiteSpace(metadataPath) || !File.Exists(metadataPath))
{
return Result<ProjectMetadataInfo>.Failure("Project metadata file not found.");
return Result<ProjectMetadataInfo>.Fail("Project metadata file not found.");
}
var metadata = await LoadMetadataAsync(metadataPath);
if (metadata == null)
{
return Result<ProjectMetadataInfo>.Failure("Project metadata file is corrupted or invalid.");
return Result<ProjectMetadataInfo>.Fail("Project metadata file is corrupted or invalid.");
}
return Result<ProjectMetadataInfo>.Success(new(metadataPath, metadata));
return new ProjectMetadataInfo(metadataPath, metadata);
}
private static async ValueTask SetupRequestFolderAsync(string projectDirectory, string templateDirectory)
@@ -201,7 +201,7 @@ internal partial class ProjectService
}
catch (Exception e)
{
return Result<ProjectMetadataInfo>.Failure($"Failed to create project: {e.Message}");
return Result<ProjectMetadataInfo>.Fail($"Failed to create project: {e.Message}");
}
}
@@ -215,7 +215,7 @@ internal partial class ProjectService
if (await HasProjectAsync(result.value.Path))
{
return Result<ProjectMetadataInfo>.Failure("Project already exists.");
return Result<ProjectMetadataInfo>.Fail("Project already exists.");
}
await AddProjectAsync(result.value.Metadata.Name, result.value.Path);

View File

@@ -36,12 +36,12 @@ public static partial class AssetDatabase
{
if (Directory.Exists(assetPath))
{
return Result<string>.Failure("Folder does not have meta data");
return Result<string>.Fail("Folder does not have meta data");
}
if (Path.GetExtension(assetPath).Equals(".meta", StringComparison.OrdinalIgnoreCase))
{
return Result<string>.Failure("Asset path cannot be a meta file");
return Result<string>.Fail("Asset path cannot be a meta file");
}
return Result<string>.Success(assetPath + ".meta");

View File

@@ -4,7 +4,6 @@ using Ghost.Editor.Core.AssetHandle;
using Ghost.Editor.View.Windows;
using Ghost.Engine;
using Ghost.Engine.Services;
using Ghost.Graphics;
using Microsoft.UI.Xaml.Media;
namespace Ghost.Editor.Core.AppState;

View File

@@ -1,5 +1,4 @@
using Ghost.Editor.Controls.Internal;
using Ghost.Graphics;
using Ghost.Graphics.Contracts;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

View File

@@ -1,6 +1,5 @@
using Ghost.Engine.Models;
using Ghost.Engine.Services;
using Ghost.Graphics;
namespace Ghost.Engine;

View File

@@ -1,5 +1,4 @@
using Ghost.Core;
using Ghost.Entities.Components;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;

View File

@@ -10,8 +10,6 @@
/* https://fmod.com/docs/2.03/api/plugin-api-dsp.html */
/* =========================================================================================*/
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace Ghost.FMOD.Core
@@ -315,7 +313,7 @@ namespace Ghost.FMOD.Core
{
var buffer = new float[numchannels][];
for (int i = 0; i < numchannels; ++i)
for (var i = 0; i < numchannels; ++i)
{
buffer[i] = new float[length];
Marshal.Copy(spectrum_internal[i], buffer[i], 0, length);
@@ -327,8 +325,8 @@ namespace Ghost.FMOD.Core
public void getSpectrum(ref float[][] buffer)
{
int bufferLength = Math.Min(buffer.Length, numchannels);
for (int i = 0; i < bufferLength; ++i)
var bufferLength = Math.Min(buffer.Length, numchannels);
for (var i = 0; i < bufferLength; ++i)
{
getSpectrum(i, ref buffer[i]);
}
@@ -336,7 +334,7 @@ namespace Ghost.FMOD.Core
public void getSpectrum(int channel, ref float[] buffer)
{
int bufferLength = Math.Min(buffer.Length, length);
var bufferLength = Math.Min(buffer.Length, length);
Marshal.Copy(spectrum_internal[channel], buffer, 0, bufferLength);
}
}
@@ -446,11 +444,17 @@ namespace Ghost.FMOD.Core
public DSP_GETUSERDATA_FUNC getuserdata;
public DSP_STATE_DFT_FUNCTIONS dft
{
get { return Marshal.PtrToStructure<DSP_STATE_DFT_FUNCTIONS>(dft_internal); }
get
{
return Marshal.PtrToStructure<DSP_STATE_DFT_FUNCTIONS>(dft_internal);
}
}
public DSP_STATE_PAN_FUNCTIONS pan
{
get { return Marshal.PtrToStructure<DSP_STATE_PAN_FUNCTIONS>(pan_internal); }
get
{
return Marshal.PtrToStructure<DSP_STATE_PAN_FUNCTIONS>(pan_internal);
}
}
}
@@ -468,7 +472,10 @@ namespace Ghost.FMOD.Core
public DSP_STATE_FUNCTIONS functions
{
get { return Marshal.PtrToStructure<DSP_STATE_FUNCTIONS>(functions_internal); }
get
{
return Marshal.PtrToStructure<DSP_STATE_FUNCTIONS>(functions_internal);
}
}
}

View File

@@ -17,89 +17,172 @@ namespace Ghost.FMOD.Core
{
switch (errcode)
{
case RESULT.OK: return "No errors.";
case RESULT.ERR_BADCOMMAND: return "Tried to call a function on a data type that does not allow this type of functionality (ie calling Sound::lock on a streaming sound).";
case RESULT.ERR_CHANNEL_ALLOC: return "Error trying to allocate a channel.";
case RESULT.ERR_CHANNEL_STOLEN: return "The specified channel has been reused to play another sound.";
case RESULT.ERR_DMA: return "DMA Failure. See debug output for more information.";
case RESULT.ERR_DSP_CONNECTION: return "DSP connection error. Connection possibly caused a cyclic dependency or connected dsps with incompatible buffer counts.";
case RESULT.ERR_DSP_DONTPROCESS: return "DSP return code from a DSP process query callback. Tells mixer not to call the process callback and therefore not consume CPU. Use this to optimize the DSP graph.";
case RESULT.ERR_DSP_FORMAT: return "DSP Format error. A DSP unit may have attempted to connect to this network with the wrong format, or a matrix may have been set with the wrong size if the target unit has a specified channel map.";
case RESULT.ERR_DSP_INUSE: return "DSP is already in the mixer's DSP network. It must be removed before being reinserted or released.";
case RESULT.ERR_DSP_NOTFOUND: return "DSP connection error. Couldn't find the DSP unit specified.";
case RESULT.ERR_DSP_RESERVED: return "DSP operation error. Cannot perform operation on this DSP as it is reserved by the system.";
case RESULT.ERR_DSP_SILENCE: return "DSP return code from a DSP process query callback. Tells mixer silence would be produced from read, so go idle and not consume CPU. Use this to optimize the DSP graph.";
case RESULT.ERR_DSP_TYPE: return "DSP operation cannot be performed on a DSP of this type.";
case RESULT.ERR_FILE_BAD: return "Error loading file.";
case RESULT.ERR_FILE_COULDNOTSEEK: return "Couldn't perform seek operation. This is a limitation of the medium (ie netstreams) or the file format.";
case RESULT.ERR_FILE_DISKEJECTED: return "Media was ejected while reading.";
case RESULT.ERR_FILE_EOF: return "End of file unexpectedly reached while trying to read essential data (truncated?).";
case RESULT.ERR_FILE_ENDOFDATA: return "End of current chunk reached while trying to read data.";
case RESULT.ERR_FILE_NOTFOUND: return "File not found.";
case RESULT.ERR_FORMAT: return "Unsupported file or audio format.";
case RESULT.ERR_HEADER_MISMATCH: return "There is a version mismatch between the FMOD header and either the FMOD Studio library or the FMOD Low Level library.";
case RESULT.ERR_HTTP: return "A HTTP error occurred. This is a catch-all for HTTP errors not listed elsewhere.";
case RESULT.ERR_HTTP_ACCESS: return "The specified resource requires authentication or is forbidden.";
case RESULT.ERR_HTTP_PROXY_AUTH: return "Proxy authentication is required to access the specified resource.";
case RESULT.ERR_HTTP_SERVER_ERROR: return "A HTTP server error occurred.";
case RESULT.ERR_HTTP_TIMEOUT: return "The HTTP request timed out.";
case RESULT.ERR_INITIALIZATION: return "FMOD was not initialized correctly to support this function.";
case RESULT.ERR_INITIALIZED: return "Cannot call this command after System::init.";
case RESULT.ERR_INTERNAL: return "An error occured in the FMOD system. Use the logging version of FMOD for more information.";
case RESULT.ERR_INVALID_FLOAT: return "Value passed in was a NaN, Inf or denormalized float.";
case RESULT.ERR_INVALID_HANDLE: return "An invalid object handle was used.";
case RESULT.ERR_INVALID_PARAM: return "An invalid parameter was passed to this function.";
case RESULT.ERR_INVALID_POSITION: return "An invalid seek position was passed to this function.";
case RESULT.ERR_INVALID_SPEAKER: return "An invalid speaker was passed to this function based on the current speaker mode.";
case RESULT.ERR_INVALID_SYNCPOINT: return "The syncpoint did not come from this sound handle.";
case RESULT.ERR_INVALID_THREAD: return "Tried to call a function on a thread that is not supported.";
case RESULT.ERR_INVALID_VECTOR: return "The vectors passed in are not unit length, or perpendicular.";
case RESULT.ERR_MAXAUDIBLE: return "Reached maximum audible playback count for this sound's soundgroup.";
case RESULT.ERR_MEMORY: return "Not enough memory or resources.";
case RESULT.ERR_MEMORY_CANTPOINT: return "Can't use FMOD_OPENMEMORY_POINT on non PCM source data, or non mp3/xma/adpcm data if FMOD_CREATECOMPRESSEDSAMPLE was used.";
case RESULT.ERR_NEEDS3D: return "Tried to call a command on a 2d sound when the command was meant for 3d sound.";
case RESULT.ERR_NEEDSHARDWARE: return "Tried to use a feature that requires hardware support.";
case RESULT.ERR_NET_CONNECT: return "Couldn't connect to the specified host.";
case RESULT.ERR_NET_SOCKET_ERROR: return "A socket error occurred. This is a catch-all for socket-related errors not listed elsewhere.";
case RESULT.ERR_NET_URL: return "The specified URL couldn't be resolved.";
case RESULT.ERR_NET_WOULD_BLOCK: return "Operation on a non-blocking socket could not complete immediately.";
case RESULT.ERR_NOTREADY: return "Operation could not be performed because specified sound/DSP connection is not ready.";
case RESULT.ERR_OUTPUT_ALLOCATED: return "Error initializing output device, but more specifically, the output device is already in use and cannot be reused.";
case RESULT.ERR_OUTPUT_CREATEBUFFER: return "Error creating hardware sound buffer.";
case RESULT.ERR_OUTPUT_DRIVERCALL: return "A call to a standard soundcard driver failed, which could possibly mean a bug in the driver or resources were missing or exhausted.";
case RESULT.ERR_OUTPUT_FORMAT: return "Soundcard does not support the specified format.";
case RESULT.ERR_OUTPUT_INIT: return "Error initializing output device.";
case RESULT.ERR_OUTPUT_NODRIVERS: return "The output device has no drivers installed. If pre-init, FMOD_OUTPUT_NOSOUND is selected as the output mode. If post-init, the function just fails.";
case RESULT.ERR_PLUGIN: return "An unspecified error has been returned from a plugin.";
case RESULT.ERR_PLUGIN_MISSING: return "A requested output, dsp unit type or codec was not available.";
case RESULT.ERR_PLUGIN_RESOURCE: return "A resource that the plugin requires cannot be allocated or found. (ie the DLS file for MIDI playback)";
case RESULT.ERR_PLUGIN_VERSION: return "A plugin was built with an unsupported SDK version.";
case RESULT.ERR_RECORD: return "An error occurred trying to initialize the recording device.";
case RESULT.ERR_REVERB_CHANNELGROUP: return "Reverb properties cannot be set on this channel because a parent channelgroup owns the reverb connection.";
case RESULT.ERR_REVERB_INSTANCE: return "Specified instance in FMOD_REVERB_PROPERTIES couldn't be set. Most likely because it is an invalid instance number or the reverb doesn't exist.";
case RESULT.ERR_SUBSOUNDS: return "The error occurred because the sound referenced contains subsounds when it shouldn't have, or it doesn't contain subsounds when it should have. The operation may also not be able to be performed on a parent sound.";
case RESULT.ERR_SUBSOUND_ALLOCATED: return "This subsound is already being used by another sound, you cannot have more than one parent to a sound. Null out the other parent's entry first.";
case RESULT.ERR_SUBSOUND_CANTMOVE: return "Shared subsounds cannot be replaced or moved from their parent stream, such as when the parent stream is an FSB file.";
case RESULT.ERR_TAGNOTFOUND: return "The specified tag could not be found or there are no tags.";
case RESULT.ERR_TOOMANYCHANNELS: return "The sound created exceeds the allowable input channel count. This can be increased using the 'maxinputchannels' parameter in System::setSoftwareFormat.";
case RESULT.ERR_TRUNCATED: return "The retrieved string is too long to fit in the supplied buffer and has been truncated.";
case RESULT.ERR_UNIMPLEMENTED: return "Something in FMOD hasn't been implemented when it should be. Contact support.";
case RESULT.ERR_UNINITIALIZED: return "This command failed because System::init or System::setDriver was not called.";
case RESULT.ERR_UNSUPPORTED: return "A command issued was not supported by this object. Possibly a plugin without certain callbacks specified.";
case RESULT.ERR_VERSION: return "The version number of this file format is not supported.";
case RESULT.ERR_EVENT_ALREADY_LOADED: return "The specified bank has already been loaded.";
case RESULT.ERR_EVENT_LIVEUPDATE_BUSY: return "The live update connection failed due to the game already being connected.";
case RESULT.ERR_EVENT_LIVEUPDATE_MISMATCH: return "The live update connection failed due to the game data being out of sync with the tool.";
case RESULT.ERR_EVENT_LIVEUPDATE_TIMEOUT: return "The live update connection timed out.";
case RESULT.ERR_EVENT_NOTFOUND: return "The requested event, bus or vca could not be found.";
case RESULT.ERR_STUDIO_UNINITIALIZED: return "The Studio::System object is not yet initialized.";
case RESULT.ERR_STUDIO_NOT_LOADED: return "The specified resource is not loaded, so it can't be unloaded.";
case RESULT.ERR_INVALID_STRING: return "An invalid string was passed to this function.";
case RESULT.ERR_ALREADY_LOCKED: return "The specified resource is already locked.";
case RESULT.ERR_NOT_LOCKED: return "The specified resource is not locked, so it can't be unlocked.";
case RESULT.ERR_RECORD_DISCONNECTED: return "The specified recording driver has been disconnected.";
case RESULT.ERR_TOOMANYSAMPLES: return "The length provided exceed the allowable limit.";
default: return "Unknown error.";
case RESULT.OK:
return "No errors.";
case RESULT.ERR_BADCOMMAND:
return "Tried to call a function on a data type that does not allow this type of functionality (ie calling Sound::lock on a streaming sound).";
case RESULT.ERR_CHANNEL_ALLOC:
return "Error trying to allocate a channel.";
case RESULT.ERR_CHANNEL_STOLEN:
return "The specified channel has been reused to play another sound.";
case RESULT.ERR_DMA:
return "DMA Failure. See debug output for more information.";
case RESULT.ERR_DSP_CONNECTION:
return "DSP connection error. Connection possibly caused a cyclic dependency or connected dsps with incompatible buffer counts.";
case RESULT.ERR_DSP_DONTPROCESS:
return "DSP return code from a DSP process query callback. Tells mixer not to call the process callback and therefore not consume CPU. Use this to optimize the DSP graph.";
case RESULT.ERR_DSP_FORMAT:
return "DSP Format error. A DSP unit may have attempted to connect to this network with the wrong format, or a matrix may have been set with the wrong size if the target unit has a specified channel map.";
case RESULT.ERR_DSP_INUSE:
return "DSP is already in the mixer's DSP network. It must be removed before being reinserted or released.";
case RESULT.ERR_DSP_NOTFOUND:
return "DSP connection error. Couldn't find the DSP unit specified.";
case RESULT.ERR_DSP_RESERVED:
return "DSP operation error. Cannot perform operation on this DSP as it is reserved by the system.";
case RESULT.ERR_DSP_SILENCE:
return "DSP return code from a DSP process query callback. Tells mixer silence would be produced from read, so go idle and not consume CPU. Use this to optimize the DSP graph.";
case RESULT.ERR_DSP_TYPE:
return "DSP operation cannot be performed on a DSP of this type.";
case RESULT.ERR_FILE_BAD:
return "Error loading file.";
case RESULT.ERR_FILE_COULDNOTSEEK:
return "Couldn't perform seek operation. This is a limitation of the medium (ie netstreams) or the file format.";
case RESULT.ERR_FILE_DISKEJECTED:
return "Media was ejected while reading.";
case RESULT.ERR_FILE_EOF:
return "End of file unexpectedly reached while trying to read essential data (truncated?).";
case RESULT.ERR_FILE_ENDOFDATA:
return "End of current chunk reached while trying to read data.";
case RESULT.ERR_FILE_NOTFOUND:
return "File not found.";
case RESULT.ERR_FORMAT:
return "Unsupported file or audio format.";
case RESULT.ERR_HEADER_MISMATCH:
return "There is a version mismatch between the FMOD header and either the FMOD Studio library or the FMOD Low Level library.";
case RESULT.ERR_HTTP:
return "A HTTP error occurred. This is a catch-all for HTTP errors not listed elsewhere.";
case RESULT.ERR_HTTP_ACCESS:
return "The specified resource requires authentication or is forbidden.";
case RESULT.ERR_HTTP_PROXY_AUTH:
return "Proxy authentication is required to access the specified resource.";
case RESULT.ERR_HTTP_SERVER_ERROR:
return "A HTTP server error occurred.";
case RESULT.ERR_HTTP_TIMEOUT:
return "The HTTP request timed out.";
case RESULT.ERR_INITIALIZATION:
return "FMOD was not initialized correctly to support this function.";
case RESULT.ERR_INITIALIZED:
return "Cannot call this command after System::init.";
case RESULT.ERR_INTERNAL:
return "An error occured in the FMOD system. Use the logging version of FMOD for more information.";
case RESULT.ERR_INVALID_FLOAT:
return "Value passed in was a NaN, Inf or denormalized float.";
case RESULT.ERR_INVALID_HANDLE:
return "An invalid object handle was used.";
case RESULT.ERR_INVALID_PARAM:
return "An invalid parameter was passed to this function.";
case RESULT.ERR_INVALID_POSITION:
return "An invalid seek position was passed to this function.";
case RESULT.ERR_INVALID_SPEAKER:
return "An invalid speaker was passed to this function based on the current speaker mode.";
case RESULT.ERR_INVALID_SYNCPOINT:
return "The syncpoint did not come from this sound handle.";
case RESULT.ERR_INVALID_THREAD:
return "Tried to call a function on a thread that is not supported.";
case RESULT.ERR_INVALID_VECTOR:
return "The vectors passed in are not unit length, or perpendicular.";
case RESULT.ERR_MAXAUDIBLE:
return "Reached maximum audible playback count for this sound's soundgroup.";
case RESULT.ERR_MEMORY:
return "Not enough memory or resources.";
case RESULT.ERR_MEMORY_CANTPOINT:
return "Can't use FMOD_OPENMEMORY_POINT on non PCM source data, or non mp3/xma/adpcm data if FMOD_CREATECOMPRESSEDSAMPLE was used.";
case RESULT.ERR_NEEDS3D:
return "Tried to call a command on a 2d sound when the command was meant for 3d sound.";
case RESULT.ERR_NEEDSHARDWARE:
return "Tried to use a feature that requires hardware support.";
case RESULT.ERR_NET_CONNECT:
return "Couldn't connect to the specified host.";
case RESULT.ERR_NET_SOCKET_ERROR:
return "A socket error occurred. This is a catch-all for socket-related errors not listed elsewhere.";
case RESULT.ERR_NET_URL:
return "The specified URL couldn't be resolved.";
case RESULT.ERR_NET_WOULD_BLOCK:
return "Operation on a non-blocking socket could not complete immediately.";
case RESULT.ERR_NOTREADY:
return "Operation could not be performed because specified sound/DSP connection is not ready.";
case RESULT.ERR_OUTPUT_ALLOCATED:
return "Error initializing output device, but more specifically, the output device is already in use and cannot be reused.";
case RESULT.ERR_OUTPUT_CREATEBUFFER:
return "Error creating hardware sound buffer.";
case RESULT.ERR_OUTPUT_DRIVERCALL:
return "A call to a standard soundcard driver failed, which could possibly mean a bug in the driver or resources were missing or exhausted.";
case RESULT.ERR_OUTPUT_FORMAT:
return "Soundcard does not support the specified format.";
case RESULT.ERR_OUTPUT_INIT:
return "Error initializing output device.";
case RESULT.ERR_OUTPUT_NODRIVERS:
return "The output device has no drivers installed. If pre-init, FMOD_OUTPUT_NOSOUND is selected as the output mode. If post-init, the function just fails.";
case RESULT.ERR_PLUGIN:
return "An unspecified error has been returned from a plugin.";
case RESULT.ERR_PLUGIN_MISSING:
return "A requested output, dsp unit type or codec was not available.";
case RESULT.ERR_PLUGIN_RESOURCE:
return "A resource that the plugin requires cannot be allocated or found. (ie the DLS file for MIDI playback)";
case RESULT.ERR_PLUGIN_VERSION:
return "A plugin was built with an unsupported SDK version.";
case RESULT.ERR_RECORD:
return "An error occurred trying to initialize the recording device.";
case RESULT.ERR_REVERB_CHANNELGROUP:
return "Reverb properties cannot be set on this channel because a parent channelgroup owns the reverb connection.";
case RESULT.ERR_REVERB_INSTANCE:
return "Specified instance in FMOD_REVERB_PROPERTIES couldn't be set. Most likely because it is an invalid instance number or the reverb doesn't exist.";
case RESULT.ERR_SUBSOUNDS:
return "The error occurred because the sound referenced contains subsounds when it shouldn't have, or it doesn't contain subsounds when it should have. The operation may also not be able to be performed on a parent sound.";
case RESULT.ERR_SUBSOUND_ALLOCATED:
return "This subsound is already being used by another sound, you cannot have more than one parent to a sound. Null out the other parent's entry first.";
case RESULT.ERR_SUBSOUND_CANTMOVE:
return "Shared subsounds cannot be replaced or moved from their parent stream, such as when the parent stream is an FSB file.";
case RESULT.ERR_TAGNOTFOUND:
return "The specified tag could not be found or there are no tags.";
case RESULT.ERR_TOOMANYCHANNELS:
return "The sound created exceeds the allowable input channel count. This can be increased using the 'maxinputchannels' parameter in System::setSoftwareFormat.";
case RESULT.ERR_TRUNCATED:
return "The retrieved string is too long to fit in the supplied buffer and has been truncated.";
case RESULT.ERR_UNIMPLEMENTED:
return "Something in FMOD hasn't been implemented when it should be. Contact support.";
case RESULT.ERR_UNINITIALIZED:
return "This command failed because System::init or System::setDriver was not called.";
case RESULT.ERR_UNSUPPORTED:
return "A command issued was not supported by this object. Possibly a plugin without certain callbacks specified.";
case RESULT.ERR_VERSION:
return "The version number of this file format is not supported.";
case RESULT.ERR_EVENT_ALREADY_LOADED:
return "The specified bank has already been loaded.";
case RESULT.ERR_EVENT_LIVEUPDATE_BUSY:
return "The live update connection failed due to the game already being connected.";
case RESULT.ERR_EVENT_LIVEUPDATE_MISMATCH:
return "The live update connection failed due to the game data being out of sync with the tool.";
case RESULT.ERR_EVENT_LIVEUPDATE_TIMEOUT:
return "The live update connection timed out.";
case RESULT.ERR_EVENT_NOTFOUND:
return "The requested event, bus or vca could not be found.";
case RESULT.ERR_STUDIO_UNINITIALIZED:
return "The Studio::System object is not yet initialized.";
case RESULT.ERR_STUDIO_NOT_LOADED:
return "The specified resource is not loaded, so it can't be unloaded.";
case RESULT.ERR_INVALID_STRING:
return "An invalid string was passed to this function.";
case RESULT.ERR_ALREADY_LOCKED:
return "The specified resource is already locked.";
case RESULT.ERR_NOT_LOCKED:
return "The specified resource is not locked, so it can't be unlocked.";
case RESULT.ERR_RECORD_DISCONNECTED:
return "The specified recording driver has been disconnected.";
case RESULT.ERR_TOOMANYSAMPLES:
return "The length provided exceed the allowable limit.";
default:
return "Unknown error.";
}
}
}

View File

@@ -1,6 +1,5 @@
using Ghost.Editor.Core.SceneGraph;
using Ghost.Entities;
using Ghost.UnitTest.TestFramework;
using System.Text.Json;
namespace Ghost.UnitTest.Test;

View File

@@ -1,5 +1,4 @@
using Ghost.Test.Core;
using Ghost.UnitTest.Windows;
using Ghost.UnitTest.Windows;
using Microsoft.UI.Xaml;
using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer;

View File

@@ -1,8 +1,7 @@
global using static TerraFX.Interop.Windows.Windows;
global using static TerraFX.Interop.DirectX.D3D12;
global using static TerraFX.Interop.DirectX.DirectX;
global using static TerraFX.Interop.DirectX.D3D12;
global using static TerraFX.Interop.DirectX.DXGI;
global using static TerraFX.Interop.Windows.Windows;
using Ghost.Core.Attributes;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;

View File

@@ -1,14 +1,15 @@
using Ghost.Core;
using Ghost.Core.Utilities;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Runtime.CompilerServices;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using static TerraFX.Aliases.D3D_Alias;
using static TerraFX.Aliases.D3D12_Alias;
using static TerraFX.Aliases.DXGI_Alias;
using static TerraFX.Aliases.D3D_Alias;
namespace Ghost.Graphics.D3D12;
@@ -65,11 +66,13 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
Dispose();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, this);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ThrowIfNotRecording()
{
if (!_isRecording)
@@ -78,6 +81,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void IncrementCommandCount()
{
_commandCount++;

View File

@@ -1,3 +1,4 @@
using Ghost.Core.Utilities;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using TerraFX.Interop.DirectX;

View File

@@ -1,4 +1,5 @@
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Core.Utilities;
using Ghost.Graphics.D3D12.Utilities;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;

View File

@@ -1,52 +1,64 @@
#undef USE_TRADITIONAL_BINDLESS
using Ghost.Core;
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Core.Utilities;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using Misaki.HighPerformance.Utilities;
using System.Runtime.InteropServices;
using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows;
using Misaki.HighPerformance.LowLevel.Utilities;
using static TerraFX.Aliases.D3D12_Alias;
using static TerraFX.Aliases.D3D_Alias;
using static TerraFX.Aliases.DXGI_Alias;
using static TerraFX.Aliases.D3D12_Alias;
namespace Ghost.Graphics.D3D12;
// TODO: Fixed root signature and use bindless samplers and textures.
// This can dramatically reduce the number of root parameters needed and improve performance.
internal class D3D12ShaderPipeline : IShaderPipeline, IDisposable
internal struct D3D12GraphicsCompiledResult : IDisposable
{
public ComPtr<ID3D12PipelineState> pipelineState;
public D3D12ShaderCompiler.CompileResult vsResult;
public D3D12ShaderCompiler.CompileResult psResult;
public D3D12ShaderCompiler.CompileResult csResult;
public PipelineType Type
{
get; init;
}
public CompileResult tsResult;
public CompileResult msResult;
public CompileResult psResult;
public void Dispose()
{
pipelineState.Dispose();
vsResult.Dispose();
tsResult.Dispose();
msResult.Dispose();
psResult.Dispose();
csResult.Dispose();
}
}
internal struct D3D12PipelineState : IDisposable
{
// NOTE: This is just a temporary cache for compiled shader code. We will implement a proper disk cache later.
public D3D12GraphicsCompiledResult compileResult;
public D3DX12_MESH_SHADER_PIPELINE_STATE_DESC psoDesc;
public void Dispose()
{
compileResult.Dispose();
}
}
internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
{
private const int _ROOT_PARAM_COUNT =
#if USE_TRADITIONAL_BINDLESS
6
#else
4
#endif
;
private readonly D3D12RenderDevice _device;
private readonly D3D12ResourceDatabase _resourceDatabase;
private ComPtr<ID3D12PipelineLibrary1> _library;
private ComPtr<ID3D12RootSignature> _defaultRootSignature;
private readonly Dictionary<Identifier<Shader>, D3D12ShaderPipeline> _shaderPipelines;
private readonly Dictionary<GraphicsPipelineKey, D3D12PipelineState> _pipelineCache;
public ID3D12RootSignature* DefaultRootSignature => _defaultRootSignature.Get();
@@ -55,20 +67,20 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
_device = device;
_resourceDatabase = resourceDatabase;
_shaderPipelines = new();
_pipelineCache = new();
InitializePipelineLibrary(cachePath);
InitializeLibrary(cachePath);
CreateDefaultRootSignature();
}
private void InitializePipelineLibrary(string? cachePath)
private void InitializeLibrary(string? filePath)
{
if (!File.Exists(cachePath))
if (!File.Exists(filePath))
{
_device.NativeDevice->CreatePipelineLibrary(null, 0, __uuidof<ID3D12PipelineLibrary1>(), _library.GetVoidAddressOf()).ThrowIfFailed();
}
var fileBytes = File.ReadAllBytes(cachePath!);
var fileBytes = File.ReadAllBytes(filePath!);
fixed (byte* pFileBytes = fileBytes)
{
_device.NativeDevice->CreatePipelineLibrary(pFileBytes, (nuint)fileBytes.Length, __uuidof<ID3D12PipelineLibrary1>(), _library.GetVoidAddressOf()).ThrowIfFailed();
@@ -77,18 +89,10 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
private void CreateDefaultRootSignature()
{
const int rootParamCount =
#if USE_TRADITIONAL_BINDLESS
6
#else
4
#endif
;
_defaultRootSignature = default;
// NOTE: Since we are targeting SM 6.6, we can use ResourceDescriptorHeap and SamplerDescriptorHeap directly without needing to set up descriptor tables.
var rootParameters = stackalloc D3D12_ROOT_PARAMETER1[rootParamCount];
var rootParameters = stackalloc D3D12_ROOT_PARAMETER1[_ROOT_PARAM_COUNT];
rootParameters[0] = new D3D12_ROOT_PARAMETER1
{
ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV,
@@ -151,7 +155,7 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
var rootSignatureDesc = new D3D12_ROOT_SIGNATURE_DESC1
{
NumParameters = rootParamCount,
NumParameters = _ROOT_PARAM_COUNT,
pParameters = rootParameters,
NumStaticSamplers = 0,
pStaticSamplers = null,
@@ -175,71 +179,227 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
var serializeResult = D3D12SerializeVersionedRootSignature(&versionedDesc, signature.GetAddressOf(), error.GetAddressOf());
if (serializeResult.FAILED)
{
var errorMsg = error.Get() != null ? Marshal.PtrToStringAnsi((nint)error.Get()->GetBufferPointer()) : "Unknown error";
var errorMsg = error.Get() != null ? Marshal.PtrToStringUTF8((nint)error.Get()->GetBufferPointer()) : "Unknown error";
throw new InvalidOperationException($"Failed to serialize default root signature: {errorMsg}");
}
_device.NativeDevice->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(),
__uuidof<ID3D12RootSignature>(), _defaultRootSignature.GetVoidAddressOf()).ThrowIfFailed();
ThrowIfFailed(_device.NativeDevice->CreateRootSignature(0, signature.Get()->GetBufferPointer(), signature.Get()->GetBufferSize(),
__uuidof<ID3D12RootSignature>(), _defaultRootSignature.GetVoidAddressOf()));
}
public void StorePipeline(string psoIdentifier, ID3D12PipelineState* pso)
private static void ValidateReflectionData(ShaderReflectionData reflectionData)
{
_library.Get()->StorePipeline(psoIdentifier.AsSpan().GetUnsafePtr(), pso);
if (reflectionData.ConstantBuffers.Count != _ROOT_PARAM_COUNT)
{
throw new InvalidOperationException($"Shader reflection data has {reflectionData.ConstantBuffers.Count} constant buffers, expected {_ROOT_PARAM_COUNT}");
}
public void* LoadGraphicsPipeline(string psoIdentifier)
if (reflectionData.OtherResources.Count != 0)
{
if (_library.Get()->LoadGraphicsPipeline(psoIdentifier.AsSpan().GetUnsafePtr(), __uuidof<ID3D12PipelineState>(), out var pso).Failure)
{
return null;
throw new NotSupportedException("Shader reflection data contains unsupported resource types. Only constant buffers are supported in the current root signature.");
}
return pso;
// TODO: Validate Cbuffer sizes and bindings.
}
public void CompileShader(Identifier<Shader> id, string shaderPath)
private static Result<D3D12GraphicsCompiledResult> CompileAndValidateFullPass(FullPassDescriptor descriptor)
{
var vsResult = D3D12ShaderCompiler.Compile(shaderPath, D3D12ShaderCompiler.ShaderStage.VertexShader, D3D12ShaderCompiler.CompilerVersion.SM_6_6);
var psResult = D3D12ShaderCompiler.Compile(shaderPath, D3D12ShaderCompiler.ShaderStage.PixelShader, D3D12ShaderCompiler.CompilerVersion.SM_6_6);
ref var shader = ref _resourceDatabase.GetShaderReference(id);
D3D12ShaderCompiler.PerformDXCReflection(ref shader, vsResult.reflection.Get());
D3D12ShaderCompiler.PerformDXCReflection(ref shader, psResult.reflection.Get());
var shaderPipeline = new D3D12ShaderPipeline
static CompileResult CompileAndValidate(ref CompilerConfig config)
{
Type = PipelineType.Graphics,
vsResult = vsResult,
var reflectionBlob = default(IDxcBlob*);
var result = D3D12ShaderCompiler.Compile(ref config, Allocator.Persistent, &reflectionBlob).GetValueOrThrow();
if (reflectionBlob != null)
{
var reflection = D3D12ShaderCompiler.PerformDXCReflection(reflectionBlob).GetValueOrThrow();
ValidateReflectionData(reflection);
}
return result;
}
var tsResult = default(CompileResult);
var tsEntry = descriptor.taskShader;
if (tsEntry.IsCreated)
{
var config = new CompilerConfig
{
defines = descriptor.defines.AsSpan(),
includes = descriptor.includes.AsSpan(),
shaderPath = tsEntry.shader,
entryPoint = tsEntry.entry,
stage = ShaderStage.TaskShader,
tier = CompilerTier.Tier0,
optimizeLevel = CompilerOptimizeLevel.O3,
options = CompilerOption.KeepReflections,
};
tsResult = CompileAndValidate(ref config);
}
CompileResult msResult;
var msEntry = descriptor.meshShader;
if (msEntry.IsCreated)
{
var config = new CompilerConfig
{
defines = descriptor.defines.AsSpan(),
includes = descriptor.includes.AsSpan(),
shaderPath = msEntry.shader,
entryPoint = msEntry.entry,
stage = ShaderStage.MeshShader,
tier = CompilerTier.Tier0,
optimizeLevel = CompilerOptimizeLevel.O3,
options = CompilerOption.KeepReflections,
};
msResult = CompileAndValidate(ref config);
}
else
{
return Result<D3D12GraphicsCompiledResult>.Fail("Mesh shader expected.");
}
CompileResult psResult;
var psEntry = descriptor.pixelShader;
if (psEntry.IsCreated)
{
var config = new CompilerConfig
{
defines = descriptor.defines.AsSpan(),
includes = descriptor.includes.AsSpan(),
shaderPath = psEntry.shader,
entryPoint = psEntry.entry,
stage = ShaderStage.PixelShader,
tier = CompilerTier.Tier0,
optimizeLevel = CompilerOptimizeLevel.O3,
options = CompilerOption.KeepReflections,
};
psResult = CompileAndValidate(ref config);
}
else
{
return Result<D3D12GraphicsCompiledResult>.Fail("Pixel shader expected.");
}
return new D3D12GraphicsCompiledResult
{
tsResult = tsResult,
msResult = msResult,
psResult = psResult
};
_shaderPipelines[id] = shaderPipeline;
}
// Create PSO from SDL (Shader Definition Language) file
private void CreatePipelineStateObject(D3D12ShaderPipeline shaderPipeline)
private static D3D12_COMPARISON_FUNC ToD3DCompare(ZTestOptions z) => z switch
{
var psoDesc = new D3D12_GRAPHICS_PIPELINE_STATE_DESC
{
pRootSignature = _defaultRootSignature.Get(),
VS = new D3D12_SHADER_BYTECODE(shaderPipeline.vsResult.bytecode.GetUnsafePtr(), (nuint)shaderPipeline.vsResult.bytecode.Count),
PS = new D3D12_SHADER_BYTECODE(shaderPipeline.psResult.bytecode.GetUnsafePtr(), (nuint)shaderPipeline.psResult.bytecode.Count),
InputLayout = D3D12PipelineResource.InputLayoutDescription,
RasterizerState = D3D12_RASTERIZER_DESC.CULL_NONE,
BlendState = D3D12_BLEND_DESC.OPAQUE,
DepthStencilState = D3D12_DEPTH_STENCIL_DESC.DEFAULT,
SampleMask = uint.MaxValue,
PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
NumRenderTargets = 1,
SampleDesc = new DXGI_SAMPLE_DESC(1, 0),
DSVFormat = DXGI_FORMAT_UNKNOWN,
ZTestOptions.Disabled => D3D12_COMPARISON_FUNC_ALWAYS,
ZTestOptions.Less => D3D12_COMPARISON_FUNC_LESS,
ZTestOptions.LessEqual => D3D12_COMPARISON_FUNC_LESS_EQUAL,
ZTestOptions.Equal => D3D12_COMPARISON_FUNC_EQUAL,
ZTestOptions.GreaterEqual => D3D12_COMPARISON_FUNC_GREATER_EQUAL,
ZTestOptions.Greater => D3D12_COMPARISON_FUNC_GREATER,
ZTestOptions.NotEqual => D3D12_COMPARISON_FUNC_NOT_EQUAL,
ZTestOptions.Always => D3D12_COMPARISON_FUNC_ALWAYS,
_ => D3D12_COMPARISON_FUNC_LESS_EQUAL
};
psoDesc.RTVFormats[0] = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT;
private static D3D12_DEPTH_STENCIL_DESC BuildDepthStencil(ref readonly PipelineDescriptor pipeline)
{
var depthEnabled = pipeline.zTest != ZTestOptions.Disabled;
var writeEnabled = pipeline.zWrite == ZWriteOptions.On;
var cmp = ToD3DCompare(pipeline.zTest);
return D3D12_DEPTH_STENCIL_DESC.Create(depthEnabled, writeEnabled, cmp);
}
_device.NativeDevice->CreateGraphicsPipelineState(&psoDesc, __uuidof<ID3D12PipelineState>(), shaderPipeline.pipelineState.GetVoidAddressOf());
private void StorePassState(ShaderPassKey id, ref readonly D3D12GraphicsCompiledResult compiled, ref readonly PipelineDescriptor pipelineDescriptor, ReadOnlySpan<TextureFormat> rtvs, TextureFormat dsv)
{
var rtvCount = (uint)Math.Min(rtvs.Length, D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT);
var desc = new D3DX12_MESH_SHADER_PIPELINE_STATE_DESC
{
pRootSignature = _defaultRootSignature.Get(),
MS = new D3D12_SHADER_BYTECODE(compiled.msResult.bytecode.GetUnsafePtr(), (nuint)compiled.msResult.bytecode.Count),
PS = new D3D12_SHADER_BYTECODE(compiled.psResult.bytecode.GetUnsafePtr(), (nuint)compiled.psResult.bytecode.Count),
PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
SampleMask = UINT32_MAX,
SampleDesc = new DXGI_SAMPLE_DESC(1, 0),
NumRenderTargets = rtvCount,
DSVFormat = dsv.ToD3D12Format(),
DepthStencilState = BuildDepthStencil(in pipelineDescriptor),
NodeMask = 0,
Flags = D3D12_PIPELINE_STATE_FLAG_NONE,
BlendState = pipelineDescriptor.blend switch
{
BlendOptions.Opaque => D3D12_BLEND_DESC.OPAQUE,
BlendOptions.Alpha => D3D12_BLEND_DESC.ALPHA_BLEND,
BlendOptions.Additive => D3D12_BLEND_DESC.ADDITIVE,
BlendOptions.Multiply => D3D12_BLEND_DESC.MULTIPLY,
BlendOptions.PremultipliedAlpha => D3D12_BLEND_DESC.PREMULTIPLIED,
_ => D3D12_BLEND_DESC.OPAQUE
},
RasterizerState = pipelineDescriptor.cull switch
{
CullOptions.Off => D3D12_RASTERIZER_DESC.CULL_NONE,
CullOptions.Front => D3D12_RASTERIZER_DESC.CULL_CLOCKWISE,
CullOptions.Back => D3D12_RASTERIZER_DESC.CULL_COUNTER_CLOCKWISE,
_ => D3D12_RASTERIZER_DESC.CULL_NONE
},
};
if (compiled.tsResult.IsCreated)
{
desc.AS = new D3D12_SHADER_BYTECODE(compiled.tsResult.bytecode.GetUnsafePtr(), (nuint)compiled.tsResult.bytecode.Count);
}
var hash = new GraphicsPipelineHash
{
id = id,
rtvCount = rtvCount,
dsvFormat = dsv,
};
for (var i = 0; i < rtvCount && i < 6; i++)
{
desc.RTVFormats[i] = rtvs[i].ToD3D12Format();
desc.BlendState.RenderTarget[i].RenderTargetWriteMask = (byte)(pipelineDescriptor.colorMask & 0x0F);
hash.rtvFormats[i] = rtvs[i];
}
var key = hash.GetKey();
ref var existing = ref CollectionsMarshal.GetValueRefOrAddDefault(_pipelineCache, hash.GetKey(), out var exists);
if (exists)
{
throw new InvalidOperationException($"Pass code cache already contains an entry for key: {key}");
}
existing.compileResult = compiled;
existing.psoDesc = desc;
}
public void CompilePass(IPassDescriptor descriptor)
{
switch (descriptor)
{
case FullPassDescriptor fullPass:
var result = CompileAndValidateFullPass(fullPass).GetValueOrThrow();
StorePassState(new(fullPass.Identifier), in result, in fullPass.localPipeline, [TextureFormat.B8G8R8A8_UNorm], TextureFormat.Unknown);
break;
// Do we need to support other pass types?
default:
break;
}
}
public void CompileShader(ShaderDescriptor descriptor)
{
foreach (var pass in descriptor.passes)
{
CompilePass(pass);
}
}
// TODO: Pipeline variants (keywords)
@@ -247,33 +407,67 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
// TODO: Async compilation
public void PreCookPipelineState()
{
foreach (var kvp in _shaderPipelines)
foreach (var kvp in _pipelineCache)
{
ref var shader = ref _resourceDatabase.GetShaderReference(kvp.Key);
var key = kvp.Key;
var state = kvp.Value;
CreatePipelineStateObject(kvp.Value);
var streamDesc = new D3D12_PIPELINE_STATE_STREAM_DESC
{
pPipelineStateSubobjectStream = &state.psoDesc,
SizeInBytes = (nuint)sizeof(D3DX12_MESH_SHADER_PIPELINE_STATE_DESC)
};
kvp.Value.vsResult.Dispose();
kvp.Value.psResult.Dispose();
kvp.Value.csResult.Dispose();
ComPtr<ID3D12PipelineState> pipelineState = default;
ThrowIfFailed(_device.NativeDevice->CreatePipelineState(&streamDesc, __uuidof<ID3D12PipelineState>(), pipelineState.GetVoidAddressOf()));
var name = key.ToString();
fixed (char* pName = name)
{
ThrowIfFailed(_library.Get()->StorePipeline(pName, pipelineState.Get()));
}
}
}
public IShaderPipeline GetShaderPipeline(Identifier<Shader> id)
public ID3D12PipelineState* LoadPipelineState(GraphicsPipelineKey key)
{
if (_shaderPipelines.TryGetValue(id, out var pipeline))
var name = key.ToString();
var state = _pipelineCache[key];
var streamDesc = new D3D12_PIPELINE_STATE_STREAM_DESC
{
return pipeline;
pPipelineStateSubobjectStream = &state.psoDesc,
SizeInBytes = (nuint)sizeof(D3DX12_MESH_SHADER_PIPELINE_STATE_DESC)
};
fixed (char* pName = name)
{
ID3D12PipelineState* pipelineState;
ThrowIfFailed(_library.Get()->LoadPipeline(pName, &streamDesc, __uuidof<ID3D12PipelineState>(), (void**)&pipelineState));
return pipelineState;
}
}
throw new KeyNotFoundException($"Shader pipeline not found for shader ID: {id}");
public void SaveLibraryToDisk(string filePath)
{
var size = _library.Get()->GetSerializedSize();
using var buffer = new UnsafeArray<byte>((int)size, Allocator.Persistent); // We use persistent heap allocation instead of stack allocation to avoid stack overflow for large pipeline libraries.
ThrowIfFailed(_library.Get()->Serialize(buffer.GetUnsafePtr(), size));
var fs = File.Open(filePath, FileMode.Create, FileAccess.Write, FileShare.None);
fs.Write(buffer.AsSpan());
}
public void Dispose()
{
foreach (var kvp in _shaderPipelines)
_defaultRootSignature.Dispose();
foreach (var kvp in _pipelineCache)
{
kvp.Value.Dispose();
}
_library.Dispose();
}
}

View File

@@ -1,3 +1,4 @@
using Ghost.Core.Utilities;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI;
using TerraFX.Interop.DirectX;
@@ -34,9 +35,9 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
{
InitializeDevice();
_graphicsQueue = new D3D12CommandQueue(_device, CommandQueueType.Graphics);
_computeQueue = new D3D12CommandQueue(_device, CommandQueueType.Compute);
_copyQueue = new D3D12CommandQueue(_device, CommandQueueType.Copy);
_graphicsQueue = new D3D12CommandQueue(_device.Get(), CommandQueueType.Graphics);
_computeQueue = new D3D12CommandQueue(_device.Get(), CommandQueueType.Compute);
_copyQueue = new D3D12CommandQueue(_device.Get(), CommandQueueType.Copy);
}
~D3D12RenderDevice()
@@ -52,7 +53,7 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
CreateDXGIFactory2(FALSE, __uuidof<IDXGIFactory7>(), _dxgiFactory.GetVoidAddressOf());
#endif
using ComPtr<IDXGIAdapter1> adapter = default;
ComPtr<IDXGIAdapter1> adapter = default;
for (uint adapterIndex = 0;
_dxgiFactory.Get()->EnumAdapterByGpuPreference(adapterIndex, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, __uuidof<IDXGIAdapter1>(), adapter.ReleaseAndGetVoidAddressOf()).SUCCEEDED;
@@ -76,6 +77,7 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
if (_device.Get() == null)
{
adapter.Dispose(); // Dispose the last adapter we tried. If the operation succeeded, we would have moved it.
throw new PlatformNotSupportedException("Cannot create ID3D12Device with feature level 12.0");
}
}

View File

@@ -1,5 +1,6 @@
using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Core.Utilities;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Collections;
@@ -28,17 +29,6 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
private UnsafeQueue<Handle<GPUResource>> _temResources = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
private Guid* IID_NULL
{
get
{
fixed (Guid* pGuid = &Guid.Empty)
{
return pGuid;
}
}
}
public D3D12ResourceAllocator(RenderSystem renderSystem, D3D12RenderDevice device, D3D12DescriptorAllocator descriptorAllocator, D3D12ResourceDatabase resourceDatabase)
{
var desc = new D3D12MA_ALLOCATOR_DESC
@@ -62,7 +52,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CheckBufferSize(uint sizeInBytes)
private static void CheckBufferSize(ulong sizeInBytes)
{
if (sizeInBytes > _MAX_BYTES)
{
@@ -409,7 +399,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
var initialState = DetermineInitialTextureState(desc.Usage);
ComPtr<D3D12MA_Allocation> allocation = default;
ThrowIfFailed(_allocator.Get()->CreateResource(&allocationDesc, &resourceDesc, initialState, null, allocation.GetAddressOf(), IID_NULL, null));
ThrowIfFailed(_allocator.Get()->CreateResource(&allocationDesc, &resourceDesc, initialState, null, allocation.GetAddressOf(), Win32Utility.IID_NULL, null));
var resourceDescriptor = ResourceViewGroup.Invalid;
if (desc.Usage.HasFlag(TextureUsage.ShaderResource))
@@ -482,7 +472,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
var initialState = DetermineInitialBufferState(desc.Usage, desc.MemoryType);
ComPtr<D3D12MA_Allocation> allocation = default;
ThrowIfFailed(_allocator.Get()->CreateResource(&allocationDesc, &resourceDescription, initialState, null, allocation.GetAddressOf(), IID_NULL, null));
ThrowIfFailed(_allocator.Get()->CreateResource(&allocationDesc, &resourceDescription, initialState, null, allocation.GetAddressOf(), Win32Utility.IID_NULL, null));
var resourceDescriptor = ResourceViewGroup.Invalid;
if (desc.Usage.HasFlag(BufferUsage.ShaderResource))
@@ -499,7 +489,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
{
srvDesc.Format = DXGI_FORMAT_R32_TYPELESS;
srvDesc.Buffer.FirstElement = 0;
srvDesc.Buffer.NumElements = desc.Size / 4;
srvDesc.Buffer.NumElements = (uint)(desc.Size / 4u);
srvDesc.Buffer.StructureByteStride = 0;
srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_RAW;
}
@@ -507,7 +497,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
{
srvDesc.Format = DXGI_FORMAT_UNKNOWN;
srvDesc.Buffer.FirstElement = 0;
srvDesc.Buffer.NumElements = desc.Size / desc.Stride;
srvDesc.Buffer.NumElements = (uint)(desc.Size / desc.Stride);
srvDesc.Buffer.StructureByteStride = desc.Stride;
srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE;
}
@@ -519,13 +509,13 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
return handle.AsGraphicsBuffer();
}
public Handle<GraphicsBuffer> CreateUploadBuffer(uint size, bool isTemp = true)
public Handle<GraphicsBuffer> CreateUploadBuffer(ulong size, bool isTemp = true)
{
var desc = new BufferDesc
{
Size = size,
Usage = BufferUsage.Upload,
MemoryType = MemoryType.Upload,
MemoryType = ResourceMemoryType.Upload,
};
return CreateBuffer(ref desc, isTemp);
@@ -538,7 +528,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
Size = (uint)(vertices.Count * Unsafe.SizeOf<Vertex>()),
Stride = (uint)Unsafe.SizeOf<Vertex>(),
Usage = BufferUsage.Vertex | BufferUsage.ShaderResource,
MemoryType = MemoryType.Default,
MemoryType = ResourceMemoryType.Default,
};
var indexBufferDesc = new BufferDesc
@@ -546,7 +536,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
Size = (uint)(indices.Count * sizeof(uint)),
Stride = sizeof(uint),
Usage = BufferUsage.Index | BufferUsage.ShaderResource,
MemoryType = MemoryType.Default,
MemoryType = ResourceMemoryType.Default,
};
var vertexBuffer = CreateBuffer(ref vertexBufferDesc);
@@ -572,11 +562,13 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
ref var shaderRef = ref _resourceDatabase.GetShaderReference(shader);
// TODO: Get per-material constant buffer size from database
var desc = new BufferDesc
{
Size = shaderRef.PerMaterialBufferInfo.Size,
Usage = BufferUsage.Constant,
MemoryType = MemoryType.Default,
MemoryType = ResourceMemoryType.Default,
};
var buffer = CreateBuffer(ref desc);
@@ -641,13 +633,13 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
return flags;
}
private static D3D12_HEAP_TYPE ConvertMemoryType(MemoryType memoryType)
private static D3D12_HEAP_TYPE ConvertMemoryType(ResourceMemoryType memoryType)
{
return memoryType switch
{
MemoryType.Default => D3D12_HEAP_TYPE_DEFAULT,
MemoryType.Upload => D3D12_HEAP_TYPE_UPLOAD,
MemoryType.Readback => D3D12_HEAP_TYPE_READBACK,
ResourceMemoryType.Default => D3D12_HEAP_TYPE_DEFAULT,
ResourceMemoryType.Upload => D3D12_HEAP_TYPE_UPLOAD,
ResourceMemoryType.Readback => D3D12_HEAP_TYPE_READBACK,
_ => throw new ArgumentException($"Unsupported memory type: {memoryType}")
};
}
@@ -672,14 +664,14 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
return D3D12_RESOURCE_STATE_COMMON;
}
private static D3D12_RESOURCE_STATES DetermineInitialBufferState(BufferUsage usage, MemoryType memoryType)
private static D3D12_RESOURCE_STATES DetermineInitialBufferState(BufferUsage usage, ResourceMemoryType memoryType)
{
if (memoryType == MemoryType.Upload)
if (memoryType == ResourceMemoryType.Upload)
{
return D3D12_RESOURCE_STATE_GENERIC_READ;
}
if (memoryType == MemoryType.Readback)
if (memoryType == ResourceMemoryType.Readback)
{
return D3D12_RESOURCE_STATE_COPY_DEST;
}

View File

@@ -1,8 +1,8 @@
using Ghost.Core;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Collections;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.InteropServices;
using TerraFX.Interop.DirectX;
@@ -12,7 +12,7 @@ namespace Ghost.Graphics.D3D12;
internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
{
internal unsafe struct ResourceInfo
internal unsafe struct ResourceRecord
{
[StructLayout(LayoutKind.Explicit)]
public struct ResourceUnion
@@ -36,14 +36,14 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
public ResourceDesc desc;
public ResourceViewGroup descriptor;
public ResourceUnion resourceUnion;
public uint cpuFenceValue;
public ResourceState state;
public uint cpuFenceValue;
public readonly bool isExternal;
public readonly bool Allocated => isExternal ? resourceUnion.resource.Get() != null : resourceUnion.allocation.Get()->IsNotNull;
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 ResourceInfo(ComPtr<D3D12MA_Allocation> allocation, uint cpuFenceValue, ResourceState state, ResourceViewGroup resourceDescriptor, ResourceDesc desc)
public ResourceRecord(ComPtr<D3D12MA_Allocation> allocation, uint cpuFenceValue, ResourceState state, ResourceViewGroup resourceDescriptor, ResourceDesc desc)
{
this.resourceUnion = new ResourceUnion(allocation);
this.isExternal = false;
@@ -54,7 +54,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
this.desc = desc;
}
public ResourceInfo(ComPtr<ID3D12Resource> resource, ResourceState state)
public ResourceRecord(ComPtr<ID3D12Resource> resource, ResourceState state)
{
this.resourceUnion = new ResourceUnion(resource);
this.isExternal = true;
@@ -65,24 +65,26 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
this.desc = ResourceDesc.FromD3D12(resource.Get()->GetDesc());
}
public uint Release()
public uint Release(D3D12DescriptorAllocator descriptorAllocator)
{
var refCount = 0u;
if (Allocated)
{
if (isExternal)
{
refCount = resourceUnion.resource.Get()->Release();
refCount = resourceUnion.resource.Reset();
}
else
{
refCount = resourceUnion.allocation.Get()->Release();
refCount = resourceUnion.allocation.Reset();
}
resourceUnion = default;
descriptor = default;
}
descriptorAllocator.Release(descriptor);
return refCount;
}
}
@@ -93,31 +95,33 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
public bool occupied;
}
private UnsafeSlotMap<ResourceInfo> _resources;
private readonly D3D12DescriptorAllocator _descriptorAllocator;
private UnsafeSlotMap<ResourceRecord> _resources;
#if DEBUG || GHOST_EDITOR
private readonly Dictionary<ResourceInfo, string> _resourceName;
private readonly Dictionary<ResourceRecord, string> _resourceName;
#endif
private readonly UnsafeSlotMap<Mesh> _meshes;
private readonly UnsafeSlotMap<Material> _materials;
// NOTE: We use a simple list since shader is not frequently added/removed. This can save 4 bytes for each ecs component.
// NOTE: We use a simple list since shaderSlot is not frequently added/removed. This can save 4 bytes for each ecs component.
private readonly DynamicArray<Slot<Shader>> _shaders;
private readonly D3D12DescriptorAllocator _descriptorAllocator;
private readonly Dictionary<ShaderPassKey, ShaderPass> _shaderPasses;
private bool _disposed;
public D3D12ResourceDatabase(D3D12DescriptorAllocator descriptorAllocator)
{
_resources = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
_resources = new(64, Allocator.Persistent);
#if DEBUG || GHOST_EDITOR
_resourceName = new(64);
#endif
_meshes = new(64, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
_materials = new(16, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
_meshes = new(64, Allocator.Persistent);
_materials = new(16, Allocator.Persistent);
_shaders = new(16);
_shaderPasses = new(16);
_descriptorAllocator = descriptorAllocator;
}
@@ -127,6 +131,12 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
Dispose();
}
private void ReleaseResource<T>(ref T resource)
where T : IResourceReleasable
{
resource.ReleaseResource(this);
}
public Handle<GPUResource> ImportExternalResource<T>(T resource, ResourceState initialState)
where T : unmanaged
{
@@ -137,7 +147,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
throw new InvalidOperationException($"Expect ComPtr<ID3D12Resource> in D3D12ResourceDatabase, but got {typeof(T)}.");
}
var id = _resources.Add(new ResourceInfo(d3d12Resource, initialState), out var generation);
var id = _resources.Add(new ResourceRecord(d3d12Resource, initialState), out var generation);
return new Handle<GPUResource>(id, generation);
}
@@ -145,11 +155,11 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
{
ObjectDisposedException.ThrowIf(_disposed, this);
var id = _resources.Add(new ResourceInfo(allocation, cpuFenceValue, initialState, resourceDescriptor, desc), out var generation);
var id = _resources.Add(new ResourceRecord(allocation, cpuFenceValue, initialState, resourceDescriptor, desc), out var generation);
return new Handle<GPUResource>(id, generation);
}
public ref ResourceInfo GetResourceInfo(Handle<GPUResource> handle)
public ref ResourceRecord GetResourceInfo(Handle<GPUResource> handle)
{
ObjectDisposedException.ThrowIf(_disposed, this);
@@ -162,7 +172,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
return ref info;
}
public ref ResourceInfo GetResourceInfo(Handle<GPUResource> handle, out bool exist)
public ref ResourceRecord GetResourceInfo(Handle<GPUResource> handle, out bool exist)
{
ObjectDisposedException.ThrowIf(_disposed, this);
return ref _resources.GetElementReferenceAt(handle.id, handle.generation, out exist);
@@ -232,7 +242,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
return;
}
var refCount = info.Release();
var refCount = info.Release(_descriptorAllocator);
#if DEBUG || GHOST_EDITOR
if (refCount > 0)
{
@@ -268,28 +278,15 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
return ref mesh;
}
private void ReleaseMeshResources(ref readonly Mesh mesh)
{
mesh.ReleaseCpuResources();
ref var vertexRef = ref GetResourceInfo(mesh.vertexBuffer.AsResource());
ref var indexRef = ref GetResourceInfo(mesh.indexBuffer.AsResource());
_descriptorAllocator.Release(vertexRef.descriptor);
_descriptorAllocator.Release(indexRef.descriptor);
ReleaseResource(mesh.vertexBuffer.AsResource());
ReleaseResource(mesh.indexBuffer.AsResource());
}
public void ReleaseMesh(Handle<Mesh> handle)
{
ref var meshSlot = ref _meshes.GetElementReferenceAt(handle.id, handle.generation, out var exist);
ref var mesh = ref _meshes.GetElementReferenceAt(handle.id, handle.generation, out var exist);
if (!exist)
{
return;
}
ReleaseMeshResources(ref meshSlot);
ReleaseResource(ref mesh);
_meshes.Remove(handle.id, handle.generation);
}
@@ -326,7 +323,8 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
return;
}
material.Dispose();
ReleaseResource(ref material);
_materials.Remove(handle.id, handle.generation);
}
public Identifier<Shader> AddShader(ref readonly Shader shader)
@@ -355,17 +353,39 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
return ref shader;
}
public void ReleaseShader(Identifier<Shader> handle)
public void ReleaseShader(Identifier<Shader> id)
{
if (!HasShader(handle))
if (!HasShader(id))
{
return;
}
ref var shader = ref _shaders[handle.value].value;
shader.Dispose();
ref var shaderSlot = ref _shaders[id.value];
ReleaseResource(ref shaderSlot.value);
shaderSlot.occupied = false;
}
public void AddShaderPass(ShaderPassKey passKey, ShaderPass pass)
{
ObjectDisposedException.ThrowIf(_disposed, this);
_shaderPasses.Add(passKey, pass);
}
public ShaderPass GetShaderPass(ShaderPassKey passKey)
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (!_shaderPasses.TryGetValue(passKey, out var pass))
{
throw new KeyNotFoundException($"Shader pass '{passKey}' not found.");
}
return pass;
}
// Should we need to release the shaderSlot pass?
public void Dispose()
{
if (_disposed)

View File

@@ -1,8 +1,10 @@
#undef SUPPORT_TEXTURE_BINDING
using Ghost.Core;
using Ghost.Core.Utilities;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using TerraFX.Interop.DirectX;
@@ -10,63 +12,192 @@ using TerraFX.Interop.Windows;
namespace Ghost.Graphics.D3D12;
internal unsafe static class D3D12ShaderCompiler
{
public enum CompilerVersion
{
SM_6_6,
SM_7_0
}
public enum ShaderStage
{
VertexShader,
PixelShader,
MeshShader,
ComputeShader
}
public struct CompileResult : IDisposable
internal unsafe struct CompileResult : IDisposable
{
public UnsafeArray<byte> bytecode;
public ComPtr<IDxcBlob> reflection;
public readonly bool IsCreated => bytecode.IsCreated;
public void Dispose()
{
bytecode.Dispose();
reflection.Dispose();
}
}
private static string GetProfileString(ShaderStage stage, CompilerVersion version)
internal readonly struct CBufferVariableInfo
{
public string Name
{
get; init;
}
public uint StartOffset
{
get; init;
}
public uint Size
{
get; init;
}
}
internal readonly struct CBufferInfo
{
public string Name
{
get; init;
}
public uint RegisterSlot
{
get; init;
}
public uint RegisterSpace
{
get; init;
}
public uint SizeInBytes
{
get; init;
}
public IReadOnlyList<CBufferVariableInfo> Variables
{
get; init;
}
}
internal readonly struct ResourceBindingInfo
{
public string Name
{
get; init;
}
public D3D_SHADER_INPUT_TYPE Type
{
get; init;
}
public uint BindPoint
{
get; init;
}
public uint BindCount
{
get; init;
}
public uint Space
{
get; init;
}
}
internal readonly struct ShaderReflectionData
{
public List<CBufferInfo> ConstantBuffers
{
get;
}
public List<ResourceBindingInfo> OtherResources
{
get;
}
// public List<ResourceBindingInfo> Samplers { get; } = new();
// public List<ResourceBindingInfo> ShaderResourceViews { get; } = new();
// public List<ResourceBindingInfo> UnorderedAccessViews { get; } = new();
public ShaderReflectionData()
{
ConstantBuffers = new List<CBufferInfo>();
OtherResources = new List<ResourceBindingInfo>();
}
}
internal static unsafe class D3D12ShaderCompiler
{
private static string GetProfileString(ShaderStage stage, CompilerTier version)
{
return (stage, version) switch
{
(ShaderStage.VertexShader, CompilerVersion.SM_6_6) => "vs_6_6",
(ShaderStage.PixelShader, CompilerVersion.SM_6_6) => "ps_6_6",
(ShaderStage.MeshShader, CompilerVersion.SM_6_6) => "ms_6_6",
(ShaderStage.ComputeShader, CompilerVersion.SM_6_6) => "cs_6_6",
(ShaderStage.VertexShader, CompilerVersion.SM_7_0) => "vs_7_0",
(ShaderStage.PixelShader, CompilerVersion.SM_7_0) => "ps_7_0",
(ShaderStage.MeshShader, CompilerVersion.SM_7_0) => "ms_7_0",
(ShaderStage.ComputeShader, CompilerVersion.SM_7_0) => "cs_7_0",
(ShaderStage.TaskShader, CompilerTier.Tier0) => "as_6_6",
(ShaderStage.PixelShader, CompilerTier.Tier0) => "ps_6_6",
(ShaderStage.MeshShader, CompilerTier.Tier0) => "ms_6_6",
(ShaderStage.ComputeShader, CompilerTier.Tier0) => "cs_6_6",
(ShaderStage.TaskShader, CompilerTier.Tier1) => "as_6_7",
(ShaderStage.PixelShader, CompilerTier.Tier1) => "ps_6_7",
(ShaderStage.MeshShader, CompilerTier.Tier1) => "ms_6_7",
(ShaderStage.ComputeShader, CompilerTier.Tier1) => "cs_6_7",
(ShaderStage.TaskShader, CompilerTier.Tier2) => "as_6_8",
(ShaderStage.PixelShader, CompilerTier.Tier2) => "ps_6_8",
(ShaderStage.MeshShader, CompilerTier.Tier2) => "ms_6_8",
(ShaderStage.ComputeShader, CompilerTier.Tier2) => "cs_6_8",
_ => throw new ArgumentOutOfRangeException(nameof(stage), "Unsupported shader stage or compiler version")
};
}
private static string GetEntryPoint(ShaderStage stage)
private static string GetOptimizeLevelString(CompilerOptimizeLevel level)
{
return stage switch
return level switch
{
ShaderStage.VertexShader => "VSMain",
ShaderStage.PixelShader => "PSMain",
ShaderStage.MeshShader => "MSMain",
ShaderStage.ComputeShader => "CSMain",
_ => throw new ArgumentOutOfRangeException(nameof(stage), "Unsupported shader stage")
CompilerOptimizeLevel.O0 => "-O0",
CompilerOptimizeLevel.O1 => "-O1",
CompilerOptimizeLevel.O2 => "-O2",
CompilerOptimizeLevel.O3 => "-O3",
_ => throw new ArgumentOutOfRangeException(nameof(level), "Unsupported optimization level")
};
}
public static CompileResult Compile(string shaderPath, ShaderStage stage, CompilerVersion version)
private static List<string> GetCompilerArguments(ref readonly CompilerConfig config)
{
var argsArray = new List<string>
{
"-T", GetProfileString(config.stage, config.tier), // Target profile (ms_6_6, ps_6_6)
"-E", config.entryPoint, // Entry point
"-HV", "2021", // HLSL version 2021
"-enable-16bit-types", // Enable 16-bit types
GetOptimizeLevelString(config.optimizeLevel), // Optimization level
};
foreach (var include in config.includes)
{
argsArray.Add("-I");
argsArray.Add(include);
}
foreach (var define in config.defines)
{
argsArray.Add("-D");
argsArray.Add(define);
}
if (!config.options.HasFlag(CompilerOption.KeepDebugInfo))
{
argsArray.Add("-Qstrip_debug");
}
if (!config.options.HasFlag(CompilerOption.KeepReflections))
{
argsArray.Add("-Qstrip_reflect");
}
if (config.options.HasFlag(CompilerOption.WarnAsError))
{
argsArray.Add("-WX");
}
return argsArray;
}
public static Result<CompileResult> Compile(ref readonly CompilerConfig config, Allocator allocator, IDxcBlob** ppReflectionBlob)
{
using ComPtr<IDxcCompiler3> compiler = default;
using ComPtr<IDxcUtils> utils = default;
@@ -76,47 +207,30 @@ internal unsafe static class D3D12ShaderCompiler
var pDxcCompiler = (Guid*)Unsafe.AsPointer(in CLSID.CLSID_DxcCompiler);
var pDxcUtils = (Guid*)Unsafe.AsPointer(in CLSID.CLSID_DxcUtils);
DxcCreateInstance(pDxcCompiler, __uuidof<IDxcCompiler3>(), compiler.GetVoidAddressOf());
DxcCreateInstance(pDxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf());
ThrowIfFailed(DxcCreateInstance(pDxcCompiler, __uuidof<IDxcCompiler3>(), compiler.GetVoidAddressOf()));
ThrowIfFailed(DxcCreateInstance(pDxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf()));
//includeHandler.Get()->LoadSource();
utils.Get()->CreateDefaultIncludeHandler(includeHandler.GetAddressOf());
// Create source blob
using ComPtr<IDxcBlobEncoding> sourceBlob = default;
//var sourceBytes = System.Text.Encoding.UTF8.GetBytes(shaderPath);
fixed (char* pShaderPath = shaderPath.AsSpan())
if (utils.Get()->LoadFile(config.shaderPath.AsSpan().GetUnsafePtr(), null, sourceBlob.GetAddressOf()).FAILED)
{
utils.Get()->LoadFile(pShaderPath, null, sourceBlob.GetAddressOf());
//utils.Get()->CreateBlob(sourceBytesPtr, (uint)sourceBytes.Length, DXC_CP_UTF8, sourceBlob.GetAddressOf());
return Result<CompileResult>.Fail($"Failed to load shader file: {config.shaderPath}");
}
// Prepare compilation arguments - NOTE: NO -Qstrip_reflect to keep reflection data
var argsArray = new string[]
var argsArray = GetCompilerArguments(in config);
var argPtrs = stackalloc char*[argsArray.Count];
for (var i = 0; i < argsArray.Count; i++)
{
"-T", GetProfileString(stage, version), // Target profile (vs_6_6, ps_6_6)
"-E", GetEntryPoint(stage), // Entry point
"-HV", "2021", // HLSL version 2021 (required for SM 6.6)
"-enable-16bit-types", // Enable 16-bit types
"-O3", // Optimization level
"-Qstrip_debug" // Strip debug info but KEEP reflection
};
// Convert to wide strings (DXC expects LPCWSTR)
var wideArgs = new nuint[argsArray.Length];
var argPointers = new IntPtr[argsArray.Length];
for (var i = 0; i < argsArray.Length; i++)
{
argPointers[i] = Marshal.StringToHGlobalUni(argsArray[i]);
wideArgs[i] = (nuint)argPointers[i];
argPtrs[i] = (char*)Marshal.StringToHGlobalUni(argsArray[i]);
}
try
{
// Compile shader
using ComPtr<IDxcResult> result = default;
fixed (nuint* argsPtr = wideArgs)
{
var buffer = new DxcBuffer
{
Ptr = sourceBlob.Get()->GetBufferPointer(),
@@ -124,8 +238,7 @@ internal unsafe static class D3D12ShaderCompiler
Encoding = DXC.DXC_CP_UTF8
};
compiler.Get()->Compile(&buffer, (char**)argsPtr, (uint)argsArray.Length, includeHandler.Get(), __uuidof<IDxcResult>(), result.GetVoidAddressOf());
}
ThrowIfFailed(compiler.Get()->Compile(&buffer, argPtrs, (uint)argsArray.Count, includeHandler.Get(), __uuidof<IDxcResult>(), result.GetVoidAddressOf()));
// Check compilation result
HRESULT hrStatus;
@@ -139,11 +252,11 @@ internal unsafe static class D3D12ShaderCompiler
if (errorBlob.Get() != null)
{
var errorMessage = Marshal.PtrToStringUni((IntPtr)errorBlob.Get()->GetBufferPointer());
throw new Exception($"DXC shader compilation failed: {errorMessage}");
return Result<CompileResult>.Fail($"DXC shader compilation failed:\n{errorMessage}");
}
else
{
throw new Exception("DXC shader compilation failed with unknown error");
return Result<CompileResult>.Fail("DXC shader compilation failed with unknown error.");
}
}
@@ -152,47 +265,46 @@ internal unsafe static class D3D12ShaderCompiler
ThrowIfFailed(result.Get()->GetResult(bytecodeBlob.GetAddressOf()));
// Get reflection data using DXC API
using ComPtr<IDxcBlob> reflectionBlob = default;
ThrowIfFailed(result.Get()->GetOutput(DXC_OUT_KIND.DXC_OUT_REFLECTION, __uuidof<IDxcBlob>(), reflectionBlob.GetVoidAddressOf(), null));
if (ppReflectionBlob != null)
{
ThrowIfFailed(result.Get()->GetOutput(DXC_OUT_KIND.DXC_OUT_REFLECTION, __uuidof<IDxcBlob>(), (void**)ppReflectionBlob, null));
}
var bytecodeSize = bytecodeBlob.Get()->GetBufferSize();
var bytecode = new UnsafeArray<byte>((int)bytecodeSize, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
var bytecode = new UnsafeArray<byte>((int)bytecodeSize, allocator);
NativeMemory.Copy(bytecodeBlob.Get()->GetBufferPointer(), bytecode.GetUnsafePtr(), bytecodeSize);
return new CompileResult
{
bytecode = bytecode,
reflection = reflectionBlob.Move()
};
}
finally
{
// Free allocated wide strings
for (var i = 0; i < argPointers.Length; i++)
for (var i = 0; i < argsArray.Count; i++)
{
Marshal.FreeHGlobal(argPointers[i]);
Marshal.FreeHGlobal((nint)argPtrs[i]);
}
}
}
private static void AddProperty(ref Shader shader, string name, PropertyInfo propertyInfo)
{
var id = shader.Properties.Count;
shader.Properties.Add(propertyInfo);
shader.PropertyNameToIdMap[name] = id;
}
// TODO: Since we are using fixed root signature layout, the reflection pass should only validate the layout, not generate it.
public static void PerformDXCReflection(ref Shader shader, IDxcBlob* reflectionBlob)
// TODO: Ideally this should return a structured reflection data instead of populating raw lists/dictionaries.
public static Result<ShaderReflectionData> PerformDXCReflection(IDxcBlob* reflectionBlob)
{
if (reflectionBlob == null)
{
return Result<ShaderReflectionData>.Fail("Reflection blob is null.");
}
// Create DXC utils to parse reflection data
var pDxcUtils = (Guid*)Unsafe.AsPointer(in CLSID.CLSID_DxcUtils);
using ComPtr<IDxcUtils> utils = default;
DxcCreateInstance(pDxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf());
ThrowIfFailed(DxcCreateInstance(pDxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf()));
// Create reflection interface from blob
var reflectionData = new DxcBuffer
var reflectionBuffer = new DxcBuffer
{
Ptr = reflectionBlob->GetBufferPointer(),
Size = reflectionBlob->GetBufferSize(),
@@ -200,94 +312,84 @@ internal unsafe static class D3D12ShaderCompiler
};
using ComPtr<ID3D12ShaderReflection> reflection = default;
ThrowIfFailed(utils.Get()->CreateReflection(&reflectionData, __uuidof<ID3D12ShaderReflection>(), reflection.GetVoidAddressOf()));
ThrowIfFailed(utils.Get()->CreateReflection(&reflectionBuffer, __uuidof<ID3D12ShaderReflection>(), reflection.GetVoidAddressOf()));
D3D12_SHADER_DESC shaderDesc;
reflection.Get()->GetDesc(&shaderDesc);
ThrowIfFailed(reflection.Get()->GetDesc(&shaderDesc));
var cbufferRegistry = new Dictionary<string, CBufferInfo>();
var textureRegistry = new Dictionary<string, TextureInfo>();
var reflectionData = new ShaderReflectionData();
for (uint i = 0; i < shaderDesc.BoundResources; i++)
{
D3D12_SHADER_INPUT_BIND_DESC bindDesc;
reflection.Get()->GetResourceBindingDesc(i, &bindDesc);
ThrowIfFailed(reflection.Get()->GetResourceBindingDesc(i, &bindDesc));
var resourceName = Marshal.PtrToStringUTF8((IntPtr)bindDesc.Name);
if (resourceName == null)
{
return Result<ShaderReflectionData>.Fail("Failed to get resource name from reflection data.");
}
switch (bindDesc.Type)
{
case D3D_SHADER_INPUT_TYPE.D3D_SIT_CBUFFER:
{
var cbufferName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
if (cbufferName == null || cbufferRegistry.ContainsKey(cbufferName))
{
continue;
}
var cbuffer = reflection.Get()->GetConstantBufferByName(bindDesc.Name);
D3D12_SHADER_BUFFER_DESC cbufferDesc;
cbuffer->GetDesc(&cbufferDesc);
ThrowIfFailed(cbuffer->GetDesc(&cbufferDesc));
var cbufferInfo = new CBufferInfo
{
Size = cbufferDesc.Size,
RegisterSlot = bindDesc.BindPoint
};
cbufferRegistry.Add(cbufferName, cbufferInfo);
var variables = new List<CBufferVariableInfo>((int)cbufferDesc.Variables);
// Now we iterate all variables for *every* cbuffer, not just b3
for (uint j = 0; j < cbufferDesc.Variables; j++)
{
var variable = cbuffer->GetVariableByIndex(j);
D3D12_SHADER_VARIABLE_DESC varDesc;
variable->GetDesc(&varDesc);
var variableName = Marshal.PtrToStringAnsi((IntPtr)varDesc.Name);
if (variableName == null || shader.PropertyNameToIdMap.ContainsKey(variableName))
var variableName = Marshal.PtrToStringUTF8((IntPtr)varDesc.Name);
if (variableName == null)
{
continue;
}
var propInfo = new PropertyInfo
variables.Add(new CBufferVariableInfo
{
CBufferIndex = cbufferInfo.RegisterSlot,
ByteOffset = varDesc.StartOffset,
Name = variableName,
StartOffset = varDesc.StartOffset,
Size = varDesc.Size
};
AddProperty(ref shader, variableName, propInfo);
});
}
break;
}
case D3D_SHADER_INPUT_TYPE.D3D_SIT_TEXTURE:
{
#if SUPPORT_TEXTURE_BINDING
var textureName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
if (textureName == null || textureRegistry.ContainsKey(textureName))
{
continue;
}
// ALL texture input slots are regular textures!
// Bindless textures don't use explicit texture inputs - they use ResourceDescriptorHeap[index]
var textureInfo = new TextureInfo
reflectionData.ConstantBuffers.Add(new CBufferInfo
{
Name = resourceName,
RegisterSlot = bindDesc.BindPoint,
RootParameterIndex = (uint)shader.ConstantBuffers.Count // Descriptor table comes after CBVs
};
RegisterSpace = bindDesc.Space,
SizeInBytes = cbufferDesc.Size,
Variables = variables
});
break;
}
// NOTE: Currently we are not support resource bindings yet, everything access through bindless heaps.
default:
{
reflectionData.OtherResources.Add(new ResourceBindingInfo
{
Name = resourceName,
Type = bindDesc.Type,
BindPoint = bindDesc.BindPoint,
BindCount = bindDesc.BindCount,
Space = bindDesc.Space
});
textureRegistry.Add(textureName, textureInfo);
break;
#endif
throw new NotSupportedException("Texture bindings are not supported in current version. Please use bindless textures.");
}
}
}
shader.PerMaterialBufferInfo.Clear();
foreach (var cbuf in cbufferRegistry.Values)
{
shader.PerMaterialBufferInfo.Add(cbuf);
}
return reflectionData;
}
}

View File

@@ -1,4 +1,5 @@
using Ghost.Core;
using Ghost.Core.Utilities;
using Ghost.Graphics.Contracts;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data;

View File

@@ -63,6 +63,8 @@ internal static class D3D12_BLEND_DESC_Extensions
public static D3D12_BLEND_DESC OPAQUE => Create(D3D12_BLEND_ONE, D3D12_BLEND_ZERO);
public static D3D12_BLEND_DESC ALPHA_BLEND => Create(D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_INV_SRC_ALPHA);
public static D3D12_BLEND_DESC ADDITIVE => Create(D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_ONE);
public static D3D12_BLEND_DESC MULTIPLY => Create(D3D12_BLEND_DEST_COLOR, D3D12_BLEND_ZERO);
public static D3D12_BLEND_DESC PREMULTIPLIED => Create(D3D12_BLEND_ONE, D3D12_BLEND_INV_SRC_ALPHA);
public static D3D12_BLEND_DESC NON_PREMULTIPLIED => Create(D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_INV_SRC_ALPHA);
public static D3D12_BLEND_DESC Create(D3D12_BLEND srcBlend, D3D12_BLEND destBlend)
@@ -149,15 +151,3 @@ internal static class D3D12_DEPTH_STENCILOP_DESC_Extensions
}
}
}
internal unsafe static class D3D12MA_Allocation_Extensions
{
extension(ref readonly D3D12MA_Allocation allocation)
{
public bool IsNull => allocation.GetResource() == null
&& allocation.GetHeap() == null
&& allocation.GetSize() == 0;
public bool IsNotNull => !allocation.IsNull;
}
}

View File

@@ -1,34 +0,0 @@
using Ghost.Core;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Ghost.Graphics.Data;
internal struct CBufferCache : IDisposable
{
public UnsafeArray<byte> CpuData
{
get;
set;
}
public Handle<GraphicsBuffer> GpuResource
{
get;
}
private readonly uint _alignedSize;
internal unsafe CBufferCache(Handle<GraphicsBuffer> buffer, uint bufferSize)
{
_alignedSize = (bufferSize + 255u) & ~255u;
CpuData = new((int)_alignedSize, Allocator.Persistent);
GpuResource = buffer;
}
public readonly void Dispose()
{
CpuData.Dispose();
}
}

View File

@@ -1,23 +1,93 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Numerics;
using Misaki.HighPerformance.Mathematics;
using System.Runtime.CompilerServices;
namespace Ghost.Graphics.Data;
public struct Material : IHandleType
internal struct CBufferCache : IResourceReleasable
{
internal CBufferCache _materialPropertiesCache;
private UnsafeArray<byte> _cpuData;
private Handle<GraphicsBuffer> _gpuResource;
private uint _alignedSize;
public Identifier<Shader> Shader
public readonly UnsafeArray<byte> CpuData => _cpuData;
public readonly Handle<GraphicsBuffer> GpuResource => _gpuResource;
public readonly uint AlignedSize => _alignedSize;
public unsafe CBufferCache(IResourceAllocator allocator, uint bufferSize)
{
get; internal set;
_alignedSize = (bufferSize + 255u) & ~255u;
_cpuData = new((int)AlignedSize, Allocator.Persistent);
var desc = new BufferDesc
{
Size = bufferSize,
Usage = BufferUsage.Constant,
MemoryType = ResourceMemoryType.Default,
};
_gpuResource = allocator.CreateBuffer(ref desc);
}
internal readonly void Dispose()
public void ReleaseResource(IResourceDatabase database)
{
_cpuData.Dispose();
database.ReleaseResource(GpuResource.AsResource());
_gpuResource = Handle<GraphicsBuffer>.Invalid;
_alignedSize = 0;
}
}
public struct Material : IResourceReleasable, IHandleType
{
private Identifier<Shader> _shader;
private UnsafeArray<CBufferCache> _materialPropertiesCache; // One per shader pass
public readonly Identifier<Shader> Shader => _shader;
public Material(Identifier<Shader> shader, IResourceAllocator allocator, IResourceDatabase database)
{
SetShader(shader, allocator, database);
}
internal ref CBufferCache GetPassCache(int passIndex)
{
return ref _materialPropertiesCache[passIndex];
}
public void SetShader(Identifier<Shader> shaderId, IResourceAllocator allocator, IResourceDatabase database)
{
if (!shaderId.IsValid)
{
throw new ArgumentException("Shader ID is invalid.");
}
_shader = shaderId;
var shader = database.GetShaderReference(shaderId);
_materialPropertiesCache = new UnsafeArray<CBufferCache>(shader.PassCount, Allocator.Persistent);
for (var i = 0; i < shader.PassCount; i++)
{
var pass = database.GetShaderPass(shader.GetPassKey(i));
var cbufferInfo = pass.PassPropertyInfo;
_materialPropertiesCache[i] = new CBufferCache(allocator, cbufferInfo.Size);
}
}
void IResourceReleasable.ReleaseResource(IResourceDatabase database)
{
foreach (var cache in _materialPropertiesCache)
{
cache.ReleaseResource(database);
}
_materialPropertiesCache.Dispose();
}
}
@@ -25,46 +95,35 @@ public struct Material : IHandleType
public ref struct MaterialAccessor
{
private ref Material _materialData;
private ref Shader _shader;
private readonly ref Shader _shader;
private readonly IResourceDatabase _resourceDatabase;
internal MaterialAccessor(ref Material materialData, IResourceDatabase resourceDatabase)
public MaterialAccessor(Handle<Material> material, IResourceDatabase resourceDatabase)
{
_resourceDatabase = resourceDatabase;
_materialData = ref materialData;
_shader = ref resourceDatabase.GetShaderReference(materialData.Shader);
_materialData = ref resourceDatabase.GetMaterialReference(material);
_shader = ref resourceDatabase.GetShaderReference(_materialData.Shader);
}
private readonly unsafe void WriteToCache<T>(int propertyId, in T value)
private readonly unsafe void WriteToCache<T>(string propertyName, in T value)
where T : unmanaged
{
if (propertyId == -1)
foreach (var index in _shader.GetPropertyPassIndices(propertyName))
{
throw new ArgumentException("Property ID is invalid.");
var passKey = _shader.GetPassKey(index);
var pass = _resourceDatabase.GetShaderPass(passKey);
var propertyInfo = pass.GetPropertyInfo(propertyName);
if (propertyInfo.Size != sizeof(T))
{
throw new ArgumentException($"Property '{propertyName}' has a size mismatch. Expected {propertyInfo.Size} bytes, but got {sizeof(T)} bytes.");
}
var propInfo = _shader.Properties[propertyId];
if (propInfo.Size != sizeof(T))
{
throw new ArgumentException($"Property '{propertyId}' has a size mismatch. Expected {propInfo.Size} bytes, but got {sizeof(T)} bytes.");
ref var cache = ref _materialData.GetPassCache(index);
Unsafe.WriteUnaligned(ref cache.CpuData[propertyInfo.ByteOffset], value);
}
var cache = _materialData._materialPropertiesCache[propInfo.CBufferIndex];
Unsafe.WriteUnaligned(ref cache.CpuData[(int)propInfo.ByteOffset], value);
}
/// <summary>
/// Sets a float property in the material's constant buffer.
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetFloat(int propertyId, in float value)
{
WriteToCache(propertyId, in value);
}
/// <summary>
@@ -75,18 +134,7 @@ public ref struct MaterialAccessor
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetFloat(string propertyName, in float value)
{
SetFloat(_shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a uint property in the material's constant buffer (useful for texture indices).
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetUInt(int propertyId, in uint value)
{
WriteToCache(propertyId, in value);
WriteToCache(propertyName, in value);
}
/// <summary>
@@ -97,18 +145,7 @@ public ref struct MaterialAccessor
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetUInt(string propertyName, in uint value)
{
SetUInt(_shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a Vector property in the material's constant buffer.
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetVector(int propertyId, in Vector4 value)
{
WriteToCache(propertyId, in value);
WriteToCache(propertyName, in value);
}
/// <summary>
@@ -117,20 +154,9 @@ public ref struct MaterialAccessor
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetVector(string propertyName, in Vector4 value)
public readonly void SetVector(string propertyName, in float4 value)
{
SetVector(_shader.GetPropertyId(propertyName), in value);
}
/// <summary>
/// Sets a Matrix property in the material's constant buffer.
/// </summary>
/// <param name="propertyId">The ID of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetMatrix(int propertyId, in Matrix4x4 value)
{
WriteToCache(propertyId, in value);
WriteToCache(propertyName, in value);
}
/// <summary>
@@ -139,9 +165,9 @@ public ref struct MaterialAccessor
/// <param name="propertyName">The name of the property to set.</param>
/// <param name="value">The value to set for the property.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetMatrix(string propertyName, in Matrix4x4 value)
public readonly void SetMatrix(string propertyName, in float4x4 value)
{
SetMatrix(_shader.GetPropertyId(propertyName), in value);
WriteToCache(propertyName, in value);
}
/// <summary>
@@ -186,8 +212,9 @@ public ref struct MaterialAccessor
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void UploadMaterialData(ICommandBuffer cmb)
{
foreach (var cache in _materialData._materialPropertiesCache)
for (var i = 0; i < _shader.PassCount; i++)
{
ref var cache = ref _materialData.GetPassCache(i);
cmb.Upload(cache.GpuResource, cache.CpuData.AsSpan());
}
}

View File

@@ -1,4 +1,5 @@
using Ghost.Core;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
@@ -7,7 +8,7 @@ using Misaki.HighPerformance.Mathematics.Geometry;
namespace Ghost.Graphics.Data;
public struct Mesh : IHandleType
public struct Mesh : IResourceReleasable, IHandleType
{
public UnsafeList<Vertex> vertices;
public UnsafeList<uint> indices;
@@ -38,6 +39,14 @@ public struct Mesh : IHandleType
vertices.Dispose();
indices.Dispose();
}
void IResourceReleasable.ReleaseResource(IResourceDatabase database)
{
ReleaseCpuResources();
database.ReleaseResource(vertexBuffer.AsResource());
database.ReleaseResource(indexBuffer.AsResource());
}
}
public static class MeshExtension

View File

@@ -134,7 +134,7 @@ public unsafe readonly ref struct RenderingContext
public void RenderMesh(Handle<Mesh> mesh, Handle<Material> material)
{
_cmd.DrawMesh(mesh, material);
//_cmd.DrawMesh(mesh, material);
}
public void ExecuteCopyCommands()

View File

@@ -1,11 +1,13 @@
using Ghost.Core;
using Ghost.Core.Contracts;
using Ghost.Core.Graphics;
using Misaki.HighPerformance.LowLevel.Buffer;
using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.InteropServices;
namespace Ghost.Graphics.Data;
internal readonly struct TextureInfo
public readonly struct TextureInfo
{
public uint RegisterSlot
{
@@ -18,7 +20,7 @@ internal readonly struct TextureInfo
}
}
internal readonly struct PropertyInfo
public readonly struct PropertyInfo
{
public uint CBufferIndex
{
@@ -36,7 +38,7 @@ internal readonly struct PropertyInfo
}
}
internal readonly struct CBufferInfo
public readonly struct CBufferInfo
{
public uint Size
{
@@ -49,65 +51,117 @@ internal readonly struct CBufferInfo
}
}
internal struct ShaderPass
public readonly unsafe struct ShaderPass
{
// NOTE: This is for per pass cbuffer only. Global, per view, and per mesh cbuffers are fixed.
private readonly Dictionary<string, int> _propertyLookup;
private readonly UnsafeList<PropertyInfo> _properties;
internal readonly CBufferInfo PassPropertyInfo
{
get;
}
/// <summary>
/// A representation of a GPU shader, including its metadata about its resources.
/// </summary>
// TODO: Multi pass and keyword support
public struct Shader : IIdentifierType
public ShaderPass(CBufferInfo info, UnsafeList<PropertyInfo> properties, Dictionary<string, int> propertyNameToIdMap)
{
private readonly ShaderDescriptor _descriptor;
private CBufferInfo _perMaterialBufferInfo;
private UnsafeList<PropertyInfo> _properties;
private Dictionary<string, int> _propertyNameToIdMap;
private bool _disposed;
internal CBufferInfo PerMaterialBufferInfo
{
readonly get => _perMaterialBufferInfo;
set => _perMaterialBufferInfo = value;
PassPropertyInfo = info;
_properties = properties;
_propertyLookup = propertyNameToIdMap;
}
internal readonly UnsafeList<PropertyInfo> Properties => _properties;
internal readonly Dictionary<string, int> PropertyNameToIdMap => _propertyNameToIdMap;
public Shader(ShaderDescriptor descriptor)
{
_descriptor = descriptor;
_properties = new(8, Allocator.Persistent);
_propertyNameToIdMap = new(8);
_disposed = false;
}
/// <summary>
/// Gets a unique, stable ID for a shader property.
/// </summary>
/// <param name="propertyName">The name of the shader property.</param>
/// <returns>The integer ID of the property, or -1 if not found.</returns>
public readonly int GetPropertyId(string propertyName)
{
return _propertyNameToIdMap.TryGetValue(propertyName, out var id) ? id : -1;
return _propertyLookup.TryGetValue(propertyName, out var id) ? id : -1;
}
internal void Dispose()
public readonly PropertyInfo GetPropertyInfo(int id)
{
if (_disposed)
return _properties[id];
}
public readonly PropertyInfo GetPropertyInfo(string propertyName)
{
return;
}
_properties.Dispose();
_propertyNameToIdMap.Clear();
_propertyNameToIdMap = null!;
_disposed = true;
return _properties[GetPropertyId(propertyName)];
}
}
/// <summary>
/// A representation of a GPU shader, including all the passes it contains.
/// </summary>
public readonly struct Shader : IResourceReleasable, IIdentifierType
{
private readonly ShaderPassKey[] _passIDs;
private readonly Dictionary<string, int> _passLookup; // pass name to index
private readonly Dictionary<string, List<int>> _propertyLookup; // property name to pass index (property name to list of pass indices that contain the property)
public int PassCount => _passIDs.Length;
internal Shader(ShaderDescriptor descriptor)
{
_passIDs = new ShaderPassKey[descriptor.passes.Count];
_passLookup = new(descriptor.passes.Count);
_propertyLookup = new(descriptor.passes.Count);
for (var i = 0; i < descriptor.passes.Count; i++)
{
var pass = descriptor.passes[i];
var passKey = new ShaderPassKey(pass.Identifier);
_passIDs[i] = passKey;
_passLookup[pass.Name] = i;
if (pass is FullPassDescriptor fullPass)
{
if (fullPass.properties == null)
{
continue;
}
foreach (var property in fullPass.properties)
{
ref var passIndices = ref CollectionsMarshal.GetValueRefOrAddDefault(_propertyLookup, property.name, out var exists);
if (!exists || passIndices == null)
{
passIndices = new List<int>();
}
passIndices.Add(i);
}
}
// TODO: handle inherited passes
}
}
public readonly ShaderPassKey GetPassKey(int index)
{
return _passIDs[index];
}
public readonly bool TryGetPassKey(string passName, out ShaderPassKey? passID)
{
var index = _passLookup.GetValueOrDefault(passName, -1);
if (index == -1)
{
passID = new(0);
return false;
}
passID = _passIDs[index];
return true;
}
public readonly IReadOnlyCollection<int> GetPropertyPassIndices(string propertyName)
{
if (_propertyLookup.TryGetValue(propertyName, out var passIndices))
{
return passIndices;
}
return Array.Empty<int>();
}
void IResourceReleasable.ReleaseResource(IResourceDatabase database)
{
// Should we do something here?
}
}

View File

@@ -13,11 +13,11 @@ public struct Vertex
public const DXGI_FORMAT ALIGNED_FORMAT = DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT;
public const int COUNT = 5;
public static readonly FixedString32 position = new("POSITION");
public static readonly FixedString32 normal = new("NORMAL");
public static readonly FixedString32 tangent = new("TANGENT");
public static readonly FixedString32 uv = new("TEXCOORD");
public static readonly FixedString32 color = new("COLOR");
public static readonly FixedText32 position = new("POSITION");
public static readonly FixedText32 normal = new("NORMAL");
public static readonly FixedText32 tangent = new("TANGENT");
public static readonly FixedText32 uv = new("TEXCOORD");
public static readonly FixedText32 color = new("COLOR");
}
public float4 position;

View File

@@ -0,0 +1,603 @@
using Misaki.HighPerformance.Utilities;
using System.IO.Hashing;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using TerraFX.Interop.DirectX;
namespace Ghost.Graphics.RHI;
public readonly struct ShaderPassKey
{
public readonly ulong value;
public ShaderPassKey(ulong value)
{
this.value = value;
}
public ShaderPassKey(string passId)
{
var passIdSpan = passId.AsSpan();
value = XxHash3.HashToUInt64(MemoryMarshal.AsBytes(passIdSpan));
}
public override string ToString()
{
return value.ToString("X16");
}
public override int GetHashCode()
{
return value.GetHashCode();
}
}
public readonly struct GraphicsPipelineKey
{
public readonly ulong value;
public GraphicsPipelineKey(ulong value)
{
this.value = value;
}
public override string ToString()
{
return value.ToString("X16");
}
public override int GetHashCode()
{
return value.GetHashCode();
}
}
internal struct GraphicsPipelineHash
{
[InlineArray(8)]
public struct rtv_array
{
public TextureFormat rtvFormats;
}
public rtv_array rtvFormats;
public ShaderPassKey id;
public uint rtvCount;
public TextureFormat dsvFormat;
// Do we need to store blend state?
// TODO: Variants
public readonly GraphicsPipelineKey GetKey()
{
Span<ulong> data = stackalloc ulong[3 + 8];
data[0] = id.value;
data[1] = rtvCount;
data[2] = (ulong)dsvFormat;
for (int i = 0; i < 8; i++)
{
data[3 + i] = (ulong)rtvFormats[i];
}
var bytes = MemoryMarshal.AsBytes(data);
return new GraphicsPipelineKey(XxHash3.HashToUInt64(bytes));
}
}
public struct ViewportDesc
{
public float x;
public float y;
public float width;
public float height;
public float minDepth;
public float maxDepth;
}
public struct RectDesc
{
public uint left;
public uint top;
public uint right;
public uint bottom;
}
public struct SubResourceData
{
public unsafe void* pData;
public nint rowPitch;
public nint slicePitch;
}
[StructLayout(LayoutKind.Explicit)]
public struct ResourceDesc
{
[FieldOffset(0)]
public TextureDesc textureDescription;
[FieldOffset(0)]
public BufferDesc bufferDescription;
public static ResourceDesc Buffer(BufferDesc desc)
{
return new ResourceDesc
{
bufferDescription = desc
};
}
public static ResourceDesc Texture(TextureDesc desc)
{
return new ResourceDesc
{
textureDescription = desc
};
}
internal static ResourceDesc FromD3D12(D3D12_RESOURCE_DESC desc)
{
if (desc.Dimension == D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_BUFFER)
{
return Buffer(new BufferDesc
{
Size = (uint)desc.Width,
Stride = 0,
Usage = BufferUsage.None,
MemoryType = ResourceMemoryType.Default
});
}
else
{
return Texture(new TextureDesc
{
Width = (uint)desc.Width,
Height = desc.Height,
Slice = desc.DepthOrArraySize,
Format = desc.Format.ToTextureFormat(),
Dimension = desc.Dimension.ToTextureDimension(),
MipLevels = desc.MipLevels,
Usage = TextureUsage.None,
});
}
}
}
/// <summary>
/// Render target description
/// Supports either color OR depth rendering, not both
/// </summary>
public struct RenderTargetDesc
{
/// <summary>
/// Width of the render target
/// </summary>
public uint Width
{
get;
set;
}
/// <summary>
/// Height of the render target
/// </summary>
public uint Height
{
get;
set;
}
/// <summary>
/// Slice of the render target
/// </summary>
public uint Slice
{
get;
set;
}
/// <summary>
/// Type of render target
/// </summary>
public RenderTargetType Type
{
get;
set;
}
/// <summary>
/// Target texture format
/// </summary>
public TextureFormat Format
{
get;
set;
}
/// <summary>
/// Texture dimension
/// </summary>
public TextureDimension Dimension
{
get;
set;
}
/// <summary>
/// Creation flags for the render target
/// </summary>
public RenderTargetCreationFlags CreationFlags
{
get;
set;
}
/// <summary>
/// Number of mip levels. 0 to generate full mip chain
/// </summary>
public uint MipLevels
{
get;
set;
}
/// <summary>
/// Number of samples for MSAA
/// </summary>
public uint SampleCount
{
get;
set;
}
/// <summary>
/// Creates a color render target
/// </summary>
public static RenderTargetDesc Color(uint width, uint height, uint slice = 1,
TextureFormat format = TextureFormat.R8G8B8A8_UNorm, TextureDimension dimension = TextureDimension.Texture2D,
RenderTargetCreationFlags creationFlags = RenderTargetCreationFlags.AllowUAV | RenderTargetCreationFlags.DynamicallyResolution | RenderTargetCreationFlags.GenerateMips,
uint mipLevels = 0u, uint sampleCount = 1)
{
return new RenderTargetDesc
{
Width = width,
Height = height,
Slice = slice,
Type = RenderTargetType.Color,
Format = format,
Dimension = dimension,
CreationFlags = creationFlags,
MipLevels = mipLevels,
SampleCount = sampleCount
};
}
/// <summary>
/// Creates a depth render target
/// </summary>
public static RenderTargetDesc Depth(uint width, uint height, uint slice = 1,
TextureFormat format = TextureFormat.D24_UNorm_S8_UInt, TextureDimension dimension = TextureDimension.Texture2D,
RenderTargetCreationFlags creationFlags = RenderTargetCreationFlags.AllowUAV | RenderTargetCreationFlags.DynamicallyResolution,
uint mipLevels = 0u, uint sampleCount = 1)
{
return new RenderTargetDesc
{
Width = width,
Height = height,
Slice = slice,
Type = RenderTargetType.Depth,
Format = format,
Dimension = dimension,
CreationFlags = creationFlags,
MipLevels = mipLevels,
SampleCount = sampleCount
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TextureDesc ToTextureDescripton(RenderTargetDesc desc)
{
var usage = desc.Type == RenderTargetType.Color ? TextureUsage.RenderTarget : TextureUsage.DepthStencil;
if (desc.CreationFlags.HasFlag(RenderTargetCreationFlags.AllowUAV))
{
usage |= TextureUsage.UnorderedAccess;
}
return new TextureDesc
{
Width = desc.Width,
Height = desc.Height,
Slice = desc.Slice,
Format = desc.Format,
Dimension = desc.Dimension,
MipLevels = desc.MipLevels,
Usage = usage,
};
}
}
/// <summary>
/// Texture description
/// </summary>
public struct TextureDesc
{
/// <summary>
/// Width of the texture
/// </summary>
public uint Width
{
get;
set;
}
/// <summary>
/// Height of the texture
/// </summary>
public uint Height
{
get;
set;
}
/// <summary>
/// Slice of the texture
/// </summary>
public uint Slice
{
get;
set;
}
/// <summary>
/// Texture format
/// </summary>
public TextureFormat Format
{
get;
set;
}
/// <summary>
/// Texture dimension
/// </summary>
public TextureDimension Dimension
{
get;
set;
}
/// <summary>
/// Number of mip levels. 0 to generate full mip chain
/// </summary>
public uint MipLevels
{
get;
set;
}
/// <summary>
/// Texture usage flags
/// </summary>
public TextureUsage Usage
{
get;
set;
}
}
/// <summary>
/// Buffer description
/// </summary>
public struct BufferDesc
{
/// <summary>
/// Size of the buffer in bytes
/// </summary>
public ulong Size
{
get;
set;
}
public uint Stride
{
get;
set;
}
/// <summary>
/// Buffer usage flags
/// </summary>
public BufferUsage Usage
{
get;
set;
}
/// <summary>
/// Memory type for the buffer
/// </summary>
public ResourceMemoryType MemoryType
{
get;
set;
}
}
public static class TextureDimensionExtension
{
public static TextureDimension ToTextureDimension(this D3D12_RESOURCE_DIMENSION dimension)
{
return dimension switch
{
D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE1D => TextureDimension.Texture2D,
D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE2D => TextureDimension.Texture2D,
D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE3D => TextureDimension.Texture3D,
_ => TextureDimension.Unknown,
};
}
}
[Flags]
public enum ResourceState
{
Common = 0,
VertexAndConstantBuffer = 1 << 0,
IndexBuffer = 1 << 1,
RenderTarget = 1 << 2,
UnorderedAccess = 1 << 3,
DepthWrite = 1 << 4,
DepthRead = 1 << 5,
PixelShaderResource = 1 << 6,
CopyDest = 1 << 7,
CopySource = 1 << 8,
GenericRead = 1 << 9,
IndirectArgument = 1 << 10,
Present = 0,
}
public enum CommandQueueType
{
Graphics,
Compute,
Copy
}
public enum CommandBufferType
{
Graphics,
Compute,
Copy
}
public enum PipelineType
{
Graphics,
Compute
}
[Flags]
public enum RenderTargetCreationFlags
{
None = 0,
AllowUAV = 1 << 0,
AllowMSAA = 1 << 1,
DynamicallyResolution = 1 << 2,
GenerateMips = 1 << 3
}
public enum ResourceMemoryType
{
Default, // GPU memory
Upload, // CPU-to-GPU memory
Readback // GPU-to-CPU memory
}
[Flags]
public enum TextureUsage
{
None = 0,
ShaderResource = 1 << 0,
RenderTarget = 1 << 1,
DepthStencil = 1 << 2,
UnorderedAccess = 1 << 3
}
public enum TextureDimension
{
Unknown = -1,
None = 0,
Texture2D = 1,
Texture3D = 2,
TextureCube = 3,
Texture2DArray = 4,
TextureCubeArray = 5
}
public enum RenderTargetType
{
Color,
Depth
}
// TODO: Support compressed formats (BCn, ASTC, ETC2, etc)
public enum TextureFormat
{
Unknown,
R8G8B8A8_UNorm,
B8G8R8A8_UNorm,
R16G16B16A16_Float,
R32G32B32A32_Float,
D24_UNorm_S8_UInt,
D32_Float
}
[Flags]
public enum BufferUsage
{
None = 0,
Vertex = 1 << 0,
Index = 1 << 1,
Constant = 1 << 2,
Structured = 1 << 3,
Raw = 1 << 4,
Upload = 1 << 5,
Readback = 1 << 6,
IndirectArgument = 1 << 7,
ShaderResource = Vertex | Index | Constant
}
public enum IndexType
{
UInt16,
UInt32
}
// Shader compiler
internal ref struct CompilerConfig
{
public ReadOnlySpan<string> includes;
public ReadOnlySpan<string> defines;
public string shaderPath;
public string entryPoint;
public ShaderStage stage;
public CompilerTier tier;
public CompilerOptimizeLevel optimizeLevel;
public CompilerOption options;
}
internal enum CompilerTier
{
Tier0,
Tier1,
Tier2
}
internal enum CompilerOptimizeLevel
{
O0,
O1,
O2,
O3
}
[Flags]
internal enum CompilerOption
{
None = 0,
KeepDebugInfo = 1 << 0,
KeepReflections = 1 << 1,
WarnAsError = 1 << 2
}
internal enum ShaderStage
{
TaskShader,
MeshShader,
PixelShader,
ComputeShader
}

View File

@@ -131,51 +131,6 @@ public interface ICommandBuffer : IDisposable
public void CopyBuffer(Handle<GraphicsBuffer> dest, Handle<GraphicsBuffer> src, ulong destOffset = 0, ulong srcOffset = 0, ulong numBytes = 0);
}
/// <summary>
/// Viewport description
/// </summary>
public struct ViewportDesc
{
public float x;
public float y;
public float width;
public float height;
public float minDepth;
public float maxDepth;
}
/// <summary>
/// Rectangle description
/// </summary>
public struct RectDesc
{
public uint left;
public uint top;
public uint right;
public uint bottom;
}
/// <summary>
/// Resource states
/// </summary>
[Flags]
public enum ResourceState
{
Common = 0,
VertexAndConstantBuffer = 1 << 0,
IndexBuffer = 1 << 1,
RenderTarget = 1 << 2,
UnorderedAccess = 1 << 3,
DepthWrite = 1 << 4,
DepthRead = 1 << 5,
PixelShaderResource = 1 << 6,
CopyDest = 1 << 7,
CopySource = 1 << 8,
GenericRead = 1 << 9,
IndirectArgument = 1 << 10,
Present = 0,
}
internal static class ResourceStateExtensions
{
public static D3D12_RESOURCE_STATES ToD3D12States(this ResourceState state)
@@ -196,11 +151,3 @@ internal static class ResourceStateExtensions
};
}
}
public struct SubResourceData
{
public unsafe void* pData;
public nint rowPitch;
public nint slicePitch;
}

View File

@@ -49,13 +49,3 @@ public interface ICommandQueue : IDisposable
/// </summary>
public void WaitIdle();
}
/// <summary>
/// Command queue types
/// </summary>
public enum CommandQueueType
{
Graphics,
Compute,
Copy
}

View File

@@ -1,5 +1,4 @@
using Ghost.Core;
using Ghost.Graphics.Data;
using Ghost.Core.Graphics;
namespace Ghost.Graphics.RHI;
@@ -16,9 +15,8 @@ public interface IShaderPipeline
public interface IPipelineLibrary
{
public void CompileShader(Identifier<Shader> id, string shaderPath);
public void PreCookPipelineState();
public IShaderPipeline GetShaderPipeline(Identifier<Shader> id);
void CompilePass(IPassDescriptor descriptor);
void CompileShader(ShaderDescriptor descriptor);
void PreCookPipelineState();
void SaveLibraryToDisk(string filePath);
}

View File

@@ -29,13 +29,3 @@ public interface IRenderDevice : IDisposable
get;
}
}
/// <summary>
/// Command buffer types
/// </summary>
public enum CommandBufferType
{
Graphics,
Compute,
Copy
}

View File

@@ -1,7 +1,3 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using TerraFX.Interop.DirectX;
namespace Ghost.Graphics.RHI;
/// <summary>
@@ -17,390 +13,3 @@ public interface IRootSignature : IDisposable
get; set;
}
}
/// <summary>
/// Pipeline types
/// </summary>
public enum PipelineType
{
Graphics,
Compute
}
[StructLayout(LayoutKind.Explicit)]
public struct ResourceDesc
{
[FieldOffset(0)]
public TextureDesc textureDescription;
[FieldOffset(0)]
public BufferDesc bufferDescription;
public static ResourceDesc Buffer(BufferDesc desc)
{
return new ResourceDesc
{
bufferDescription = desc
};
}
public static ResourceDesc Texture(TextureDesc desc)
{
return new ResourceDesc
{
textureDescription = desc
};
}
internal static ResourceDesc FromD3D12(D3D12_RESOURCE_DESC desc)
{
if (desc.Dimension == D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_BUFFER)
{
return Buffer(new BufferDesc
{
Size = (uint)desc.Width,
Stride = 0,
Usage = BufferUsage.None,
MemoryType = MemoryType.Default
});
}
else
{
return Texture(new TextureDesc
{
Width = (uint)desc.Width,
Height = desc.Height,
Slice = desc.DepthOrArraySize,
Format = desc.Format.ToTextureFormat(),
Dimension = desc.Dimension.ToTextureDimension(),
MipLevels = desc.MipLevels,
Usage = TextureUsage.None,
});
}
}
}
/// <summary>
/// Render target description
/// Supports either color OR depth rendering, not both
/// </summary>
public struct RenderTargetDesc
{
/// <summary>
/// Width of the render target
/// </summary>
public uint Width
{
get;
set;
}
/// <summary>
/// Height of the render target
/// </summary>
public uint Height
{
get;
set;
}
/// <summary>
/// Slice of the render target
/// </summary>
public uint Slice
{
get;
set;
}
/// <summary>
/// Type of render target
/// </summary>
public RenderTargetType Type
{
get;
set;
}
/// <summary>
/// Target texture format
/// </summary>
public TextureFormat Format
{
get;
set;
}
/// <summary>
/// Texture dimension
/// </summary>
public TextureDimension Dimension
{
get;
set;
}
/// <summary>
/// Creation flags for the render target
/// </summary>
public RenderTargetCreationFlags CreationFlags
{
get;
set;
}
/// <summary>
/// Number of mip levels. 0 to generate full mip chain
/// </summary>
public uint MipLevels
{
get;
set;
}
/// <summary>
/// Number of samples for MSAA
/// </summary>
public uint SampleCount
{
get;
set;
}
/// <summary>
/// Creates a color render target
/// </summary>
public static RenderTargetDesc Color(uint width, uint height, uint slice = 1,
TextureFormat format = TextureFormat.R8G8B8A8_UNorm, TextureDimension dimension = TextureDimension.Texture2D,
RenderTargetCreationFlags creationFlags = RenderTargetCreationFlags.AllowUAV | RenderTargetCreationFlags.DynamicallyResolution | RenderTargetCreationFlags.GenerateMips,
uint mipLevels = 0u, uint sampleCount = 1)
{
return new RenderTargetDesc
{
Width = width,
Height = height,
Slice = slice,
Type = RenderTargetType.Color,
Format = format,
Dimension = dimension,
CreationFlags = creationFlags,
MipLevels = mipLevels,
SampleCount = sampleCount
};
}
/// <summary>
/// Creates a depth render target
/// </summary>
public static RenderTargetDesc Depth(uint width, uint height, uint slice = 1,
TextureFormat format = TextureFormat.D24_UNorm_S8_UInt, TextureDimension dimension = TextureDimension.Texture2D,
RenderTargetCreationFlags creationFlags = RenderTargetCreationFlags.AllowUAV | RenderTargetCreationFlags.DynamicallyResolution,
uint mipLevels = 0u, uint sampleCount = 1)
{
return new RenderTargetDesc
{
Width = width,
Height = height,
Slice = slice,
Type = RenderTargetType.Depth,
Format = format,
Dimension = dimension,
CreationFlags = creationFlags,
MipLevels = mipLevels,
SampleCount = sampleCount
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TextureDesc ToTextureDescripton(RenderTargetDesc desc)
{
var usage = desc.Type == RenderTargetType.Color ? TextureUsage.RenderTarget : TextureUsage.DepthStencil;
if (desc.CreationFlags.HasFlag(RenderTargetCreationFlags.AllowUAV))
{
usage |= TextureUsage.UnorderedAccess;
}
return new TextureDesc
{
Width = desc.Width,
Height = desc.Height,
Slice = desc.Slice,
Format = desc.Format,
Dimension = desc.Dimension,
MipLevels = desc.MipLevels,
Usage = usage,
};
}
}
/// <summary>
/// Texture description
/// </summary>
public struct TextureDesc
{
/// <summary>
/// Width of the texture
/// </summary>
public uint Width
{
get;
set;
}
/// <summary>
/// Height of the texture
/// </summary>
public uint Height
{
get;
set;
}
/// <summary>
/// Slice of the texture
/// </summary>
public uint Slice
{
get;
set;
}
/// <summary>
/// Texture format
/// </summary>
public TextureFormat Format
{
get;
set;
}
/// <summary>
/// Texture dimension
/// </summary>
public TextureDimension Dimension
{
get;
set;
}
/// <summary>
/// Number of mip levels. 0 to generate full mip chain
/// </summary>
public uint MipLevels
{
get;
set;
}
/// <summary>
/// Texture usage flags
/// </summary>
public TextureUsage Usage
{
get;
set;
}
}
/// <summary>
/// Buffer description
/// </summary>
public struct BufferDesc
{
/// <summary>
/// Size of the buffer in bytes
/// </summary>
public uint Size
{
get;
set;
}
public uint Stride
{
get;
set;
}
/// <summary>
/// Buffer usage flags
/// </summary>
public BufferUsage Usage
{
get;
set;
}
/// <summary>
/// Memory type for the buffer
/// </summary>
public MemoryType MemoryType
{
get;
set;
}
}
/// <summary>
/// Texture usage flags
/// </summary>
[Flags]
public enum TextureUsage
{
None = 0,
ShaderResource = 1 << 0,
RenderTarget = 1 << 1,
DepthStencil = 1 << 2,
UnorderedAccess = 1 << 3
}
/// <summary>
/// Dimensions of the texture
/// </summary>
public enum TextureDimension
{
Unknown = -1,
None = 0,
Texture2D = 1,
Texture3D = 2,
TextureCube = 3,
Texture2DArray = 4,
TextureCubeArray = 5
}
public static class TextureDimensionExtension
{
public static TextureDimension ToTextureDimension(this D3D12_RESOURCE_DIMENSION dimension)
{
return dimension switch
{
D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE1D => TextureDimension.Texture2D,
D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE2D => TextureDimension.Texture2D,
D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE3D => TextureDimension.Texture3D,
_ => TextureDimension.Unknown,
};
}
}
/// <summary>
/// Render target creation flags
/// </summary>
[Flags]
public enum RenderTargetCreationFlags
{
None = 0,
AllowUAV = 1 << 0,
AllowMSAA = 1 << 1,
DynamicallyResolution = 1 << 2,
GenerateMips = 1 << 3
}
/// <summary>
/// Memory types for resources
/// </summary>
public enum MemoryType
{
Default, // GPU memory
Upload, // CPU-to-GPU memory
Readback // GPU-to-CPU memory
}

View File

@@ -2,56 +2,6 @@ using TerraFX.Interop.DirectX;
namespace Ghost.Graphics.RHI;
/// <summary>
/// Type of render target
/// </summary>
public enum RenderTargetType
{
Color,
Depth
}
/// <summary>
/// Texture format enumeration
/// </summary>
// TODO: Support compressed formats (BCn, ASTC, ETC2, etc)
public enum TextureFormat
{
Unknown,
R8G8B8A8_UNorm,
B8G8R8A8_UNorm,
R16G16B16A16_Float,
R32G32B32A32_Float,
D24_UNorm_S8_UInt,
D32_Float
}
/// <summary>
/// Buffer usage flags
/// </summary>
[Flags]
public enum BufferUsage
{
None = 0,
Vertex = 1 << 0,
Index = 1 << 1,
Constant = 1 << 2,
Structured = 1 << 3,
Raw = 1 << 4,
Upload = 1 << 5,
Readback = 1 << 6,
IndirectArgument = 1 << 7,
ShaderResource = Vertex | Index | Constant
}
public enum IndexType
{
UInt16,
UInt32
}
internal static class TextureFormatExtensions
{
public static DXGI_FORMAT ToD3D12Format(this TextureFormat format)

View File

@@ -3,6 +3,14 @@ using Ghost.Graphics.Data;
namespace Ghost.Graphics.RHI;
public interface IResourceReleasable
{
/// <summary>
/// A method to release GPU resources.
/// </summary>
void ReleaseResource(IResourceDatabase database);
}
public interface IResourceDatabase
{
/// <summary>
@@ -12,7 +20,7 @@ public interface IResourceDatabase
/// <param name="resourcePtr">A pointer to the external unmanaged resource to be imported. Must remain valid for the duration of the resource's usage.</param>
/// <param name="initialState">The initial state to assign to the imported resource.</param>
/// <returns>A handle representing the imported resource, which can be used for subsequent operations.</returns>
public unsafe Handle<GPUResource> ImportExternalResource<T>(T resourcePtr, ResourceState initialState)
unsafe Handle<GPUResource> ImportExternalResource<T>(T resourcePtr, ResourceState initialState)
where T : unmanaged;
/// <summary>
@@ -20,113 +28,129 @@ public interface IResourceDatabase
/// </summary>
/// <param name="handle">The handle that uniquely identifies the resource whose state is to be retrieved. Must not be null.</param>
/// <returns>A ResourceState value representing the current state of the resource associated with the specified handle.</returns>
public ResourceState GetResourceState(Handle<GPUResource> handle);
ResourceState GetResourceState(Handle<GPUResource> handle);
/// <summary>
/// Sets the state of the specified resource handle to the given value.
/// </summary>
/// <param name="handle">The handle that identifies the resource whose state will be updated. Cannot be null.</param>
/// <param name="state">The new state to assign to the resource represented by <paramref name="handle"/>.</param>
public void SetResourceState(Handle<GPUResource> handle, ResourceState state);
void SetResourceState(Handle<GPUResource> handle, ResourceState state);
/// <summary>
/// Retrieves the description of a GPU resource associated with the specified handle.
/// </summary>
/// <param name="handle">A handle that identifies the GPU resource for which to obtain the description. Must reference a valid resource.</param>
/// <returns>A ResourceDesc structure containing details about the specified GPU resource.</returns>
public ResourceDesc GetResourceDescription(Handle<GPUResource> handle);
ResourceDesc GetResourceDescription(Handle<GPUResource> handle);
/// <summary>
/// Retrieves the bindless index associated with the specified GPU resource handle.
/// </summary>
/// <param name="handle">A handle to the GPU resource for which to obtain the bindless index. Must reference a valid, currently registered resource.</param>
/// <returns>The bindless index corresponding to the specified GPU resource handle. -1 if the resource does not support bindless access or is not found.</returns>
public int GetBindlessIndex(Handle<GPUResource> handle);
int GetBindlessIndex(Handle<GPUResource> handle);
/// <summary>
/// Removes a resource from the database using its handle.
/// </summary>
/// <param name="handle">The handle of the resource to be removed.</param>
public void ReleaseResource(Handle<GPUResource> handle);
void ReleaseResource(Handle<GPUResource> handle);
/// <summary>
/// Adds a mesh to the resource database and returns its handle.
/// </summary>
/// <param name="mesh">The mesh data to be added to the database.</param>
/// <returns>The <see cref="Handle{Mesh}"/> representing the newly added mesh.</returns>"/>
public Handle<Mesh> AddMesh(ref readonly Mesh mesh);
Handle<Mesh> AddMesh(ref readonly Mesh mesh);
/// <summary>
/// Determines whether a mesh with the specified Handle exists.
/// </summary>
/// <param name="handle">The handle of the mesh to check for existence. Cannot be null.</param>
/// <returns>true if a mesh with the specified Handle exists; otherwise, false.</returns>
public bool HasMesh(Handle<Mesh> handle);
bool HasMesh(Handle<Mesh> handle);
/// <summary>
/// Returns a reference to the mesh associated with the specified handle.
/// </summary>
/// <param name="handle">The handle of the mesh to retrieve. Must refer to a valid mesh; otherwise, the behavior is undefined.</param>
/// <returns>A reference to the mesh corresponding to the specified handle.</returns>
public ref Mesh GetMeshReference(Handle<Mesh> handle);
ref Mesh GetMeshReference(Handle<Mesh> handle);
/// <summary>
/// Releases the mesh resource associated with the specified handle, freeing any resources held by it. Includes both CPU and GPU resources.
/// </summary>
/// <param name="handle">The handle of the mesh to release. Must refer to a mesh that was previously created and not already released.</param>
public void ReleaseMesh(Handle<Mesh> handle);
void ReleaseMesh(Handle<Mesh> handle);
/// <summary>
/// Adds a new material to the collection and returns its unique handle.
/// </summary>
/// <param name="material">The material to add. The material must be fully initialized before calling this method.</param>
/// <returns>The <see cref="Handle{Material}"/> representing the newly added material.</returns>
public Handle<Material> AddMaterial(ref readonly Material material);
Handle<Material> AddMaterial(ref readonly Material material);
/// <summary>
/// Determines whether a material with the specified handle exists in the collection.
/// </summary>
/// <param name="handle">The handle of the material to check for existence.</param>
/// <returns>true if a material with the specified handle exists; otherwise, false.</returns>
public bool HasMaterial(Handle<Material> handle);
bool HasMaterial(Handle<Material> handle);
/// <summary>
/// Gets a reference to the material associated with the specified handle.
/// </summary>
/// <param name="handle">The handle of the material to retrieve. Must refer to a valid material.</param>
/// <returns>A reference to the material corresponding to the specified handle.</returns>
public ref Material GetMaterialReference(Handle<Material> handle);
ref Material GetMaterialReference(Handle<Material> handle);
/// <summary>
/// Releases the material associated with the specified handle, making it available for reuse or disposal.
/// </summary>
/// <param name="handle">The handle of the material to release. Must refer to a material that has been previously acquired.</param>
public void ReleaseMaterial(Handle<Material> handle);
void ReleaseMaterial(Handle<Material> handle);
/// <summary>
/// Adds the specified shader to the collection and returns its unique identifier.
/// </summary>
/// <param name="shader">The shader to add. The shader is passed by read-only reference and will not be modified.</param>
/// <returns>The <see cref="Identifier{Shader}"/> representing the newly added shader.</returns>
public Identifier<Shader> AddShader(ref readonly Shader shader);
Identifier<Shader> AddShader(ref readonly Shader shader);
/// <summary>
/// Determines whether a shader with the specified identifier exists in the collection.
/// </summary>
/// <param name="id">The identifier of the shader to check for existence.</param>
/// <returns>true if a shader with the specified identifier exists; otherwise, false.</returns>
public bool HasShader(Identifier<Shader> id);
bool HasShader(Identifier<Shader> id);
/// <summary>
/// Returns a reference to the shader associated with the specified identifier.
/// </summary>
/// <param name="id">The identifier of the shader to retrieve. Must refer to a valid shader.</param>
/// <returns>A reference to the shader corresponding to the specified identifier.</returns>
public ref Shader GetShaderReference(Identifier<Shader> id);
ref Shader GetShaderReference(Identifier<Shader> id);
/// <summary>
/// Releases the shader associated with the specified identifier, freeing any resources allocated to it.
/// </summary>
/// <param name="id">The identifier of the shader to release. Must refer to a valid, previously created shader.</param>
public void ReleaseShader(Identifier<Shader> id);
void ReleaseShader(Identifier<Shader> id);
// TODO: Use xxhash3 to generate passKey from string id.
/// <summary>
/// Adds a shader pass to the collection using the specified identifier.
/// </summary>
/// <param name="passKey">The unique identifier for the shader pass.</param>
/// <param name="pass">The shader pass to add. Cannot be null.</param>
void AddShaderPass(ShaderPassKey passKey, ShaderPass pass);
/// <summary>
/// Retrieves the shader pass associated with the specified pass identifier.
/// </summary>
/// <param name="passKey">The unique identifier of the shader pass to retrieve.</param>
/// <returns>The <see cref="ShaderPass"/> corresponding to the specified identifier, or null if no matching shader pass is found.</returns>
ShaderPass GetShaderPass(ShaderPassKey passKey);
}

View File

@@ -4,8 +4,6 @@ using Ghost.Graphics.Data;
using Ghost.Graphics.RHI;
using Ghost.Graphics.Utilities;
using Misaki.HighPerformance.Image;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
namespace Ghost.Graphics.RenderPasses;

View File

@@ -28,38 +28,43 @@ struct PixelInput
float4 uv : TEXCOORD0;
};
// Bindless vertex shader that fetches vertex data from bindless buffers
PixelInput VSMain(uint vertexId : SV_VertexID, uint instanceId : SV_InstanceID)
[NumThreads(3, 1, 1)] // 3 threads per triangle
[OutputTopology("triangle")]
void MSMain(
uint3 groupThreadID : SV_GroupThreadID,
uint groupID : SV_GroupID,
out vertices PixelInput outVerts[3],
out indices uint3 outTris[1])
{
// Get bindless buffers
// Fetch bindless buffers
ByteAddressBuffer vertexBuffer = ResourceDescriptorHeap[_VertexBufferIndex];
ByteAddressBuffer indexBuffer = ResourceDescriptorHeap[_IndexBufferIndex];
// For fully bindless rendering, we use instanced drawing where:
// - Each instance represents a triangle (instanceId = triangle index)
// - vertexId goes from 0 to 2 (the 3 vertices of the triangle)
// Calculate the index into the index buffer
uint indexOffset = (instanceId * 3 + vertexId) * 4; // 4 bytes per index (uint32)
// Compute the triangles vertex indices
uint vertexId = groupThreadID.x;
uint indexOffset = (groupID.x * 3 + vertexId) * 4; // uint32 index
uint vertexIndex = indexBuffer.Load(indexOffset);
// Calculate the offset into the vertex buffer
uint vertexOffset = vertexIndex * 80; // 80 bytes per vertex (5 * float4)
// Load vertex attributes
uint vertexOffset = vertexIndex * 80; // 80 bytes per vertex
Vertex v;
v.position = asfloat(vertexBuffer.Load4(vertexOffset + 0));
v.normal = asfloat(vertexBuffer.Load4(vertexOffset + 16));
v.tangent = asfloat(vertexBuffer.Load4(vertexOffset + 32));
v.color = asfloat(vertexBuffer.Load4(vertexOffset + 48));
v.uv = asfloat(vertexBuffer.Load4(vertexOffset + 64));
// Load vertex data from bindless vertex buffer
Vertex vertex;
vertex.position = asfloat(vertexBuffer.Load4(vertexOffset + 0));
vertex.normal = asfloat(vertexBuffer.Load4(vertexOffset + 16));
vertex.tangent = asfloat(vertexBuffer.Load4(vertexOffset + 32));
vertex.color = asfloat(vertexBuffer.Load4(vertexOffset + 48));
vertex.uv = asfloat(vertexBuffer.Load4(vertexOffset + 64));
// Write vertex output
outVerts[vertexId].position = v.position;
outVerts[vertexId].color = v.color;
outVerts[vertexId].uv = v.uv;
// Output transformed vertex
PixelInput output;
output.position = vertex.position;
output.color = vertex.color;
output.uv = vertex.uv;
return output;
// Thread 0 defines topology
if (vertexId == 0)
{
SetMeshOutputCounts(3, 1);
outTris[0] = uint3(0, 1, 2);
}
}
float4 PSMain(PixelInput input) : SV_TARGET

View File

@@ -1,12 +1,7 @@
shader "MyShader/Standard"
{
fallback("Ghost/Standard"); // This is a test comment.
// Another comment.
properties
{
global uint test;
global texture2d global_texture;
float4 color = float4(1, 1, 1, 1);
texture2d texture1 = texture2d(black);
texture2d texture2 = texture2d(white);
@@ -23,30 +18,9 @@ shader "MyShader/Standard"
color_mask = 0;
}
/*
This is a
multi-line comment.
*/
pass "Forward"
{
vs("F:/csharp/GhostEngine/Ghost.Graphics/RenderPasses/ShaderCode.hlsl", "VSMain");
ps("F:/csharp/GhostEngine/Ghost.Graphics/RenderPasses/ShaderCode.hlsl", "PSMain");
includes
{
"F:/csharp/GhostEngine/Ghost.Shader/BuiltIn/Common.hlsl";
}
}
pass "DepthOnly"
{
properties
{
float testProp = float(0.5);
}
vs("F:/csharp/GhostEngine/Ghost.Graphics/RenderPasses/ShaderCode.hlsl", "VSMain");
ms("F:/csharp/GhostEngine/Ghost.Graphics/RenderPasses/ShaderCode.hlsl", "MSMain");
ps("F:/csharp/GhostEngine/Ghost.Graphics/RenderPasses/ShaderCode.hlsl", "PSMain");
includes

View File

@@ -1,11 +1,10 @@
using Ghost.Shader.Compiler;
using Ghost.Shader.Generator;
using Misaki.HighPerformance.Mathematics;
using System.Numerics;
ShaderStructGenerator.GenerateHLSL([typeof(TestStruct), typeof(TestEnum), typeof(TestEnumFlags)], PackingRules.Exact, "C:/Users/Misaki/Downloads/Archive/Test.cs.hlsl");
//ShaderStructGenerator.GenerateHLSL([typeof(TestStruct), typeof(TestEnum), typeof(TestEnumFlags)], PackingRules.Exact, "C:/Users/Misaki/Downloads/Archive/Test.cs.hlsl");
return;
//return;
var source = File.ReadAllText("F:/csharp/GhostEngine/Ghost.Graphics/test.gshader");
@@ -17,6 +16,10 @@ var model = ShaderCompiler.SemanticAnalysis(shaderInfo[0], out var errors);
foreach (var error in errors)
{
Console.WriteLine(error);
}
if (errors.Count != 0)
{
return;
}

View File

@@ -1,5 +1,3 @@
using Ghost.Shader.Compiler;
namespace Ghost.Shader.Compiler.Parser;
internal class DefinesBlock : IBlockParser<List<Token>, List<string>>

View File

@@ -1,5 +1,3 @@
using Ghost.Shader.Compiler;
namespace Ghost.Shader.Compiler.Parser;
internal class IncludesBlock : IBlockParser<List<Token>, List<string>>

View File

@@ -1,4 +1,4 @@
using Ghost.Shader.Compiler.Parser;
using Ghost.Core.Graphics;
namespace Ghost.Shader.Compiler.Parser;

View File

@@ -1,3 +1,5 @@
using Ghost.Core.Graphics;
namespace Ghost.Shader.Compiler.Parser;
// TODO: Add pass template support.
@@ -66,7 +68,7 @@ internal class PassBlock : IBlockParser<PassSyntax, PassSemantic>
return null;
}
var model = new PassSemantic
var semantic = new PassSemantic
{
name = syntax.name.lexeme,
defines = DefinesBlock.SemanticAnalysis(syntax.defines, errors),
@@ -76,8 +78,8 @@ internal class PassBlock : IBlockParser<PassSyntax, PassSemantic>
localPipeline = PipelineBlock.SemanticAnalysis(syntax.localPipeline, errors),
};
if (model.localProperties != null
&& model.localProperties.Any(p => p.scope == PropertyScope.Global))
if (semantic.localProperties != null
&& semantic.localProperties.Any(p => p.scope == PropertyScope.Global))
{
errors.Add(new ShaderError
{
@@ -93,50 +95,22 @@ internal class PassBlock : IBlockParser<PassSyntax, PassSemantic>
{
switch (func.name.lexeme)
{
case "vs":
if (func.arguments?.Count != 2)
{
errors.Add(new ShaderError
{
message = "Vertex shader declaration requires exactly two arguments: (shaderPath, entryPoint).",
line = func.name.line,
column = func.name.column
});
}
else
{
model.vertexShader = new ShaderEntryPoint
{
shader = func.arguments[0].lexeme,
entry = func.arguments[1].lexeme
};
}
case TokenLexicon.KnownFunctions.TASK_SHADER:
AnalysisShaderEntry(errors, func, ref semantic.taskShader);
break;
case "ps":
if (func.arguments?.Count != 2)
{
errors.Add(new ShaderError
{
message = "Pixel shader declaration requires exactly two arguments: (shaderPath, entryPoint).",
line = func.name.line,
column = func.name.column
});
}
else
{
model.pixelShader = new ShaderEntryPoint
{
shader = func.arguments[0].lexeme,
entry = func.arguments[1].lexeme
};
}
case TokenLexicon.KnownFunctions.MESH_SHADER:
AnalysisShaderEntry(errors, func, ref semantic.meshShader);
break;
case TokenLexicon.KnownFunctions.PIXEL_SHADER:
AnalysisShaderEntry(errors, func, ref semantic.pixelShader);
break;
default:
errors.Add(new ShaderError
{
message = $"Unknown function '{func.name.lexeme}' in pass.",
message = $"Unknown function '{func.name.lexeme}' in pass {syntax.name.lexeme}.",
line = func.name.line,
column = func.name.column
});
@@ -145,18 +119,39 @@ internal class PassBlock : IBlockParser<PassSyntax, PassSemantic>
}
}
if (model.vertexShader.shader == null || model.pixelShader.shader == null)
if (semantic.meshShader.shader == null || semantic.pixelShader.shader == null)
{
// TODO: Inheritance from base pass.
// TODO: Add mesh shader support.
errors.Add(new ShaderError
{
message = "Pass must contain a vertex shader (vs) and a pixel shader (ps) declaration.",
message = $"Pass {syntax.name.lexeme} must contain a mesh shader (ms) and a pixel shader (ps) declaration.",
line = syntax.name.line,
column = syntax.name.column
});
}
return model;
return semantic;
}
private static void AnalysisShaderEntry(List<ShaderError> errors, FunctionCallDeclaration func, ref ShaderEntryPoint shaderEntryPoint)
{
if (func.arguments?.Count != 2)
{
errors.Add(new ShaderError
{
message = "Shader declaration requires exactly two arguments: (shaderPath, entryPoint).",
line = func.name.line,
column = func.name.column
});
}
else
{
shaderEntryPoint = new ShaderEntryPoint
{
shader = func.arguments[0].lexeme,
entry = func.arguments[1].lexeme
};
}
}
}

View File

@@ -1,3 +1,5 @@
using Ghost.Core.Graphics;
namespace Ghost.Shader.Compiler.Parser;
internal class PipelineBlock : IBlockParser<PipelineSyntax, PipelineSemantic>

View File

@@ -1,4 +1,4 @@
using Ghost.Shader.Compiler.Parser;
using Ghost.Core.Graphics;
using Misaki.HighPerformance.Mathematics;
using System.Globalization;

View File

@@ -1,6 +1,4 @@
using System.Runtime.InteropServices;
namespace Ghost.Shader.Compiler.Parser;
namespace Ghost.Shader.Compiler.Parser;
internal class ShaderBlock : IBlockParser<ShaderSyntax, ShaderSemantics>
{

View File

@@ -1,5 +1,5 @@
using Ghost.Shader.Compiler.Parser;
using System.Collections.Generic;
using Ghost.Core.Graphics;
using Ghost.Shader.Compiler.Parser;
using System.Text;
namespace Ghost.Shader.Compiler;
@@ -227,7 +227,8 @@ internal static class ShaderCompiler
var fullPass = new FullPassDescriptor
{
uniqueIdentifier = GetPassUniqueId(semantics, pass),
vertexShader = pass.vertexShader,
taskShader = pass.taskShader,
meshShader = pass.meshShader,
pixelShader = pass.pixelShader,
localPipeline = localPipeline,
defines = pass.defines,

View File

@@ -28,7 +28,8 @@ internal class PipelineSemantic
internal class PassSemantic
{
public string name = string.Empty;
public ShaderEntryPoint vertexShader;
public ShaderEntryPoint taskShader;
public ShaderEntryPoint meshShader;
public ShaderEntryPoint pixelShader;
public List<string>? defines;
public List<string>? includes;

View File

@@ -137,9 +137,9 @@ internal static class TokenLexicon
public static class KnownFunctions
{
public const string VERTEX_SHADER = "vs";
public const string PIXEL_SHADER = "ps";
public const string TASK_SHADER = "ts";
public const string MESH_SHADER = "ms";
public const string PIXEL_SHADER = "ps";
public const string COMPUTE_SHADER = "cs";
public const string DYNAMIC = "dynamic";
public const string STATIC = "static";
@@ -201,7 +201,7 @@ internal static class TokenLexicon
private static readonly HashSet<string> s_functions = new()
{
KnownFunctions.VERTEX_SHADER,
KnownFunctions.TASK_SHADER,
KnownFunctions.PIXEL_SHADER,
KnownFunctions.MESH_SHADER,
KnownFunctions.COMPUTE_SHADER,

View File

@@ -4,259 +4,261 @@ namespace Ghost.Zeux.MeshOptimizer;
public static unsafe partial class Api
{
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
private const string _DLL_NAME = "meshoptimizer";
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_generateVertexRemap([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("const void *")] void* vertices, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_size);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_generateVertexRemapMulti([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("const struct meshopt_Stream *")] meshopt_Stream* streams, [NativeTypeName("size_t")] nuint stream_count);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_generateVertexRemapCustom([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("const float *")] float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride, [NativeTypeName("int (*)(void *, unsigned int, unsigned int)")] delegate* unmanaged[Cdecl]<void*, uint, uint, int> callback, void* context);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_remapVertexBuffer(void* destination, [NativeTypeName("const void *")] void* vertices, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_size, [NativeTypeName("const unsigned int *")] uint* remap);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_remapIndexBuffer([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("const unsigned int *")] uint* remap);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_generateShadowIndexBuffer([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("const void *")] void* vertices, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_size, [NativeTypeName("size_t")] nuint vertex_stride);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_generateShadowIndexBufferMulti([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("const struct meshopt_Stream *")] meshopt_Stream* streams, [NativeTypeName("size_t")] nuint stream_count);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_generatePositionRemap([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const float *")] float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_generateAdjacencyIndexBuffer([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("const float *")] float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_generateTessellationIndexBuffer([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("const float *")] float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_generateProvokingIndexBuffer([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("unsigned int *")] uint* reorder, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("size_t")] nuint vertex_count);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_optimizeVertexCache([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("size_t")] nuint vertex_count);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_optimizeVertexCacheStrip([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("size_t")] nuint vertex_count);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_optimizeVertexCacheFifo([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("unsigned int")] uint cache_size);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_optimizeOverdraw([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("const float *")] float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride, float threshold);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_optimizeVertexFetch(void* destination, [NativeTypeName("unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("const void *")] void* vertices, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_size);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_optimizeVertexFetchRemap([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("size_t")] nuint vertex_count);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_encodeIndexBuffer([NativeTypeName("unsigned char *")] byte* buffer, [NativeTypeName("size_t")] nuint buffer_size, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_encodeIndexBufferBound([NativeTypeName("size_t")] nuint index_count, [NativeTypeName("size_t")] nuint vertex_count);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_encodeIndexVersion(int version);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int meshopt_decodeIndexBuffer(void* destination, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("size_t")] nuint index_size, [NativeTypeName("const unsigned char *")] byte* buffer, [NativeTypeName("size_t")] nuint buffer_size);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int meshopt_decodeIndexVersion([NativeTypeName("const unsigned char *")] byte* buffer, [NativeTypeName("size_t")] nuint buffer_size);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_encodeIndexSequence([NativeTypeName("unsigned char *")] byte* buffer, [NativeTypeName("size_t")] nuint buffer_size, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_encodeIndexSequenceBound([NativeTypeName("size_t")] nuint index_count, [NativeTypeName("size_t")] nuint vertex_count);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int meshopt_decodeIndexSequence(void* destination, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("size_t")] nuint index_size, [NativeTypeName("const unsigned char *")] byte* buffer, [NativeTypeName("size_t")] nuint buffer_size);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_encodeVertexBuffer([NativeTypeName("unsigned char *")] byte* buffer, [NativeTypeName("size_t")] nuint buffer_size, [NativeTypeName("const void *")] void* vertices, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_size);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_encodeVertexBufferBound([NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_size);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_encodeVertexBufferLevel([NativeTypeName("unsigned char *")] byte* buffer, [NativeTypeName("size_t")] nuint buffer_size, [NativeTypeName("const void *")] void* vertices, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_size, int level, int version);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_encodeVertexVersion(int version);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int meshopt_decodeVertexBuffer(void* destination, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_size, [NativeTypeName("const unsigned char *")] byte* buffer, [NativeTypeName("size_t")] nuint buffer_size);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int meshopt_decodeVertexVersion([NativeTypeName("const unsigned char *")] byte* buffer, [NativeTypeName("size_t")] nuint buffer_size);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_decodeFilterOct(void* buffer, [NativeTypeName("size_t")] nuint count, [NativeTypeName("size_t")] nuint stride);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_decodeFilterQuat(void* buffer, [NativeTypeName("size_t")] nuint count, [NativeTypeName("size_t")] nuint stride);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_decodeFilterExp(void* buffer, [NativeTypeName("size_t")] nuint count, [NativeTypeName("size_t")] nuint stride);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_decodeFilterColor(void* buffer, [NativeTypeName("size_t")] nuint count, [NativeTypeName("size_t")] nuint stride);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_encodeFilterOct(void* destination, [NativeTypeName("size_t")] nuint count, [NativeTypeName("size_t")] nuint stride, int bits, [NativeTypeName("const float *")] float* data);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_encodeFilterQuat(void* destination, [NativeTypeName("size_t")] nuint count, [NativeTypeName("size_t")] nuint stride, int bits, [NativeTypeName("const float *")] float* data);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_encodeFilterExp(void* destination, [NativeTypeName("size_t")] nuint count, [NativeTypeName("size_t")] nuint stride, int bits, [NativeTypeName("const float *")] float* data, [NativeTypeName("enum meshopt_EncodeExpMode")] meshopt_EncodeExpMode mode);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_encodeFilterColor(void* destination, [NativeTypeName("size_t")] nuint count, [NativeTypeName("size_t")] nuint stride, int bits, [NativeTypeName("const float *")] float* data);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_simplify([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("const float *")] float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride, [NativeTypeName("size_t")] nuint target_index_count, float target_error, [NativeTypeName("unsigned int")] uint options, float* result_error);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_simplifyWithAttributes([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("const float *")] float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride, [NativeTypeName("const float *")] float* vertex_attributes, [NativeTypeName("size_t")] nuint vertex_attributes_stride, [NativeTypeName("const float *")] float* attribute_weights, [NativeTypeName("size_t")] nuint attribute_count, [NativeTypeName("const unsigned char *")] byte* vertex_lock, [NativeTypeName("size_t")] nuint target_index_count, float target_error, [NativeTypeName("unsigned int")] uint options, float* result_error);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_simplifyWithUpdate([NativeTypeName("unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride, float* vertex_attributes, [NativeTypeName("size_t")] nuint vertex_attributes_stride, [NativeTypeName("const float *")] float* attribute_weights, [NativeTypeName("size_t")] nuint attribute_count, [NativeTypeName("const unsigned char *")] byte* vertex_lock, [NativeTypeName("size_t")] nuint target_index_count, float target_error, [NativeTypeName("unsigned int")] uint options, float* result_error);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_simplifySloppy([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("const float *")] float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride, [NativeTypeName("const unsigned char *")] byte* vertex_lock, [NativeTypeName("size_t")] nuint target_index_count, float target_error, float* result_error);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_simplifyPrune([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("const float *")] float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride, float target_error);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_simplifyPoints([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const float *")] float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride, [NativeTypeName("const float *")] float* vertex_colors, [NativeTypeName("size_t")] nuint vertex_colors_stride, float color_weight, [NativeTypeName("size_t")] nuint target_vertex_count);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern float meshopt_simplifyScale([NativeTypeName("const float *")] float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_stripify([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("unsigned int")] uint restart_index);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_stripifyBound([NativeTypeName("size_t")] nuint index_count);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_unstripify([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("unsigned int")] uint restart_index);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_unstripifyBound([NativeTypeName("size_t")] nuint index_count);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("struct meshopt_VertexCacheStatistics")]
public static extern meshopt_VertexCacheStatistics meshopt_analyzeVertexCache([NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("unsigned int")] uint cache_size, [NativeTypeName("unsigned int")] uint warp_size, [NativeTypeName("unsigned int")] uint primgroup_size);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("struct meshopt_VertexFetchStatistics")]
public static extern meshopt_VertexFetchStatistics meshopt_analyzeVertexFetch([NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_size);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("struct meshopt_OverdrawStatistics")]
public static extern meshopt_OverdrawStatistics meshopt_analyzeOverdraw([NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("const float *")] float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("struct meshopt_CoverageStatistics")]
public static extern meshopt_CoverageStatistics meshopt_analyzeCoverage([NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("const float *")] float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_buildMeshlets([NativeTypeName("struct meshopt_Meshlet *")] meshopt_Meshlet* meshlets, [NativeTypeName("unsigned int *")] uint* meshlet_vertices, [NativeTypeName("unsigned char *")] byte* meshlet_triangles, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("const float *")] float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride, [NativeTypeName("size_t")] nuint max_vertices, [NativeTypeName("size_t")] nuint max_triangles, float cone_weight);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_buildMeshletsScan([NativeTypeName("struct meshopt_Meshlet *")] meshopt_Meshlet* meshlets, [NativeTypeName("unsigned int *")] uint* meshlet_vertices, [NativeTypeName("unsigned char *")] byte* meshlet_triangles, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint max_vertices, [NativeTypeName("size_t")] nuint max_triangles);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_buildMeshletsBound([NativeTypeName("size_t")] nuint index_count, [NativeTypeName("size_t")] nuint max_vertices, [NativeTypeName("size_t")] nuint max_triangles);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_buildMeshletsFlex([NativeTypeName("struct meshopt_Meshlet *")] meshopt_Meshlet* meshlets, [NativeTypeName("unsigned int *")] uint* meshlet_vertices, [NativeTypeName("unsigned char *")] byte* meshlet_triangles, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("const float *")] float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride, [NativeTypeName("size_t")] nuint max_vertices, [NativeTypeName("size_t")] nuint min_triangles, [NativeTypeName("size_t")] nuint max_triangles, float cone_weight, float split_factor);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_buildMeshletsSpatial([NativeTypeName("struct meshopt_Meshlet *")] meshopt_Meshlet* meshlets, [NativeTypeName("unsigned int *")] uint* meshlet_vertices, [NativeTypeName("unsigned char *")] byte* meshlet_triangles, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("const float *")] float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride, [NativeTypeName("size_t")] nuint max_vertices, [NativeTypeName("size_t")] nuint min_triangles, [NativeTypeName("size_t")] nuint max_triangles, float fill_weight);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_optimizeMeshlet([NativeTypeName("unsigned int *")] uint* meshlet_vertices, [NativeTypeName("unsigned char *")] byte* meshlet_triangles, [NativeTypeName("size_t")] nuint triangle_count, [NativeTypeName("size_t")] nuint vertex_count);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("struct meshopt_Bounds")]
public static extern meshopt_Bounds meshopt_computeClusterBounds([NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("const float *")] float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("struct meshopt_Bounds")]
public static extern meshopt_Bounds meshopt_computeMeshletBounds([NativeTypeName("const unsigned int *")] uint* meshlet_vertices, [NativeTypeName("const unsigned char *")] byte* meshlet_triangles, [NativeTypeName("size_t")] nuint triangle_count, [NativeTypeName("const float *")] float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("struct meshopt_Bounds")]
public static extern meshopt_Bounds meshopt_computeSphereBounds([NativeTypeName("const float *")] float* positions, [NativeTypeName("size_t")] nuint count, [NativeTypeName("size_t")] nuint positions_stride, [NativeTypeName("const float *")] float* radii, [NativeTypeName("size_t")] nuint radii_stride);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("size_t")]
public static extern nuint meshopt_partitionClusters([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const unsigned int *")] uint* cluster_indices, [NativeTypeName("size_t")] nuint total_index_count, [NativeTypeName("const unsigned int *")] uint* cluster_index_counts, [NativeTypeName("size_t")] nuint cluster_count, [NativeTypeName("const float *")] float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride, [NativeTypeName("size_t")] nuint target_partition_size);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_spatialSortRemap([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const float *")] float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_spatialSortTriangles([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const unsigned int *")] uint* indices, [NativeTypeName("size_t")] nuint index_count, [NativeTypeName("const float *")] float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_spatialClusterPoints([NativeTypeName("unsigned int *")] uint* destination, [NativeTypeName("const float *")] float* vertex_positions, [NativeTypeName("size_t")] nuint vertex_count, [NativeTypeName("size_t")] nuint vertex_positions_stride, [NativeTypeName("size_t")] nuint cluster_size);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[return: NativeTypeName("unsigned short")]
public static extern ushort meshopt_quantizeHalf(float v);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern float meshopt_quantizeFloat(float v, int N);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern float meshopt_dequantizeHalf([NativeTypeName("unsigned short")] ushort h);
[DllImport("meshoptimizer_native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
[DllImport(_DLL_NAME, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void meshopt_setAllocator([NativeTypeName("void *(*)(size_t) __attribute__((cdecl))")] delegate* unmanaged[Cdecl]<nuint, void*> allocate, [NativeTypeName("void (*)(void *) __attribute__((cdecl))")] delegate* unmanaged[Cdecl]<void*, void> deallocate);
public static int meshopt_quantizeUnorm(float v, int N)
{
float scale = (float)((1 << N) - 1);
var scale = (float)((1 << N) - 1);
v = (v >= 0) ? v : 0;
v = (v <= 1) ? v : 1;
@@ -265,8 +267,8 @@ public static unsafe partial class Api
public static int meshopt_quantizeSnorm(float v, int N)
{
float scale = (float)((1 << (N - 1)) - 1);
float round = (v >= 0 ? 0.5f : -0.5f);
var scale = (float)((1 << (N - 1)) - 1);
var round = (v >= 0 ? 0.5f : -0.5f);
v = (v >= -1) ? v : -1;
v = (v <= +1) ? v : +1;

View File

@@ -1,40 +0,0 @@
namespace Ghost.Zeux.MeshOptimizer;
using System;
using System.Diagnostics;
/// <summary>Defines the type of a member as it was used in the native signature.</summary>
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = false, Inherited = true)]
[Conditional("DEBUG")]
internal sealed partial class NativeTypeNameAttribute : Attribute
{
private readonly string _name;
/// <summary>Initializes a new instance of the <see cref="NativeTypeNameAttribute" /> class.</summary>
/// <param name="name">The name of the type that was used in the native signature.</param>
public NativeTypeNameAttribute(string name)
{
_name = name;
}
/// <summary>Gets the name of the type that was used in the native signature.</summary>
public string Name => _name;
}
/// <summary>Defines the annotation found in a native declaration.</summary>
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
[Conditional("DEBUG")]
internal sealed partial class NativeAnnotationAttribute : Attribute
{
private readonly string _annotation;
/// <summary>Initializes a new instance of the <see cref="NativeAnnotationAttribute" /> class.</summary>
/// <param name="annotation">The annotation that was used in the native declaration.</param>
public NativeAnnotationAttribute(string annotation)
{
_annotation = annotation;
}
/// <summary>Gets the annotation that was used in the native declaration.</summary>
public string Annotation => _annotation;
}

View File

@@ -16,7 +16,7 @@
</PropertyGroup>
<ItemGroup>
<Content Include="runtime\win-x64\native\meshoptimizer_native.dll">
<Content Include="runtime\win-x64\native\meshoptimizer.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>

View File

@@ -1,261 +0,0 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Zeux.MeshOptimizer;
public unsafe partial struct meshopt_Stream
{
[NativeTypeName("const void *")]
public void* data;
[NativeTypeName("size_t")]
public nuint size;
[NativeTypeName("size_t")]
public nuint stride;
}
public enum meshopt_EncodeExpMode
{
meshopt_EncodeExpSeparate,
meshopt_EncodeExpSharedVector,
meshopt_EncodeExpSharedComponent,
meshopt_EncodeExpClamped,
}
public enum meshopt_SimplifyOptions
{
meshopt_SimplifyLockBorder = 1 << 0,
meshopt_SimplifySparse = 1 << 1,
meshopt_SimplifyErrorAbsolute = 1 << 2,
meshopt_SimplifyPrune = 1 << 3,
meshopt_SimplifyRegularize = 1 << 4,
meshopt_SimplifyPermissive = 1 << 5,
}
public enum meshopt_SimplifyVertexFlags
{
meshopt_SimplifyVertex_Lock = 1 << 0,
meshopt_SimplifyVertex_Protect = 1 << 1,
}
public partial struct meshopt_VertexCacheStatistics
{
[NativeTypeName("unsigned int")]
public uint vertices_transformed;
[NativeTypeName("unsigned int")]
public uint warps_executed;
public float acmr;
public float atvr;
}
public partial struct meshopt_VertexFetchStatistics
{
[NativeTypeName("unsigned int")]
public uint bytes_fetched;
public float overfetch;
}
public partial struct meshopt_OverdrawStatistics
{
[NativeTypeName("unsigned int")]
public uint pixels_covered;
[NativeTypeName("unsigned int")]
public uint pixels_shaded;
public float overdraw;
}
public partial struct meshopt_CoverageStatistics
{
[NativeTypeName("float[3]")]
public _coverage_e__FixedBuffer coverage;
public float extent;
[InlineArray(3)]
public partial struct _coverage_e__FixedBuffer
{
public float e0;
}
}
public partial struct meshopt_Meshlet
{
[NativeTypeName("unsigned int")]
public uint vertex_offset;
[NativeTypeName("unsigned int")]
public uint triangle_offset;
[NativeTypeName("unsigned int")]
public uint vertex_count;
[NativeTypeName("unsigned int")]
public uint triangle_count;
}
public partial struct meshopt_Bounds
{
[NativeTypeName("float[3]")]
public _center_e__FixedBuffer center;
public float radius;
[NativeTypeName("float[3]")]
public _cone_apex_e__FixedBuffer cone_apex;
[NativeTypeName("float[3]")]
public _cone_axis_e__FixedBuffer cone_axis;
public float cone_cutoff;
[NativeTypeName("signed char[3]")]
public _cone_axis_s8_e__FixedBuffer cone_axis_s8;
[NativeTypeName("signed char")]
public sbyte cone_cutoff_s8;
[InlineArray(3)]
public partial struct _center_e__FixedBuffer
{
public float e0;
}
[InlineArray(3)]
public partial struct _cone_apex_e__FixedBuffer
{
public float e0;
}
[InlineArray(3)]
public partial struct _cone_axis_e__FixedBuffer
{
public float e0;
}
[InlineArray(3)]
public partial struct _cone_axis_s8_e__FixedBuffer
{
public sbyte e0;
}
}
public unsafe partial struct meshopt_Allocator
{
private static readonly Storage s_storage;
[NativeTypeName("void *[24]")]
private _blocks_e__FixedBuffer _blocks;
[NativeTypeName("size_t")]
private nuint _count;
static meshopt_Allocator()
{
s_storage = new Storage
{
// Use .NET's unmanaged memory functions.
allocate = &NativeMemory_Alloc,
deallocate = &NativeMemory_Free,
};
}
public meshopt_Allocator()
{
_count = 0;
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static void* NativeMemory_Alloc(nuint size)
{
return NativeMemory.Alloc(size);
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static void NativeMemory_Free(void* ptr)
{
NativeMemory.Free(ptr);
}
public T* allocate<T>(nuint size) where T : unmanaged
{
Debug.Assert(_count < 24, "Allocator block limit reached");
// Calculate total bytes needed
var bytes = size * (nuint)sizeof(T);
var result = (T*)s_storage.allocate(bytes);
_blocks[_count++] = result;
return result;
}
public void deallocate(void* ptr)
{
Debug.Assert(_count > 0 && _blocks[_count - 1] == ptr, "Deallocation is not in LIFO order");
s_storage.deallocate(ptr);
_count--;
}
public unsafe partial struct Storage
{
[NativeTypeName("void *(*)(size_t) __attribute__((cdecl))")]
public delegate* unmanaged[Cdecl]<nuint, void*> allocate;
[NativeTypeName("void (*)(void *) __attribute__((cdecl))")]
public delegate* unmanaged[Cdecl]<void*, void> deallocate;
}
public void Dispose()
{
for (var i = _count; i > 0; --i)
{
s_storage.deallocate(_blocks[i - 1]);
}
}
private unsafe partial struct _blocks_e__FixedBuffer
{
private void* e0;
private void* e1;
private void* e2;
private void* e3;
private void* e4;
private void* e5;
private void* e6;
private void* e7;
private void* e8;
private void* e9;
private void* e10;
private void* e11;
private void* e12;
private void* e13;
private void* e14;
private void* e15;
private void* e16;
private void* e17;
private void* e18;
private void* e19;
private void* e20;
private void* e21;
private void* e22;
private void* e23;
public ref void* this[nuint index]
{
get
{
fixed (void** pThis = &e0)
{
return ref pThis[index];
}
}
}
}
}

View File

@@ -0,0 +1,22 @@
using System.Diagnostics;
namespace Ghost.Zeux.MeshOptimizer
{
/// <summary>Defines the annotation found in a native declaration.</summary>
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
[Conditional("DEBUG")]
internal sealed partial class NativeAnnotationAttribute : Attribute
{
private readonly string _annotation;
/// <summary>Initializes a new instance of the <see cref="NativeAnnotationAttribute" /> class.</summary>
/// <param name="annotation">The annotation that was used in the native declaration.</param>
public NativeAnnotationAttribute(string annotation)
{
_annotation = annotation;
}
/// <summary>Gets the annotation that was used in the native declaration.</summary>
public string Annotation => _annotation;
}
}

View File

@@ -0,0 +1,22 @@
using System.Diagnostics;
namespace Ghost.Zeux.MeshOptimizer
{
/// <summary>Defines the type of a member as it was used in the native signature.</summary>
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = false, Inherited = true)]
[Conditional("DEBUG")]
internal sealed partial class NativeTypeNameAttribute : Attribute
{
private readonly string _name;
/// <summary>Initializes a new instance of the <see cref="NativeTypeNameAttribute" /> class.</summary>
/// <param name="name">The name of the type that was used in the native signature.</param>
public NativeTypeNameAttribute(string name)
{
_name = name;
}
/// <summary>Gets the name of the type that was used in the native signature.</summary>
public string Name => _name;
}
}

View File

@@ -0,0 +1,119 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ghost.Zeux.MeshOptimizer
{
public unsafe partial struct meshopt_Allocator
{
private static readonly Storage s_storage;
[NativeTypeName("void *[24]")]
private _blocks_e__FixedBuffer _blocks;
[NativeTypeName("size_t")]
private nuint _count;
static meshopt_Allocator()
{
s_storage = new Storage
{
// Use .NET's unmanaged memory functions.
allocate = &NativeMemory_Alloc,
deallocate = &NativeMemory_Free,
};
}
public meshopt_Allocator()
{
_count = 0;
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static void* NativeMemory_Alloc(nuint size)
{
return NativeMemory.Alloc(size);
}
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
private static void NativeMemory_Free(void* ptr)
{
NativeMemory.Free(ptr);
}
public T* allocate<T>(nuint size) where T : unmanaged
{
Debug.Assert(_count < 24, "Allocator block limit reached");
// Calculate total bytes needed
var bytes = size * (nuint)sizeof(T);
var result = (T*)s_storage.allocate(bytes);
_blocks[_count++] = result;
return result;
}
public void deallocate(void* ptr)
{
Debug.Assert(_count > 0 && _blocks[_count - 1] == ptr, "Deallocation is not in LIFO order");
s_storage.deallocate(ptr);
_count--;
}
public unsafe partial struct Storage
{
[NativeTypeName("void *(*)(size_t) __attribute__((cdecl))")]
public delegate* unmanaged[Cdecl]<nuint, void*> allocate;
[NativeTypeName("void (*)(void *) __attribute__((cdecl))")]
public delegate* unmanaged[Cdecl]<void*, void> deallocate;
}
public void Dispose()
{
for (var i = _count; i > 0; --i)
{
s_storage.deallocate(_blocks[i - 1]);
}
}
private unsafe partial struct _blocks_e__FixedBuffer
{
private void* e0;
private void* e1;
private void* e2;
private void* e3;
private void* e4;
private void* e5;
private void* e6;
private void* e7;
private void* e8;
private void* e9;
private void* e10;
private void* e11;
private void* e12;
private void* e13;
private void* e14;
private void* e15;
private void* e16;
private void* e17;
private void* e18;
private void* e19;
private void* e20;
private void* e21;
private void* e22;
private void* e23;
public ref void* this[nuint index]
{
get
{
fixed (void** pThis = &e0)
{
return ref pThis[index];
}
}
}
}
}
}

View File

@@ -0,0 +1,50 @@
using System.Runtime.CompilerServices;
namespace Ghost.Zeux.MeshOptimizer
{
public partial struct meshopt_Bounds
{
[NativeTypeName("float[3]")]
public _center_e__FixedBuffer center;
public float radius;
[NativeTypeName("float[3]")]
public _cone_apex_e__FixedBuffer cone_apex;
[NativeTypeName("float[3]")]
public _cone_axis_e__FixedBuffer cone_axis;
public float cone_cutoff;
[NativeTypeName("signed char[3]")]
public _cone_axis_s8_e__FixedBuffer cone_axis_s8;
[NativeTypeName("signed char")]
public sbyte cone_cutoff_s8;
[InlineArray(3)]
public partial struct _center_e__FixedBuffer
{
public float e0;
}
[InlineArray(3)]
public partial struct _cone_apex_e__FixedBuffer
{
public float e0;
}
[InlineArray(3)]
public partial struct _cone_axis_e__FixedBuffer
{
public float e0;
}
[InlineArray(3)]
public partial struct _cone_axis_s8_e__FixedBuffer
{
public sbyte e0;
}
}
}

View File

@@ -0,0 +1,18 @@
using System.Runtime.CompilerServices;
namespace Ghost.Zeux.MeshOptimizer
{
public partial struct meshopt_CoverageStatistics
{
[NativeTypeName("float[3]")]
public _coverage_e__FixedBuffer coverage;
public float extent;
[InlineArray(3)]
public partial struct _coverage_e__FixedBuffer
{
public float e0;
}
}
}

View File

@@ -0,0 +1,10 @@
namespace Ghost.Zeux.MeshOptimizer
{
public enum meshopt_EncodeExpMode
{
meshopt_EncodeExpSeparate,
meshopt_EncodeExpSharedVector,
meshopt_EncodeExpSharedComponent,
meshopt_EncodeExpClamped,
}
}

View File

@@ -0,0 +1,17 @@
namespace Ghost.Zeux.MeshOptimizer
{
public partial struct meshopt_Meshlet
{
[NativeTypeName("unsigned int")]
public uint vertex_offset;
[NativeTypeName("unsigned int")]
public uint triangle_offset;
[NativeTypeName("unsigned int")]
public uint vertex_count;
[NativeTypeName("unsigned int")]
public uint triangle_count;
}
}

View File

@@ -0,0 +1,13 @@
namespace Ghost.Zeux.MeshOptimizer
{
public partial struct meshopt_OverdrawStatistics
{
[NativeTypeName("unsigned int")]
public uint pixels_covered;
[NativeTypeName("unsigned int")]
public uint pixels_shaded;
public float overdraw;
}
}

View File

@@ -0,0 +1,12 @@
namespace Ghost.Zeux.MeshOptimizer
{
public enum meshopt_SimplifyOptions
{
meshopt_SimplifyLockBorder = 1 << 0,
meshopt_SimplifySparse = 1 << 1,
meshopt_SimplifyErrorAbsolute = 1 << 2,
meshopt_SimplifyPrune = 1 << 3,
meshopt_SimplifyRegularize = 1 << 4,
meshopt_SimplifyPermissive = 1 << 5,
}
}

View File

@@ -0,0 +1,8 @@
namespace Ghost.Zeux.MeshOptimizer
{
public enum meshopt_SimplifyVertexFlags
{
meshopt_SimplifyVertex_Lock = 1 << 0,
meshopt_SimplifyVertex_Protect = 1 << 1,
}
}

View File

@@ -0,0 +1,14 @@
namespace Ghost.Zeux.MeshOptimizer
{
public unsafe partial struct meshopt_Stream
{
[NativeTypeName("const void *")]
public void* data;
[NativeTypeName("size_t")]
public nuint size;
[NativeTypeName("size_t")]
public nuint stride;
}
}

View File

@@ -0,0 +1,15 @@
namespace Ghost.Zeux.MeshOptimizer
{
public partial struct meshopt_VertexCacheStatistics
{
[NativeTypeName("unsigned int")]
public uint vertices_transformed;
[NativeTypeName("unsigned int")]
public uint warps_executed;
public float acmr;
public float atvr;
}
}

View File

@@ -0,0 +1,10 @@
namespace Ghost.Zeux.MeshOptimizer
{
public partial struct meshopt_VertexFetchStatistics
{
[NativeTypeName("unsigned int")]
public uint bytes_fetched;
public float overfetch;
}
}