Refactor and enhance graphics and audio systems

Updated target frameworks to .NET 10.0 across multiple projects for compatibility with the latest features. Refactored namespaces and introduced new classes for shader descriptors, FMOD integration, and DirectX 12 utilities using TerraFX. Replaced `Win32` bindings with TerraFX equivalents for DirectX 12. Added a C# wrapper for FMOD Studio API, including DSP and error handling. Enhanced entity queries, component storage, and query filters for better performance and type safety. Introduced new test projects and updated the solution structure. Added `meshoptimizer` bindings and integrated `meshoptimizer_native.dll`. Improved code readability, maintainability, and performance.
This commit is contained in:
2025-10-09 05:16:28 +09:00
parent 01a850ff94
commit 682200cbf1
126 changed files with 25587 additions and 3247 deletions

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>

View File

@@ -1,4 +1,4 @@
namespace Ghost.Shader; namespace Ghost.Core.Graphics;
public enum ZTestOptions public enum ZTestOptions
{ {

View File

@@ -0,0 +1,35 @@
namespace Ghost.Core.Graphics;
/// <summary>
/// The layout of the root signature is:
/// <list type="bullet">
/// <item>
/// Global buffer (b0)
/// </item>
/// <item>
/// Per-view buffer (b1)
/// </item>
/// <item>
/// Per-object buffer (b2)
/// </item>
/// <item>
/// Per-material buffer (b3)
/// </item>
/// <item>
/// Descriptor table for bindless textures (t0)
/// </item>
/// <item>
/// Descriptor table for bindless samplers (s0)
/// </item>
/// </list>
/// </summary>
public static class RootSignatureLayout
{
public const int GLOBAL_BUFFER_SLOT = 0;
public const int PER_VIEW_BUFFER_SLOT = 1;
public const int PER_OBJECT_BUFFER_SLOT = 2;
public const int PER_MATERIAL_BUFFER_SLOT = 3;
public const int TEXTURE_HEAP_SLOT = 0;
public const int SAMPLER_HEAP_SLOT = 0;
}

View File

@@ -0,0 +1,90 @@
namespace Ghost.Core.Graphics;
public enum KeywordType
{
Static,
Dynamic,
}
public enum ShaderPropertyType
{
None,
Float, Float2, Float3, Float4,
Int, Int2, Int3, Int4,
UInt, UInt2, UInt3, UInt4,
Bool, Bool2, Bool3, Bool4,
Texture2D, Texture3D, TextureCube,
Texture2DArray, TextureCubeArray,
}
public struct ShaderEntryPoint
{
public string entry;
public string shader;
}
public struct KeywordsGroup
{
public KeywordType type;
public List<string>? keywords;
}
public struct PipelineDescriptor
{
public ZTestOptions zTest;
public ZWriteOptions zWrite;
public CullOptions cull;
public BlendOptions blend;
public uint colorMask;
public static PipelineDescriptor Default = new PipelineDescriptor
{
zTest = ZTestOptions.LessEqual,
zWrite = ZWriteOptions.On,
cull = CullOptions.Back,
blend = BlendOptions.Opaque,
colorMask = 0
};
}
public interface IPassDescriptor
{
public string Identifier
{
get;
}
}
public class FullPassDescriptor : IPassDescriptor
{
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;
}
public class FallbackPassDescriptor : IPassDescriptor
{
public string fallbackPassIdentifier = string.Empty;
public string Identifier => fallbackPassIdentifier;
}
public struct PropertyDescriptor
{
public ShaderPropertyType type;
public string name;
public object? defaultValue;
}
public class ShaderDescriptor
{
public string name = string.Empty;
public List<PropertyDescriptor> globalProperties = new();
public List<IPassDescriptor> passes = new();
}

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0-windows10.0.22621.0</TargetFramework> <TargetFramework>net10.0-windows10.0.22621.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion> <TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>Ghost.Editor.Core</RootNamespace> <RootNamespace>Ghost.Editor.Core</RootNamespace>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers> <RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows10.0.22621.0</TargetFramework> <TargetFramework>net10.0-windows10.0.22621.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion> <TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<Platforms>x86;x64;ARM64</Platforms> <Platforms>x86;x64;ARM64</Platforms>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers> <RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>

View File

@@ -1,11 +1,9 @@
using Ghost.Entities; using Ghost.Entities.Components;
using Ghost.Entities.Components;
using Ghost.Entities.Systems; using Ghost.Entities.Systems;
using Ghost.UnitTest.Services; using Ghost.Test.Core;
using Ghost.UnitTest.TestFramework;
using System.Numerics; using System.Numerics;
namespace Ghost.UnitTest.Test; namespace Ghost.Entities.Test;
public partial class EntityTest : ITest public partial class EntityTest : ITest
{ {
@@ -64,9 +62,9 @@ public class TestSystem : ISystem
public void OnUpdate(in SystemState state) public void OnUpdate(in SystemState state)
{ {
foreach (var (entity, transform) in state.World.Query<Transform>()) foreach (var (entity, transform) in state.World.Query<Transform>().WithAbsent<Mesh>())
{ {
LoggingService.Info($"Entity {entity.ID}: Transform Position = {transform.ValueRO.position}"); Console.WriteLine($"Entity {entity.ID}: Transform Position = {transform.ValueRO.position}");
} }
} }
@@ -86,7 +84,7 @@ public class TestSystem2 : ISystem
{ {
foreach (var (entity, mesh) in state.World.Query<Mesh>()) foreach (var (entity, mesh) in state.World.Query<Mesh>())
{ {
LoggingService.Info($"Entity {entity.ID}: Mesh Index = {mesh.ValueRO.index}"); Console.WriteLine($"Entity {entity.ID}: Mesh Index = {mesh.ValueRO.index}");
} }
} }
@@ -113,17 +111,17 @@ public class UserScript : ScriptComponent
public override void Start() public override void Start()
{ {
LoggingService.Info("UserScript started for entity: " + Owner.ID); Console.WriteLine("UserScript started for entity: " + Owner.ID);
} }
public override void Update() public override void Update()
{ {
LoggingService.Info("UserScript updating for entity: " + Owner.ID); Console.WriteLine("UserScript updating for entity: " + Owner.ID);
} }
public override void OnDestroy() public override void OnDestroy()
{ {
LoggingService.Info("UserScript destroyed for entity: " + Owner.ID); Console.WriteLine("UserScript destroyed for entity: " + Owner.ID);
} }
} }
@@ -131,17 +129,17 @@ public class UIManager : ScriptComponent
{ {
public override void Start() public override void Start()
{ {
LoggingService.Info("UIManager started for entity: " + Owner.ID); Console.WriteLine("UIManager started for entity: " + Owner.ID);
} }
public override void Update() public override void Update()
{ {
LoggingService.Info("UIManager updating for entity: " + Owner.ID); Console.WriteLine("UIManager updating for entity: " + Owner.ID);
} }
public override void OnDestroy() public override void OnDestroy()
{ {
LoggingService.Info("UIManager destroyed for entity: " + Owner.ID); Console.WriteLine("UIManager destroyed for entity: " + Owner.ID);
} }
} }
@@ -149,16 +147,16 @@ public class EventManager : ScriptComponent
{ {
public override void Start() public override void Start()
{ {
LoggingService.Info("EventManager started for entity: " + Owner.ID); Console.WriteLine("EventManager started for entity: " + Owner.ID);
} }
public override void Update() public override void Update()
{ {
LoggingService.Info("EventManager updating for entity: " + Owner.ID); Console.WriteLine("EventManager updating for entity: " + Owner.ID);
} }
public override void OnDestroy() public override void OnDestroy()
{ {
LoggingService.Info("EventManager destroyed for entity: " + Owner.ID); Console.WriteLine("EventManager destroyed for entity: " + Owner.ID);
} }
} }

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ghost.Entities\Ghost.Entities.csproj" />
<ProjectReference Include="..\Ghost.Test.Core\Ghost.Test.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,5 @@
using Ghost.Entities.Test;
using Ghost.Test.Core;
TestRunner.Run<EntityTest>();

View File

@@ -7,6 +7,6 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Ghost.Engine")] [assembly: InternalsVisibleTo("Ghost.Engine")]
[assembly: InternalsVisibleTo("Ghost.Editor.Core")] [assembly: InternalsVisibleTo("Ghost.Editor.Core")]
[assembly: InternalsVisibleTo("Ghost.UnitTest")] [assembly: InternalsVisibleTo("Ghost.Entities.Test")]
[assembly: EngineAssembly] [assembly: EngineAssembly]

View File

@@ -663,9 +663,8 @@ internal struct ComponentStorage : IDisposable
return set.Value; return set.Value;
} }
public UnsafeBitSet GetOrCreateMask(Type type) public UnsafeBitSet GetOrCreateMask(TypeHandle typeHandle)
{ {
var typeHandle = TypeHandle.Get(type);
if (!_typeIDMap.TryGetValue(typeHandle, out var id)) if (!_typeIDMap.TryGetValue(typeHandle, out var id))
{ {
id = GetTypeID(typeHandle); id = GetTypeID(typeHandle);
@@ -684,6 +683,11 @@ internal struct ComponentStorage : IDisposable
return set.Value; return set.Value;
} }
public UnsafeBitSet GetOrCreateMask(Type type)
{
return GetOrCreateMask(TypeHandle.Get(type));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void RebuildExecutionList() public readonly void RebuildExecutionList()
{ {

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>

View File

@@ -1,5 +1,7 @@
using Ghost.Core; using Ghost.Core;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
namespace Ghost.Entities.Query; namespace Ghost.Entities.Query;
@@ -27,63 +29,68 @@ internal struct QueryFilter()
public readonly UnsafeBitSet ComputeFilterBitMask(World world) public readonly UnsafeBitSet ComputeFilterBitMask(World world)
{ {
UnsafeBitSet? allMask = null; UnsafeBitSet allMask = default;
UnsafeBitSet? anyMask = null; UnsafeBitSet anyMask = default;
UnsafeBitSet? absentMask = null; UnsafeBitSet absentMask = default;
using var scope = AllocationManager.CreateStackScope();
foreach (var typeHandle in _all) foreach (var typeHandle in _all)
{ {
var mask = world.ComponentStorage.GetOrCreateMask(typeHandle); var mask = world.ComponentStorage.GetOrCreateMask(typeHandle);
if (!allMask.HasValue) if (!allMask.IsCreated)
{ {
allMask = new UnsafeBitSet(mask.Length); allMask = new UnsafeBitSet(mask.Length, Allocator.Stack, AllocationOption.Clear);
allMask.Value.SetAll(); allMask.SetAll();
} }
allMask &= mask; allMask.AndOperation(mask);
} }
foreach (var typeHandle in _any) foreach (var typeHandle in _any)
{ {
var mask = world.ComponentStorage.GetOrCreateMask(typeHandle); var mask = world.ComponentStorage.GetOrCreateMask(typeHandle);
if (!anyMask.HasValue) if (!anyMask.IsCreated)
{ {
anyMask = new UnsafeBitSet(mask.Length); anyMask = new UnsafeBitSet(mask.Length, Allocator.Stack, AllocationOption.Clear);
} }
anyMask |= mask; anyMask.OrOperation(mask);
} }
foreach (var typeHandle in _absent) foreach (var typeHandle in _absent)
{ {
var mask = world.ComponentStorage.GetOrCreateMask(typeHandle); var mask = world.ComponentStorage.GetOrCreateMask(typeHandle);
if (!absentMask.HasValue) if (!absentMask.IsCreated)
{ {
absentMask = new UnsafeBitSet(mask.Length); absentMask = new UnsafeBitSet(mask.Length, Allocator.Stack, AllocationOption.Clear);
} }
absentMask |= mask; absentMask.OrOperation(mask);
} }
var result = new UnsafeBitSet(world.EntityManager.EntityCount); var result = new UnsafeBitSet(world.EntityManager.EntityCount, Allocator.Persistent);
result.SetAll(); result.SetAll();
if (allMask.HasValue) if (allMask.IsCreated)
{ {
result &= allMask.Value; result.AndOperation(allMask);
allMask.Dispose();
} }
if (anyMask.HasValue) if (anyMask.IsCreated)
{ {
result &= anyMask.Value; result.AndOperation(anyMask);
anyMask.Dispose();
} }
if (absentMask.HasValue) if (absentMask.IsCreated)
{ {
result &= ~absentMask.Value; result.AndOperation(~absentMask);
absentMask.Dispose();
} }
return result; return result;

View File

@@ -36,7 +36,37 @@ public ref struct CompRef<T> : IQueryTypeParameter
IsValid = isValid; IsValid = isValid;
} }
public CompRef(ref T value) : this(ref value, true) public CompRef(ref T value)
: this(ref value, true)
{
}
}
public ref struct CompRO<T> : IQueryTypeParameter
where T : IComponentData
{
internal readonly ref T _value;
public readonly ref T ValueRO
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref _value;
}
public readonly bool IsValid
{
get;
init;
}
public CompRO(ref T value, bool isValid)
{
_value = ref value;
IsValid = isValid;
}
public CompRO(ref T value)
: this(ref value, true)
{ {
} }
} }

4266
Ghost.FMOD/Core/fmod.cs Normal file

File diff suppressed because it is too large Load Diff

1007
Ghost.FMOD/Core/fmod_dsp.cs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,106 @@
/* ============================================================================================== */
/* FMOD Core / Studio API - Error string header file. */
/* Copyright (c), Firelight Technologies Pty, Ltd. 2004-2025. */
/* */
/* Use this header if you want to store or display a string version / english explanation */
/* of the FMOD error codes. */
/* */
/* For more detail visit: */
/* https://fmod.com/docs/2.03/api/core-api-common.html#fmod_result */
/* =============================================================================================== */
namespace Ghost.FMOD.Core
{
public class Error
{
public static string String(RESULT errcode)
{
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.";
}
}
}
}

View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Remove="fmod.dll" />
<None Remove="fmodstudio.dll" />
</ItemGroup>
<ItemGroup>
<Content Include="fmod.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="fmodstudio.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

File diff suppressed because it is too large Load Diff

BIN
Ghost.FMOD/fmod.dll Normal file

Binary file not shown.

BIN
Ghost.FMOD/fmodstudio.dll Normal file

Binary file not shown.

View File

@@ -13,4 +13,8 @@
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Shader\" />
</ItemGroup>
</Project> </Project>

View File

Before

Width:  |  Height:  |  Size: 432 B

After

Width:  |  Height:  |  Size: 432 B

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 637 B

After

Width:  |  Height:  |  Size: 637 B

View File

Before

Width:  |  Height:  |  Size: 456 B

After

Width:  |  Height:  |  Size: 456 B

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows10.0.22621.0</TargetFramework> <TargetFramework>net10.0-windows10.0.22621.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion> <TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>Ghost.UnitTest</RootNamespace> <RootNamespace>Ghost.UnitTest</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>app.manifest</ApplicationManifest>
@@ -56,6 +56,7 @@
<ProjectReference Include="..\Ghost.Editor.Core\Ghost.Editor.Core.csproj" /> <ProjectReference Include="..\Ghost.Editor.Core\Ghost.Editor.Core.csproj" />
<ProjectReference Include="..\Ghost.Engine\Ghost.Engine.csproj" /> <ProjectReference Include="..\Ghost.Engine\Ghost.Engine.csproj" />
<ProjectReference Include="..\Ghost.Entities\Ghost.Entities.csproj" /> <ProjectReference Include="..\Ghost.Entities\Ghost.Entities.csproj" />
<ProjectReference Include="..\Ghost.Test.Core\Ghost.Test.Core.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,5 +1,4 @@
using Ghost.UnitTest.Test; using Ghost.Test.Core;
using Ghost.UnitTest.TestFramework;
using Ghost.UnitTest.Windows; using Ghost.UnitTest.Windows;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer; using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer;
@@ -38,6 +37,6 @@ public partial class UnitTestApp : Application
UITestMethodAttribute.DispatcherQueue = _window.DispatcherQueue; UITestMethodAttribute.DispatcherQueue = _window.DispatcherQueue;
Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.Run(Environment.CommandLine); Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.Run(Environment.CommandLine);
TestRunner.Run<EntityTest>(); //TestRunner.Run<EntityTest>();
} }
} }

View File

@@ -1,9 +1,12 @@
using Ghost.Core.Attributes; global using static TerraFX.Interop.Windows.Windows;
global using static TerraFX.Interop.DirectX.DirectX;
global using static TerraFX.Interop.DirectX.D3D12;
global using static TerraFX.Interop.DirectX.DXGI;
using Ghost.Core.Attributes;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using static TerraFX.Interop.Windows.Windows;
[assembly: InternalsVisibleTo("Ghost.Engine")] [assembly: InternalsVisibleTo("Ghost.Engine")]
[assembly: InternalsVisibleTo("Ghost.Editor")] [assembly: InternalsVisibleTo("Ghost.Editor")]
[assembly: InternalsVisibleTo("Ghost.Editor.Core")] [assembly: InternalsVisibleTo("Ghost.Editor.Core")]

View File

@@ -1,14 +1,14 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data; using Ghost.Graphics.Data;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.LowLevel.Utilities;
using TerraFX.Interop.DirectX; using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows; using TerraFX.Interop.Windows;
using Win32;
using Win32.Graphics.Direct3D; using static TerraFX.Aliases.D3D12_Alias;
using Win32.Graphics.Direct3D12; using static TerraFX.Aliases.DXGI_Alias;
using Win32.Graphics.Dxgi.Common; using static TerraFX.Aliases.D3D_Alias;
using Win32.Numerics;
namespace Ghost.Graphics.D3D12; namespace Ghost.Graphics.D3D12;
@@ -20,7 +20,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
private ComPtr<ID3D12CommandAllocator> _allocator; private ComPtr<ID3D12CommandAllocator> _allocator;
private ComPtr<ID3D12GraphicsCommandList10> _commandList; private ComPtr<ID3D12GraphicsCommandList10> _commandList;
private readonly D3D12PipelineLibrary _stateController; private readonly D3D12PipelineLibrary _pipelineLibrary;
private readonly D3D12ResourceDatabase _resourceDatabase; private readonly D3D12ResourceDatabase _resourceDatabase;
private readonly D3D12ResourceAllocator _resourceAllocator; private readonly D3D12ResourceAllocator _resourceAllocator;
private readonly D3D12DescriptorAllocator _descriptorAllocator; private readonly D3D12DescriptorAllocator _descriptorAllocator;
@@ -48,9 +48,9 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
var commandListType = ConvertCommandBufferType(type); var commandListType = ConvertCommandBufferType(type);
device.NativeDevice->CreateCommandAllocator(commandListType, __uuidof<ID3D12CommandAllocator>(), _allocator.GetVoidAddressOf()); device.NativeDevice->CreateCommandAllocator(commandListType, __uuidof<ID3D12CommandAllocator>(), _allocator.GetVoidAddressOf());
device.NativeDevice->CreateCommandList1(0u, commandListType, CommandListFlags.None, __uuidof<ID3D12GraphicsCommandList10>(), _commandList.GetVoidAddressOf()); device.NativeDevice->CreateCommandList1(0u, commandListType, D3D12_COMMAND_LIST_FLAG_NONE, __uuidof<ID3D12GraphicsCommandList10>(), _commandList.GetVoidAddressOf());
_stateController = stateController; _pipelineLibrary = stateController;
_resourceDatabase = resourceDatabase; _resourceDatabase = resourceDatabase;
_resourceAllocator = resourceAllocator; _resourceAllocator = resourceAllocator;
_descriptorAllocator = descriptorAllocator; _descriptorAllocator = descriptorAllocator;
@@ -113,7 +113,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
ThrowIfNotRecording(); ThrowIfNotRecording();
IncrementCommandCount(); IncrementCommandCount();
var rtvHandles = stackalloc CpuDescriptorHandle[renderTargets.Length]; var rtvHandles = stackalloc D3D12_CPU_DESCRIPTOR_HANDLE[renderTargets.Length];
for (var i = 0; i < renderTargets.Length; i++) for (var i = 0; i < renderTargets.Length; i++)
{ {
var handle = renderTargets[i]; var handle = renderTargets[i];
@@ -126,13 +126,13 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
rtvHandles[i] = _descriptorAllocator.GetCpuHandle(descriptor.rtv); rtvHandles[i] = _descriptorAllocator.GetCpuHandle(descriptor.rtv);
} }
var dsvHandle = stackalloc CpuDescriptorHandle[depthTarget.IsValid ? 1 : 0]; var dsvHandle = stackalloc D3D12_CPU_DESCRIPTOR_HANDLE[depthTarget.IsValid ? 1 : 0];
if (dsvHandle != null) if (dsvHandle != null)
{ {
*dsvHandle = _descriptorAllocator.GetCpuHandle(_resourceDatabase.GetResourceInfo(depthTarget.AsResource()).descriptor.dsv); *dsvHandle = _descriptorAllocator.GetCpuHandle(_resourceDatabase.GetResourceInfo(depthTarget.AsResource()).descriptor.dsv);
} }
_commandList.Get()->OMSetRenderTargets((uint)renderTargets.Length, rtvHandles, Bool32.False, dsvHandle); _commandList.Get()->OMSetRenderTargets((uint)renderTargets.Length, rtvHandles, FALSE, dsvHandle);
} }
public void BeginRenderPass(Handle<Texture> renderTarget, Handle<Texture> depthTarget, Color128 clearColor) public void BeginRenderPass(Handle<Texture> renderTarget, Handle<Texture> depthTarget, Color128 clearColor)
@@ -155,7 +155,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
ThrowIfNotRecording(); ThrowIfNotRecording();
IncrementCommandCount(); IncrementCommandCount();
var d3d12Viewport = new Viewport(viewport.width, viewport.height, viewport.x, viewport.y, viewport.minDepth, viewport.maxDepth); var d3d12Viewport = new D3D12_VIEWPORT(viewport.width, viewport.height, viewport.x, viewport.y, viewport.minDepth, viewport.maxDepth);
_commandList.Get()->RSSetViewports(1, &d3d12Viewport); _commandList.Get()->RSSetViewports(1, &d3d12Viewport);
} }
@@ -165,7 +165,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
ThrowIfNotRecording(); ThrowIfNotRecording();
IncrementCommandCount(); IncrementCommandCount();
var d3d12Rect = new Rect((int)rect.left, (int)rect.top, (int)rect.right, (int)rect.bottom); var d3d12Rect = new RECT((int)rect.left, (int)rect.top, (int)rect.right, (int)rect.bottom);
_commandList.Get()->RSSetScissorRects(1, &d3d12Rect); _commandList.Get()->RSSetScissorRects(1, &d3d12Rect);
} }
@@ -176,9 +176,10 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
IncrementCommandCount(); IncrementCommandCount();
var d3d12Resource = _resourceDatabase.GetResource(resource); var d3d12Resource = _resourceDatabase.GetResource(resource);
var barrier = D3D12_RESOURCE_BARRIER.InitTransition(d3d12Resource,
_commandList.Get()->ResourceBarrierTransition(d3d12Resource,
before.ToD3D12States(), after.ToD3D12States()); before.ToD3D12States(), after.ToD3D12States());
_commandList.Get()->ResourceBarrier(1, &barrier);
} }
public void SetRootSignature(IRootSignature rootSignature) public void SetRootSignature(IRootSignature rootSignature)
@@ -200,7 +201,7 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
IncrementCommandCount(); IncrementCommandCount();
var pResource = _resourceDatabase.GetResource(buffer.AsResource()); var pResource = _resourceDatabase.GetResource(buffer.AsResource());
var vbView = new VertexBufferView var vbView = new D3D12_VERTEX_BUFFER_VIEW
{ {
BufferLocation = pResource->GetGPUVirtualAddress() + offset, BufferLocation = pResource->GetGPUVirtualAddress() + offset,
SizeInBytes = (uint)(pResource->GetDesc().Width - offset), SizeInBytes = (uint)(pResource->GetDesc().Width - offset),
@@ -217,11 +218,11 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
IncrementCommandCount(); IncrementCommandCount();
var pResource = _resourceDatabase.GetResource(buffer.AsResource()); var pResource = _resourceDatabase.GetResource(buffer.AsResource());
var ibView = new IndexBufferView var ibView = new D3D12_INDEX_BUFFER_VIEW
{ {
BufferLocation = pResource->GetGPUVirtualAddress() + offset, BufferLocation = pResource->GetGPUVirtualAddress() + offset,
SizeInBytes = (uint)(pResource->GetDesc().Width - offset), SizeInBytes = (uint)(pResource->GetDesc().Width - offset),
Format = type == IndexType.UInt16 ? Format.R16Uint : Format.R32Uint Format = type == IndexType.UInt16 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT
}; };
_commandList.Get()->IASetIndexBuffer(&ibView); _commandList.Get()->IASetIndexBuffer(&ibView);
@@ -256,36 +257,36 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
ref var materialRef = ref _resourceDatabase.GetMaterialReference(material); ref var materialRef = ref _resourceDatabase.GetMaterialReference(material);
ref var shaderRef = ref _resourceDatabase.GetShaderReference(materialRef.Shader); ref var shaderRef = ref _resourceDatabase.GetShaderReference(materialRef.Shader);
var shaderPipeline = _stateController.GetShaderPipeline(materialRef.Shader); var shaderPipeline = _pipelineLibrary.GetShaderPipeline(materialRef.Shader);
if (shaderPipeline is not D3D12ShaderPipeline d3d12Pipeline) if (shaderPipeline is not D3D12ShaderPipeline d3d12Pipeline)
{ {
throw new InvalidOperationException("Shader pipeline is not compiled or invalid"); throw new InvalidOperationException("Shader pipeline is not compiled or invalid");
} }
_commandList.Get()->SetPipelineState(d3d12Pipeline.pipelineState.Get()); _commandList.Get()->SetPipelineState(d3d12Pipeline.pipelineState.Get());
_commandList.Get()->SetGraphicsRootSignature(d3d12Pipeline.rootSignature.Get()); _commandList.Get()->SetGraphicsRootSignature(_pipelineLibrary.DefaultRootSignature);
// Set descriptor heaps - CRUCIAL: Use the specialized bindless heap for SM 6.6 // Set descriptor heaps - CRUCIAL: Use the specialized bindless heap for SM 6.6
var heaps = stackalloc ID3D12DescriptorHeap*[2]; var heaps = stackalloc ID3D12DescriptorHeap*[2];
heaps[0] = _descriptorAllocator.GetCbvSrvUavHeap(); // Specialized bindless heap heaps[0] = _descriptorAllocator.GetCbvSrvUavHeap(); // Bindless resource heap
heaps[1] = d3d12Pipeline.samplerHeap.Get(); // Sampler heap from shader heaps[1] = _descriptorAllocator.GetSamplerHeap(); // Bindless sampler heap
_commandList.Get()->SetDescriptorHeaps(2, heaps); _commandList.Get()->SetDescriptorHeaps(2, heaps);
var rootParamIndex = 0u; var rootParamIndex = 0u;
foreach (var cbufferInfo in shaderRef.ConstantBuffers) foreach (var cbufferInfo in shaderRef.PerMaterialBufferInfo)
{ {
ref var cache = ref materialRef._cBufferCaches[(int)cbufferInfo.RegisterSlot]; ref var cache = ref materialRef._materialPropertiesCache[(int)cbufferInfo.RegisterSlot];
var resource = _resourceDatabase.GetResource(cache.GpuResource.AsResource()); var resource = _resourceDatabase.GetResource(cache.GpuResource.AsResource());
_commandList.Get()->SetGraphicsRootConstantBufferView(rootParamIndex++, resource->GetGPUVirtualAddress()); _commandList.Get()->SetGraphicsRootConstantBufferView(rootParamIndex++, resource->GetGPUVirtualAddress());
} }
var samplerGpuHandle = d3d12Pipeline.samplerHeap.Get()->GetGPUDescriptorHandleForHeapStart(); var samplerGpuHandle = _descriptorAllocator.GetSamplerHeap()->GetGPUDescriptorHandleForHeapStart();
_commandList.Get()->SetGraphicsRootDescriptorTable(rootParamIndex, samplerGpuHandle); _commandList.Get()->SetGraphicsRootDescriptorTable(rootParamIndex, samplerGpuHandle);
// For fully bindless rendering, we don't use the Input Assembler stage // For fully bindless rendering, we don't use the Input Assembler stage
// Instead, we use instanced drawing where each "instance" represents a triangle // Instead, we use instanced drawing where each "instance" represents a triangle
// The shader will use SV_InstanceID to index into the index buffer and then into the vertex buffer // The shader will use SV_InstanceID to index into the index buffer and then into the vertex buffer
_commandList.Get()->IASetPrimitiveTopology(PrimitiveTopology.TriangleList); _commandList.Get()->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// Draw without vertex/index buffers - use instanced drawing // Draw without vertex/index buffers - use instanced drawing
// Each instance represents a triangle (3 vertices) // Each instance represents a triangle (3 vertices)
@@ -341,10 +342,10 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
var uploadHandle = _resourceAllocator.CreateUploadBuffer(requiredSize); var uploadHandle = _resourceAllocator.CreateUploadBuffer(requiredSize);
var pUploadResource = _resourceDatabase.GetResource(uploadHandle.AsResource()); var pUploadResource = _resourceDatabase.GetResource(uploadHandle.AsResource());
var d3d12Subresources = stackalloc SubresourceData[subresources.Length]; var d3d12Subresources = stackalloc D3D12_SUBRESOURCE_DATA[subresources.Length];
for (var i = 0; i < subresources.Length; i++) for (var i = 0; i < subresources.Length; i++)
{ {
d3d12Subresources[i] = new SubresourceData d3d12Subresources[i] = new D3D12_SUBRESOURCE_DATA
{ {
pData = subresources[i].pData, pData = subresources[i].pData,
RowPitch = subresources[i].rowPitch, RowPitch = subresources[i].rowPitch,
@@ -385,13 +386,13 @@ internal unsafe class D3D12CommandBuffer : ICommandBuffer
} }
} }
private static CommandListType ConvertCommandBufferType(CommandBufferType type) private static D3D12_COMMAND_LIST_TYPE ConvertCommandBufferType(CommandBufferType type)
{ {
return type switch return type switch
{ {
CommandBufferType.Graphics => CommandListType.Direct, CommandBufferType.Graphics => D3D12_COMMAND_LIST_TYPE_DIRECT,
CommandBufferType.Compute => CommandListType.Compute, CommandBufferType.Compute => D3D12_COMMAND_LIST_TYPE_COMPUTE,
CommandBufferType.Copy => CommandListType.Copy, CommandBufferType.Copy => D3D12_COMMAND_LIST_TYPE_COPY,
_ => throw new ArgumentException($"Unknown command buffer type: {type}") _ => throw new ArgumentException($"Unknown command buffer type: {type}")
}; };
} }

View File

@@ -1,8 +1,7 @@
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using System.Runtime.CompilerServices; using TerraFX.Interop.DirectX;
using System.Runtime.InteropServices; using TerraFX.Interop.Windows;
using Win32;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12; namespace Ghost.Graphics.D3D12;
@@ -29,11 +28,11 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
_fenceEvent = new AutoResetEvent(false); _fenceEvent = new AutoResetEvent(false);
_fenceValue = 0; _fenceValue = 0;
var queueDesc = new CommandQueueDescription var queueDesc = new D3D12_COMMAND_QUEUE_DESC
{ {
Type = ConvertCommandQueueType(type), Type = ConvertCommandQueueType(type),
Priority = (int)CommandQueuePriority.Normal, Priority = (int)D3D12_COMMAND_QUEUE_PRIORITY.D3D12_COMMAND_QUEUE_PRIORITY_NORMAL,
Flags = CommandQueueFlags.None, Flags = D3D12_COMMAND_QUEUE_FLAGS.D3D12_COMMAND_QUEUE_FLAG_NONE,
}; };
fixed (void* queuePtr = &_queue) fixed (void* queuePtr = &_queue)
@@ -41,7 +40,7 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
pDevice->CreateCommandQueue(&queueDesc, __uuidof<ID3D12CommandQueue>(), (void**)queuePtr); pDevice->CreateCommandQueue(&queueDesc, __uuidof<ID3D12CommandQueue>(), (void**)queuePtr);
} }
pDevice->CreateFence(0, FenceFlags.None, __uuidof<ID3D12Fence1>(), _fence.GetVoidAddressOf()); pDevice->CreateFence(0, D3D12_FENCE_FLAGS.D3D12_FENCE_FLAG_NONE, __uuidof<ID3D12Fence1>(), _fence.GetVoidAddressOf());
} }
~D3D12CommandQueue() ~D3D12CommandQueue()
@@ -120,8 +119,8 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
{ {
if (_fence.Get()->GetCompletedValue() < value) if (_fence.Get()->GetCompletedValue() < value)
{ {
var handle = new Handle((void*)_fenceEvent.SafeWaitHandle.DangerousGetHandle()); var handle = new HANDLE((void*)_fenceEvent.SafeWaitHandle.DangerousGetHandle());
if (_fence.Get()->SetEventOnCompletion(value, handle).Success) if (_fence.Get()->SetEventOnCompletion(value, handle).SUCCEEDED)
{ {
_fenceEvent.WaitOne(); _fenceEvent.WaitOne();
} }
@@ -139,13 +138,13 @@ internal unsafe class D3D12CommandQueue : ICommandQueue
WaitForValue(fenceValue); WaitForValue(fenceValue);
} }
private static CommandListType ConvertCommandQueueType(CommandQueueType type) private static D3D12_COMMAND_LIST_TYPE ConvertCommandQueueType(CommandQueueType type)
{ {
return type switch return type switch
{ {
CommandQueueType.Graphics => CommandListType.Direct, CommandQueueType.Graphics => D3D12_COMMAND_LIST_TYPE.D3D12_COMMAND_LIST_TYPE_DIRECT,
CommandQueueType.Compute => CommandListType.Compute, CommandQueueType.Compute => D3D12_COMMAND_LIST_TYPE.D3D12_COMMAND_LIST_TYPE_COMPUTE,
CommandQueueType.Copy => CommandListType.Copy, CommandQueueType.Copy => D3D12_COMMAND_LIST_TYPE.D3D12_COMMAND_LIST_TYPE_COPY,
_ => throw new ArgumentException($"Unknown command queue type: {type}") _ => throw new ArgumentException($"Unknown command queue type: {type}")
}; };
} }

View File

@@ -1,6 +1,8 @@
using Win32; using Ghost.Graphics.D3D12.Utilities;
using Win32.Graphics.Direct3D12; using TerraFX.Interop.DirectX;
using Win32.Graphics.Dxgi; using TerraFX.Interop.Windows;
using static TerraFX.Aliases.DXGI_Alias;
namespace Ghost.Graphics.D3D12; namespace Ghost.Graphics.D3D12;
@@ -19,13 +21,13 @@ internal unsafe class D3D12DebugLayer
_dxgiDebug.Get()->EnableLeakTrackingForThread(); _dxgiDebug.Get()->EnableLeakTrackingForThread();
DXGIGetDebugInterface1(0u, __uuidof<IDXGIInfoQueue>(), _dxgiInfoQueue.GetVoidAddressOf()); DXGIGetDebugInterface1(0u, __uuidof<IDXGIInfoQueue>(), _dxgiInfoQueue.GetVoidAddressOf());
_dxgiInfoQueue.Get()->SetBreakOnSeverity(DXGI_DEBUG_ALL, InfoQueueMessageSeverity.Error, true); _dxgiInfoQueue.Get()->SetBreakOnSeverity(DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_ERROR, true);
_dxgiInfoQueue.Get()->SetBreakOnSeverity(DXGI_DEBUG_ALL, InfoQueueMessageSeverity.Corruption, true); _dxgiInfoQueue.Get()->SetBreakOnSeverity(DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_CORRUPTION, true);
} }
public void Dispose() public void Dispose()
{ {
_dxgiDebug.Get()->ReportLiveObjects(DXGI_DEBUG_ALL, ReportLiveObjectFlags.All); _dxgiDebug.Get()->ReportLiveObjects(DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_ALL | DXGI_DEBUG_RLO_IGNORE_INTERNAL);
_d3d12Debug.Dispose(); _d3d12Debug.Dispose();
_dxgiDebug.Dispose(); _dxgiDebug.Dispose();

View File

@@ -1,7 +1,9 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Graphics.D3D12.Utilities; using Ghost.Graphics.D3D12.Utilities;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Win32.Graphics.Direct3D12; using TerraFX.Interop.DirectX;
using static TerraFX.Aliases.D3D12_Alias;
namespace Ghost.Graphics.D3D12; namespace Ghost.Graphics.D3D12;
@@ -19,10 +21,10 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable
public unsafe D3D12DescriptorAllocator(D3D12RenderDevice device, int initialRtvCount = 256, int initialDsvCount = 256, int initialSrvCount = 200_000, int initialSamplerCount = 256) public unsafe D3D12DescriptorAllocator(D3D12RenderDevice device, int initialRtvCount = 256, int initialDsvCount = 256, int initialSrvCount = 200_000, int initialSamplerCount = 256)
{ {
_rtvHeap = new D3D12DescriptorHeap("rtv", device, DescriptorHeapType.Rtv, initialRtvCount, initialRtvCount / 2); _rtvHeap = new D3D12DescriptorHeap("rtv", device, D3D12_DESCRIPTOR_HEAP_TYPE_RTV, initialRtvCount, initialRtvCount / 2);
_dsvHeap = new D3D12DescriptorHeap("dsv", device, DescriptorHeapType.Dsv, initialDsvCount, initialDsvCount / 2); _dsvHeap = new D3D12DescriptorHeap("dsv", device, D3D12_DESCRIPTOR_HEAP_TYPE_DSV, initialDsvCount, initialDsvCount / 2);
_cbvSrvUavHeap = new D3D12DescriptorHeap("srv", device, DescriptorHeapType.CbvSrvUav, initialSrvCount, initialSrvCount /2); _cbvSrvUavHeap = new D3D12DescriptorHeap("srv", device, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, initialSrvCount, initialSrvCount /2);
_samplerHeap = new D3D12DescriptorHeap("sampler", device, DescriptorHeapType.Sampler, initialSamplerCount, initialSamplerCount); _samplerHeap = new D3D12DescriptorHeap("sampler", device, D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, initialSamplerCount, initialSamplerCount);
} }
~D3D12DescriptorAllocator() ~D3D12DescriptorAllocator()
@@ -66,7 +68,7 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public CpuDescriptorHandle GetCpuHandle(Identifier<RTVDesc> descriptor) public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(Identifier<RTVDesc> descriptor)
{ {
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
return _rtvHeap.GetCpuHandle(descriptor.value); return _rtvHeap.GetCpuHandle(descriptor.value);
@@ -151,7 +153,7 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable
return descriptors; return descriptors;
} }
public CpuDescriptorHandle GetCpuHandle(Identifier<DSVDesc> descriptor) public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(Identifier<DSVDesc> descriptor)
{ {
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
return _dsvHeap.GetCpuHandle(descriptor.value); return _dsvHeap.GetCpuHandle(descriptor.value);
@@ -236,19 +238,19 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable
return descriptors; return descriptors;
} }
public CpuDescriptorHandle GetCpuHandle(Identifier<CbvSrvUavDesc> descriptor) public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(Identifier<CbvSrvUavDesc> descriptor)
{ {
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
return _cbvSrvUavHeap.GetCpuHandle(descriptor.value); return _cbvSrvUavHeap.GetCpuHandle(descriptor.value);
} }
public CpuDescriptorHandle GetCpuHandleShaderVisible(Identifier<CbvSrvUavDesc> descriptor) public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandleShaderVisible(Identifier<CbvSrvUavDesc> descriptor)
{ {
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
return _cbvSrvUavHeap.GetCpuHandleShaderVisible(descriptor.value); return _cbvSrvUavHeap.GetCpuHandleShaderVisible(descriptor.value);
} }
public GpuDescriptorHandle GetGpuHandle(Identifier<CbvSrvUavDesc> descriptor) public D3D12_GPU_DESCRIPTOR_HANDLE GetGpuHandle(Identifier<CbvSrvUavDesc> descriptor)
{ {
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
return _cbvSrvUavHeap.GetGpuHandle(descriptor.value); return _cbvSrvUavHeap.GetGpuHandle(descriptor.value);
@@ -333,19 +335,19 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable
return descriptors; return descriptors;
} }
public CpuDescriptorHandle GetCpuHandle(Identifier<SamplerDesc> descriptor) public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(Identifier<SamplerDesc> descriptor)
{ {
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
return _samplerHeap.GetCpuHandle(descriptor.value); return _samplerHeap.GetCpuHandle(descriptor.value);
} }
public CpuDescriptorHandle GetCpuHandleShaderVisible(Identifier<SamplerDesc> descriptor) public D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandleShaderVisible(Identifier<SamplerDesc> descriptor)
{ {
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
return _samplerHeap.GetCpuHandleShaderVisible(descriptor.value); return _samplerHeap.GetCpuHandleShaderVisible(descriptor.value);
} }
public GpuDescriptorHandle GetGpuHandle(Identifier<SamplerDesc> descriptor) public D3D12_GPU_DESCRIPTOR_HANDLE GetGpuHandle(Identifier<SamplerDesc> descriptor)
{ {
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
return _samplerHeap.GetGpuHandle(descriptor.value); return _samplerHeap.GetGpuHandle(descriptor.value);
@@ -370,7 +372,7 @@ internal unsafe class D3D12DescriptorAllocator : IDisposable
#endregion #endregion
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Release(D3D12ResourceDescriptor descriptor) public void Release(ResourceViewGroup descriptor)
{ {
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);

View File

@@ -35,7 +35,7 @@ internal unsafe class D3D12GraphicsEngine : IGraphicsEngine
_resourceDatabase = new(_descriptorAllocator); _resourceDatabase = new(_descriptorAllocator);
_resourceAllocator = new(renderSystem, _device, _descriptorAllocator, _resourceDatabase); _resourceAllocator = new(renderSystem, _device, _descriptorAllocator, _resourceDatabase);
_stateController = new(_device, _resourceDatabase); _stateController = new(_device, _resourceDatabase, null);
_copyCommandBuffer = new( _copyCommandBuffer = new(
_device, _device,
_stateController, _stateController,

View File

@@ -1,15 +1,17 @@
#undef USE_TRANDITIONAL_BINDLESS #undef USE_TRADITIONAL_BINDLESS
using Ghost.Core; using Ghost.Core;
using Ghost.Graphics.D3D12.Utilities; using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data; using Ghost.Graphics.Data;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Ghost.Graphics.Utilities;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Win32; using TerraFX.Interop.DirectX;
using Win32.Graphics.Direct3D; using TerraFX.Interop.Windows;
using Win32.Graphics.Direct3D12; using Misaki.HighPerformance.LowLevel.Utilities;
using Win32.Graphics.Dxgi.Common;
using static TerraFX.Aliases.D3D12_Alias;
using static TerraFX.Aliases.D3D_Alias;
using static TerraFX.Aliases.DXGI_Alias;
namespace Ghost.Graphics.D3D12; namespace Ghost.Graphics.D3D12;
@@ -36,47 +38,6 @@ internal class D3D12ShaderPipeline : IShaderPipeline, IDisposable
} }
} }
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal unsafe struct D3D12GraphicsPipelineStream
{
public PipelineStateSubObjectType rootSignatureType;
public ID3D12RootSignature* pRootSignature;
public PipelineStateSubObjectType vsType;
public ShaderBytecode vs;
public PipelineStateSubObjectType psType;
public ShaderBytecode ps;
public PipelineStateSubObjectType rasterizerType;
public RasterizerDescription rasterizer;
public PipelineStateSubObjectType blendType;
public BlendDescription blend;
public PipelineStateSubObjectType depthStencilType;
public DepthStencilDescription depthStencil;
public PipelineStateSubObjectType topologyType;
public PrimitiveTopologyType primitiveTopology;
public PipelineStateSubObjectType rtvFormatType;
public RtFormatArray rtvFormats;
public PipelineStateSubObjectType dsvFormatType;
public Format dsvFormat;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal unsafe struct ComputePipelineStream
{
public PipelineStateSubObjectType rootSignatureType;
public ID3D12RootSignature* pRootSignature;
public PipelineStateSubObjectType csType;
public ShaderBytecode cs;
}
internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
{ {
private readonly D3D12RenderDevice _device; private readonly D3D12RenderDevice _device;
@@ -87,6 +48,8 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
private readonly Dictionary<Identifier<Shader>, D3D12ShaderPipeline> _shaderPipelines; private readonly Dictionary<Identifier<Shader>, D3D12ShaderPipeline> _shaderPipelines;
public ID3D12RootSignature* DefaultRootSignature => _defaultRootSignature.Get();
public D3D12PipelineLibrary(D3D12RenderDevice device, D3D12ResourceDatabase resourceDatabase, string? cachePath) public D3D12PipelineLibrary(D3D12RenderDevice device, D3D12ResourceDatabase resourceDatabase, string? cachePath)
{ {
_device = device; _device = device;
@@ -115,7 +78,7 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
private void CreateDefaultRootSignature() private void CreateDefaultRootSignature()
{ {
const int rootParamCount = const int rootParamCount =
#if USE_TRANDITIONAL_BINDLESS #if USE_TRADITIONAL_BINDLESS
6 6
#else #else
4 4
@@ -124,93 +87,85 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
_defaultRootSignature = default; _defaultRootSignature = default;
// The layout of the root signature is:
// - Global buffer (b0)
// - Per-view buffer (b1)
// - Per-object buffer (b2)
// - Per-instance buffer (b3)
// - Descriptor table for bindless textures (t0)
// - Descriptor table for bindless samplers (s0)
// NOTE: Since we are targeting SM 6.6, we can use ResourceDescriptorHeap and SamplerDescriptorHeap directly without needing to set up descriptor tables. // 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 RootParameter1[rootParamCount]; rootParameters[0] = new D3D12_ROOT_PARAMETER1
rootParameters[0] = new RootParameter1
{ {
ParameterType = RootParameterType.Cbv, ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV,
ShaderVisibility = ShaderVisibility.All, ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL,
Descriptor = new RootDescriptor1(0, 0), // b0 Descriptor = new D3D12_ROOT_DESCRIPTOR1(0, 0), // b0
}; };
rootParameters[1] = new RootParameter1 rootParameters[1] = new D3D12_ROOT_PARAMETER1
{ {
ParameterType = RootParameterType.Cbv, ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV,
ShaderVisibility = ShaderVisibility.All, ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL,
Descriptor = new RootDescriptor1(1, 0), // b1 Descriptor = new D3D12_ROOT_DESCRIPTOR1(1, 0), // b1
}; };
rootParameters[2] = new RootParameter1 rootParameters[2] = new D3D12_ROOT_PARAMETER1
{ {
ParameterType = RootParameterType.Cbv, ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV,
ShaderVisibility = ShaderVisibility.All, ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL,
Descriptor = new RootDescriptor1(2, 0), // b2 Descriptor = new D3D12_ROOT_DESCRIPTOR1(2, 0), // b2
}; };
rootParameters[3] = new RootParameter1 rootParameters[3] = new D3D12_ROOT_PARAMETER1
{ {
ParameterType = RootParameterType.Cbv, ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV,
ShaderVisibility = ShaderVisibility.All, ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL,
Descriptor = new RootDescriptor1(3, 0), // b3 Descriptor = new D3D12_ROOT_DESCRIPTOR1(3, 0), // b3
}; };
#if USE_TRANDITIONAL_BINDLESS #if USE_TRADITIONAL_BINDLESS
// Descriptor table for bindless textures // Descriptor table for bindless textures
var srvRange = new DescriptorRange1( var srvRange = new D3D12_DESCRIPTOR_RANGE1(
DescriptorRangeType.Srv, D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
~0u, ~0u,
0, 0,
0, 0,
DescriptorRangeFlags.DataVolatile); D3D12_DESCRIPTOR_RANGE_FLAGS_DATA_VOLATILE);
rootParameters[4] = new RootParameter1 rootParameters[4] = new D3D12_ROOT_PARAMETER1
{ {
ParameterType = RootParameterType.DescriptorTable, ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
ShaderVisibility = ShaderVisibility.All, ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL,
DescriptorTable = new RootDescriptorTable1(1, &srvRange) DescriptorTable = new D3D12_ROOT_DESCRIPTOR_TABLE1(1, &srvRange)
}; };
// Descriptor table for bindless samplers // Descriptor table for bindless samplers
var sampRange = new DescriptorRange1( var sampRange = new D3D12_DESCRIPTOR_RANGE1(
DescriptorRangeType.Sampler, D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER,
~0u, ~0u,
0, 0,
0, 0,
DescriptorRangeFlags.DataVolatile); D3D12_DESCRIPTOR_RANGE_FLAGS_DATA_VOLATILE);
rootParameters[5] = new RootParameter1 rootParameters[5] = new D3D12_ROOT_PARAMETER1
{ {
ParameterType = RootParameterType.DescriptorTable, ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
ShaderVisibility = ShaderVisibility.All, ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL,
DescriptorTable = new RootDescriptorTable1(1, &sampRange) DescriptorTable = new D3D12_ROOT_DESCRIPTOR_TABLE1(1, &sampRange)
}; };
#endif #endif
var rootSignatureDesc = new RootSignatureDescription1 var rootSignatureDesc = new D3D12_ROOT_SIGNATURE_DESC1
{ {
NumParameters = rootParamCount, NumParameters = rootParamCount,
pParameters = rootParameters, pParameters = rootParameters,
NumStaticSamplers = 0, NumStaticSamplers = 0,
pStaticSamplers = null, pStaticSamplers = null,
Flags = RootSignatureFlags.AllowInputAssemblerInputLayout Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT
#if !USE_TRANDITIONAL_BINDLESS #if !USE_TRADITIONAL_BINDLESS
| RootSignatureFlags.CbvSrvUavHeapDirectlyIndexed | D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED
| RootSignatureFlags.SamplerHeapDirectlyIndexed | D3D12_ROOT_SIGNATURE_FLAG_SAMPLER_HEAP_DIRECTLY_INDEXED
#endif #endif
}; };
var versionedDesc = new VersionedRootSignatureDescription
var versionedDesc = new D3D12_VERSIONED_ROOT_SIGNATURE_DESC
{ {
Version = RootSignatureVersion.V1_1, Version = D3D_ROOT_SIGNATURE_VERSION_1_1,
Desc_1_1 = rootSignatureDesc Desc_1_1 = rootSignatureDesc
}; };
@@ -218,7 +173,7 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
using ComPtr<ID3DBlob> error = default; using ComPtr<ID3DBlob> error = default;
var serializeResult = D3D12SerializeVersionedRootSignature(&versionedDesc, signature.GetAddressOf(), error.GetAddressOf()); var serializeResult = D3D12SerializeVersionedRootSignature(&versionedDesc, signature.GetAddressOf(), error.GetAddressOf());
if (serializeResult.Failure) if (serializeResult.FAILED)
{ {
var errorMsg = error.Get() != null ? Marshal.PtrToStringAnsi((nint)error.Get()->GetBufferPointer()) : "Unknown error"; var errorMsg = error.Get() != null ? Marshal.PtrToStringAnsi((nint)error.Get()->GetBufferPointer()) : "Unknown error";
throw new InvalidOperationException($"Failed to serialize default root signature: {errorMsg}"); throw new InvalidOperationException($"Failed to serialize default root signature: {errorMsg}");
@@ -230,12 +185,12 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
public void StorePipeline(string psoIdentifier, ID3D12PipelineState* pso) public void StorePipeline(string psoIdentifier, ID3D12PipelineState* pso)
{ {
_library.Get()->StorePipeline(psoIdentifier.AsSpan().GetPointer(), pso); _library.Get()->StorePipeline(psoIdentifier.AsSpan().GetUnsafePtr(), pso);
} }
public void* LoadGraphicsPipeline(string psoIdentifier) public void* LoadGraphicsPipeline(string psoIdentifier)
{ {
if (_library.Get()->LoadGraphicsPipeline(psoIdentifier.AsSpan().GetPointer(), __uuidof<ID3D12PipelineState>(), out var pso).Failure) if (_library.Get()->LoadGraphicsPipeline(psoIdentifier.AsSpan().GetUnsafePtr(), __uuidof<ID3D12PipelineState>(), out var pso).Failure)
{ {
return null; return null;
} }
@@ -266,20 +221,20 @@ internal unsafe class D3D12PipelineLibrary : IPipelineLibrary, IDisposable
// Create PSO from SDL (Shader Definition Language) file // Create PSO from SDL (Shader Definition Language) file
private void CreatePipelineStateObject(D3D12ShaderPipeline shaderPipeline) private void CreatePipelineStateObject(D3D12ShaderPipeline shaderPipeline)
{ {
var psoDesc = new GraphicsPipelineStateDescription var psoDesc = new D3D12_GRAPHICS_PIPELINE_STATE_DESC
{ {
pRootSignature = _defaultRootSignature.Get(), pRootSignature = _defaultRootSignature.Get(),
VS = new ShaderBytecode(shaderPipeline.vsResult.bytecode.GetUnsafePtr(), (nuint)shaderPipeline.vsResult.bytecode.Count), VS = new D3D12_SHADER_BYTECODE(shaderPipeline.vsResult.bytecode.GetUnsafePtr(), (nuint)shaderPipeline.vsResult.bytecode.Count),
PS = new ShaderBytecode(shaderPipeline.psResult.bytecode.GetUnsafePtr(), (nuint)shaderPipeline.vsResult.bytecode.Count), PS = new D3D12_SHADER_BYTECODE(shaderPipeline.psResult.bytecode.GetUnsafePtr(), (nuint)shaderPipeline.psResult.bytecode.Count),
InputLayout = D3D12PipelineResource.InputLayoutDescription, InputLayout = D3D12PipelineResource.InputLayoutDescription,
RasterizerState = RasterizerDescription.CullNone, RasterizerState = D3D12_RASTERIZER_DESC.CULL_NONE,
BlendState = BlendDescription.Opaque, BlendState = D3D12_BLEND_DESC.OPAQUE,
DepthStencilState = DepthStencilDescription.Default, DepthStencilState = D3D12_DEPTH_STENCIL_DESC.DEFAULT,
SampleMask = uint.MaxValue, SampleMask = uint.MaxValue,
PrimitiveTopologyType = PrimitiveTopologyType.Triangle, PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
NumRenderTargets = 1, NumRenderTargets = 1,
SampleDesc = new SampleDescription(1, 0), SampleDesc = new DXGI_SAMPLE_DESC(1, 0),
DSVFormat = Format.Unknown, DSVFormat = DXGI_FORMAT_UNKNOWN,
}; };
psoDesc.RTVFormats[0] = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT; psoDesc.RTVFormats[0] = D3D12PipelineResource.SWAP_CHAIN_BACK_BUFFER_FORMAT;

View File

@@ -1,10 +1,9 @@
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using TerraFX.Interop.DirectX; using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows; using TerraFX.Interop.Windows;
using Win32; using static TerraFX.Aliases.D3D_Alias;
using Win32.Graphics.Direct3D; using static TerraFX.Aliases.DXGI_Alias;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi;
namespace Ghost.Graphics.D3D12; namespace Ghost.Graphics.D3D12;
@@ -48,27 +47,27 @@ internal unsafe class D3D12RenderDevice : IRenderDevice
private void InitializeDevice() private void InitializeDevice()
{ {
#if DEBUG #if DEBUG
CreateDXGIFactory2(true, __uuidof<IDXGIFactory7>(), _dxgiFactory.GetVoidAddressOf()); CreateDXGIFactory2(TRUE, __uuidof<IDXGIFactory7>(), _dxgiFactory.GetVoidAddressOf());
#else #else
CreateDXGIFactory2(false, __uuidof<IDXGIFactory7>(), _dxgiFactory.GetVoidAddressOf()); CreateDXGIFactory2(FALSE, __uuidof<IDXGIFactory7>(), _dxgiFactory.GetVoidAddressOf());
#endif #endif
using ComPtr<IDXGIAdapter1> adapter = default; using ComPtr<IDXGIAdapter1> adapter = default;
for (uint adapterIndex = 0; for (uint adapterIndex = 0;
_dxgiFactory.Get()->EnumAdapterByGpuPreference(adapterIndex, GpuPreference.HighPerformance, __uuidof<IDXGIAdapter1>(), adapter.ReleaseAndGetVoidAddressOf()).Success; _dxgiFactory.Get()->EnumAdapterByGpuPreference(adapterIndex, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, __uuidof<IDXGIAdapter1>(), adapter.ReleaseAndGetVoidAddressOf()).SUCCEEDED;
adapterIndex++) adapterIndex++)
{ {
AdapterDescription1 desc = default; DXGI_ADAPTER_DESC1 desc = default;
adapter.Get()->GetDesc1(&desc); adapter.Get()->GetDesc1(&desc);
// Don't select the Basic Render Driver adapter. // Don't select the Basic Render Driver adapter.
if ((desc.Flags & AdapterFlags.Software) != AdapterFlags.None) if (desc.Flags.HasFlag(DXGI_ADAPTER_FLAG_SOFTWARE))
{ {
continue; continue;
} }
if (D3D12CreateDevice((IUnknown*)adapter.Get(), FeatureLevel.Level_12_0, __uuidof<ID3D12Device14>(), _device.GetVoidAddressOf()).Success) if (D3D12CreateDevice((IUnknown*)adapter.Get(), D3D_FEATURE_LEVEL_12_0, __uuidof<ID3D12Device14>(), _device.GetVoidAddressOf()).SUCCEEDED)
{ {
_adapter = adapter.Move(); _adapter = adapter.Move();
break; break;

View File

@@ -1,13 +1,15 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.Graphics.Data; using Ghost.Graphics.Data;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Win32.Graphics.D3D12MemoryAllocator; using TerraFX.Interop.DirectX;
using Win32.Graphics.Direct3D12; using TerraFX.Interop.Windows;
using Win32.Graphics.Dxgi; using static TerraFX.Aliases.D3D12_Alias;
using Win32.Graphics.Dxgi.Common; using static TerraFX.Aliases.D3D12MA_Alias;
using static Win32.Graphics.D3D12MemoryAllocator.Apis; using static TerraFX.Aliases.DXGI_Alias;
using static TerraFX.Interop.DirectX.D3D12MemAlloc;
namespace Ghost.Graphics.D3D12; namespace Ghost.Graphics.D3D12;
@@ -17,9 +19,9 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
private const uint _MAX_TEXTURE2D_DIMENSION = 16384u; private const uint _MAX_TEXTURE2D_DIMENSION = 16384u;
private const uint _MAX_TEXTURE3D_DIMENSION = 2048u; private const uint _MAX_TEXTURE3D_DIMENSION = 2048u;
private readonly ID3D12Device14* _device; private ComPtr<D3D12MA_Allocator> _allocator;
private readonly Allocator _allocator; private readonly D3D12RenderDevice _device;
private readonly RenderSystem _renderSystem; private readonly RenderSystem _renderSystem;
private readonly D3D12DescriptorAllocator _descriptorAllocator; private readonly D3D12DescriptorAllocator _descriptorAllocator;
private readonly D3D12ResourceDatabase _resourceDatabase; private readonly D3D12ResourceDatabase _resourceDatabase;
@@ -39,16 +41,16 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
public D3D12ResourceAllocator(RenderSystem renderSystem, D3D12RenderDevice device, D3D12DescriptorAllocator descriptorAllocator, D3D12ResourceDatabase resourceDatabase) public D3D12ResourceAllocator(RenderSystem renderSystem, D3D12RenderDevice device, D3D12DescriptorAllocator descriptorAllocator, D3D12ResourceDatabase resourceDatabase)
{ {
var desc = new AllocatorDesc var desc = new D3D12MA_ALLOCATOR_DESC
{ {
pAdapter = (IDXGIAdapter*)device.Adapter, pAdapter = (IDXGIAdapter*)device.Adapter,
pDevice = (ID3D12Device*)device.NativeDevice, pDevice = (ID3D12Device*)device.NativeDevice,
Flags = AllocatorFlags.DefaultPoolsNotZeroed | AllocatorFlags.MSAATexturesAlwaysCommitted Flags = D3D12MA_ALLOCATOR_FLAG_DEFAULT_POOLS_NOT_ZEROED | D3D12MA_ALLOCATOR_FLAG_MSAA_TEXTURES_ALWAYS_COMMITTED,
}; };
CreateAllocator(in desc, out _allocator); D3D12MA_CreateAllocator(&desc, _allocator.GetAddressOf());
_device = device.NativeDevice; _device = device;
_renderSystem = renderSystem; _renderSystem = renderSystem;
_descriptorAllocator = descriptorAllocator; _descriptorAllocator = descriptorAllocator;
_resourceDatabase = resourceDatabase; _resourceDatabase = resourceDatabase;
@@ -78,9 +80,9 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private Handle<GPUResource> TrackResource(ref readonly Allocation allocation, ResourceStates state, D3D12ResourceDescriptor resourceDescriptor, ResourceDesc desc, bool isTemp) private Handle<GPUResource> TrackResource(ComPtr<D3D12MA_Allocation> allocation, D3D12_RESOURCE_STATES state, ResourceViewGroup resourceDescriptor, ResourceDesc desc, bool isTemp)
{ {
var handle = _resourceDatabase.AddResource(in allocation, _renderSystem.CPUFenceValue, D3D12StatesToRHIState(state), resourceDescriptor, desc); var handle = _resourceDatabase.AddResource(allocation, _renderSystem.CPUFenceValue, D3D12StatesToRHIState(state), resourceDescriptor, desc);
if (isTemp) if (isTemp)
{ {
@@ -90,58 +92,266 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
return handle; return handle;
} }
private void CreateSRV(ID3D12Resource* pResource, CpuDescriptorHandle descriptorHandle, Format format, TextureDimension dimension, uint mipLevels, uint arraySize) private D3D12_SHADER_RESOURCE_VIEW_DESC CreateSrvDesc(ID3D12Resource* pResource, bool isCubeMap, uint mipLevels, uint arraySize)
{ {
var srvDesc = new ShaderResourceViewDescription var resourceDesc = pResource->GetDesc();
var srvDesc = new D3D12_SHADER_RESOURCE_VIEW_DESC
{ {
Format = format, Format = resourceDesc.Format,
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
}; };
switch (dimension) switch (resourceDesc.Dimension)
{ {
case TextureDimension.Texture2D: case D3D12_RESOURCE_DIMENSION_BUFFER:
srvDesc.ViewDimension = SrvDimension.Texture2D; srvDesc.ViewDimension = D3D12_SRV_DIMENSION_BUFFER;
srvDesc.Texture2D = new Texture2DSrv srvDesc.Buffer = new D3D12_BUFFER_SRV
{ {
MipLevels = mipLevels, FirstElement = 0,
NumElements = (uint)(resourceDesc.Width / 4),
StructureByteStride = 0,
Flags = D3D12_BUFFER_SRV_FLAG_RAW,
}; };
break; break;
case TextureDimension.Texture3D:
srvDesc.ViewDimension = SrvDimension.Texture3D; case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
srvDesc.Texture3D = new Texture3DSrv if (resourceDesc.DepthOrArraySize > 1)
{ {
MipLevels = 0, srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1DARRAY;
}; srvDesc.Texture1DArray = new D3D12_TEX1D_ARRAY_SRV
break;
case TextureDimension.Texture2DArray:
srvDesc.ViewDimension = SrvDimension.Texture2DArray;
srvDesc.Texture2DArray = new Texture2DArraySrv
{ {
MipLevels = mipLevels, MipLevels = mipLevels,
ArraySize = arraySize, ArraySize = arraySize,
}; };
break; }
case TextureDimension.TextureCube: else
srvDesc.ViewDimension = SrvDimension.TextureCube; {
srvDesc.TextureCube = new TexureCubeSrv srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1D;
srvDesc.Texture1D = new D3D12_TEX1D_SRV
{ {
MipLevels = mipLevels, MipLevels = mipLevels,
}; };
}
break; break;
case TextureDimension.TextureCubeArray:
srvDesc.ViewDimension = SrvDimension.TextureCubeArray; case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
srvDesc.TextureCubeArray = new TexureCubeArraySrv if (resourceDesc.DepthOrArraySize > 1)
{
if (isCubeMap)
{
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBEARRAY;
srvDesc.TextureCubeArray = new D3D12_TEXCUBE_ARRAY_SRV
{ {
MipLevels = mipLevels, MipLevels = mipLevels,
NumCubes = arraySize / 6, NumCubes = arraySize / 6,
}; };
}
else
{
srvDesc.ViewDimension = resourceDesc.SampleDesc.Count > 1 ? D3D12_SRV_DIMENSION_TEXTURE2DMSARRAY : D3D12_SRV_DIMENSION_TEXTURE2DARRAY;
srvDesc.Texture2DArray = new D3D12_TEX2D_ARRAY_SRV
{
MipLevels = mipLevels,
ArraySize = arraySize,
};
}
}
else
{
if (isCubeMap)
{
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE;
srvDesc.TextureCube = new D3D12_TEXCUBE_SRV
{
MipLevels = mipLevels,
};
}
else
{
srvDesc.ViewDimension = resourceDesc.SampleDesc.Count > 1 ? D3D12_SRV_DIMENSION_TEXTURE2DMS : D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D = new D3D12_TEX2D_SRV
{
MipLevels = mipLevels,
};
}
}
break;
case D3D12_RESOURCE_DIMENSION_TEXTURE3D:
srvDesc.Texture3D = new D3D12_TEX3D_SRV
{
MipLevels = mipLevels,
};
break; break;
default: default:
throw new ArgumentException($"Unsupported texture dimension for SRV: {dimension}"); throw new ArgumentException($"Unsupported texture dimension for SRV: {resourceDesc.Dimension}");
} }
_device->CreateShaderResourceView(pResource, &srvDesc, descriptorHandle); return srvDesc;
}
private D3D12_RENDER_TARGET_VIEW_DESC CreateRtvDesc(ID3D12Resource* pResource, uint mipSlice = 0, uint firstArraySlice = 0, uint planeSlice = 0)
{
var resourceDesc = pResource->GetDesc();
var rtvDesc = new D3D12_RENDER_TARGET_VIEW_DESC();
switch (resourceDesc.Dimension)
{
case D3D12_RESOURCE_DIMENSION_BUFFER:
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_BUFFER;
break;
case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
rtvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_RTV_DIMENSION_TEXTURE1DARRAY : D3D12_RTV_DIMENSION_TEXTURE1D;
break;
case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
if (resourceDesc.SampleDesc.Count > 1)
{
rtvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY : D3D12_RTV_DIMENSION_TEXTURE2DMS;
}
else
{
rtvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_RTV_DIMENSION_TEXTURE2DARRAY : D3D12_RTV_DIMENSION_TEXTURE2D;
}
break;
case D3D12_RESOURCE_DIMENSION_TEXTURE3D:
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE3D;
break;
default:
throw new ArgumentException($"Unsupported texture dimension for SRV: {resourceDesc.Dimension}");
}
rtvDesc.Format = resourceDesc.Format;
var isArray =
rtvDesc.ViewDimension == D3D12_RTV_DIMENSION_TEXTURE2DARRAY ||
rtvDesc.ViewDimension == D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY;
var arraySize = 1u;
if (isArray)
{
arraySize = resourceDesc.ArraySize() - firstArraySlice;
}
switch (rtvDesc.ViewDimension)
{
case D3D12_RTV_DIMENSION_BUFFER:
rtvDesc.Buffer.FirstElement = firstArraySlice;
rtvDesc.Buffer.NumElements = arraySize;
break;
case D3D12_RTV_DIMENSION_TEXTURE1D:
rtvDesc.Texture1D.MipSlice = mipSlice;
break;
case D3D12_RTV_DIMENSION_TEXTURE1DARRAY:
rtvDesc.Texture1DArray.MipSlice = mipSlice;
rtvDesc.Texture1DArray.FirstArraySlice = firstArraySlice;
rtvDesc.Texture1DArray.ArraySize = arraySize;
break;
case D3D12_RTV_DIMENSION_TEXTURE2D:
rtvDesc.Texture2D.MipSlice = mipSlice;
rtvDesc.Texture2D.PlaneSlice = planeSlice;
break;
case D3D12_RTV_DIMENSION_TEXTURE2DARRAY:
rtvDesc.Texture2DArray.MipSlice = mipSlice;
rtvDesc.Texture2DArray.FirstArraySlice = firstArraySlice;
rtvDesc.Texture2DArray.ArraySize = arraySize;
rtvDesc.Texture2DArray.PlaneSlice = planeSlice;
break;
case D3D12_RTV_DIMENSION_TEXTURE2DMS:
break;
case D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY:
rtvDesc.Texture2DMSArray.FirstArraySlice = firstArraySlice;
rtvDesc.Texture2DMSArray.ArraySize = arraySize;
break;
case D3D12_RTV_DIMENSION_TEXTURE3D:
rtvDesc.Texture3D.MipSlice = mipSlice;
rtvDesc.Texture3D.FirstWSlice = firstArraySlice;
rtvDesc.Texture3D.WSize = arraySize;
break;
default:
throw new ArgumentException($"Unsupported RTV dimension: {rtvDesc.ViewDimension}");
}
return rtvDesc;
}
private D3D12_DEPTH_STENCIL_VIEW_DESC CreateDsvDesc(ID3D12Resource* pResource, uint mipSlice = 0, uint firstArraySlice = 0, D3D12_DSV_FLAGS flags = D3D12_DSV_FLAG_NONE)
{
var resourceDesc = pResource->GetDesc();
var dsvDesc = new D3D12_DEPTH_STENCIL_VIEW_DESC
{
Flags = flags,
};
switch (resourceDesc.Dimension)
{
case D3D12_RESOURCE_DIMENSION_TEXTURE1D:
dsvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_DSV_DIMENSION_TEXTURE1DARRAY : D3D12_DSV_DIMENSION_TEXTURE1D;
break;
case D3D12_RESOURCE_DIMENSION_TEXTURE2D:
if (resourceDesc.SampleDesc.Count > 1)
{
dsvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY : D3D12_DSV_DIMENSION_TEXTURE2DMS;
}
else
{
dsvDesc.ViewDimension = resourceDesc.DepthOrArraySize > 1 ? D3D12_DSV_DIMENSION_TEXTURE2DARRAY : D3D12_DSV_DIMENSION_TEXTURE2D;
}
break;
}
dsvDesc.Format = resourceDesc.Format;
var isArray =
dsvDesc.ViewDimension == D3D12_DSV_DIMENSION_TEXTURE2DARRAY ||
dsvDesc.ViewDimension == D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY;
var arraySize = 1u;
if (isArray)
{
arraySize = resourceDesc.ArraySize() - firstArraySlice;
}
switch (dsvDesc.ViewDimension)
{
case D3D12_DSV_DIMENSION_TEXTURE1D:
dsvDesc.Texture1D.MipSlice = mipSlice;
break;
case D3D12_DSV_DIMENSION_TEXTURE1DARRAY:
dsvDesc.Texture1DArray.MipSlice = mipSlice;
dsvDesc.Texture1DArray.FirstArraySlice = firstArraySlice;
dsvDesc.Texture1DArray.ArraySize = arraySize;
break;
case D3D12_DSV_DIMENSION_TEXTURE2D:
dsvDesc.Texture2D.MipSlice = mipSlice;
break;
case D3D12_DSV_DIMENSION_TEXTURE2DARRAY:
dsvDesc.Texture2DArray.MipSlice = mipSlice;
dsvDesc.Texture2DArray.FirstArraySlice = firstArraySlice;
dsvDesc.Texture2DArray.ArraySize = arraySize;
break;
case D3D12_DSV_DIMENSION_TEXTURE2DMS:
break;
case D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY:
dsvDesc.Texture2DMSArray.FirstArraySlice = firstArraySlice;
dsvDesc.Texture2DMSArray.ArraySize = arraySize;
break;
default:
break;
}
return dsvDesc;
} }
public Handle<Texture> CreateTexture(ref readonly TextureDesc desc, bool isTemp = false) public Handle<Texture> CreateTexture(ref readonly TextureDesc desc, bool isTemp = false)
@@ -154,33 +364,33 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
var resourceFlags = ConvertTextureUsage(desc.Usage); var resourceFlags = ConvertTextureUsage(desc.Usage);
var resourceDesc = desc.Dimension switch var resourceDesc = desc.Dimension switch
{ {
TextureDimension.Texture2D => ResourceDescription.Tex2D( TextureDimension.Texture2D => D3D12_RESOURCE_DESC.Tex2D(
d3d12Format, d3d12Format,
desc.Width, desc.Width,
desc.Height, desc.Height,
mipLevels: mipLevels, mipLevels: mipLevels,
flags: resourceFlags), flags: resourceFlags),
TextureDimension.Texture3D => ResourceDescription.Tex3D( TextureDimension.Texture3D => D3D12_RESOURCE_DESC.Tex3D(
d3d12Format, d3d12Format,
desc.Width, desc.Width,
desc.Height, desc.Height,
(ushort)desc.Slice, (ushort)desc.Slice,
flags: resourceFlags), flags: resourceFlags),
TextureDimension.TextureCube => ResourceDescription.Tex2D( TextureDimension.TextureCube => D3D12_RESOURCE_DESC.Tex2D(
d3d12Format, d3d12Format,
desc.Width, desc.Width,
desc.Height, desc.Height,
mipLevels: mipLevels, mipLevels: mipLevels,
arraySize: 6, arraySize: 6,
flags: resourceFlags), flags: resourceFlags),
TextureDimension.Texture2DArray => ResourceDescription.Tex2D( TextureDimension.Texture2DArray => D3D12_RESOURCE_DESC.Tex2D(
d3d12Format, d3d12Format,
desc.Width, desc.Width,
desc.Height, desc.Height,
mipLevels: mipLevels, mipLevels: mipLevels,
arraySize: (ushort)desc.Slice, arraySize: (ushort)desc.Slice,
flags: resourceFlags), flags: resourceFlags),
TextureDimension.TextureCubeArray => ResourceDescription.Tex2D( TextureDimension.TextureCubeArray => D3D12_RESOURCE_DESC.Tex2D(
d3d12Format, d3d12Format,
desc.Width, desc.Width,
desc.Height, desc.Height,
@@ -190,57 +400,64 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
_ => throw new ArgumentException($"Unsupported texture dimension: {desc.Dimension}"), _ => throw new ArgumentException($"Unsupported texture dimension: {desc.Dimension}"),
}; };
var allocationDesc = new AllocationDesc var allocationDesc = new D3D12MA_ALLOCATION_DESC
{ {
HeapType = HeapType.Default, HeapType = D3D12_HEAP_TYPE_DEFAULT,
Flags = AllocationFlags.None Flags = D3D12MA_ALLOCATION_FLAG_NONE
}; };
var initialState = DetermineInitialTextureState(desc.Usage); var initialState = DetermineInitialTextureState(desc.Usage);
Allocation allocation = default; ComPtr<D3D12MA_Allocation> allocation = default;
ThrowIfFailed(_allocator.CreateResource(&allocationDesc, in resourceDesc, initialState, null, &allocation, IID_NULL, null)); ThrowIfFailed(_allocator.Get()->CreateResource(&allocationDesc, &resourceDesc, initialState, null, allocation.GetAddressOf(), IID_NULL, null));
var resourceDescriptor = D3D12ResourceDescriptor.Invalid; var resourceDescriptor = ResourceViewGroup.Invalid;
if (desc.Usage.HasFlag(TextureUsage.ShaderResource)) if (desc.Usage.HasFlag(TextureUsage.ShaderResource))
{ {
resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav(isTemp); resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav(isTemp);
var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv); var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv);
CreateSRV(allocation.Resource, cpuHandle, d3d12Format, desc.Dimension, mipLevels, desc.Slice); var isCubeMap = desc.Dimension == TextureDimension.TextureCube || desc.Dimension == TextureDimension.TextureCubeArray;
var srvDesc = CreateSrvDesc(allocation.Get()->GetResource(), isCubeMap, mipLevels, desc.Slice);
_device.NativeDevice->CreateShaderResourceView(allocation.Get()->GetResource(), &srvDesc, cpuHandle);
} }
if (desc.Usage.HasFlag(TextureUsage.RenderTarget)) if (desc.Usage.HasFlag(TextureUsage.RenderTarget))
{ {
resourceDescriptor.rtv = _descriptorAllocator.AllocateRTV(isTemp); resourceDescriptor.rtv = _descriptorAllocator.AllocateRTV(isTemp);
var rtvDesc = new RenderTargetViewDescription(allocation.Resource); var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.rtv);
_device->CreateRenderTargetView(allocation.Resource, &rtvDesc, _descriptorAllocator.GetCpuHandle(resourceDescriptor.rtv)); var rtvDesc = CreateRtvDesc(allocation.Get()->GetResource());
_device.NativeDevice->CreateRenderTargetView(allocation.Get()->GetResource(), &rtvDesc, cpuHandle);
} }
if (desc.Usage.HasFlag(TextureUsage.DepthStencil)) if (desc.Usage.HasFlag(TextureUsage.DepthStencil))
{ {
resourceDescriptor.dsv = _descriptorAllocator.AllocateDSV(isTemp); resourceDescriptor.dsv = _descriptorAllocator.AllocateDSV(isTemp);
var dsvDesc = new DepthStencilViewDescription(allocation.Resource); var cpuHandle = _descriptorAllocator.GetCpuHandle(resourceDescriptor.dsv);
_device->CreateDepthStencilView(allocation.Resource, &dsvDesc, _descriptorAllocator.GetCpuHandle(resourceDescriptor.dsv)); var dsvDesc = CreateDsvDesc(allocation.Get()->GetResource());
_device.NativeDevice->CreateDepthStencilView(allocation.Get()->GetResource(), &dsvDesc, cpuHandle);
} }
if (desc.Usage.HasFlag(TextureUsage.UnorderedAccess)) if (desc.Usage.HasFlag(TextureUsage.UnorderedAccess))
{ {
resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav(isTemp); resourceDescriptor.uav = _descriptorAllocator.AllocateCbvSrvUav(isTemp);
var uavDesc = new UnorderedAccessViewDescription var uavDesc = new D3D12_UNORDERED_ACCESS_VIEW_DESC
{ {
ViewDimension = UavDimension.Texture2D, ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D,
Format = d3d12Format, Format = d3d12Format,
Texture2D = new Texture2DUav Texture2D = new D3D12_TEX2D_UAV
{ {
MipSlice = 0, MipSlice = 0,
PlaneSlice = 0, PlaneSlice = 0,
} }
}; };
_device->CreateUnorderedAccessView(allocation.Resource, null, &uavDesc, _descriptorAllocator.GetCpuHandle(resourceDescriptor.uav)); _device.NativeDevice->CreateUnorderedAccessView(allocation.Get()->GetResource(), null, &uavDesc, _descriptorAllocator.GetCpuHandle(resourceDescriptor.uav));
} }
var handle = TrackResource(ref allocation, initialState, resourceDescriptor, ResourceDesc.Texture(desc), isTemp); var handle = TrackResource(allocation, initialState, resourceDescriptor, ResourceDesc.Texture(desc), isTemp);
return handle.AsTexture(); return handle.AsTexture();
} }
@@ -253,56 +470,56 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
public Handle<GraphicsBuffer> CreateBuffer(ref readonly BufferDesc desc, bool isTemp = false) public Handle<GraphicsBuffer> CreateBuffer(ref readonly BufferDesc desc, bool isTemp = false)
{ {
CheckBufferSize((uint)desc.Size); CheckBufferSize(desc.Size);
var resourceDescription = ResourceDescription.Buffer(desc.Size, ConvertBufferUsage(desc.Usage)); var resourceDescription = D3D12_RESOURCE_DESC.Buffer(desc.Size, ConvertBufferUsage(desc.Usage));
var allocationDesc = new AllocationDesc var allocationDesc = new D3D12MA_ALLOCATION_DESC
{ {
HeapType = ConvertMemoryType(desc.MemoryType), HeapType = ConvertMemoryType(desc.MemoryType),
Flags = AllocationFlags.None Flags = D3D12MA_ALLOCATION_FLAG_NONE
}; };
var initialState = DetermineInitialBufferState(desc.Usage, desc.MemoryType); var initialState = DetermineInitialBufferState(desc.Usage, desc.MemoryType);
Allocation allocation = default; ComPtr<D3D12MA_Allocation> allocation = default;
ThrowIfFailed(_allocator.CreateResource(&allocationDesc, in resourceDescription, initialState, null, &allocation, IID_NULL, null)); ThrowIfFailed(_allocator.Get()->CreateResource(&allocationDesc, &resourceDescription, initialState, null, allocation.GetAddressOf(), IID_NULL, null));
var resourceDescriptor = D3D12ResourceDescriptor.Invalid; var resourceDescriptor = ResourceViewGroup.Invalid;
if (desc.Usage.HasFlag(BufferUsage.ShaderResource) && desc.CreationFlags.HasFlag(BufferCreationFlags.Bindless)) if (desc.Usage.HasFlag(BufferUsage.ShaderResource))
{ {
resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav(isTemp); resourceDescriptor.srv = _descriptorAllocator.AllocateCbvSrvUav(isTemp);
var srvDesc = new ShaderResourceViewDescription var srvDesc = new D3D12_SHADER_RESOURCE_VIEW_DESC
{ {
ViewDimension = SrvDimension.Buffer, ViewDimension = D3D12_SRV_DIMENSION_BUFFER,
Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING
}; };
if (desc.Usage.HasFlag(BufferUsage.Raw)) if (desc.Usage.HasFlag(BufferUsage.Raw))
{ {
srvDesc.Format = Format.R32Typeless; srvDesc.Format = DXGI_FORMAT_R32_TYPELESS;
srvDesc.Buffer.FirstElement = 0; srvDesc.Buffer.FirstElement = 0;
srvDesc.Buffer.NumElements = (uint)(desc.Size / 4); srvDesc.Buffer.NumElements = desc.Size / 4;
srvDesc.Buffer.StructureByteStride = 0; srvDesc.Buffer.StructureByteStride = 0;
srvDesc.Buffer.Flags = BufferSrvFlags.Raw; srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_RAW;
} }
else else
{ {
srvDesc.Format = Format.Unknown; srvDesc.Format = DXGI_FORMAT_UNKNOWN;
srvDesc.Buffer.FirstElement = 0; srvDesc.Buffer.FirstElement = 0;
srvDesc.Buffer.NumElements = (uint)(desc.Size / desc.Stride); srvDesc.Buffer.NumElements = desc.Size / desc.Stride;
srvDesc.Buffer.StructureByteStride = desc.Stride; srvDesc.Buffer.StructureByteStride = desc.Stride;
srvDesc.Buffer.Flags = BufferSrvFlags.None; srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE;
} }
_device->CreateShaderResourceView(allocation.Resource, &srvDesc, _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv)); _device.NativeDevice->CreateShaderResourceView(allocation.Get()->GetResource(), &srvDesc, _descriptorAllocator.GetCpuHandle(resourceDescriptor.srv));
} }
var handle = TrackResource(ref allocation, initialState, resourceDescriptor, ResourceDesc.Buffer(desc), isTemp); var handle = TrackResource(allocation, initialState, resourceDescriptor, ResourceDesc.Buffer(desc), isTemp);
return handle.AsGraphicsBuffer(); return handle.AsGraphicsBuffer();
} }
public Handle<GraphicsBuffer> CreateUploadBuffer(ulong size, bool isTemp = true) public Handle<GraphicsBuffer> CreateUploadBuffer(uint size, bool isTemp = true)
{ {
var desc = new BufferDesc var desc = new BufferDesc
{ {
@@ -318,20 +535,18 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
{ {
var vertexBufferDesc = new BufferDesc var vertexBufferDesc = new BufferDesc
{ {
Size = (ulong)(vertices.Count * Unsafe.SizeOf<Vertex>()), Size = (uint)(vertices.Count * Unsafe.SizeOf<Vertex>()),
Stride = (uint)Unsafe.SizeOf<Vertex>(), Stride = (uint)Unsafe.SizeOf<Vertex>(),
Usage = BufferUsage.Vertex | BufferUsage.ShaderResource, Usage = BufferUsage.Vertex | BufferUsage.ShaderResource,
MemoryType = MemoryType.Default, MemoryType = MemoryType.Default,
CreationFlags = BufferCreationFlags.Bindless
}; };
var indexBufferDesc = new BufferDesc var indexBufferDesc = new BufferDesc
{ {
Size = (ulong)(indices.Count * sizeof(uint)), Size = (uint)(indices.Count * sizeof(uint)),
Stride = sizeof(uint), Stride = sizeof(uint),
Usage = BufferUsage.Index | BufferUsage.ShaderResource, Usage = BufferUsage.Index | BufferUsage.ShaderResource,
MemoryType = MemoryType.Default, MemoryType = MemoryType.Default,
CreationFlags = BufferCreationFlags.Bindless
}; };
var vertexBuffer = CreateBuffer(ref vertexBufferDesc); var vertexBuffer = CreateBuffer(ref vertexBufferDesc);
@@ -357,30 +572,20 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
ref var shaderRef = ref _resourceDatabase.GetShaderReference(shader); ref var shaderRef = ref _resourceDatabase.GetShaderReference(shader);
if (shaderRef.ConstantBuffers.Count > 0)
{
var maxSlot = shaderRef.ConstantBuffers.Max(cb => cb.RegisterSlot);
materialData._cBufferCaches = new UnsafeArray<CBufferCache>((int)maxSlot + 1, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
foreach (var cbufferInfo in shaderRef.ConstantBuffers)
{
var desc = new BufferDesc var desc = new BufferDesc
{ {
Size = cbufferInfo.Size, Size = shaderRef.PerMaterialBufferInfo.Size,
Usage = BufferUsage.Constant, Usage = BufferUsage.Constant,
MemoryType = MemoryType.Default, MemoryType = MemoryType.Default,
}; };
var buffer = CreateBuffer(ref desc); var buffer = CreateBuffer(ref desc);
materialData._materialPropertiesCache = new CBufferCache(buffer, shaderRef.PerMaterialBufferInfo.Size);
materialData._cBufferCaches[cbufferInfo.RegisterSlot] = new CBufferCache(buffer, cbufferInfo.Size);
}
}
return _resourceDatabase.AddMaterial(ref materialData); return _resourceDatabase.AddMaterial(ref materialData);
} }
public Identifier<Shader> CreateShader() public Identifier<Shader> CreateShader(ShaderDescriptor descriptor)
{ {
var shaderData = new Shader(); var shaderData = new Shader();
return _resourceDatabase.AddShader(ref shaderData); return _resourceDatabase.AddShader(ref shaderData);
@@ -388,149 +593,136 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
#region Conversion Methods #region Conversion Methods
private static Format ConvertTextureFormat(TextureFormat format) private static DXGI_FORMAT ConvertTextureFormat(TextureFormat format)
{ {
return format switch return format switch
{ {
TextureFormat.R8G8B8A8_UNorm => Format.R8G8B8A8Unorm, TextureFormat.R8G8B8A8_UNorm => DXGI_FORMAT_R8G8B8A8_UNORM,
TextureFormat.B8G8R8A8_UNorm => Format.B8G8R8A8Unorm, TextureFormat.B8G8R8A8_UNorm => DXGI_FORMAT_B8G8R8A8_UNORM,
TextureFormat.R16G16B16A16_Float => Format.R16G16B16A16Float, TextureFormat.R16G16B16A16_Float => DXGI_FORMAT_R16G16B16A16_FLOAT,
TextureFormat.R32G32B32A32_Float => Format.R32G32B32A32Float, TextureFormat.R32G32B32A32_Float => DXGI_FORMAT_R32G32B32A32_FLOAT,
TextureFormat.D24_UNorm_S8_UInt => Format.D24UnormS8Uint, TextureFormat.D24_UNorm_S8_UInt => DXGI_FORMAT_D24_UNORM_S8_UINT,
TextureFormat.D32_Float => Format.D32Float, TextureFormat.D32_Float => DXGI_FORMAT_D32_FLOAT,
_ => throw new ArgumentException($"Unsupported texture format: {format}") _ => throw new ArgumentException($"Unsupported texture format: {format}")
}; };
} }
private static ResourceFlags ConvertTextureUsage(TextureUsage usage) private static D3D12_RESOURCE_FLAGS ConvertTextureUsage(TextureUsage usage)
{ {
var flags = ResourceFlags.None; var flags = D3D12_RESOURCE_FLAG_NONE;
if (usage.HasFlag(TextureUsage.RenderTarget)) if (usage.HasFlag(TextureUsage.RenderTarget))
{ {
flags |= ResourceFlags.AllowRenderTarget; flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
} }
if (usage.HasFlag(TextureUsage.DepthStencil)) if (usage.HasFlag(TextureUsage.DepthStencil))
{ {
flags |= ResourceFlags.AllowDepthStencil; flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
} }
if (usage.HasFlag(TextureUsage.UnorderedAccess)) if (usage.HasFlag(TextureUsage.UnorderedAccess))
{ {
flags |= ResourceFlags.AllowUnorderedAccess; flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
} }
return flags; return flags;
} }
private static ResourceFlags ConvertBufferUsage(BufferUsage usage) private static D3D12_RESOURCE_FLAGS ConvertBufferUsage(BufferUsage usage)
{ {
var flags = ResourceFlags.None; var flags = D3D12_RESOURCE_FLAG_NONE;
if (usage.HasFlag(BufferUsage.Raw)) if (usage.HasFlag(BufferUsage.Raw))
{ {
flags |= ResourceFlags.AllowUnorderedAccess; flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
} }
return flags; return flags;
} }
private static HeapType ConvertMemoryType(MemoryType memoryType) private static D3D12_HEAP_TYPE ConvertMemoryType(MemoryType memoryType)
{ {
return memoryType switch return memoryType switch
{ {
MemoryType.Default => HeapType.Default, MemoryType.Default => D3D12_HEAP_TYPE_DEFAULT,
MemoryType.Upload => HeapType.Upload, MemoryType.Upload => D3D12_HEAP_TYPE_UPLOAD,
MemoryType.Readback => HeapType.Readback, MemoryType.Readback => D3D12_HEAP_TYPE_READBACK,
_ => throw new ArgumentException($"Unsupported memory type: {memoryType}") _ => throw new ArgumentException($"Unsupported memory type: {memoryType}")
}; };
} }
private static ResourceStates DetermineInitialTextureState(TextureUsage usage) private static D3D12_RESOURCE_STATES DetermineInitialTextureState(TextureUsage usage)
{ {
if (usage.HasFlag(TextureUsage.RenderTarget)) if (usage.HasFlag(TextureUsage.RenderTarget))
{ {
return ResourceStates.RenderTarget; return D3D12_RESOURCE_STATE_RENDER_TARGET;
} }
if (usage.HasFlag(TextureUsage.DepthStencil)) if (usage.HasFlag(TextureUsage.DepthStencil))
{ {
return ResourceStates.DepthWrite; return D3D12_RESOURCE_STATE_DEPTH_WRITE;
} }
if (usage.HasFlag(TextureUsage.UnorderedAccess)) if (usage.HasFlag(TextureUsage.UnorderedAccess))
{ {
return ResourceStates.UnorderedAccess; return D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
} }
return ResourceStates.Common; return D3D12_RESOURCE_STATE_COMMON;
} }
private static ResourceStates DetermineInitialBufferState(BufferUsage usage, MemoryType memoryType) private static D3D12_RESOURCE_STATES DetermineInitialBufferState(BufferUsage usage, MemoryType memoryType)
{ {
if (memoryType == MemoryType.Upload) if (memoryType == MemoryType.Upload)
{ {
return ResourceStates.GenericRead; return D3D12_RESOURCE_STATE_GENERIC_READ;
} }
if (memoryType == MemoryType.Readback) if (memoryType == MemoryType.Readback)
{ {
return ResourceStates.CopyDest; return D3D12_RESOURCE_STATE_COPY_DEST;
} }
if (usage.HasFlag(BufferUsage.Vertex)) if (usage.HasFlag(BufferUsage.Vertex))
{ {
return ResourceStates.VertexAndConstantBuffer; return D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER;
} }
if (usage.HasFlag(BufferUsage.Index)) if (usage.HasFlag(BufferUsage.Index))
{ {
return ResourceStates.IndexBuffer; return D3D12_RESOURCE_STATE_INDEX_BUFFER;
} }
if (usage.HasFlag(BufferUsage.Constant)) if (usage.HasFlag(BufferUsage.Constant))
{ {
return ResourceStates.VertexAndConstantBuffer; return D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER;
} }
return ResourceStates.Common; return D3D12_RESOURCE_STATE_COMMON;
} }
private static ResourceState D3D12StatesToRHIState(ResourceStates states) private static ResourceState D3D12StatesToRHIState(D3D12_RESOURCE_STATES states)
{ {
switch (states) return states switch
{ {
//case ResourceStates.None: //case ResourceStates.None:
//case ResourceStates.Present: //case ResourceStates.Present:
case ResourceStates.Common: D3D12_RESOURCE_STATE_COMMON => ResourceState.Common,
return ResourceState.Common; D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER => ResourceState.VertexAndConstantBuffer,
case ResourceStates.VertexAndConstantBuffer: D3D12_RESOURCE_STATE_INDEX_BUFFER => ResourceState.IndexBuffer,
return ResourceState.VertexAndConstantBuffer; D3D12_RESOURCE_STATE_RENDER_TARGET => ResourceState.RenderTarget,
case ResourceStates.IndexBuffer: D3D12_RESOURCE_STATE_UNORDERED_ACCESS => ResourceState.UnorderedAccess,
return ResourceState.IndexBuffer; D3D12_RESOURCE_STATE_DEPTH_WRITE => ResourceState.DepthWrite,
case ResourceStates.RenderTarget: D3D12_RESOURCE_STATE_DEPTH_READ => ResourceState.DepthRead,
return ResourceState.RenderTarget; D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE => ResourceState.PixelShaderResource,
case ResourceStates.UnorderedAccess:
return ResourceState.UnorderedAccess;
case ResourceStates.DepthWrite:
return ResourceState.DepthWrite;
case ResourceStates.DepthRead:
return ResourceState.DepthRead;
case ResourceStates.PixelShaderResource:
return ResourceState.PixelShaderResource;
//case ResourceStates.Predication: //case ResourceStates.Predication:
case ResourceStates.IndirectArgument: D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT => ResourceState.IndirectArgument,
return ResourceState.IndirectArgument; D3D12_RESOURCE_STATE_COPY_DEST => ResourceState.CopyDest,
case ResourceStates.CopyDest: D3D12_RESOURCE_STATE_COPY_SOURCE => ResourceState.CopySource,
return ResourceState.CopyDest; D3D12_RESOURCE_STATE_GENERIC_READ => ResourceState.GenericRead,
case ResourceStates.CopySource: _ => ResourceState.Common,
return ResourceState.CopySource; };
case ResourceStates.GenericRead:
return ResourceState.GenericRead;
default:
return ResourceState.Common;
}
} }
#endregion #endregion
@@ -580,7 +772,7 @@ internal unsafe class D3D12ResourceAllocator : IResourceAllocator, IDisposable
} }
_temResources.Dispose(); _temResources.Dispose();
_allocator.Release(); _allocator.Dispose();
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }

View File

@@ -1,12 +1,12 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data; using Ghost.Graphics.Data;
using Ghost.Graphics.RHI; using Ghost.Graphics.RHI;
using Misaki.HighPerformance.Collections; using Misaki.HighPerformance.Collections;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Win32; using TerraFX.Interop.DirectX;
using Win32.Graphics.D3D12MemoryAllocator; using TerraFX.Interop.Windows;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12; namespace Ghost.Graphics.D3D12;
@@ -15,42 +15,38 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
internal unsafe struct ResourceInfo internal unsafe struct ResourceInfo
{ {
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit)]
private struct ResourceUnion public struct ResourceUnion
{ {
[FieldOffset(0)] [FieldOffset(0)]
public Allocation allocation; public ComPtr<D3D12MA_Allocation> allocation;
[FieldOffset(0)] [FieldOffset(0)]
public ComPtr<ID3D12Resource> resource; public ComPtr<ID3D12Resource> resource;
public ResourceUnion(Allocation allocation) public ResourceUnion(ComPtr<D3D12MA_Allocation> allocation)
{ {
this.allocation = allocation; this.allocation = allocation;
this.resource = default;
} }
public ResourceUnion(ComPtr<ID3D12Resource> resource) public ResourceUnion(ComPtr<ID3D12Resource> resource)
{ {
this.resource = resource; this.resource = resource;
this.allocation = default;
} }
} }
private ResourceUnion _resourceUnion; public ResourceDesc desc;
private readonly bool _isExternal; public ResourceViewGroup descriptor;
public ResourceUnion resourceUnion;
public D3D12ResourceDescriptor descriptor;
public uint cpuFenceValue; public uint cpuFenceValue;
public ResourceState state; public ResourceState state;
public ResourceDesc desc; public readonly bool isExternal;
public readonly bool Allocated => _isExternal ? _resourceUnion.resource.Get() != null : _resourceUnion.allocation.IsNotNull; public readonly bool Allocated => isExternal ? resourceUnion.resource.Get() != null : resourceUnion.allocation.Get()->IsNotNull;
public readonly ID3D12Resource* ResourcePtr => _isExternal ? _resourceUnion.resource.Get() : _resourceUnion.allocation.Resource; public readonly ID3D12Resource* ResourcePtr => isExternal ? resourceUnion.resource.Get() : resourceUnion.allocation.Get()->GetResource();
public ResourceInfo(in Allocation allocation, uint cpuFenceValue, ResourceState state, D3D12ResourceDescriptor resourceDescriptor, ResourceDesc desc) public ResourceInfo(ComPtr<D3D12MA_Allocation> allocation, uint cpuFenceValue, ResourceState state, ResourceViewGroup resourceDescriptor, ResourceDesc desc)
{ {
this._resourceUnion = new ResourceUnion(allocation); this.resourceUnion = new ResourceUnion(allocation);
this._isExternal = false; this.isExternal = false;
this.descriptor = resourceDescriptor; this.descriptor = resourceDescriptor;
this.cpuFenceValue = cpuFenceValue; this.cpuFenceValue = cpuFenceValue;
@@ -60,8 +56,8 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
public ResourceInfo(ComPtr<ID3D12Resource> resource, ResourceState state) public ResourceInfo(ComPtr<ID3D12Resource> resource, ResourceState state)
{ {
this._resourceUnion = new ResourceUnion(resource); this.resourceUnion = new ResourceUnion(resource);
this._isExternal = true; this.isExternal = true;
this.descriptor = default; this.descriptor = default;
this.cpuFenceValue = ~0u; this.cpuFenceValue = ~0u;
@@ -74,16 +70,16 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
var refCount = 0u; var refCount = 0u;
if (Allocated) if (Allocated)
{ {
if (_isExternal) if (isExternal)
{ {
refCount = _resourceUnion.resource.Get()->Release(); refCount = resourceUnion.resource.Get()->Release();
} }
else else
{ {
refCount = _resourceUnion.allocation.Release(); refCount = resourceUnion.allocation.Get()->Release();
} }
_resourceUnion = default; resourceUnion = default;
descriptor = default; descriptor = default;
} }
@@ -131,7 +127,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
Dispose(); Dispose();
} }
public unsafe Handle<GPUResource> ImportExternalResource<T>(T resource, ResourceState initialState) public Handle<GPUResource> ImportExternalResource<T>(T resource, ResourceState initialState)
where T : unmanaged where T : unmanaged
{ {
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
@@ -145,7 +141,7 @@ internal class D3D12ResourceDatabase : IResourceDatabase, IDisposable
return new Handle<GPUResource>(id, generation); return new Handle<GPUResource>(id, generation);
} }
public Handle<GPUResource> AddResource(ref readonly Allocation allocation, uint cpuFenceValue, ResourceState initialState, D3D12ResourceDescriptor resourceDescriptor, ResourceDesc desc) public Handle<GPUResource> AddResource(ComPtr<D3D12MA_Allocation> allocation, uint cpuFenceValue, ResourceState initialState, ResourceViewGroup resourceDescriptor, ResourceDesc desc)
{ {
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);

View File

@@ -1,10 +1,12 @@
#undef SUPPORT_TEXTURE_BINDING
using Ghost.Graphics.D3D12.Utilities;
using Ghost.Graphics.Data; using Ghost.Graphics.Data;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Win32; using TerraFX.Interop.DirectX;
using Win32.Graphics.Direct3D; using TerraFX.Interop.Windows;
using Win32.Graphics.Direct3D.Dxc;
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.D3D12; namespace Ghost.Graphics.D3D12;
@@ -71,8 +73,11 @@ internal unsafe static class D3D12ShaderCompiler
using ComPtr<IDxcIncludeHandler> includeHandler = default; using ComPtr<IDxcIncludeHandler> includeHandler = default;
// Create DXC compiler and utils // Create DXC compiler and utils
DxcCreateInstance(in CLSID_DxcCompiler, __uuidof<IDxcCompiler3>(), compiler.GetVoidAddressOf()); var pDxcCompiler = (Guid*)Unsafe.AsPointer(in CLSID.CLSID_DxcCompiler);
DxcCreateInstance(in CLSID_DxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf()); var pDxcUtils = (Guid*)Unsafe.AsPointer(in CLSID.CLSID_DxcUtils);
DxcCreateInstance(pDxcCompiler, __uuidof<IDxcCompiler3>(), compiler.GetVoidAddressOf());
DxcCreateInstance(pDxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf());
utils.Get()->CreateDefaultIncludeHandler(includeHandler.GetAddressOf()); utils.Get()->CreateDefaultIncludeHandler(includeHandler.GetAddressOf());
// Create source blob // Create source blob
@@ -116,16 +121,16 @@ internal unsafe static class D3D12ShaderCompiler
{ {
Ptr = sourceBlob.Get()->GetBufferPointer(), Ptr = sourceBlob.Get()->GetBufferPointer(),
Size = sourceBlob.Get()->GetBufferSize(), Size = sourceBlob.Get()->GetBufferSize(),
Encoding = DXC_CP_UTF8 Encoding = DXC.DXC_CP_UTF8
}; };
compiler.Get()->Compile(&buffer, (char**)argsPtr, (uint)argsArray.Length, includeHandler.Get(), __uuidof<IDxcResult>(), result.GetVoidAddressOf()); compiler.Get()->Compile(&buffer, (char**)argsPtr, (uint)argsArray.Length, includeHandler.Get(), __uuidof<IDxcResult>(), result.GetVoidAddressOf());
} }
// Check compilation result // Check compilation result
HResult hrStatus; HRESULT hrStatus;
result.Get()->GetStatus(&hrStatus); result.Get()->GetStatus(&hrStatus);
if (hrStatus.Failure) if (hrStatus.FAILED)
{ {
// Get error messages // Get error messages
using ComPtr<IDxcBlobEncoding> errorBlob = default; using ComPtr<IDxcBlobEncoding> errorBlob = default;
@@ -153,7 +158,7 @@ internal unsafe static class D3D12ShaderCompiler
// Get reflection data using DXC API // Get reflection data using DXC API
using ComPtr<IDxcBlob> reflectionBlob = default; using ComPtr<IDxcBlob> reflectionBlob = default;
result.Get()->GetOutput(DxcOutKind.Reflection, __uuidof<IDxcBlob>(), reflectionBlob.GetVoidAddressOf(), null); result.Get()->GetOutput(DXC_OUT_KIND.DXC_OUT_REFLECTION, __uuidof<IDxcBlob>(), reflectionBlob.GetVoidAddressOf(), null);
if (reflectionBlob.Get() == null) if (reflectionBlob.Get() == null)
{ {
@@ -188,18 +193,20 @@ internal unsafe static class D3D12ShaderCompiler
shader.PropertyNameToIdMap[name] = id; 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) public static void PerformDXCReflection(ref Shader shader, IDxcBlob* reflectionBlob)
{ {
// Create DXC utils to parse reflection data // Create DXC utils to parse reflection data
var pDxcUtils = (Guid*)Unsafe.AsPointer(in CLSID.CLSID_DxcUtils);
using ComPtr<IDxcUtils> utils = default; using ComPtr<IDxcUtils> utils = default;
DxcCreateInstance(CLSID_DxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf()); DxcCreateInstance(pDxcUtils, __uuidof<IDxcUtils>(), utils.GetVoidAddressOf());
// Create reflection interface from blob // Create reflection interface from blob
var reflectionData = new DxcBuffer var reflectionData = new DxcBuffer
{ {
Ptr = reflectionBlob->GetBufferPointer(), Ptr = reflectionBlob->GetBufferPointer(),
Size = reflectionBlob->GetBufferSize(), Size = reflectionBlob->GetBufferSize(),
Encoding = DXC_CP_ACP Encoding = DXC.DXC_CP_ACP
}; };
using ComPtr<ID3D12ShaderReflection> reflection = default; using ComPtr<ID3D12ShaderReflection> reflection = default;
@@ -210,7 +217,7 @@ internal unsafe static class D3D12ShaderCompiler
throw new Exception("Failed to create shader reflection from DXC output"); throw new Exception("Failed to create shader reflection from DXC output");
} }
ShaderDescription shaderDesc; D3D12_SHADER_DESC shaderDesc;
reflection.Get()->GetDesc(&shaderDesc); reflection.Get()->GetDesc(&shaderDesc);
var cbufferRegistry = new Dictionary<string, CBufferInfo>(); var cbufferRegistry = new Dictionary<string, CBufferInfo>();
@@ -218,12 +225,12 @@ internal unsafe static class D3D12ShaderCompiler
for (uint i = 0; i < shaderDesc.BoundResources; i++) for (uint i = 0; i < shaderDesc.BoundResources; i++)
{ {
ShaderInputBindDescription bindDesc; D3D12_SHADER_INPUT_BIND_DESC bindDesc;
reflection.Get()->GetResourceBindingDesc(i, &bindDesc); reflection.Get()->GetResourceBindingDesc(i, &bindDesc);
switch (bindDesc.Type) switch (bindDesc.Type)
{ {
case ShaderInputType.ConstantBuffer: case D3D_SHADER_INPUT_TYPE.D3D_SIT_CBUFFER:
{ {
var cbufferName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name); var cbufferName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
if (cbufferName == null || cbufferRegistry.ContainsKey(cbufferName)) if (cbufferName == null || cbufferRegistry.ContainsKey(cbufferName))
@@ -232,7 +239,7 @@ internal unsafe static class D3D12ShaderCompiler
} }
var cbuffer = reflection.Get()->GetConstantBufferByName(bindDesc.Name); var cbuffer = reflection.Get()->GetConstantBufferByName(bindDesc.Name);
ShaderBufferDescription cbufferDesc; D3D12_SHADER_BUFFER_DESC cbufferDesc;
cbuffer->GetDesc(&cbufferDesc); cbuffer->GetDesc(&cbufferDesc);
var cbufferInfo = new CBufferInfo var cbufferInfo = new CBufferInfo
@@ -245,7 +252,7 @@ internal unsafe static class D3D12ShaderCompiler
for (uint j = 0; j < cbufferDesc.Variables; j++) for (uint j = 0; j < cbufferDesc.Variables; j++)
{ {
var variable = cbuffer->GetVariableByIndex(j); var variable = cbuffer->GetVariableByIndex(j);
ShaderVariableDescription varDesc; D3D12_SHADER_VARIABLE_DESC varDesc;
variable->GetDesc(&varDesc); variable->GetDesc(&varDesc);
var variableName = Marshal.PtrToStringAnsi((IntPtr)varDesc.Name); var variableName = Marshal.PtrToStringAnsi((IntPtr)varDesc.Name);
@@ -267,8 +274,9 @@ internal unsafe static class D3D12ShaderCompiler
break; break;
} }
case ShaderInputType.Texture: case D3D_SHADER_INPUT_TYPE.D3D_SIT_TEXTURE:
{ {
#if SUPPORT_TEXTURE_BINDING
var textureName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name); var textureName = Marshal.PtrToStringAnsi((IntPtr)bindDesc.Name);
if (textureName == null || textureRegistry.ContainsKey(textureName)) if (textureName == null || textureRegistry.ContainsKey(textureName))
{ {
@@ -285,20 +293,16 @@ internal unsafe static class D3D12ShaderCompiler
textureRegistry.Add(textureName, textureInfo); textureRegistry.Add(textureName, textureInfo);
break; break;
#endif
throw new NotSupportedException("Texture bindings are not supported in current version. Please use bindless textures.");
} }
} }
} }
shader.ConstantBuffers.Clear(); shader.PerMaterialBufferInfo.Clear();
foreach (var cbuf in cbufferRegistry.Values) foreach (var cbuf in cbufferRegistry.Values)
{ {
shader.ConstantBuffers.Add(cbuf); shader.PerMaterialBufferInfo.Add(cbuf);
}
shader.RegularTextures.Clear();
foreach (var tex in textureRegistry.Values)
{
shader.RegularTextures.Add(tex);
} }
} }
} }

View File

@@ -6,10 +6,10 @@ using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Win32; using TerraFX.Interop.DirectX;
using Win32.Graphics.Direct3D12; using TerraFX.Interop.Windows;
using Win32.Graphics.Dxgi;
using Win32.Graphics.Dxgi.Common; using static TerraFX.Aliases.DXGI_Alias;
namespace Ghost.Graphics.D3D12; namespace Ghost.Graphics.D3D12;
@@ -55,18 +55,18 @@ internal unsafe class D3D12SwapChain : ISwapChain
private void CreateSwapChain(IDXGIFactory7* pFactory, ID3D12CommandQueue* commandQueue, SwapChainDesc desc) private void CreateSwapChain(IDXGIFactory7* pFactory, ID3D12CommandQueue* commandQueue, SwapChainDesc desc)
{ {
var swapChainDesc = new SwapChainDescription1 var swapChainDesc = new DXGI_SWAP_CHAIN_DESC1
{ {
Width = desc.width, Width = desc.width,
Height = desc.height, Height = desc.height,
Format = ConvertTextureFormat(desc.format), Format = desc.format.ToD3D12Format(),
SampleDesc = new SampleDescription(1, 0), SampleDesc = new DXGI_SAMPLE_DESC(1, 0),
BufferUsage = Usage.BackBuffer | Usage.RenderTargetOutput, BufferUsage = DXGI_USAGE_BACK_BUFFER | DXGI_USAGE_RENDER_TARGET_OUTPUT,
BufferCount = D3D12PipelineResource.BACK_BUFFER_COUNT, BufferCount = D3D12PipelineResource.BACK_BUFFER_COUNT,
Scaling = Scaling.Stretch, Scaling = DXGI_SCALING_STRETCH,
SwapEffect = SwapEffect.FlipDiscard, SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
AlphaMode = AlphaMode.Ignore, AlphaMode = DXGI_ALPHA_MODE_IGNORE,
Flags = SwapChainFlags.AllowTearing, Flags = (uint)DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING,
Stereo = false, Stereo = false,
}; };
@@ -86,14 +86,14 @@ internal unsafe class D3D12SwapChain : ISwapChain
break; break;
case SwapChainTargetType.WindowHandle: case SwapChainTargetType.WindowHandle:
var swapChainFullscreenDesc = new SwapChainFullscreenDescription var swapChainFullscreenDesc = new DXGI_SWAP_CHAIN_FULLSCREEN_DESC
{ {
Windowed = true, Windowed = true,
}; };
pFactory->CreateSwapChainForHwnd( pFactory->CreateSwapChainForHwnd(
(IUnknown*)commandQueue, (IUnknown*)commandQueue,
desc.target.windowHandle, new HWND(desc.target.windowHandle.ToPointer()),
&swapChainDesc, &swapChainDesc,
&swapChainFullscreenDesc, &swapChainFullscreenDesc,
null, null,
@@ -104,7 +104,7 @@ internal unsafe class D3D12SwapChain : ISwapChain
throw new ArgumentException("Unsupported swap chain target type."); throw new ArgumentException("Unsupported swap chain target type.");
} }
if (tempSwapChain.Get()->QueryInterface(__uuidof<IDXGISwapChain4>(), _swapChain.GetVoidAddressOf()).Failure) if (tempSwapChain.Get()->QueryInterface(__uuidof<IDXGISwapChain4>(), _swapChain.GetVoidAddressOf()).FAILED)
{ {
throw new InvalidOperationException("Failed to create IDXGISwapChain4 interface."); throw new InvalidOperationException("Failed to create IDXGISwapChain4 interface.");
} }
@@ -130,10 +130,10 @@ internal unsafe class D3D12SwapChain : ISwapChain
public void Present(bool vsync = true) public void Present(bool vsync = true)
{ {
var presentFlags = PresentFlags.None; var presentFlags = 0u;
var syncInterval = vsync ? 1u : 0u; var syncInterval = vsync ? 1u : 0u;
if (_swapChain.Get()->Present(syncInterval, presentFlags).Failure) if (_swapChain.Get()->Present(syncInterval, presentFlags).FAILED)
{ {
throw new InvalidOperationException("Failed to present swap chain."); throw new InvalidOperationException("Failed to present swap chain.");
} }
@@ -153,7 +153,7 @@ internal unsafe class D3D12SwapChain : ISwapChain
} }
// Resize the swap chain // Resize the swap chain
if (_swapChain.Get()->ResizeBuffers(BufferCount, width, height, Format.B8G8R8A8Unorm, SwapChainFlags.AllowTearing).Failure) if (_swapChain.Get()->ResizeBuffers(BufferCount, width, height, DXGI_FORMAT_B8G8R8A8_UNORM, (uint)DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING).FAILED)
{ {
throw new InvalidOperationException("Failed to resize swap chain buffers."); throw new InvalidOperationException("Failed to resize swap chain buffers.");
} }
@@ -165,18 +165,6 @@ internal unsafe class D3D12SwapChain : ISwapChain
CreateBackBuffers(); CreateBackBuffers();
} }
private static Format ConvertTextureFormat(TextureFormat format)
{
return format switch
{
TextureFormat.R8G8B8A8_UNorm => Format.R8G8B8A8Unorm,
TextureFormat.B8G8R8A8_UNorm => Format.B8G8R8A8Unorm,
TextureFormat.R16G16B16A16_Float => Format.R16G16B16A16Float,
TextureFormat.R32G32B32A32_Float => Format.R32G32B32A32Float,
_ => throw new ArgumentException($"Unsupported texture format: {format}")
};
}
public void Dispose() public void Dispose()
{ {
if (_disposed) if (_disposed)

View File

@@ -7,7 +7,7 @@ internal readonly struct DSVDesc : IIdentifierType;
internal readonly struct CbvSrvUavDesc : IIdentifierType; internal readonly struct CbvSrvUavDesc : IIdentifierType;
internal readonly struct SamplerDesc : IIdentifierType; internal readonly struct SamplerDesc : IIdentifierType;
internal struct D3D12ResourceDescriptor internal struct ResourceViewGroup
{ {
public Identifier<RTVDesc> rtv; public Identifier<RTVDesc> rtv;
public Identifier<DSVDesc> dsv; public Identifier<DSVDesc> dsv;
@@ -16,11 +16,13 @@ internal struct D3D12ResourceDescriptor
public Identifier<CbvSrvUavDesc> uav; public Identifier<CbvSrvUavDesc> uav;
public Identifier<SamplerDesc> sampler; public Identifier<SamplerDesc> sampler;
public static D3D12ResourceDescriptor Invalid => new() public static ResourceViewGroup Invalid => new()
{ {
rtv = Identifier<RTVDesc>.Invalid, rtv = Identifier<RTVDesc>.Invalid,
dsv = Identifier<DSVDesc>.Invalid, dsv = Identifier<DSVDesc>.Invalid,
srv = Identifier<CbvSrvUavDesc>.Invalid, srv = Identifier<CbvSrvUavDesc>.Invalid,
cbv = Identifier<CbvSrvUavDesc>.Invalid,
uav = Identifier<CbvSrvUavDesc>.Invalid,
sampler = Identifier<SamplerDesc>.Invalid, sampler = Identifier<SamplerDesc>.Invalid,
}; };
} }

View File

@@ -1,32 +1,34 @@
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Diagnostics; using System.Diagnostics;
using System.Numerics; using System.Numerics;
using Win32; using TerraFX.Interop.DirectX;
using Win32.Graphics.Direct3D12; using TerraFX.Interop.Windows;
using DescriptorIndex = System.Int32;
using static TerraFX.Aliases.D3D12_Alias;
namespace Ghost.Graphics.D3D12.Utilities; namespace Ghost.Graphics.D3D12.Utilities;
internal unsafe struct D3D12DescriptorHeap : IDisposable internal unsafe struct D3D12DescriptorHeap : IDisposable
{ {
private const DescriptorIndex _INVALID_DESCRIPTOR_INDEX = -1; private const int _INVALID_DESCRIPTOR_INDEX = -1;
private readonly D3D12RenderDevice _device; private readonly D3D12RenderDevice _device;
private ComPtr<ID3D12DescriptorHeap> _heap; private ComPtr<ID3D12DescriptorHeap> _heap;
private ComPtr<ID3D12DescriptorHeap> _shaderVisibleHeap; private ComPtr<ID3D12DescriptorHeap> _shaderVisibleHeap;
private CpuDescriptorHandle _startCpuHandle; private D3D12_CPU_DESCRIPTOR_HANDLE _startCpuHandle;
private CpuDescriptorHandle _startCpuHandleShaderVisible; private D3D12_CPU_DESCRIPTOR_HANDLE _startCpuHandleShaderVisible;
private GpuDescriptorHandle _startGpuHandleShaderVisible; private D3D12_GPU_DESCRIPTOR_HANDLE _startGpuHandleShaderVisible;
private DescriptorIndex _searchStart; private int _searchStart;
private UnsafeArray<bool> _allocatedDescriptors; private UnsafeArray<bool> _allocatedDescriptors;
private readonly DescriptorIndex _dynamicHeapStart; private readonly int _dynamicHeapStart;
private DescriptorIndex _currentDynamicOffset; private int _currentDynamicOffset;
private readonly Lock _lock = new(); private readonly Lock _lock = new();
public DescriptorHeapType HeapType public D3D12_DESCRIPTOR_HEAP_TYPE HeapType
{ {
get; get;
} }
@@ -54,7 +56,7 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
public readonly ID3D12DescriptorHeap* Heap => _heap.Get(); public readonly ID3D12DescriptorHeap* Heap => _heap.Get();
public readonly ID3D12DescriptorHeap* ShaderVisibleHeap => _shaderVisibleHeap.Get(); public readonly ID3D12DescriptorHeap* ShaderVisibleHeap => _shaderVisibleHeap.Get();
public D3D12DescriptorHeap(string name, D3D12RenderDevice device, DescriptorHeapType type, int numDescriptors, int dynamicHeapStart) public D3D12DescriptorHeap(string name, D3D12RenderDevice device, D3D12_DESCRIPTOR_HEAP_TYPE type, int numDescriptors, int dynamicHeapStart)
{ {
numDescriptors = Math.Max(64, numDescriptors); numDescriptors = Math.Max(64, numDescriptors);
@@ -62,7 +64,7 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
HeapType = type; HeapType = type;
NumDescriptors = numDescriptors; NumDescriptors = numDescriptors;
ShaderVisible = type == DescriptorHeapType.CbvSrvUav || type == DescriptorHeapType.Sampler; ShaderVisible = type == D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV || type == D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
Stride = device.NativeDevice->GetDescriptorHandleIncrementSize(type); Stride = device.NativeDevice->GetDescriptorHandleIncrementSize(type);
_dynamicHeapStart = Math.Clamp(dynamicHeapStart, 0, numDescriptors); _dynamicHeapStart = Math.Clamp(dynamicHeapStart, 0, numDescriptors);
@@ -71,16 +73,16 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
var success = AllocateResources(numDescriptors); var success = AllocateResources(numDescriptors);
Debug.Assert(success); Debug.Assert(success);
_heap.Get()->SetName(name); _heap.Get()->SetName(name.AsSpan().GetUnsafePtr());
if (ShaderVisible) if (ShaderVisible)
{ {
_shaderVisibleHeap.Get()->SetName($"{name} Shader Visible"); _shaderVisibleHeap.Get()->SetName($"{name} Shader Visible".AsSpan().GetUnsafePtr());
} }
} }
public DescriptorIndex AllocateDescriptor() => AllocateDescriptors(1); public int AllocateDescriptor() => AllocateDescriptors(1);
public DescriptorIndex AllocateDescriptors(int count) public int AllocateDescriptors(int count)
{ {
lock (_lock) lock (_lock)
{ {
@@ -125,9 +127,9 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
} }
} }
public DescriptorIndex AllocateDescriptorDynamic() => AllocateDescriptorsDynamic(1); public int AllocateDescriptorDynamic() => AllocateDescriptorsDynamic(1);
public DescriptorIndex AllocateDescriptorsDynamic(int count) public int AllocateDescriptorsDynamic(int count)
{ {
if (count <= 0) if (count <= 0)
{ {
@@ -157,9 +159,9 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
} }
} }
public void ReleaseDescriptor(DescriptorIndex index) => ReleaseDescriptors(index, 1); public void ReleaseDescriptor(int index) => ReleaseDescriptors(index, 1);
public void ReleaseDescriptors(DescriptorIndex baseIndex, int count = 1) public void ReleaseDescriptors(int baseIndex, int count = 1)
{ {
if (count == 0) if (count == 0)
{ {
@@ -203,7 +205,7 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
} }
} }
public readonly CpuDescriptorHandle GetCpuHandle(DescriptorIndex index) public readonly D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(int index)
{ {
if (index < 0 || index >= NumDescriptors) if (index < 0 || index >= NumDescriptors)
{ {
@@ -213,7 +215,7 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
return _startCpuHandle.Offset(index, Stride); return _startCpuHandle.Offset(index, Stride);
} }
public readonly CpuDescriptorHandle GetCpuHandleShaderVisible(DescriptorIndex index) public readonly D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandleShaderVisible(int index)
{ {
if (index < 0 || index >= NumDescriptors) if (index < 0 || index >= NumDescriptors)
{ {
@@ -228,7 +230,7 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
return _startCpuHandleShaderVisible.Offset(index, Stride); return _startCpuHandleShaderVisible.Offset(index, Stride);
} }
public readonly GpuDescriptorHandle GetGpuHandle(DescriptorIndex index) public readonly D3D12_GPU_DESCRIPTOR_HANDLE GetGpuHandle(int index)
{ {
if (index < 0 || index >= NumDescriptors) if (index < 0 || index >= NumDescriptors)
{ {
@@ -243,7 +245,7 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
return _startGpuHandleShaderVisible.Offset(index, Stride); return _startGpuHandleShaderVisible.Offset(index, Stride);
} }
public DescriptorIndex CopyToPersistentHeap(DescriptorIndex index, int count = 1) public int CopyToPersistentHeap(int index, int count = 1)
{ {
if (index < _dynamicHeapStart) if (index < _dynamicHeapStart)
{ {
@@ -256,7 +258,7 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
return newLocation; return newLocation;
} }
public readonly void CopyToShaderVisibleHeap(DescriptorIndex index, int count = 1) public readonly void CopyToShaderVisibleHeap(int index, int count = 1)
{ {
_device.NativeDevice->CopyDescriptorsSimple((uint)count, GetCpuHandleShaderVisible(index), GetCpuHandle(index), HeapType); _device.NativeDevice->CopyDescriptorsSimple((uint)count, GetCpuHandleShaderVisible(index), GetCpuHandle(index), HeapType);
} }
@@ -267,18 +269,18 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
_heap.Dispose(); _heap.Dispose();
_shaderVisibleHeap.Dispose(); _shaderVisibleHeap.Dispose();
DescriptorHeapDescription heapDesc = new() D3D12_DESCRIPTOR_HEAP_DESC heapDesc = new()
{ {
Type = HeapType, Type = HeapType,
NumDescriptors = (uint)numDescriptors, NumDescriptors = (uint)numDescriptors,
Flags = DescriptorHeapFlags.None, Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
NodeMask = 0 NodeMask = 0
}; };
fixed (void* heapPtr = &_heap) fixed (void* heapPtr = &_heap)
{ {
var hr = _device.NativeDevice->CreateDescriptorHeap(&heapDesc, __uuidof<ID3D12DescriptorHeap>(), (void**)heapPtr); var hr = _device.NativeDevice->CreateDescriptorHeap(&heapDesc, __uuidof<ID3D12DescriptorHeap>(), (void**)heapPtr);
if (hr.Failure) if (hr.FAILED)
{ {
return false; return false;
} }
@@ -289,12 +291,12 @@ internal unsafe struct D3D12DescriptorHeap : IDisposable
if (ShaderVisible) if (ShaderVisible)
{ {
heapDesc.Flags = DescriptorHeapFlags.ShaderVisible; heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
fixed (void* heapPtr = &_shaderVisibleHeap) fixed (void* heapPtr = &_shaderVisibleHeap)
{ {
var hr = _device.NativeDevice->CreateDescriptorHeap(&heapDesc, __uuidof<ID3D12DescriptorHeap>(), (void**)heapPtr); var hr = _device.NativeDevice->CreateDescriptorHeap(&heapDesc, __uuidof<ID3D12DescriptorHeap>(), (void**)heapPtr);
if (hr.Failure) if (hr.FAILED)
{ {
return false; return false;
} }

View File

@@ -1,7 +1,6 @@
using Ghost.Graphics.Data; using Ghost.Graphics.Data;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Win32.Graphics.Direct3D12; using TerraFX.Interop.DirectX;
using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.D3D12.Utilities; namespace Ghost.Graphics.D3D12.Utilities;
@@ -9,19 +8,19 @@ internal unsafe static class D3D12PipelineResource
{ {
public const int BACK_BUFFER_COUNT = 2; public const int BACK_BUFFER_COUNT = 2;
private readonly static InputElementDescription[] s_inputElementDescs = [ private readonly static D3D12_INPUT_ELEMENT_DESC[] s_inputElementDescs = [
new InputElementDescription{ SemanticName = Vertex.Semantic.position.GetUnsafePointer(), SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 0u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 }, new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.position.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 0u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
new InputElementDescription{ SemanticName = Vertex.Semantic.normal.GetUnsafePointer(), SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 16u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 }, new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.normal.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 16u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
new InputElementDescription{ SemanticName = Vertex.Semantic.tangent.GetUnsafePointer(), SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 32u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 }, new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.tangent.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 32u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
new InputElementDescription{ SemanticName = Vertex.Semantic.uv.GetUnsafePointer(), SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 48u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 }, new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.uv.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 48u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
new InputElementDescription{ SemanticName = Vertex.Semantic.color.GetUnsafePointer(), SemanticIndex = 0u, Format = Format.R32G32B32A32Float, InputSlot = 0u, AlignedByteOffset = 64u, InputSlotClass = InputClassification.PerVertexData, InstanceDataStepRate = 0 }, new D3D12_INPUT_ELEMENT_DESC{ SemanticName = (sbyte*)Vertex.Semantic.color.GetUnsafePointer(), SemanticIndex = 0u, Format = Vertex.Semantic.ALIGNED_FORMAT, InputSlot = 0u, AlignedByteOffset = 64u, InputSlotClass = D3D12_INPUT_CLASSIFICATION.D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, InstanceDataStepRate = 0 },
]; ];
public const Format SWAP_CHAIN_BACK_BUFFER_FORMAT = Format.B8G8R8A8Unorm; public const DXGI_FORMAT SWAP_CHAIN_BACK_BUFFER_FORMAT = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM;
public static InputLayoutDescription InputLayoutDescription => new() public static D3D12_INPUT_LAYOUT_DESC InputLayoutDescription => new()
{ {
pInputElementDescs = (InputElementDescription*)Unsafe.AsPointer(ref s_inputElementDescs[0]), pInputElementDescs = (D3D12_INPUT_ELEMENT_DESC*)Unsafe.AsPointer(ref s_inputElementDescs[0]),
NumElements = (uint)s_inputElementDescs.Length NumElements = (uint)s_inputElementDescs.Length
}; };
} }

View File

@@ -1,5 +1,163 @@
namespace Ghost.Graphics.D3D12.Utilities; using Misaki.HighPerformance.LowLevel.Utilities;
using TerraFX.Interop.DirectX;
internal static class D3D12Utility using static TerraFX.Aliases.D3D12_Alias;
namespace Ghost.Graphics.D3D12.Utilities;
internal unsafe static class ID3D12Resource_Extensions
{ {
extension(ID3D12Resource resource)
{
public void SetName(ReadOnlySpan<char> name)
{
resource.SetName(name.GetUnsafePtr());
}
}
}
internal static class D3D12_RASTERIZER_DESC_Extensions
{
extension(D3D12_RASTERIZER_DESC)
{
public static D3D12_RASTERIZER_DESC CULL_NONE => Create(D3D12_FILL_MODE_SOLID, D3D12_CULL_MODE_NONE);
public static D3D12_RASTERIZER_DESC CULL_CLOCKWISE => Create(D3D12_FILL_MODE_SOLID, D3D12_CULL_MODE_FRONT);
public static D3D12_RASTERIZER_DESC CULL_COUNTER_CLOCKWISE => Create(D3D12_FILL_MODE_SOLID, D3D12_CULL_MODE_BACK);
public static D3D12_RASTERIZER_DESC WIREFRAME => Create(D3D12_FILL_MODE_WIREFRAME, D3D12_CULL_MODE_NONE);
public static D3D12_RASTERIZER_DESC Create(
D3D12_FILL_MODE fillMode,
D3D12_CULL_MODE cullMode,
bool frontCounterClockwise = false,
int depthBias = D3D12_DEFAULT_DEPTH_BIAS,
float depthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP,
float slopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS,
bool depthClipEnable = true,
bool multisampleEnable = true,
bool antialiasedLineEnable = false,
uint forcedSampleCount = 0,
D3D12_CONSERVATIVE_RASTERIZATION_MODE conservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF)
{
return new D3D12_RASTERIZER_DESC
{
FillMode = fillMode,
CullMode = cullMode,
FrontCounterClockwise = frontCounterClockwise ? TRUE : FALSE,
DepthBias = depthBias,
DepthBiasClamp = depthBiasClamp,
SlopeScaledDepthBias = slopeScaledDepthBias,
DepthClipEnable = depthClipEnable ? TRUE : FALSE,
MultisampleEnable = multisampleEnable ? TRUE : FALSE,
AntialiasedLineEnable = antialiasedLineEnable ? TRUE : FALSE,
ForcedSampleCount = forcedSampleCount,
ConservativeRaster = conservativeRaster
};
}
}
}
internal static class D3D12_BLEND_DESC_Extensions
{
extension(D3D12_BLEND_DESC)
{
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 NON_PREMULTIPLIED => Create(D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_INV_SRC_ALPHA);
public static D3D12_BLEND_DESC Create(D3D12_BLEND srcBlend, D3D12_BLEND destBlend)
{
var blendDesc = new D3D12_BLEND_DESC
{
AlphaToCoverageEnable = false,
IndependentBlendEnable = false
};
for (var i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; i++)
{
blendDesc.RenderTarget[i].BlendEnable = srcBlend != D3D12_BLEND_ONE || destBlend != D3D12_BLEND_ZERO;
blendDesc.RenderTarget[i].LogicOp = D3D12_LOGIC_OP_NOOP;
blendDesc.RenderTarget[i].SrcBlend = srcBlend;
blendDesc.RenderTarget[i].DestBlend = destBlend;
blendDesc.RenderTarget[i].BlendOp = D3D12_BLEND_OP_ADD;
blendDesc.RenderTarget[i].SrcBlendAlpha = srcBlend;
blendDesc.RenderTarget[i].DestBlendAlpha = destBlend;
blendDesc.RenderTarget[i].BlendOpAlpha = D3D12_BLEND_OP_ADD;
blendDesc.RenderTarget[i].RenderTargetWriteMask = (byte)D3D12_COLOR_WRITE_ENABLE_ALL;
}
return blendDesc;
}
}
}
internal static class D3D12_DEPTH_STENCIL_DESC_Extensions
{
extension(D3D12_DEPTH_STENCIL_DESC)
{
public static D3D12_DEPTH_STENCIL_DESC NONE => Create(false, false, D3D12_COMPARISON_FUNC_LESS_EQUAL);
public static D3D12_DEPTH_STENCIL_DESC READ => Create(true, false, D3D12_COMPARISON_FUNC_LESS_EQUAL);
public static D3D12_DEPTH_STENCIL_DESC REVERSE_Z => Create(true, true, D3D12_COMPARISON_FUNC_GREATER_EQUAL);
public static D3D12_DEPTH_STENCIL_DESC READ_REVERSE_Z => Create(true, false, D3D12_COMPARISON_FUNC_GREATER_EQUAL);
public static D3D12_DEPTH_STENCIL_DESC Create(bool depthEnable,
bool depthWriteEnable,
D3D12_COMPARISON_FUNC depthFunc,
bool stencilEnable = false,
byte stencilReadMask = D3D12_DEFAULT_STENCIL_READ_MASK,
byte stencilWriteMask = D3D12_DEFAULT_STENCIL_WRITE_MASK,
D3D12_STENCIL_OP frontStencilFailOp = D3D12_STENCIL_OP_KEEP,
D3D12_STENCIL_OP frontStencilDepthFailOp = D3D12_STENCIL_OP_KEEP,
D3D12_STENCIL_OP frontStencilPassOp = D3D12_STENCIL_OP_KEEP,
D3D12_COMPARISON_FUNC frontStencilFunc = D3D12_COMPARISON_FUNC_ALWAYS,
D3D12_STENCIL_OP backStencilFailOp = D3D12_STENCIL_OP_KEEP,
D3D12_STENCIL_OP backStencilDepthFailOp = D3D12_STENCIL_OP_KEEP,
D3D12_STENCIL_OP backStencilPassOp = D3D12_STENCIL_OP_KEEP,
D3D12_COMPARISON_FUNC backStencilFunc = D3D12_COMPARISON_FUNC_ALWAYS)
{
return new D3D12_DEPTH_STENCIL_DESC
{
DepthEnable = depthEnable,
DepthWriteMask = depthWriteEnable ? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO,
DepthFunc = depthFunc,
StencilEnable = stencilEnable,
StencilReadMask = stencilReadMask,
StencilWriteMask = stencilWriteMask,
FrontFace = D3D12_DEPTH_STENCILOP_DESC.Create(frontStencilFailOp, frontStencilDepthFailOp, frontStencilPassOp, frontStencilFunc),
BackFace = D3D12_DEPTH_STENCILOP_DESC.Create(backStencilFailOp, backStencilDepthFailOp, backStencilPassOp, backStencilFunc)
};
}
}
}
internal static class D3D12_DEPTH_STENCILOP_DESC_Extensions
{
extension(D3D12_DEPTH_STENCILOP_DESC)
{
public static D3D12_DEPTH_STENCILOP_DESC DEFAULT => Create(D3D12_STENCIL_OP_KEEP, D3D12_STENCIL_OP_KEEP, D3D12_STENCIL_OP_KEEP, D3D12_COMPARISON_FUNC_ALWAYS);
public static D3D12_DEPTH_STENCILOP_DESC Create(D3D12_STENCIL_OP stencilFailOp, D3D12_STENCIL_OP stencilDepthFailOp, D3D12_STENCIL_OP stencilPassOp, D3D12_COMPARISON_FUNC stencilFunc)
{
return new D3D12_DEPTH_STENCILOP_DESC
{
StencilFailOp = stencilFailOp,
StencilDepthFailOp = stencilDepthFailOp,
StencilPassOp = stencilPassOp,
StencilFunc = stencilFunc
};
}
}
}
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;
}
} }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,41 @@
using System.Runtime.CompilerServices;
using TerraFX.Interop.Windows;
namespace Ghost.Graphics.D3D12.Utilities;
internal unsafe static class Win32Utility
{
public static void ThrowIfFailed(this HRESULT hr)
{
if (hr.FAILED)
{
throw new InvalidOperationException($"Operation failed with HRESULT: 0x{hr.Value:X8}");
}
}
public static void** GetVoidAddressOf<T>(this ComPtr<T> comPtr)
where T : unmanaged, IUnknown.Interface
{
return (void**)comPtr.GetAddressOf();
}
public static void** ReleaseAndGetVoidAddressOf<T>(this ComPtr<T> comPtr)
where T : unmanaged, IUnknown.Interface
{
return (void**)comPtr.ReleaseAndGetAddressOf();
}
public static ComPtr<T> Move<T>(this ComPtr<T> comPtr)
where T : unmanaged, IUnknown.Interface
{
ComPtr<T> copy = default;
Unsafe.AsRef(in comPtr).Swap(ref copy);
return copy;
}
public static bool HasFlag<T>(this uint flags, T flag)
where T : Enum
{
return (flags & Unsafe.As<T, uint>(ref flag)) != 0;
}
}

View File

@@ -9,21 +9,16 @@ namespace Ghost.Graphics.Data;
public struct Material : IHandleType public struct Material : IHandleType
{ {
internal UnsafeArray<CBufferCache> _cBufferCaches; internal CBufferCache _materialPropertiesCache;
public Identifier<Shader> Shader public Identifier<Shader> Shader
{ {
get; internal set; get; internal set;
} }
internal void Dispose() internal readonly void Dispose()
{ {
foreach (var cache in _cBufferCaches) _materialPropertiesCache.Dispose();
{
cache.Dispose();
}
_cBufferCaches.Dispose();
} }
} }
@@ -56,7 +51,7 @@ public ref struct MaterialAccessor
throw new ArgumentException($"Property '{propertyId}' has a size mismatch. Expected {propInfo.Size} bytes, but got {sizeof(T)} bytes."); throw new ArgumentException($"Property '{propertyId}' has a size mismatch. Expected {propInfo.Size} bytes, but got {sizeof(T)} bytes.");
} }
var cache = _materialData._cBufferCaches[propInfo.CBufferIndex]; var cache = _materialData._materialPropertiesCache[propInfo.CBufferIndex];
Unsafe.WriteUnaligned(ref cache.CpuData[(int)propInfo.ByteOffset], value); Unsafe.WriteUnaligned(ref cache.CpuData[(int)propInfo.ByteOffset], value);
} }
@@ -155,9 +150,15 @@ public ref struct MaterialAccessor
/// <param name="propertyName">The name of the shader property (e.g., "_TextureIndex1")</param> /// <param name="propertyName">The name of the shader property (e.g., "_TextureIndex1")</param>
/// <param name="texture">The bindless texture to reference</param> /// <param name="texture">The bindless texture to reference</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetTextureIndex(string propertyName, Handle<Texture> texture) public readonly void SetTextureBindless(string propertyName, Handle<Texture> texture)
{ {
SetUInt(propertyName, texture.BindlessDescriptor.Index); var bindlessIndex = _resourceDatabase.GetBindlessIndex(texture.AsResource());
if (bindlessIndex == -1)
{
throw new ArgumentException("The provided texture does not have a valid bindless index. Ensure the texture is created with bindless support.");
}
SetUInt(propertyName, (uint)bindlessIndex);
} }
/// <summary> /// <summary>
@@ -167,10 +168,15 @@ public ref struct MaterialAccessor
/// <param name="vertexBufferIndexProperty">The name of the vertex buffer index property</param> /// <param name="vertexBufferIndexProperty">The name of the vertex buffer index property</param>
/// <param name="indexBufferIndexProperty">The name of the index buffer index property</param> /// <param name="indexBufferIndexProperty">The name of the index buffer index property</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetMeshBufferIndex(ref readonly Mesh mesh, string vertexBufferIndexProperty = "_VertexBufferIndex", string indexBufferIndexProperty = "_IndexBufferIndex") public readonly void SetBufferBindless(string propertyName, Handle<GraphicsBuffer> buffer)
{ {
SetUInt(vertexBufferIndexProperty, mesh.vertexBuffer.BindlessDescriptor.Index); var bindlessIndex = _resourceDatabase.GetBindlessIndex(buffer.AsResource());
SetUInt(indexBufferIndexProperty, mesh.indexBuffer.BindlessDescriptor.Index); if (bindlessIndex == -1)
{
throw new ArgumentException("The provided buffer does not have a valid bindless index. Ensure the buffer is created with bindless support.");
}
SetUInt(propertyName, (uint)bindlessIndex);
} }
/// <summary> /// <summary>
@@ -180,7 +186,7 @@ public ref struct MaterialAccessor
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void UploadMaterialData(ICommandBuffer cmb) public readonly void UploadMaterialData(ICommandBuffer cmb)
{ {
foreach (var cache in _materialData._cBufferCaches) foreach (var cache in _materialData._materialPropertiesCache)
{ {
cmb.Upload(cache.GpuResource, cache.CpuData.AsSpan()); cmb.Upload(cache.GpuResource, cache.CpuData.AsSpan());
} }

View File

@@ -3,7 +3,6 @@ using Ghost.Graphics.RHI;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.LowLevel.Utilities;
using Win32;
namespace Ghost.Graphics.Data; namespace Ghost.Graphics.Data;
@@ -110,7 +109,7 @@ public unsafe readonly ref struct RenderingContext
var subresourceData = new SubResourceData var subresourceData = new SubResourceData
{ {
pData = data.GetPointer(), pData = data.GetUnsafePtr(),
rowPitch = rowPitch, rowPitch = rowPitch,
slicePitch = slicePitch slicePitch = slicePitch
}; };

View File

@@ -1,4 +1,5 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Core.Graphics;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
@@ -59,19 +60,26 @@ internal struct ShaderPass
// TODO: Multi pass and keyword support // TODO: Multi pass and keyword support
public struct Shader : IIdentifierType public struct Shader : IIdentifierType
{ {
private UnsafeList<CBufferInfo> _constantBuffers; private readonly ShaderDescriptor _descriptor;
private CBufferInfo _perMaterialBufferInfo;
private UnsafeList<PropertyInfo> _properties; private UnsafeList<PropertyInfo> _properties;
// TODO: Possible to move this to unmanaged heap?
private Dictionary<string, int> _propertyNameToIdMap; private Dictionary<string, int> _propertyNameToIdMap;
private bool _disposed; private bool _disposed;
internal readonly UnsafeList<CBufferInfo> ConstantBuffers => _constantBuffers; internal CBufferInfo PerMaterialBufferInfo
{
readonly get => _perMaterialBufferInfo;
set => _perMaterialBufferInfo = value;
}
internal readonly UnsafeList<PropertyInfo> Properties => _properties; internal readonly UnsafeList<PropertyInfo> Properties => _properties;
internal readonly Dictionary<string, int> PropertyNameToIdMap => _propertyNameToIdMap; internal readonly Dictionary<string, int> PropertyNameToIdMap => _propertyNameToIdMap;
public Shader() public Shader(ShaderDescriptor descriptor)
{ {
_constantBuffers = new(8, Allocator.Persistent); _descriptor = descriptor;
_properties = new(8, Allocator.Persistent); _properties = new(8, Allocator.Persistent);
_propertyNameToIdMap = new(8); _propertyNameToIdMap = new(8);
@@ -95,7 +103,6 @@ public struct Shader : IIdentifierType
return; return;
} }
_constantBuffers.Dispose();
_properties.Dispose(); _properties.Dispose();
_propertyNameToIdMap.Clear(); _propertyNameToIdMap.Clear();

View File

@@ -1,7 +1,7 @@
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Mathematics; using Misaki.HighPerformance.Mathematics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Win32.Graphics.Dxgi.Common; using TerraFX.Interop.DirectX;
namespace Ghost.Graphics.Data; namespace Ghost.Graphics.Data;
@@ -10,7 +10,7 @@ public struct Vertex
{ {
public unsafe static class Semantic public unsafe static class Semantic
{ {
public const Format ALIGNED_FORMAT = Format.R32G32B32A32Float; public const DXGI_FORMAT ALIGNED_FORMAT = DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT;
public const int COUNT = 5; public const int COUNT = 5;
public static readonly FixedString32 position = new("POSITION"); public static readonly FixedString32 position = new("POSITION");

View File

@@ -1,231 +0,0 @@
using Ghost.Core;
using Ghost.Graphics.Data;
using Ghost.Graphics.RenderGraphModule;
using System.Numerics;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.Examples;
/// <summary>
/// Example demonstrating render graph usage with history buffers and multiple cameras
/// </summary>
public static class RenderGraphExample
{
// Example pass data structures
public struct DepthPrePassData
{
public RGTextureHandle DepthBuffer;
// Add other render targets, constants, etc.
}
public struct GBufferPassData
{
public RGTextureHandle DepthBuffer;
public RGTextureHandle AlbedoBuffer;
public RGTextureHandle NormalBuffer;
public RGTextureHandle MaterialBuffer;
}
public struct LightingPassData
{
public RGTextureHandle DepthBuffer;
public RGTextureHandle AlbedoBuffer;
public RGTextureHandle NormalBuffer;
public RGTextureHandle MaterialBuffer;
public RGTextureHandle SceneColor;
public RGTextureHandle PreviousFrameColor; // History buffer
}
public struct PostProcessPassData
{
public RGTextureHandle SceneColor;
public RGTextureHandle FinalColor;
}
/// <summary>
/// Example camera/view state class to store history buffers (like Unreal's FViewState)
/// </summary>
public class CameraViewState
{
public Handle<Texture>? PreviousFrameColorBuffer;
public Handle<Texture>? PreviousFrameDepthBuffer;
public Handle<Texture>? VelocityBuffer;
public Matrix4x4 PreviousViewProjectionMatrix;
}
public static void ExampleRenderGraph(CameraViewState viewState, Handle<Texture> backBuffer)
{
// Create render graph with optional descriptor
var renderGraphDesc = new RenderGraphDesc(initialResourceCapacity: 512, initialPassCapacity: 128);
using var renderGraph = new RenderGraph("MainRenderGraph", renderGraphDesc);
// Begin recording passes
renderGraph.BeginRecord();
// Import external resources (backbuffer, history buffers)
var backBufferHandle = renderGraph.ImportTexture(
"BackBuffer",
backBuffer,
new TextureDescription(1920, 1080, 1, Format.R8G8B8A8Unorm, ResourceFlags.AllowRenderTarget));
// Import history buffer if available (for temporal effects like TAA)
RGTextureHandle? previousFrameColorHandle = null;
if (viewState.PreviousFrameColorBuffer.HasValue)
{
previousFrameColorHandle = renderGraph.ImportTexture(
"PreviousFrameColor",
viewState.PreviousFrameColorBuffer.Value,
new TextureDescription(1920, 1080, 1, Format.R8G8B8A8Unorm));
}
// Depth Pre-pass
renderGraph.CreatePass<DepthPrePassData>("DepthPrePass")
.Setup((ref DepthPrePassData data, RenderPassBuilder builder) =>
{
// Create depth buffer as transient resource
data.DepthBuffer = builder.CreateTexture("DepthBuffer",
new TextureDescription(1920, 1080, 1, Format.D32Float, ResourceFlags.AllowDepthStencil));
// Declare write access to depth buffer
data.DepthBuffer = builder.WriteTexture(data.DepthBuffer);
})
.SetRenderFunc((ref DepthPrePassData data, RenderPassContext ctx) =>
{
// Render depth only
// Use data.DepthBuffer for rendering
})
.Compile();
// G-Buffer Pass
var gBufferOutputs = renderGraph.CreatePass<GBufferPassData>("GBufferPass")
.Setup((ref GBufferPassData data, RenderPassBuilder builder) =>
{
// Read depth buffer from previous pass
data.DepthBuffer = builder.ReadTexture(data.DepthBuffer); // This will be resolved during compilation
// Create G-Buffer targets
data.AlbedoBuffer = builder.CreateTexture("AlbedoBuffer",
new TextureDescription(1920, 1080, 1, Format.R8G8B8A8Unorm, ResourceFlags.AllowRenderTarget));
data.NormalBuffer = builder.CreateTexture("NormalBuffer",
new TextureDescription(1920, 1080, 1, Format.R8G8B8A8Snorm, ResourceFlags.AllowRenderTarget));
data.MaterialBuffer = builder.CreateTexture("MaterialBuffer",
new TextureDescription(1920, 1080, 1, Format.R8G8B8A8Unorm, ResourceFlags.AllowRenderTarget));
// Declare write access
data.AlbedoBuffer = builder.WriteTexture(data.AlbedoBuffer);
data.NormalBuffer = builder.WriteTexture(data.NormalBuffer);
data.MaterialBuffer = builder.WriteTexture(data.MaterialBuffer);
})
.SetRenderFunc((ref GBufferPassData data, RenderPassContext ctx) =>
{
// Render geometry to G-Buffer
// Use data.DepthBuffer, data.AlbedoBuffer, etc.
});
gBufferOutputs.Compile();
// Lighting Pass
var lightingOutput = renderGraph.CreatePass<LightingPassData>("LightingPass")
.Setup((ref LightingPassData data, RenderPassBuilder builder) =>
{
// Read G-Buffer outputs
data.DepthBuffer = builder.ReadTexture(data.DepthBuffer);
data.AlbedoBuffer = builder.ReadTexture(data.AlbedoBuffer);
data.NormalBuffer = builder.ReadTexture(data.NormalBuffer);
data.MaterialBuffer = builder.ReadTexture(data.MaterialBuffer);
// Create scene color output
data.SceneColor = builder.CreateTexture("SceneColor",
new TextureDescription(1920, 1080, 1, Format.R16G16B16A16Float, ResourceFlags.AllowRenderTarget));
data.SceneColor = builder.WriteTexture(data.SceneColor);
// Use previous frame color if available (for temporal effects)
if (previousFrameColorHandle.HasValue)
{
data.PreviousFrameColor = builder.ReadTexture(previousFrameColorHandle.Value);
}
})
.SetRenderFunc((ref LightingPassData data, RenderPassContext ctx) =>
{
// Perform deferred lighting
// Can access previous frame color for temporal anti-aliasing, etc.
});
lightingOutput.Compile();
// Post-Processing Pass
renderGraph.CreatePass<PostProcessPassData>("PostProcessPass")
.Setup((ref PostProcessPassData data, RenderPassBuilder builder) =>
{
// Read scene color from lighting pass
data.SceneColor = builder.ReadTexture(data.SceneColor);
// Write to backbuffer
data.FinalColor = builder.WriteTexture(backBufferHandle);
})
.SetRenderFunc((ref PostProcessPassData data, RenderPassContext ctx) =>
{
// Apply tone mapping, gamma correction, etc.
// Copy to backbuffer
});
// End recording
renderGraph.EndRecord();
// Compile the render graph (dependency analysis, topological sort, resource lifetime analysis)
renderGraph.Compile();
// Export resources for next frame (history buffers)
if (lightingOutput != null) // In real implementation, you'd track the scene color handle
{
// viewState.PreviousFrameColorBuffer = renderGraph.ExportTexture(sceneColorHandle);
}
// Execute the render graph
renderGraph.Execute();
}
/// <summary>
/// Example with multiple cameras rendering to different targets
/// </summary>
public static void MultiCameraExample(List<CameraViewState> cameras, List<Handle<Texture>> renderTargets)
{
using var renderGraph = new RenderGraph("MultiCameraRenderGraph");
renderGraph.BeginRecord();
for (var i = 0; i < cameras.Count; i++)
{
var camera = cameras[i];
var renderTarget = renderTargets[i];
var cameraName = $"Camera{i}";
// Import render target
var renderTargetHandle = renderGraph.ImportTexture(
$"{cameraName}_RenderTarget",
renderTarget,
new TextureDescription(1920, 1080, 1, Format.R8G8B8A8Unorm, ResourceFlags.AllowRenderTarget));
// Camera-specific rendering passes
renderGraph.CreatePass<DepthPrePassData>($"{cameraName}_DepthPrePass")
.Setup((ref DepthPrePassData data, RenderPassBuilder builder) =>
{
data.DepthBuffer = builder.CreateTexture($"{cameraName}_DepthBuffer",
new TextureDescription(1920, 1080, 1, Format.D32Float, ResourceFlags.AllowDepthStencil));
data.DepthBuffer = builder.WriteTexture(data.DepthBuffer);
})
.SetRenderFunc((ref DepthPrePassData data, RenderPassContext ctx) =>
{
// Render with camera[i] view/projection matrices
})
.Compile();
// Additional passes for this camera...
}
renderGraph.EndRecord();
renderGraph.Compile();
renderGraph.Execute();
}
}

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
@@ -17,14 +17,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<!--<PackageReference Include="StbImageSharp" Version="2.30.15" />-->
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.1.0" /> <PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.1.0" />
<PackageReference Include="TerraFX.Interop.D3D12MemoryAllocator" Version="2.0.1.5" /> <PackageReference Include="TerraFX.Interop.D3D12MemoryAllocator" Version="2.0.1.5" />
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.2" /> <PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.2" />
<!--<PackageReference Include="Vortice.Dxc.Native" Version="1.0.4" />
<PackageReference Include="Vortice.Win32.Graphics.D3D12MemoryAllocator" Version="2.3.0" />
<PackageReference Include="Vortice.Win32.Graphics.Direct3D.Dxc" Version="2.3.0" />
<PackageReference Include="Vortice.Win32.Graphics.Direct3D.Fxc" Version="2.3.0" />-->
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -43,22 +38,12 @@
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<!--<ItemGroup> <ItemGroup>
<Using Include="Win32.Apis"> <Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
<Static>True</Static> </ItemGroup>
</Using>
<Using Include="Win32.Graphics.Dxgi.Apis"> <ItemGroup>
<Static>True</Static> <Folder Include="RenderGraphModule\" />
</Using> </ItemGroup>
<Using Include="Win32.Graphics.Direct3D12.Apis">
<Static>True</Static>
</Using>
<Using Include="Win32.Graphics.Direct3D.Fxc.Apis">
<Static>True</Static>
</Using>
<Using Include="Win32.Graphics.Direct3D.Dxc.Apis">
<Static>True</Static>
</Using>
</ItemGroup>-->
</Project> </Project>

View File

@@ -1,6 +1,6 @@
using Ghost.Core; using Ghost.Core;
using Ghost.Graphics.Data; using Ghost.Graphics.Data;
using Win32.Graphics.Direct3D12; using TerraFX.Interop.DirectX;
namespace Ghost.Graphics.RHI; namespace Ghost.Graphics.RHI;
@@ -178,20 +178,20 @@ public enum ResourceState
internal static class ResourceStateExtensions internal static class ResourceStateExtensions
{ {
public static ResourceStates ToD3D12States(this ResourceState state) public static D3D12_RESOURCE_STATES ToD3D12States(this ResourceState state)
{ {
return state switch return state switch
{ {
ResourceState.Common or ResourceState.Present => ResourceStates.Common, ResourceState.Common or ResourceState.Present => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_COMMON,
ResourceState.VertexAndConstantBuffer => ResourceStates.VertexAndConstantBuffer, ResourceState.VertexAndConstantBuffer => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER,
ResourceState.IndexBuffer => ResourceStates.IndexBuffer, ResourceState.IndexBuffer => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_INDEX_BUFFER,
ResourceState.RenderTarget => ResourceStates.RenderTarget, ResourceState.RenderTarget => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_RENDER_TARGET,
ResourceState.UnorderedAccess => ResourceStates.UnorderedAccess, ResourceState.UnorderedAccess => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
ResourceState.DepthWrite => ResourceStates.DepthWrite, ResourceState.DepthWrite => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_DEPTH_WRITE,
ResourceState.DepthRead => ResourceStates.DepthRead, ResourceState.DepthRead => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_DEPTH_READ,
ResourceState.PixelShaderResource => ResourceStates.PixelShaderResource, ResourceState.PixelShaderResource => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
ResourceState.CopyDest => ResourceStates.CopyDest, ResourceState.CopyDest => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_COPY_DEST,
ResourceState.CopySource => ResourceStates.CopySource, ResourceState.CopySource => D3D12_RESOURCE_STATES.D3D12_RESOURCE_STATE_COPY_SOURCE,
_ => throw new ArgumentException($"Unknown resource state: {state}") _ => throw new ArgumentException($"Unknown resource state: {state}")
}; };
} }

View File

@@ -1,6 +1,6 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Win32.Graphics.Direct3D12; using TerraFX.Interop.DirectX;
namespace Ghost.Graphics.RHI; namespace Ghost.Graphics.RHI;
@@ -52,16 +52,15 @@ public struct ResourceDesc
}; };
} }
internal static ResourceDesc FromD3D12(ResourceDescription desc) internal static ResourceDesc FromD3D12(D3D12_RESOURCE_DESC desc)
{ {
if (desc.Dimension == ResourceDimension.Buffer) if (desc.Dimension == D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_BUFFER)
{ {
return Buffer(new BufferDesc return Buffer(new BufferDesc
{ {
Size = desc.Width, Size = (uint)desc.Width,
Stride = 0, Stride = 0,
Usage = BufferUsage.None, Usage = BufferUsage.None,
CreationFlags = BufferCreationFlags.None,
MemoryType = MemoryType.Default MemoryType = MemoryType.Default
}); });
} }
@@ -76,7 +75,6 @@ public struct ResourceDesc
Dimension = desc.Dimension.ToTextureDimension(), Dimension = desc.Dimension.ToTextureDimension(),
MipLevels = desc.MipLevels, MipLevels = desc.MipLevels,
Usage = TextureUsage.None, Usage = TextureUsage.None,
CreationFlags = TextureCreationFlags.None
}); });
} }
} }
@@ -174,7 +172,7 @@ public struct RenderTargetDesc
/// </summary> /// </summary>
public static RenderTargetDesc Color(uint width, uint height, uint slice = 1, public static RenderTargetDesc Color(uint width, uint height, uint slice = 1,
TextureFormat format = TextureFormat.R8G8B8A8_UNorm, TextureDimension dimension = TextureDimension.Texture2D, TextureFormat format = TextureFormat.R8G8B8A8_UNorm, TextureDimension dimension = TextureDimension.Texture2D,
RenderTargetCreationFlags creationFlags = RenderTargetCreationFlags.AllowUAV | RenderTargetCreationFlags.DynamicallyScalable | RenderTargetCreationFlags.GenerateMips, RenderTargetCreationFlags creationFlags = RenderTargetCreationFlags.AllowUAV | RenderTargetCreationFlags.DynamicallyResolution | RenderTargetCreationFlags.GenerateMips,
uint mipLevels = 0u, uint sampleCount = 1) uint mipLevels = 0u, uint sampleCount = 1)
{ {
return new RenderTargetDesc return new RenderTargetDesc
@@ -196,7 +194,7 @@ public struct RenderTargetDesc
/// </summary> /// </summary>
public static RenderTargetDesc Depth(uint width, uint height, uint slice = 1, public static RenderTargetDesc Depth(uint width, uint height, uint slice = 1,
TextureFormat format = TextureFormat.D24_UNorm_S8_UInt, TextureDimension dimension = TextureDimension.Texture2D, TextureFormat format = TextureFormat.D24_UNorm_S8_UInt, TextureDimension dimension = TextureDimension.Texture2D,
RenderTargetCreationFlags creationFlags = RenderTargetCreationFlags.AllowUAV | RenderTargetCreationFlags.DynamicallyScalable, RenderTargetCreationFlags creationFlags = RenderTargetCreationFlags.AllowUAV | RenderTargetCreationFlags.DynamicallyResolution,
uint mipLevels = 0u, uint sampleCount = 1) uint mipLevels = 0u, uint sampleCount = 1)
{ {
return new RenderTargetDesc return new RenderTargetDesc
@@ -231,7 +229,6 @@ public struct RenderTargetDesc
Dimension = desc.Dimension, Dimension = desc.Dimension,
MipLevels = desc.MipLevels, MipLevels = desc.MipLevels,
Usage = usage, Usage = usage,
CreationFlags = TextureCreationFlags.Bindless
}; };
} }
} }
@@ -303,15 +300,6 @@ public struct TextureDesc
get; get;
set; set;
} }
/// <summary>
/// Texture creation flags
/// </summary>
public TextureCreationFlags CreationFlags
{
get;
set;
}
} }
/// <summary> /// <summary>
@@ -322,7 +310,7 @@ public struct BufferDesc
/// <summary> /// <summary>
/// Size of the buffer in bytes /// Size of the buffer in bytes
/// </summary> /// </summary>
public ulong Size public uint Size
{ {
get; get;
set; set;
@@ -343,12 +331,6 @@ public struct BufferDesc
set; set;
} }
public BufferCreationFlags CreationFlags
{
get;
set;
}
/// <summary> /// <summary>
/// Memory type for the buffer /// Memory type for the buffer
/// </summary> /// </summary>
@@ -388,13 +370,13 @@ public enum TextureDimension
public static class TextureDimensionExtension public static class TextureDimensionExtension
{ {
public static TextureDimension ToTextureDimension(this ResourceDimension dimension) public static TextureDimension ToTextureDimension(this D3D12_RESOURCE_DIMENSION dimension)
{ {
return dimension switch return dimension switch
{ {
ResourceDimension.Texture1D => TextureDimension.Texture2D, D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE1D => TextureDimension.Texture2D,
ResourceDimension.Texture2D => TextureDimension.Texture2D, D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE2D => TextureDimension.Texture2D,
ResourceDimension.Texture3D => TextureDimension.Texture3D, D3D12_RESOURCE_DIMENSION.D3D12_RESOURCE_DIMENSION_TEXTURE3D => TextureDimension.Texture3D,
_ => TextureDimension.Unknown, _ => TextureDimension.Unknown,
}; };
} }
@@ -409,7 +391,7 @@ public enum RenderTargetCreationFlags
None = 0, None = 0,
AllowUAV = 1 << 0, AllowUAV = 1 << 0,
AllowMSAA = 1 << 1, AllowMSAA = 1 << 1,
DynamicallyScalable = 1 << 2, DynamicallyResolution = 1 << 2,
GenerateMips = 1 << 3 GenerateMips = 1 << 3
} }
@@ -422,10 +404,3 @@ public enum MemoryType
Upload, // CPU-to-GPU memory Upload, // CPU-to-GPU memory
Readback // GPU-to-CPU memory Readback // GPU-to-CPU memory
} }
[Flags]
public enum TextureCreationFlags
{
None = 0,
Bindless = 1 << 0
}

View File

@@ -1,4 +1,4 @@
using Win32.Graphics.Dxgi.Common; using TerraFX.Interop.DirectX;
namespace Ghost.Graphics.RHI; namespace Ghost.Graphics.RHI;
@@ -46,13 +46,6 @@ public enum BufferUsage
ShaderResource = Vertex | Index | Constant ShaderResource = Vertex | Index | Constant
} }
[Flags]
public enum BufferCreationFlags
{
None = 0,
Bindless = 1 << 0
}
public enum IndexType public enum IndexType
{ {
UInt16, UInt16,
@@ -61,30 +54,30 @@ public enum IndexType
internal static class TextureFormatExtensions internal static class TextureFormatExtensions
{ {
public static Format ToD3D12Format(this TextureFormat format) public static DXGI_FORMAT ToD3D12Format(this TextureFormat format)
{ {
return format switch return format switch
{ {
TextureFormat.R8G8B8A8_UNorm => Format.R8G8B8A8Unorm, TextureFormat.R8G8B8A8_UNorm => DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM,
TextureFormat.B8G8R8A8_UNorm => Format.B8G8R8A8Unorm, TextureFormat.B8G8R8A8_UNorm => DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM,
TextureFormat.R16G16B16A16_Float => Format.R16G16B16A16Float, TextureFormat.R16G16B16A16_Float => DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT,
TextureFormat.R32G32B32A32_Float => Format.R32G32B32A32Float, TextureFormat.R32G32B32A32_Float => DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT,
TextureFormat.D24_UNorm_S8_UInt => Format.D24UnormS8Uint, TextureFormat.D24_UNorm_S8_UInt => DXGI_FORMAT.DXGI_FORMAT_D24_UNORM_S8_UINT,
TextureFormat.D32_Float => Format.D32Float, TextureFormat.D32_Float => DXGI_FORMAT.DXGI_FORMAT_D32_FLOAT,
_ => throw new NotSupportedException($"Texture format {format} is not supported."), _ => throw new NotSupportedException($"Texture format {format} is not supported."),
}; };
} }
public static TextureFormat ToTextureFormat(this Format format) public static TextureFormat ToTextureFormat(this DXGI_FORMAT format)
{ {
return format switch return format switch
{ {
Format.R8G8B8A8Unorm => TextureFormat.R8G8B8A8_UNorm, DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM => TextureFormat.R8G8B8A8_UNorm,
Format.B8G8R8A8Unorm => TextureFormat.B8G8R8A8_UNorm, DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM => TextureFormat.B8G8R8A8_UNorm,
Format.R16G16B16A16Float => TextureFormat.R16G16B16A16_Float, DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT => TextureFormat.R16G16B16A16_Float,
Format.R32G32B32A32Float => TextureFormat.R32G32B32A32_Float, DXGI_FORMAT.DXGI_FORMAT_R32G32B32A32_FLOAT => TextureFormat.R32G32B32A32_Float,
Format.D24UnormS8Uint => TextureFormat.D24_UNorm_S8_UInt, DXGI_FORMAT.DXGI_FORMAT_D24_UNORM_S8_UINT => TextureFormat.D24_UNorm_S8_UInt,
Format.D32Float => TextureFormat.D32_Float, DXGI_FORMAT.DXGI_FORMAT_D32_FLOAT => TextureFormat.D32_Float,
_ => TextureFormat.Unknown, _ => TextureFormat.Unknown,
}; };
} }

View File

@@ -1,418 +0,0 @@
using Ghost.Core;
using Ghost.Graphics.Data;
namespace Ghost.Graphics.RenderGraphModule;
/// <summary>
/// Descriptor for render graph configuration
/// </summary>
public readonly struct RenderGraphDesc
{
public readonly int InitialResourceCapacity;
public readonly int InitialPassCapacity;
public RenderGraphDesc(int initialResourceCapacity = 256, int initialPassCapacity = 64)
{
InitialResourceCapacity = initialResourceCapacity;
InitialPassCapacity = initialPassCapacity;
}
}
/// <summary>
/// Main render graph class for managing transient resources and render passes
/// </summary>
public sealed class RenderGraph : IDisposable
{
private readonly string _name;
private readonly List<RenderGraphResource> _resources;
private readonly List<RenderPass> _passes;
private readonly List<int> _compiledPassOrder;
private readonly List<RenderGraphBarrier> _barriers;
private bool _isRecording;
private bool _isCompiled;
private bool _isExecuted;
private bool _disposed;
public RenderGraph(string name, RenderGraphDesc desc = default)
{
_name = name;
_resources = new(desc.InitialResourceCapacity > 0 ? desc.InitialResourceCapacity : 256);
_passes = new(desc.InitialPassCapacity > 0 ? desc.InitialPassCapacity : 64);
_compiledPassOrder = new();
_barriers = new();
}
public string Name => _name;
public bool IsRecording => _isRecording;
public bool IsCompiled => _isCompiled;
/// <summary>
/// Begin recording render passes
/// </summary>
public void BeginRecord()
{
if (_isRecording)
throw new InvalidOperationException("Render graph is already recording");
if (_isCompiled)
throw new InvalidOperationException("Cannot record on a compiled render graph");
_isRecording = true;
_isExecuted = false;
// Clear previous frame data
foreach (var resource in _resources)
{
if (resource.Lifetime == ResourceLifetime.Transient)
{
resource.ReleaseResource();
}
}
_resources.RemoveAll(r => r.Lifetime == ResourceLifetime.Transient);
_passes.Clear();
_compiledPassOrder.Clear();
_barriers.Clear();
}
/// <summary>
/// End recording render passes
/// </summary>
public void EndRecord()
{
if (!_isRecording)
throw new InvalidOperationException("Render graph is not recording");
_isRecording = false;
}
/// <summary>
/// Create a new render pass
/// </summary>
public RenderPassCreator<TPassData> CreatePass<TPassData>(string passName)
where TPassData : struct
{
if (!_isRecording)
throw new InvalidOperationException("Cannot create pass when not recording");
return new RenderPassCreator<TPassData>(this, passName);
}
/// <summary>
/// Create a transient texture resource
/// </summary>
public RGTextureHandle CreateTexture(string name, ResourceLifetime lifetime, TextureDescription description)
{
var texture = new RenderGraphTexture(_resources.Count, name, lifetime, description);
_resources.Add(texture);
return new RGTextureHandle(texture.Id, this);
}
/// <summary>
/// Create a transient buffer resource
/// </summary>
public RGBufferHandle CreateBuffer(string name, ResourceLifetime lifetime, BufferDescription description)
{
var buffer = new RenderGraphBuffer(_resources.Count, name, lifetime, description);
_resources.Add(buffer);
return new RGBufferHandle(buffer.Id, this);
}
/// <summary>
/// Import an external texture (e.g., from previous frame, swap chain)
/// </summary>
public RGTextureHandle ImportTexture(string name, Handle<Texture> externalHandle, TextureDescription description)
{
var texture = new RenderGraphTexture(_resources.Count, name, externalHandle, description);
_resources.Add(texture);
return new RGTextureHandle(texture.Id, this);
}
/// <summary>
/// Import an external buffer (e.g., from previous frame)
/// </summary>
public RGBufferHandle ImportBuffer(string name, Handle<GraphicsBuffer> externalHandle, BufferDescription description)
{
var buffer = new RenderGraphBuffer(_resources.Count, name, externalHandle, description);
_resources.Add(buffer);
return new RGBufferHandle(buffer.Id, this);
}
/// <summary>
/// Export a resource for use in the next frame (for history buffers)
/// </summary>
public Handle<Texture> ExportTexture(RGTextureHandle handle)
{
if (!handle.IsValid || handle._resourceId >= _resources.Count)
throw new ArgumentException("Invalid texture handle", nameof(handle));
var texture = (RenderGraphTexture)_resources[handle._resourceId];
texture.Lifetime = ResourceLifetime.Persistent;
return texture.Handle;
}
/// <summary>
/// Export a buffer for use in the next frame
/// </summary>
public Handle<GraphicsBuffer> ExportBuffer(RGBufferHandle handle)
{
if (!handle.IsValid || handle._resourceId >= _resources.Count)
throw new ArgumentException("Invalid buffer handle", nameof(handle));
var buffer = (RenderGraphBuffer)_resources[handle._resourceId];
buffer.Lifetime = ResourceLifetime.Persistent;
return buffer.Handle;
}
public RenderGraphTexture GetTextureResource(RGTextureHandle handle)
{
if (!handle.IsValid || handle._resourceId >= _resources.Count)
throw new ArgumentException("Invalid texture handle", nameof(handle));
return (RenderGraphTexture)_resources[handle._resourceId];
}
public RenderGraphBuffer GetBufferResource(RGBufferHandle handle)
{
if (!handle.IsValid || handle._resourceId >= _resources.Count)
throw new ArgumentException("Invalid buffer handle", nameof(handle));
return (RenderGraphBuffer)_resources[handle._resourceId];
}
/// <summary>
/// Internal method to add a pass to the render graph
/// </summary>
internal void AddPass(RenderPass pass)
{
pass.Index = _passes.Count;
_passes.Add(pass);
}
/// <summary>
/// Internal method to update resource lifetime during pass setup
/// </summary>
internal void UpdateResourceLifetime(int resourceId, int passIndex)
{
if (resourceId >= _resources.Count)
return;
var resource = _resources[resourceId];
if (resource.FirstPassIndex == -1)
resource.FirstPassIndex = passIndex;
resource.LastPassIndex = Math.Max(resource.LastPassIndex, passIndex);
}
/// <summary>
/// Compile the render graph - performs dependency analysis, topological sort, and resource lifetime analysis
/// </summary>
public void Compile()
{
if (_isRecording)
throw new InvalidOperationException("Cannot compile while recording");
if (_isCompiled)
throw new InvalidOperationException("Render graph is already compiled");
// Setup all passes to gather resource dependencies
SetupPasses();
// Build dependency graph and perform topological sort
BuildDependencyGraph();
TopologicalSort();
// Analyze resource lifetimes and generate barriers
AnalyzeResourceLifetimes();
GenerateBarriers();
_isCompiled = true;
}
/// <summary>
/// Execute the compiled render graph
/// </summary>
public void Execute()
{
if (!_isCompiled)
throw new InvalidOperationException("Render graph must be compiled before execution");
if (_isExecuted)
throw new InvalidOperationException("Render graph has already been executed");
// Execute passes in topological order
foreach (var passIndex in _compiledPassOrder)
{
var pass = _passes[passIndex];
var context = new RenderPassContext(passIndex);
CreateTransientResources(passIndex);
ApplyBarriersForPass(passIndex);
// Execute the pass
pass.Execute(context);
}
_isExecuted = true;
}
private void SetupPasses()
{
for (var i = 0; i < _passes.Count; i++)
{
var pass = _passes[i];
var builder = new RenderPassBuilder(this, i);
pass.Setup(builder);
}
}
private void BuildDependencyGraph()
{
// Build dependencies based on resource usage
foreach (var pass in _passes)
{
var writeResources = new HashSet<int>();
var readResources = new HashSet<int>();
// Categorize resource accesses
foreach (var access in pass.ResourceAccesses)
{
switch (access.accessType)
{
case ResourceAccessType.Write:
writeResources.Add(access.resourceId);
break;
case ResourceAccessType.Read:
readResources.Add(access.resourceId);
break;
case ResourceAccessType.ReadWrite:
writeResources.Add(access.resourceId);
readResources.Add(access.resourceId);
break;
}
}
// Add dependencies based on Write-After-Read, Read-After-Write, Write-After-Write
for (var otherPassIndex = 0; otherPassIndex < pass.Index; otherPassIndex++)
{
var otherPass = _passes[otherPassIndex];
var hasDependency = false;
foreach (var otherAccess in otherPass.ResourceAccesses)
{
// WAR, RAW, WAW dependencies
if ((writeResources.Contains(otherAccess.resourceId) && otherAccess.accessType == ResourceAccessType.Read) ||
(readResources.Contains(otherAccess.resourceId) && otherAccess.accessType != ResourceAccessType.Read) ||
(writeResources.Contains(otherAccess.resourceId) && otherAccess.accessType != ResourceAccessType.Read))
{
hasDependency = true;
break;
}
}
if (hasDependency && !pass.Dependencies.Contains(otherPassIndex))
{
pass.Dependencies.Add(otherPassIndex);
}
}
}
}
private void TopologicalSort()
{
_compiledPassOrder.Clear();
var visited = new bool[_passes.Count];
var inDegree = new int[_passes.Count];
// Calculate in-degrees
foreach (var pass in _passes)
{
foreach (var dependency in pass.Dependencies)
{
inDegree[pass.Index]++;
}
}
// Kahn's algorithm for topological sorting
var queue = new Queue<int>();
for (var i = 0; i < _passes.Count; i++)
{
if (inDegree[i] == 0)
{
queue.Enqueue(i);
}
}
while (queue.Count > 0)
{
var passIndex = queue.Dequeue();
_compiledPassOrder.Add(passIndex);
var currentPass = _passes[passIndex];
foreach (var dependentPassIndex in _passes.Where(p => p.Dependencies.Contains(passIndex)).Select(p => p.Index))
{
inDegree[dependentPassIndex]--;
if (inDegree[dependentPassIndex] == 0)
{
queue.Enqueue(dependentPassIndex);
}
}
}
if (_compiledPassOrder.Count != _passes.Count)
{
throw new InvalidOperationException("Circular dependency detected in render graph");
}
}
private void AnalyzeResourceLifetimes()
{
// Resource lifetimes are already tracked during pass setup
// Additional analysis can be added here if needed
}
private void GenerateBarriers()
{
_barriers.Clear();
// TODO: Implement barrier generation based on resource state transitions
// This would analyze the resource usage patterns and generate appropriate D3D12 barriers
}
private void CreateTransientResources(int passIndex)
{
var pass = _passes[passIndex];
foreach (var access in pass.ResourceAccesses)
{
var resource = _resources[access.resourceId];
if (!resource.IsCreated)
{
resource.CreateResource();
}
}
}
private void ApplyBarriersForPass(int passIndex)
{
// TODO: Apply the generated barriers for the given pass
// This would involve creating D3D12 resource barriers and executing them on the command list
}
public void Dispose()
{
if (_disposed)
return;
foreach (var resource in _resources)
{
resource.ReleaseResource();
}
_resources.Clear();
_passes.Clear();
_compiledPassOrder.Clear();
_barriers.Clear();
_disposed = true;
}
}

View File

@@ -1,121 +0,0 @@
using Win32.Graphics.Direct3D12;
namespace Ghost.Graphics.RenderGraphModule;
/// <summary>
/// Handle for render graph texture resource
/// </summary>
public readonly struct RGTextureHandle : IEquatable<RGTextureHandle>
{
internal readonly RenderGraph? _renderGraph;
internal readonly int _resourceId;
internal RGTextureHandle(int resourceId, RenderGraph renderGraph)
{
_resourceId = resourceId;
_renderGraph = renderGraph;
}
public bool IsValid => _resourceId >= 0 && _renderGraph != null;
public bool Equals(RGTextureHandle other)
{
return _resourceId == other._resourceId && ReferenceEquals(_renderGraph, other._renderGraph);
}
public override bool Equals(object? obj)
{
return obj is RGTextureHandle other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(_resourceId, _renderGraph);
}
public static bool operator ==(RGTextureHandle left, RGTextureHandle right)
{
return left.Equals(right);
}
public static bool operator !=(RGTextureHandle left, RGTextureHandle right)
{
return !(left == right);
}
}
/// <summary>
/// Handle for render graph buffer resource
/// </summary>
public readonly struct RGBufferHandle : IEquatable<RGBufferHandle>
{
internal readonly RenderGraph? _renderGraph;
internal readonly int _resourceId;
internal RGBufferHandle(int resourceId, RenderGraph renderGraph)
{
_resourceId = resourceId;
_renderGraph = renderGraph;
}
public bool IsValid => _resourceId >= 0 && _renderGraph != null;
public bool Equals(RGBufferHandle other)
{
return _resourceId == other._resourceId && ReferenceEquals(_renderGraph, other._renderGraph);
}
public override bool Equals(object? obj)
{
return obj is RGBufferHandle other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(_resourceId, _renderGraph);
}
public static bool operator ==(RGBufferHandle left, RGBufferHandle right)
{
return left.Equals(right);
}
public static bool operator !=(RGBufferHandle left, RGBufferHandle right)
{
return !(left == right);
}
}
/// <summary>
/// Resource access information for dependency tracking
/// </summary>
internal readonly struct ResourceAccess
{
public readonly int resourceId;
public readonly int passIndex;
public readonly ResourceAccessType accessType;
public ResourceAccess(int resourceId, ResourceAccessType accessType, int passIndex)
{
this.resourceId = resourceId;
this.accessType = accessType;
this.passIndex = passIndex;
}
}
/// <summary>
/// Represents a barrier to be executed for resource state transitions
/// </summary>
internal readonly struct RenderGraphBarrier
{
public readonly int resourceId;
public readonly ResourceStates stateBefore;
public readonly ResourceStates stateAfter;
public RenderGraphBarrier(int resourceId, ResourceStates stateBefore, ResourceStates stateAfter)
{
this.resourceId = resourceId;
this.stateBefore = stateBefore;
this.stateAfter = stateAfter;
}
}

View File

@@ -1,290 +0,0 @@
using Ghost.Core;
using Ghost.Graphics.Data;
using Win32.Graphics.Direct3D12;
using Win32.Graphics.Dxgi.Common;
namespace Ghost.Graphics.RenderGraphModule;
/// <summary>
/// Represents different resource access types in the render graph
/// </summary>
public enum ResourceAccessType
{
Read,
Write,
ReadWrite
}
/// <summary>
/// Resource lifetime within the render graph
/// </summary>
public enum ResourceLifetime
{
/// <summary>
/// Resource is created and destroyed within a single frame
/// </summary>
Transient,
/// <summary>
/// Resource is imported from external source (e.g., history buffers, backbuffer)
/// </summary>
External,
/// <summary>
/// Resource persists across multiple frames (exported for next frame)
/// </summary>
Persistent
}
/// <summary>
/// Base class for render graph resources
/// </summary>
public abstract class RenderGraphResource
{
public int FirstPassIndex
{
get; set;
}
internal int LastPassIndex
{
get; set;
}
public int Id
{
get;
}
public string Name
{
get; set;
}
public ResourceLifetime Lifetime
{
get; internal set;
}
public bool IsImported
{
get; init;
}
public bool IsCreated
{
get; protected set;
}
protected RenderGraphResource(int id, string name, ResourceLifetime lifetime)
{
Id = id;
Name = name;
Lifetime = lifetime;
FirstPassIndex = -1;
LastPassIndex = -1;
}
internal abstract void CreateResource();
internal abstract void ReleaseResource();
}
/// <summary>
/// Represents a texture resource in the render graph
/// </summary>
public sealed class RenderGraphTexture : RenderGraphResource
{
internal Handle<Texture> Handle
{
get; set;
}
internal TextureDescription Description
{
get; private set;
}
public RenderGraphTexture(int id, string name, ResourceLifetime lifetime, TextureDescription description)
: base(id, name, lifetime)
{
Description = description;
}
public RenderGraphTexture(int id, string name, Handle<Texture> handle, TextureDescription description)
: base(id, name, ResourceLifetime.External)
{
Handle = handle;
Description = description;
IsImported = true;
IsCreated = true;
}
internal override void CreateResource()
{
if (IsCreated || IsImported)
return;
var allocFlags = Lifetime == ResourceLifetime.Transient
? Win32.Graphics.D3D12MemoryAllocator.AllocationFlags.CanAlias
: Win32.Graphics.D3D12MemoryAllocator.AllocationFlags.None;
Handle = GraphicsPipeline.ResourceAllocator.CreateTexture2D(
Description.Width,
Description.Height,
Description.MipLevels,
Description.Format,
Description.Flags,
allocFlags,
Description.InitialState);
IsCreated = true;
}
internal override void ReleaseResource()
{
if (!IsCreated || IsImported)
return;
Handle.Dispose();
IsCreated = false;
}
}
/// <summary>
/// Represents a buffer resource in the render graph
/// </summary>
public sealed class RenderGraphBuffer : RenderGraphResource
{
internal Handle<GraphicsBuffer> Handle
{
get; set;
}
internal BufferDescription Description
{
get; private set;
}
public RenderGraphBuffer(int id, string name, ResourceLifetime lifetime, BufferDescription description)
: base(id, name, lifetime)
{
Description = description;
}
public RenderGraphBuffer(int id, string name, Handle<GraphicsBuffer> handle, BufferDescription description)
: base(id, name, ResourceLifetime.External)
{
Handle = handle;
Description = description;
IsImported = true;
IsCreated = true;
}
internal override void CreateResource()
{
if (IsCreated || IsImported)
return;
var allocFlags = Lifetime == ResourceLifetime.Transient
? Win32.Graphics.D3D12MemoryAllocator.AllocationFlags.CanAlias
: Win32.Graphics.D3D12MemoryAllocator.AllocationFlags.None;
Handle = GraphicsPipeline.ResourceAllocator.CreateBuffer(
Description.SizeInBytes,
Description.HeapType,
Description.Flags,
allocFlags,
Description.InitialState);
IsCreated = true;
}
internal override void ReleaseResource()
{
if (!IsCreated || IsImported)
return;
Handle.Dispose();
IsCreated = false;
}
}
/// <summary>
/// Texture description for render graph texture creation
/// </summary>
public readonly struct TextureDescription
{
public readonly uint Width
{
get;
}
public readonly uint Height
{
get;
}
public readonly ushort MipLevels
{
get;
}
public readonly Format Format
{
get;
}
public readonly ResourceFlags Flags
{
get;
}
public readonly ResourceStates InitialState
{
get;
}
public TextureDescription(uint width, uint height, ushort mipLevels = 1,
Format format = Format.R8G8B8A8Unorm, ResourceFlags flags = ResourceFlags.None,
ResourceStates initialState = ResourceStates.Common)
{
Width = width;
Height = height;
MipLevels = mipLevels;
Format = format;
Flags = flags;
InitialState = initialState;
}
}
/// <summary>
/// Buffer description for render graph buffer creation
/// </summary>
public readonly struct BufferDescription
{
public readonly uint SizeInBytes
{
get;
}
public readonly HeapType HeapType
{
get;
}
public readonly ResourceFlags Flags
{
get;
}
public readonly ResourceStates InitialState
{
get;
}
public BufferDescription(uint sizeInBytes, HeapType heapType = HeapType.Default,
ResourceFlags flags = ResourceFlags.None, ResourceStates initialState = ResourceStates.Common)
{
SizeInBytes = sizeInBytes;
HeapType = heapType;
Flags = flags;
InitialState = initialState;
}
}

View File

@@ -1,125 +0,0 @@
namespace Ghost.Graphics.RenderGraphModule;
/// <summary>
/// Delegate for pass setup function
/// </summary>
public delegate void PassSetupFunction<TPassData>(ref TPassData data, RenderPassBuilder builder) where TPassData : struct;
/// <summary>
/// Delegate for pass execution function
/// </summary>
public delegate void PassExecuteFunction<TPassData>(ref TPassData data, RenderPassContext context) where TPassData : struct;
/// <summary>
/// Base class for render passes in the render graph
/// </summary>
internal abstract class RenderPass
{
public string Name
{
get;
}
public int Index
{
get; internal set;
}
public List<ResourceAccess> ResourceAccesses
{
get;
}
public List<int> Dependencies
{
get;
}
protected RenderPass(string name)
{
Name = name;
ResourceAccesses = new List<ResourceAccess>();
Dependencies = new List<int>();
}
public abstract void Setup(RenderPassBuilder builder);
public abstract void Execute(RenderPassContext context);
}
/// <summary>
/// Typed render pass implementation
/// </summary>
internal sealed class RenderPass<TPassData> : RenderPass
where TPassData : struct
{
private readonly PassSetupFunction<TPassData> _setupFunction;
private readonly PassExecuteFunction<TPassData> _executeFunction;
private TPassData _passData;
public RenderPass(string name, PassSetupFunction<TPassData> setupFunction, PassExecuteFunction<TPassData> executeFunction)
: base(name)
{
_setupFunction = setupFunction;
_executeFunction = executeFunction;
}
public override void Setup(RenderPassBuilder builder)
{
_setupFunction(ref _passData, builder);
ResourceAccesses.AddRange(builder.ResourceAccesses);
}
public override void Execute(RenderPassContext context)
{
_executeFunction(ref _passData, context);
}
public void SetPassData(TPassData passData)
{
_passData = passData;
}
}
/// <summary>
/// Builder for creating render passes
/// </summary>
public sealed class RenderPassCreator<TPassData>
where TPassData : struct
{
private readonly RenderGraph _renderGraph;
private readonly string _passName;
private PassSetupFunction<TPassData>? _setupFunction;
private PassExecuteFunction<TPassData>? _executeFunction;
internal RenderPassCreator(RenderGraph renderGraph, string passName)
{
_renderGraph = renderGraph;
_passName = passName;
}
/// <summary>
/// Set the setup function for the render pass
/// </summary>
public RenderPassCreator<TPassData> Setup(PassSetupFunction<TPassData> setupFunction)
{
_setupFunction = setupFunction;
return this;
}
/// <summary>
/// Set the render function for the render pass
/// </summary>
public RenderPassCreator<TPassData> SetRenderFunc(PassExecuteFunction<TPassData> executeFunction)
{
_executeFunction = executeFunction;
return this;
}
public void Compile()
{
if (_setupFunction == null)
throw new InvalidOperationException($"Setup function not set for pass '{_passName}'");
if (_executeFunction == null)
throw new InvalidOperationException($"Execute function not set for pass '{_passName}'");
var pass = new RenderPass<TPassData>(_passName, _setupFunction, _executeFunction);
_renderGraph.AddPass(pass);
}
}

View File

@@ -1,128 +0,0 @@
namespace Ghost.Graphics.RenderGraphModule;
/// <summary>
/// Context passed to render pass execution functions
/// </summary>
public readonly struct RenderPassContext
{
internal readonly int PassIndex;
// TODO: Add command list and other rendering context when available
internal RenderPassContext(int passIndex)
{
PassIndex = passIndex;
}
}
/// <summary>
/// Builder for configuring render pass resource dependencies
/// </summary>
public sealed class RenderPassBuilder
{
private readonly RenderGraph _renderGraph;
private readonly int _passIndex;
private readonly List<ResourceAccess> _resourceAccesses;
internal RenderPassBuilder(RenderGraph renderGraph, int passIndex)
{
_renderGraph = renderGraph;
_passIndex = passIndex;
_resourceAccesses = new List<ResourceAccess>();
}
internal IReadOnlyList<ResourceAccess> ResourceAccesses => _resourceAccesses;
/// <summary>
/// Declare a texture read dependency
/// </summary>
public RGTextureHandle ReadTexture(RGTextureHandle handle)
{
if (!handle.IsValid)
throw new ArgumentException("Invalid texture handle", nameof(handle));
_resourceAccesses.Add(new ResourceAccess(handle._resourceId, ResourceAccessType.Read, _passIndex));
_renderGraph.UpdateResourceLifetime(handle._resourceId, _passIndex);
return handle;
}
/// <summary>
/// Declare a texture write dependency
/// </summary>
public RGTextureHandle WriteTexture(RGTextureHandle handle)
{
if (!handle.IsValid)
throw new ArgumentException("Invalid texture handle", nameof(handle));
_resourceAccesses.Add(new ResourceAccess(handle._resourceId, ResourceAccessType.Write, _passIndex));
_renderGraph.UpdateResourceLifetime(handle._resourceId, _passIndex);
return handle;
}
/// <summary>
/// Declare a texture read-write dependency
/// </summary>
public RGTextureHandle ReadWriteTexture(RGTextureHandle handle)
{
if (!handle.IsValid)
throw new ArgumentException("Invalid texture handle", nameof(handle));
_resourceAccesses.Add(new ResourceAccess(handle._resourceId, ResourceAccessType.ReadWrite, _passIndex));
_renderGraph.UpdateResourceLifetime(handle._resourceId, _passIndex);
return handle;
}
/// <summary>
/// Declare a buffer read dependency
/// </summary>
public RGBufferHandle ReadBuffer(RGBufferHandle handle)
{
if (!handle.IsValid)
throw new ArgumentException("Invalid buffer handle", nameof(handle));
_resourceAccesses.Add(new ResourceAccess(handle._resourceId, ResourceAccessType.Read, _passIndex));
_renderGraph.UpdateResourceLifetime(handle._resourceId, _passIndex);
return handle;
}
/// <summary>
/// Declare a buffer write dependency
/// </summary>
public RGBufferHandle WriteBuffer(RGBufferHandle handle)
{
if (!handle.IsValid)
throw new ArgumentException("Invalid buffer handle", nameof(handle));
_resourceAccesses.Add(new ResourceAccess(handle._resourceId, ResourceAccessType.Write, _passIndex));
_renderGraph.UpdateResourceLifetime(handle._resourceId, _passIndex);
return handle;
}
/// <summary>
/// Declare a buffer read-write dependency
/// </summary>
public RGBufferHandle ReadWriteBuffer(RGBufferHandle handle)
{
if (!handle.IsValid)
throw new ArgumentException("Invalid buffer handle", nameof(handle));
_resourceAccesses.Add(new ResourceAccess(handle._resourceId, ResourceAccessType.ReadWrite, _passIndex));
_renderGraph.UpdateResourceLifetime(handle._resourceId, _passIndex);
return handle;
}
/// <summary>
/// Create a new transient texture within this pass
/// </summary>
public RGTextureHandle CreateTexture(string name, TextureDescription description)
{
return _renderGraph.CreateTexture(name, ResourceLifetime.Transient, description);
}
/// <summary>
/// Create a new transient buffer within this pass
/// </summary>
public RGBufferHandle CreateBuffer(string name, BufferDescription description)
{
return _renderGraph.CreateBuffer(name, ResourceLifetime.Transient, description);
}
}

View File

@@ -14,9 +14,9 @@ namespace Ghost.Graphics.RenderPasses;
/// </summary> /// </summary>
internal unsafe class MeshRenderPass : IRenderPass internal unsafe class MeshRenderPass : IRenderPass
{ {
private Identifier<Mesh> _mesh; private Handle<Mesh> _mesh;
private Handle<Material> _material;
private Identifier<Shader> _shader; private Identifier<Shader> _shader;
private Identifier<Material> _material;
private Handle<Texture>[]? _textures; private Handle<Texture>[]? _textures;
// Texture file paths for this demo // Texture file paths for this demo
@@ -52,7 +52,6 @@ internal unsafe class MeshRenderPass : IRenderPass
Width = imageData.Width, Width = imageData.Width,
Height = imageData.Height, Height = imageData.Height,
Dimension = TextureDimension.Texture2D, Dimension = TextureDimension.Texture2D,
CreationFlags = TextureCreationFlags.Bindless,
Format = TextureFormat.R8G8B8A8_UNorm, Format = TextureFormat.R8G8B8A8_UNorm,
MipLevels = 1, MipLevels = 1,
Slice = 1, Slice = 1,

View File

@@ -1,14 +0,0 @@
using Win32;
namespace Ghost.Graphics.Utilities;
internal static class Win32Utility
{
public static void ThrowIfFailed(this HResult hr)
{
if (hr.Failure)
{
throw new InvalidOperationException($"Operation failed with HRESULT: 0x{hr.Value:X8}");
}
}
}

View File

@@ -1,50 +0,0 @@
shader "MyShader/Standard"
{
properties
{
float4 color = float4(1, 1, 1, 1);
texture2d albedo;
}
pipeline
{
ztest = less_equal;
zwrite = on;
cull = back;
blend = opaque;
}
pass "Forward"
{
vs("ForwardVS.hlsl", "main");
ps("ForwardPS.hlsl", "main");
defines
{
USE_FOG;
}
keywords
{
dynamic(SHADOWS_ON, SHADOWS_OFF);
static(_ALBEDO_ON, _ALBEDO_OFF);
}
includes
{
"Common.hlsl";
}
}
pass "DepthOnly"
{
vs("DepthOnly.hlsl", "vsMain");
ps("DepthOnly.hlsl", "psMain");
pipeline
{
zwrite = on;
color_mask = 0;
}
}
}

View File

@@ -0,0 +1,57 @@
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);
texture2d texture3 = texture2d(grey);
texture2d texture4 = texture2d(normal);
}
pipeline
{
ztest = less_equal;
zwrite = on;
cull = back;
blend = opaque;
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");
ps("F:/csharp/GhostEngine/Ghost.Graphics/RenderPasses/ShaderCode.hlsl", "PSMain");
includes
{
"F:/csharp/GhostEngine/Ghost.Shader/BuiltIn/Common.hlsl";
}
}
}

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<PublishAot>True</PublishAot> <PublishAot>True</PublishAot>

View File

@@ -1,18 +1,56 @@
using Ghost.Shader; 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");
return;
var source = File.ReadAllText("F:/csharp/GhostEngine/Ghost.Graphics/test.gshader");
var source = File.ReadAllText("F:\\csharp\\GhostEngine\\Ghost.Graphics\\test.ghostshader");
var lexer = new Lexer(source); var lexer = new Lexer(source);
var stream = new TokenStream(lexer.Tokenize());
//foreach (var token in lexer.Tokenize())
//{
// Console.WriteLine($"{token.type} : '{token.lexeme}' at line {token.line}");
//}
var stream = new TokenStream(lexer.Tokenize().ToArray());
var shaderInfo = ShaderCompiler.ParseShaders(stream); var shaderInfo = ShaderCompiler.ParseShaders(stream);
var model = ShaderCompiler.SemanticAnalysis(shaderInfo[0], out var errors); var model = ShaderCompiler.SemanticAnalysis(shaderInfo[0], out var errors);
foreach (var error in errors) foreach (var error in errors)
{ {
Console.WriteLine(error); Console.WriteLine(error);
return;
}
if (model == null)
{
Console.WriteLine("Failed to compile shader due to errors.");
return;
}
var descriptor = ShaderCompiler.ResolveShader(model);
ShaderCompiler.CompileShader(descriptor, "C:/Users/Misaki/Downloads/Archive");
Console.WriteLine("Shader compiled successfully:");
public struct TestStruct
{
public int A;
public float B;
public Vector3 C;
public float3x4 D;
}
public enum TestEnum
{
First,
Second,
Third
}
public enum TestEnumFlags
{
None = 0,
First = 1 << 0,
Second = 1 << 1,
Third = 1 << 2,
} }

View File

@@ -0,0 +1,58 @@
#ifndef COMMON_HLSL
#define COMMON_HLSL
#undef USE_TRADITIONAL_BINDLESS // Just for testing, this should be handled by engine feature level.
#if defined(USE_TRADITIONAL_BINDLESS)
#define GLOBAL_TEXTURE2D_HEAP_SIZE 32768
#define GLOBAL_TEXTURE3D_HEAP_SIZE 32
#define GLOBAL_TEXTURECUBE_HEAP_SIZE 32
#define GLOBAL_TEXTURE2D_ARRAY_HEAP_SIZE 256
#define GLOBAL_TEXTURECUBE_ARRAY_HEAP_SIZE 32
#define GLOBAL_SAMPLER_HEAP_SIZE 32
#define GLOBAL_TEXTURE2D_HEAP GlobalTexture2DHeap
#define GLOBAL_TEXTURE3D_HEAP GlobalTexture3DHeap
#define GLOBAL_TEXTURECUBE_HEAP GlobalTextureCubeHeap
#define GLOBAL_TEXTURE2D_ARRAY_HEAP GlobalTexture2DArrayHeap
#define GLOBAL_TEXTURECUBE_ARRAY_HEAP GlobalTextureCubeArrayHeap
#define GLOBAL_SAMPLER_HEAP GlobalSamplerHeap
#else
#define GLOBAL_TEXTURE2D_HEAP ResourceDescriptorHeap
#define GLOBAL_TEXTURE3D_HEAP ResourceDescriptorHeap
#define GLOBAL_TEXTURECUBE_HEAP ResourceDescriptorHeap
#define GLOBAL_TEXTURE2D_ARRAY_HEAP ResourceDescriptorHeap
#define GLOBAL_TEXTURECUBE_ARRAY_HEAP ResourceDescriptorHeap
#define GLOBAL_SAMPLER_HEAP SamplerDescriptorHeap
#endif
#define TEXTURE2D_BINDLESS uint
#define TEXTURE3D_BINDLESS uint
#define TEXTURECUBE_BINDLESS uint
#define TEXTURE2D_ARRAY_BINDLESS uint
#define TEXTURECUBE_ARRAY_BINDLESS uint
#define SAMPLER_BINDLESS uint
#define STRUCT_BUFFER_BINDLESS uint
#define BYTE_ADDRESS_BUFFER_BINDLESS uint
#define GET_TEXTURE2D_BINDLESS(id) GLOBAL_TEXTURE2D_HEAP[id]
#define GET_TEXTURE2D_ARRAY_BINDLESS(id) GLOBAL_TEXTURE2D_ARRAY_HEAP[id]
#define GET_TEXTURE3D_BINDLESS(id) GLOBAL_TEXTURE3D_HEAP[id]
#define GET_TEXTURECUBE_BINDLESS(id) GLOBAL_TEXTURECUBE_HEAP[id]
#define GET_TEXTURECUBE_ARRAY_BINDLESS(id) GLOBAL_TEXTURECUBE_ARRAY_HEAP[id]
#define GET_SAMPLER_BINDLESS(id) GLOBAL_SAMPLER_HEAP[id]
#define SAMPLE_TEXTURE2D(tex, samp, uv) tex.Sample(samp, uv)
#define SAMPLE_TEXTURE2D_LEVEL(tex, samp, uv, level) tex.SampleLevel(samp, uv, level)
#define SAMPLE_TEXTURE2D_BINDLESS(texId, sampId, uv) GET_TEXTURE2D_BINDLESS(texId).Sample(GET_BINDLESS_SAMPLER(sampId), uv)
#define SAMPLE_TEXTURE2D_LEVEL_BINDLESS(texId, sampId, uv, level) GET_TEXTURE2D_BINDLESS(texId).SampleLevel(GET_BINDLESS_SAMPLER(sampId), uv, level)
#define SAMPLE_TEXTURE2D_ARRAY(tex, samp, uv, index) tex.Sample(samp, uv, index)
#define SAMPLE_TEXTURE2D_ARRAY_BINDLESS(texId, sampId, uv, index) GET_TEXTURE2D_ARRAY_BINDLESS(texId).Sample(GET_BINDLESS_SAMPLER(sampId), uv, index)
#endif // COMMON_HLSL

View File

@@ -0,0 +1,50 @@
#ifndef PROPERTIES_HLSL
#define PROPERTIES_HLSL
#include "F:/csharp/GhostEngine/Ghost.Shader/BuiltIn/Common.hlsl"
struct PerViewData
{
float4x4 cameraMatrix;
float4 screenSize; // xy = size, zw = 1/size
};
struct PerObjectData
{
float4x4 localToWorld;
float3 worldBoundsMin;
BYTE_ADDRESS_BUFFER_BINDLESS vertexBuffer;
float3 worldBoundsMax;
BYTE_ADDRESS_BUFFER_BINDLESS indexBuffer;
};
cbuffer GlobalConstants : register(b0)
{
GlobalData g_GlobalData;
};
cbuffer PerViewConstants : register(b1)
{
PerViewData g_PerViewData;
};
cbuffer PerObjectConstants : register(b2)
{
PerObjectData g_PerObjectData;
};
cbuffer PerMaterialConstants : register(b3)
{
PerMaterialData g_PerMaterialData;
};
#if defined(USE_TRADITIONAL_BINDLESS)
Texture2D GlobalTexture2DHeap[GLOBAL_TEXTURE2D_HEAP_SIZE] : register(t0);
Texture3D GlobalTexture3DHeap[GLOBAL_TEXTURE3D_HEAP_SIZE] : register(t0);
TextureCube GlobalTextureCubeHeap[GLOBAL_TEXTURECUBE_HEAP_SIZE] : register(t0);
Texture2DArray GlobalTexture2DArrayHeap[GLOBAL_TEXTURE2D_ARRAY_HEAP_SIZE] : register(t0);
TextureCubeArray GlobalTextureCubeArrayHeap[GLOBAL_TEXTURECUBE_ARRAY_HEAP_SIZE] : register(t0);
SamplerState GlobalSamplerHeap[GLOBAL_SAMPLER_HEAP_SIZE] : register(s0);
#endif
#endif

View File

@@ -0,0 +1,310 @@
namespace Ghost.Shader.Compiler;
public class Lexer
{
private readonly string _source;
private int _pos = 0;
private int _line = 1; // Lines typically start at 1
private int _column = 1; // Columns typically start at 1
public Lexer(string source)
{
_source = source;
}
public IEnumerable<Token> Tokenize()
{
while (!IsAtEnd())
{
var token = ScanToken();
if (token != Token.Null)
{
yield return token;
}
}
yield return new Token(TokenType.EndOfFile, string.Empty, _line, _column);
}
#region Core Scanning Logic
private Token ScanToken()
{
var c = Consume();
// Rule 1: Skip whitespace and handle line tracking
if (char.IsWhiteSpace(c))
{
HandleWhitespace(c);
return Token.Null;
}
// Rule 2: Handle comments
if (c == '/')
{
if (HandleComments())
{
return Token.Null;
}
// If not a comment, fall through to handle '/' as an operator if needed
}
// Rule 3: Handle string literals
if (c == '"')
{
return ScanStringLiteral();
}
// Rule 4: Handle numeric literals
if (char.IsDigit(c) || (c == '.' && char.IsDigit(Peek())))
{
return ScanNumericLiteral(c);
}
// Rule 5: Handle identifiers and keywords
if (char.IsLetter(c) || c == '_')
{
return ScanIdentifierOrKeyword(c);
}
// Rule 6: Handle single-character tokens (punctuation)
var punctuationToken = ScanPunctuation(c);
if (punctuationToken != Token.Null)
{
return punctuationToken;
}
// Rule 7: Skip unknown characters (could log warning in production)
return Token.Null;
}
#endregion
#region Rule Implementations
private void HandleWhitespace(char c)
{
if (c == '\n')
{
_line++;
_column = 1;
}
else if (c == '\r')
{
// Handle Windows line endings - peek for \n
if (Peek() == '\n')
{
Consume();
}
_line++;
_column = 1;
}
else
{
_column++;
}
}
private bool HandleComments()
{
var next = Peek();
if (next == '/') // Single-line comment
{
return ScanSingleLineComment();
}
else if (next == '*') // Multi-line comment
{
return ScanMultiLineComment();
}
return false; // Not a comment
}
private bool ScanSingleLineComment()
{
// Skip the second '/'
Consume();
// Consume until end of line
while (!IsAtEnd() && Peek() != '\n' && Peek() != '\r')
{
Consume();
}
return true;
}
private bool ScanMultiLineComment()
{
// Skip the '*'
Consume();
while (!IsAtEnd())
{
var c = Consume();
if (c == '\n')
{
_line++;
_column = 1;
}
else if (c == '*' && Peek() == '/')
{
Consume(); // Consume closing '/'
return true;
}
else
{
_column++;
}
}
// Unclosed comment - could throw error in production
return true;
}
private Token ScanStringLiteral()
{
var startLine = _line;
var startColumn = _column - 1; // Account for opening quote
var start = _pos;
while (!IsAtEnd() && Peek() != '"')
{
var c = Peek();
if (c == '\n')
{
_line++;
_column = 1;
}
else if (c == '\\')
{
// Handle escape sequences
Consume(); // Skip backslash
if (!IsAtEnd())
{
Consume();
}
continue;
}
Consume();
}
if (IsAtEnd())
{
// Unterminated string - could throw error in production
var unterminatedText = _source[start.._pos];
return new Token(TokenType.StringLiteral, unterminatedText, startLine, startColumn);
}
var text = _source[start.._pos];
Consume(); // Consume closing quote
return new Token(TokenType.StringLiteral, text, startLine, startColumn);
}
private Token ScanNumericLiteral(char firstChar)
{
var startColumn = _column - 1;
var start = _pos - 1; // Include the first character
var hasDot = firstChar == '.';
while (!IsAtEnd())
{
var c = Peek();
if (char.IsDigit(c))
{
Consume();
}
else if (c == '.' && !hasDot)
{
hasDot = true;
Consume();
}
else
{
break;
}
}
var number = _source[start.._pos];
return new Token(TokenType.Number, number, _line, startColumn);
}
private Token ScanIdentifierOrKeyword(char firstChar)
{
var startColumn = _column - 1;
var start = _pos - 1; // Include the first character
while (!IsAtEnd() && (char.IsLetterOrDigit(Peek()) || Peek() == '_'))
{
Consume();
}
var text = _source[start.._pos];
var tokenType = DetermineIdentifierType(text);
return new Token(tokenType, text, _line, startColumn);
}
private Token ScanPunctuation(char c)
{
var startColumn = _column - 1;
return c switch
{
'=' => new Token(TokenType.Equals, "=", _line, startColumn),
';' => new Token(TokenType.Semicolon, ";", _line, startColumn),
',' => new Token(TokenType.Comma, ",", _line, startColumn),
'{' => new Token(TokenType.LBrace, "{", _line, startColumn),
'}' => new Token(TokenType.RBrace, "}", _line, startColumn),
'(' => new Token(TokenType.LParen, "(", _line, startColumn),
')' => new Token(TokenType.RParen, ")", _line, startColumn),
_ => Token.Null // Unknown punctuation
};
}
#endregion
#region Classification Rules
private static TokenType DetermineIdentifierType(string text)
{
// Rule: Check if it's a known keyword first
if (TokenLexicon.IsKeyword(text))
{
return TokenType.Keyword;
}
// Rule: All other identifiers are treated as identifiers
// (Could extend this to handle functions, types, etc. as separate token types)
return TokenType.Identifier;
}
#endregion
#region Helper Methods
private bool IsAtEnd() => _pos >= _source.Length;
private char Consume()
{
if (IsAtEnd())
return '\0';
var c = _source[_pos];
_pos++;
_column++;
return c;
}
private char Peek() => IsAtEnd() ? '\0' : _source[_pos];
private char PeekNext() => _pos + 1 >= _source.Length ? '\0' : _source[_pos + 1];
#endregion
}

View File

@@ -0,0 +1,47 @@
using Ghost.Shader.Compiler;
namespace Ghost.Shader.Compiler.Parser;
internal class DefinesBlock : IBlockParser<List<Token>, List<string>>
{
public static bool ShouldEnter(Token token)
{
return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.DEFINES);
}
public static List<Token> Parse(TokenStreamSlice stream)
{
stream.Expect(TokenType.Keyword);
stream.Expect(TokenType.LBrace);
var defines = new List<Token>();
var bodyStream = stream.Slice(stream.Remaining - 1);
while (bodyStream.HasMore)
{
var defineToken = bodyStream.Expect(TokenType.Identifier);
defines.Add(defineToken);
bodyStream.Expect(TokenType.Semicolon);
}
stream.Expect(TokenType.RBrace);
return defines;
}
public static List<string>? SemanticAnalysis(List<Token>? syntax, List<ShaderError> errors)
{
if (syntax == null)
{
return null;
}
var defines = new List<string>(syntax.Count);
foreach (var defineToken in syntax)
{
defines.Add(defineToken.lexeme);
}
return defines;
}
}

View File

@@ -0,0 +1,8 @@
namespace Ghost.Shader.Compiler.Parser;
internal interface IBlockParser<T, U>
{
public static abstract bool ShouldEnter(Token token);
public static abstract T? Parse(TokenStreamSlice ts);
public static abstract U? SemanticAnalysis(T? syntax, List<ShaderError> errors);
}

View File

@@ -0,0 +1,61 @@
using Ghost.Shader.Compiler;
namespace Ghost.Shader.Compiler.Parser;
internal class IncludesBlock : IBlockParser<List<Token>, List<string>>
{
public static bool ShouldEnter(Token token)
{
return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.INCLUDES);
}
public static List<Token> Parse(TokenStreamSlice stream)
{
stream.Expect(TokenType.Keyword);
stream.Expect(TokenType.LBrace);
var includes = new List<Token>();
var bodyStream = stream.Slice(stream.Remaining - 1);
while (bodyStream.HasMore)
{
var includeToken = bodyStream.Expect(TokenType.StringLiteral);
includes.Add(includeToken);
bodyStream.Expect(TokenType.Semicolon);
}
stream.Expect(TokenType.RBrace);
return includes;
}
public static List<string>? SemanticAnalysis(List<Token>? syntax, List<ShaderError> errors)
{
if (syntax == null || syntax.Count == 0)
{
return null;
}
var includes = new List<string>(syntax.Count);
foreach (var includeToken in syntax)
{
var path = includeToken.lexeme;
if (File.Exists(path))
{
includes.Add(path);
}
else
{
errors.Add(new ShaderError
{
message = $"Included file '{path}' not found.",
line = includeToken.line,
column = includeToken.column
});
continue;
}
}
return includes;
}
}

View File

@@ -0,0 +1,84 @@
using Ghost.Shader.Compiler.Parser;
namespace Ghost.Shader.Compiler.Parser;
internal class KeywordsBlock : IBlockParser<List<FunctionCallDeclaration>, List<KeywordsGroup>>
{
public static bool ShouldEnter(Token token)
{
return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.KEYWORDS);
}
public static List<FunctionCallDeclaration> Parse(TokenStreamSlice stream)
{
stream.Expect(TokenType.Keyword);
stream.Expect(TokenType.LBrace);
var keywords = new List<FunctionCallDeclaration>();
var bodyStream = stream.Slice(stream.Remaining - 1);
while (bodyStream.HasMore)
{
var keywordToken = bodyStream.Expect(TokenType.Identifier);
var args = ParseUtility.ParseFunctionArguments(ref bodyStream, TokenType.Identifier);
keywords.Add(new FunctionCallDeclaration { name = keywordToken, arguments = args });
bodyStream.Expect(TokenType.Semicolon);
}
stream.Expect(TokenType.RBrace);
return keywords;
}
public static List<KeywordsGroup>? SemanticAnalysis(List<FunctionCallDeclaration>? syntax, List<ShaderError> errors)
{
if (syntax == null)
{
return null;
}
var keywords = new List<KeywordsGroup>(syntax.Count);
foreach (var keyword in syntax)
{
if (keyword.arguments == null || keyword.arguments.Count == 0)
{
errors.Add(new ShaderError
{
message = $"Function '{keyword.name.lexeme}' must have at least one argument.",
line = keyword.name.line,
column = keyword.name.column
});
continue;
}
var group = new KeywordsGroup();
switch (keyword.name.lexeme)
{
case TokenLexicon.KnownFunctions.DYNAMIC:
group.type = KeywordType.Dynamic;
break;
case TokenLexicon.KnownFunctions.STATIC:
group.type = KeywordType.Static;
break;
default:
errors.Add(new ShaderError
{
message = $"Unknown function name '{keyword.name.lexeme}'.",
line = keyword.name.line,
column = keyword.name.column
});
continue;
}
foreach (var arg in keyword.arguments)
{
group.keywords ??= new List<string>(keyword.arguments.Count);
group.keywords.Add(arg.lexeme);
}
keywords.Add(group);
}
return keywords;
}
}

View File

@@ -1,4 +1,4 @@
namespace Ghost.Shader; namespace Ghost.Shader.Compiler.Parser;
internal static class ParseUtility internal static class ParseUtility
{ {
@@ -26,6 +26,19 @@ internal static class ParseUtility
return args; return args;
} }
public static FunctionCallDeclaration ParseFunction(ref TokenStreamSlice stream, TokenType tokenType)
{
var functionToken = stream.Expect(TokenType.Identifier);
var args = ParseFunctionArguments(ref stream, tokenType);
stream.Expect(TokenType.Semicolon);
return new FunctionCallDeclaration
{
name = functionToken,
arguments = args
};
}
public static bool TrySliceLine(ref TokenStreamSlice stream, out TokenStreamSlice lineStream) public static bool TrySliceLine(ref TokenStreamSlice stream, out TokenStreamSlice lineStream)
{ {
var length = 0; var length = 0;

View File

@@ -0,0 +1,162 @@
namespace Ghost.Shader.Compiler.Parser;
// TODO: Add pass template support.
// Pass templates let user to inject their own custom code into the generated HLSL code.
// This is useful for adding custom lighting models, custom shadowing techniques, or other advanced effects without touching the core shader code.
internal class PassBlock : IBlockParser<PassSyntax, PassSemantic>
{
public static bool ShouldEnter(Token token)
{
return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.PASS);
}
public static PassSyntax Parse(TokenStreamSlice stream)
{
var pass = new PassSyntax();
stream.Expect(TokenType.Keyword);
pass.name = stream.Expect(TokenType.StringLiteral);
stream.Expect(TokenType.LBrace);
var bodyStream = stream.Slice(stream.Remaining - 1);
while (bodyStream.TryPeek(out var nextToken))
{
if (DefinesBlock.ShouldEnter(nextToken))
{
pass.defines = DefinesBlock.Parse(bodyStream.SliceNextBlock());
}
else if (IncludesBlock.ShouldEnter(nextToken))
{
pass.includes = IncludesBlock.Parse(bodyStream.SliceNextBlock());
}
else if (KeywordsBlock.ShouldEnter(nextToken))
{
pass.keywords = KeywordsBlock.Parse(bodyStream.SliceNextBlock());
}
else if (PipelineBlock.ShouldEnter(nextToken))
{
pass.localPipeline = PipelineBlock.Parse(bodyStream.SliceNextBlock());
}
else if (PropertiesBlock.ShouldEnter(nextToken))
{
pass.localProperties = PropertiesBlock.Parse(bodyStream.SliceNextBlock());
}
else if (nextToken.Match(TokenType.Identifier))
{
var func = ParseUtility.ParseFunction(ref bodyStream, TokenType.StringLiteral);
pass.functionCalls ??= new();
pass.functionCalls.Add(func);
}
else
{
throw new Exception($"Unexpected token '{nextToken}' in pass body.");
}
}
stream.Expect(TokenType.RBrace);
return pass;
}
public static PassSemantic? SemanticAnalysis(PassSyntax? syntax, List<ShaderError> errors)
{
if (syntax == null)
{
return null;
}
var model = new PassSemantic
{
name = syntax.name.lexeme,
defines = DefinesBlock.SemanticAnalysis(syntax.defines, errors),
includes = IncludesBlock.SemanticAnalysis(syntax.includes, errors),
keywords = KeywordsBlock.SemanticAnalysis(syntax.keywords, errors),
localProperties = PropertiesBlock.SemanticAnalysis(syntax.localProperties, errors),
localPipeline = PipelineBlock.SemanticAnalysis(syntax.localPipeline, errors),
};
if (model.localProperties != null
&& model.localProperties.Any(p => p.scope == PropertyScope.Global))
{
errors.Add(new ShaderError
{
message = "Global properties cannot be declared inside a pass. Move them to the shader properties block.",
line = syntax.name.line,
column = syntax.name.column
});
}
if (syntax.functionCalls != null)
{
foreach (var func in syntax.functionCalls)
{
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
};
}
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
};
}
break;
default:
errors.Add(new ShaderError
{
message = $"Unknown function '{func.name.lexeme}' in pass.",
line = func.name.line,
column = func.name.column
});
break;
}
}
}
if (model.vertexShader.shader == null || model.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.",
line = syntax.name.line,
column = syntax.name.column
});
}
return model;
}
}

View File

@@ -0,0 +1,187 @@
namespace Ghost.Shader.Compiler.Parser;
internal class PipelineBlock : IBlockParser<PipelineSyntax, PipelineSemantic>
{
public static bool ShouldEnter(Token token)
{
return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.PIPELINE);
}
public static PipelineSyntax Parse(TokenStreamSlice stream)
{
stream.Expect(TokenType.Keyword);
stream.Expect(TokenType.LBrace);
var pipeline = new PipelineSyntax();
var bodyStream = stream.Slice(stream.Remaining - 1);
while (bodyStream.HasMore)
{
var stateToken = bodyStream.Expect(TokenType.Identifier);
bodyStream.Expect(TokenType.Equals);
var valueToken = bodyStream.Expect(TokenType.Identifier | TokenType.Number);
pipeline.values ??= new();
pipeline.values.Add(new ValueDeclaration
{
name = stateToken,
value = valueToken
});
bodyStream.Expect(TokenType.Semicolon);
}
stream.Expect(TokenType.RBrace);
return pipeline;
}
public static PipelineSemantic? SemanticAnalysis(PipelineSyntax? syntax, List<ShaderError> errors)
{
if (syntax == null)
{
return null;
}
var semantic = new PipelineSemantic();
if (syntax.values != null)
{
foreach (var valueDecl in syntax.values)
{
switch (valueDecl.name.lexeme)
{
case TokenLexicon.KnownPipelineProperties.ZTEST:
switch (valueDecl.value.lexeme)
{
case "disable":
semantic.zTest = ZTestOptions.Disabled;
break;
case "less":
semantic.zTest = ZTestOptions.Less;
break;
case "less_equal":
semantic.zTest = ZTestOptions.LessEqual;
break;
case "equal":
semantic.zTest = ZTestOptions.Equal;
break;
case "greater_equal":
semantic.zTest = ZTestOptions.GreaterEqual;
break;
case "greater":
semantic.zTest = ZTestOptions.Greater;
break;
case "not_equal":
semantic.zTest = ZTestOptions.NotEqual;
break;
case "always":
semantic.zTest = ZTestOptions.Always;
break;
default:
errors.Add(new ShaderError
{
message = $"Invalid ZTest option: {valueDecl.value.lexeme}",
line = valueDecl.value.line,
column = valueDecl.value.column
});
break;
}
break;
case TokenLexicon.KnownPipelineProperties.ZWRITE:
switch (valueDecl.value.lexeme)
{
case "on":
semantic.zWrite = ZWriteOptions.On;
break;
case "off":
semantic.zWrite = ZWriteOptions.Off;
break;
default:
errors.Add(new ShaderError
{
message = $"Invalid ZWrite option: {valueDecl.value.lexeme}",
line = valueDecl.value.line,
column = valueDecl.value.column
});
break;
}
break;
case TokenLexicon.KnownPipelineProperties.CULL:
switch (valueDecl.value.lexeme)
{
case "off":
semantic.cull = CullOptions.Off;
break;
case "front":
semantic.cull = CullOptions.Front;
break;
case "back":
semantic.cull = CullOptions.Back;
break;
default:
errors.Add(new ShaderError
{
message = $"Invalid Cull option: {valueDecl.value.lexeme}",
line = valueDecl.value.line,
column = valueDecl.value.column
});
break;
}
break;
case TokenLexicon.KnownPipelineProperties.BLEND:
switch (valueDecl.value.lexeme)
{
case "opaque":
semantic.blend = BlendOptions.Opaque;
break;
case "alpha":
semantic.blend = BlendOptions.Alpha;
break;
case "additive":
semantic.blend = BlendOptions.Additive;
break;
case "multiply":
semantic.blend = BlendOptions.Multiply;
break;
case "premultiplied":
semantic.blend = BlendOptions.PremultipliedAlpha;
break;
default:
errors.Add(new ShaderError
{
message = $"Invalid Blend option: {valueDecl.value.lexeme}",
line = valueDecl.value.line,
column = valueDecl.value.column
});
break;
}
break;
case TokenLexicon.KnownPipelineProperties.COLORMASK:
if (uint.TryParse(valueDecl.value.lexeme, out var colorMask))
{
semantic.colorMask = colorMask;
}
else
{
errors.Add(new ShaderError
{
message = $"Invalid Color Mask value: {valueDecl.value.lexeme}",
line = valueDecl.value.line,
column = valueDecl.value.column
});
}
break;
default:
break;
}
}
}
return semantic;
}
}

View File

@@ -0,0 +1,467 @@
using Ghost.Shader.Compiler.Parser;
using Misaki.HighPerformance.Mathematics;
using System.Globalization;
namespace Ghost.Shader.Compiler.Parser;
internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySemantic>>
{
private delegate object? PropertyValueBuilder(List<Token> tokens, List<ShaderError> errors);
private sealed record PropTypeInfo(int ArgCount, TokenType ArgTokenType, PropertyValueBuilder? Builder);
private static readonly Dictionary<ShaderPropertyType, PropTypeInfo> s_propTypeInfo = new()
{
// Floats
[ShaderPropertyType.Float] = new(1, TokenType.Number, (syntax, errors) => ParseFloatValue(syntax[0], errors)),
[ShaderPropertyType.Float2] = new(2, TokenType.Number, (syntax, errors) => new float2(
ParseFloatValue(syntax[0], errors),
ParseFloatValue(syntax[1], errors))),
[ShaderPropertyType.Float3] = new(3, TokenType.Number, (syntax, errors) => new float3(
ParseFloatValue(syntax[0], errors),
ParseFloatValue(syntax[1], errors),
ParseFloatValue(syntax[2], errors))),
[ShaderPropertyType.Float4] = new(4, TokenType.Number, (syntax, errors) => new float4(
ParseFloatValue(syntax[0], errors),
ParseFloatValue(syntax[1], errors),
ParseFloatValue(syntax[2], errors),
ParseFloatValue(syntax[3], errors))),
// Ints
[ShaderPropertyType.Int] = new(1, TokenType.Number, (syntax, errors) => ParseIntValue(syntax[0], errors)),
[ShaderPropertyType.Int2] = new(2, TokenType.Number, (syntax, errors) => new int2(
ParseIntValue(syntax[0], errors),
ParseIntValue(syntax[1], errors))),
[ShaderPropertyType.Int3] = new(3, TokenType.Number, (syntax, errors) => new int3(
ParseIntValue(syntax[0], errors),
ParseIntValue(syntax[1], errors),
ParseIntValue(syntax[2], errors))),
[ShaderPropertyType.Int4] = new(4, TokenType.Number, (syntax, errors) => new int4(
ParseIntValue(syntax[0], errors),
ParseIntValue(syntax[1], errors),
ParseIntValue(syntax[2], errors),
ParseIntValue(syntax[3], errors))),
// UInts
[ShaderPropertyType.UInt] = new(1, TokenType.Number, (syntax, errors) => ParseUIntValue(syntax[0], errors)),
[ShaderPropertyType.UInt2] = new(2, TokenType.Number, (syntax, errors) => new uint2(
ParseUIntValue(syntax[0], errors),
ParseUIntValue(syntax[1], errors))),
[ShaderPropertyType.UInt3] = new(3, TokenType.Number, (syntax, errors) => new uint3(
ParseUIntValue(syntax[0], errors),
ParseUIntValue(syntax[1], errors),
ParseUIntValue(syntax[2], errors))),
[ShaderPropertyType.UInt4] = new(4, TokenType.Number, (syntax, errors) => new uint4(
ParseUIntValue(syntax[0], errors),
ParseUIntValue(syntax[1], errors),
ParseUIntValue(syntax[2], errors),
ParseUIntValue(syntax[3], errors))),
// Bools (numbers 0/1)
[ShaderPropertyType.Bool] = new(1, TokenType.Number, (syntax, errors) => ParseBoolValue(syntax[0], errors)),
[ShaderPropertyType.Bool2] = new(2, TokenType.Number, (syntax, errors) => new bool2(
ParseBoolValue(syntax[0], errors),
ParseBoolValue(syntax[1], errors))),
[ShaderPropertyType.Bool3] = new(3, TokenType.Number, (syntax, errors) => new bool3(
ParseBoolValue(syntax[0], errors),
ParseBoolValue(syntax[1], errors),
ParseBoolValue(syntax[2], errors))),
[ShaderPropertyType.Bool4] = new(4, TokenType.Number, (syntax, errors) => new bool4(
ParseBoolValue(syntax[0], errors),
ParseBoolValue(syntax[1], errors),
ParseBoolValue(syntax[2], errors),
ParseBoolValue(syntax[3], errors))),
// Textures (single identifier argument)
[ShaderPropertyType.Texture2D] = new(1, TokenType.Identifier, (syntax, errors) => ParseTextureDefault(syntax[0], errors)),
[ShaderPropertyType.Texture3D] = new(1, TokenType.Identifier, (syntax, errors) => ParseTextureDefault(syntax[0], errors)),
[ShaderPropertyType.TextureCube] = new(1, TokenType.Identifier, (syntax, errors) => ParseTextureDefault(syntax[0], errors)),
};
private static float ParseFloatValue(Token token, List<ShaderError> errors)
{
if (!float.TryParse(token.lexeme, CultureInfo.InvariantCulture, out var result))
{
errors.Add(new ShaderError
{
message = $"Failed to parse float value '{token.lexeme}'.",
line = token.line,
column = token.column
});
}
return result;
}
private static int ParseIntValue(Token token, List<ShaderError> errors)
{
if (!int.TryParse(token.lexeme, CultureInfo.InvariantCulture, out var result))
{
errors.Add(new ShaderError
{
message = $"Failed to parse int value '{token.lexeme}'.",
line = token.line,
column = token.column
});
}
return result;
}
private static uint ParseUIntValue(Token token, List<ShaderError> errors)
{
if (!uint.TryParse(token.lexeme, CultureInfo.InvariantCulture, out var result))
{
errors.Add(new ShaderError
{
message = $"Failed to parse uint value '{token.lexeme}'.",
line = token.line,
column = token.column
});
}
return result;
}
private static bool ParseBoolValue(Token token, List<ShaderError> errors)
{
if (!bool.TryParse(token.lexeme, out var result))
{
errors.Add(new ShaderError
{
message = $"Failed to parse bool value '{token.lexeme}'.",
line = token.line,
column = token.column
});
}
return result;
}
private static string ParseTextureDefault(Token token, List<ShaderError> errors)
{
if (!TokenLexicon.IsTextureDefaultValue(token.lexeme))
{
errors.Add(new ShaderError
{
message = $"Texture default value '{token.lexeme}' is not valid.",
line = token.line,
column = token.column
});
}
return token.lexeme;
}
public static bool ShouldEnter(Token token)
{
return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.PROPERTIES);
}
private static ShaderPropertyType FromString(string type)
{
return type.ToLower() switch
{
"float" => ShaderPropertyType.Float,
"float2" => ShaderPropertyType.Float2,
"float3" => ShaderPropertyType.Float3,
"float4" => ShaderPropertyType.Float4,
"int" => ShaderPropertyType.Int,
"int2" => ShaderPropertyType.Int2,
"int3" => ShaderPropertyType.Int3,
"int4" => ShaderPropertyType.Int4,
"uint" => ShaderPropertyType.UInt,
"uint2" => ShaderPropertyType.UInt2,
"uint3" => ShaderPropertyType.UInt3,
"uint4" => ShaderPropertyType.UInt4,
"bool" => ShaderPropertyType.Bool,
"bool2" => ShaderPropertyType.Bool2,
"bool3" => ShaderPropertyType.Bool3,
"bool4" => ShaderPropertyType.Bool4,
"texture2d" => ShaderPropertyType.Texture2D,
"texture3d" => ShaderPropertyType.Texture3D,
"texturecube" => ShaderPropertyType.TextureCube,
_ => ShaderPropertyType.None,
};
}
public static PropertiesSyntax Parse(TokenStreamSlice stream)
{
stream.Expect(TokenType.Keyword);
stream.Expect(TokenType.LBrace);
var syntax = new PropertiesSyntax();
var bodyStream = stream.Slice(stream.Remaining - 1);
while (bodyStream.HasMore)
{
var shaderProperty = new PropertyDeclaration();
if (bodyStream.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.GLOBAL)
|| bodyStream.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.LOCAL))
{
var scopeToken = bodyStream.Consume();
shaderProperty.scope = scopeToken;
}
var typeToken = bodyStream.Expect(TokenType.Identifier);
var nameToken = bodyStream.Expect(TokenType.Identifier);
shaderProperty.type = typeToken;
shaderProperty.name = nameToken;
var nextToken = bodyStream.Consume();
switch (nextToken.type)
{
case TokenType.Equals:
{
var constructorTypeToken = bodyStream.Expect(TokenType.Identifier);
var args = ParseUtility.ParseFunctionArguments(ref bodyStream, TokenType.Identifier | TokenType.Number);
shaderProperty.propertyConstructor = new FunctionCallDeclaration
{
name = constructorTypeToken,
arguments = args
};
bodyStream.Expect(TokenType.Semicolon);
break;
}
case TokenType.Semicolon:
break;
default:
throw new Exception($"Unexpected token '{nextToken.lexeme}' in property declaration.");
}
syntax.properties ??= new();
syntax.properties.Add(shaderProperty);
}
stream.Expect(TokenType.RBrace);
return syntax;
}
public static List<PropertySemantic>? SemanticAnalysis(PropertiesSyntax? syntax, List<ShaderError> errors)
{
if (syntax == null)
{
return null;
}
var models = new List<PropertySemantic>();
var usedPropertyNames = new HashSet<string>();
if (syntax.properties != null)
{
foreach (var property in syntax.properties)
{
var model = new PropertySemantic
{
scope = property.scope.lexeme switch
{
TokenLexicon.KnownKeywords.GLOBAL => PropertyScope.Global,
TokenLexicon.KnownKeywords.LOCAL => PropertyScope.Local,
_ => PropertyScope.Local,
}
};
var flowControl = ValidatePropertyType(errors, property, model);
if (!flowControl)
{
continue;
}
flowControl = ValidatePropertyName(errors, usedPropertyNames, property, model);
if (!flowControl)
{
continue;
}
if (property.propertyConstructor != null)
{
flowControl = ValidatePropertyConstructor(errors, property, model);
if (!flowControl)
{
continue;
}
}
usedPropertyNames.Add(property.name.lexeme);
models.Add(model);
}
}
return models;
}
private static bool ValidatePropertyType(List<ShaderError> errors, PropertyDeclaration property, PropertySemantic model)
{
if (!TokenLexicon.IsType(property.type.lexeme))
{
errors.Add(new ShaderError
{
message = $"Shader property type '{property.type.lexeme}' is not a valid type.",
line = property.type.line,
column = property.type.column
});
return false;
}
model.type = FromString(property.type.lexeme);
return true;
}
private static bool ValidatePropertyName(List<ShaderError> errors, HashSet<string> usedPropertyNames, PropertyDeclaration property, PropertySemantic model)
{
if (string.IsNullOrWhiteSpace(property.name.lexeme))
{
errors.Add(new ShaderError
{
message = "Shader property has an empty name.",
line = property.name.line,
column = property.name.column
});
return false;
}
else if (usedPropertyNames.Contains(property.name.lexeme))
{
errors.Add(new ShaderError
{
message = $"Shader property name '{property.name.lexeme}' is duplicated.",
line = property.name.line,
column = property.name.column
});
return false;
}
model.name = property.name.lexeme;
return true;
}
private static bool ValidatePropertyConstructor(List<ShaderError> errors, PropertyDeclaration property, PropertySemantic model)
{
var constructor = property.propertyConstructor;
if (!constructor.HasValue)
{
errors.Add(new ShaderError
{
message = "Shader property constructor is null.",
line = property.name.line,
column = property.name.column
});
return false;
}
var constructorValue = constructor.Value;
if (string.IsNullOrWhiteSpace(constructorValue.name.lexeme))
{
errors.Add(new ShaderError
{
message = "Shader property constructor has an empty name.",
line = constructorValue.name.line,
column = constructorValue.name.column
});
return false;
}
if (constructorValue.name.lexeme != property.type.lexeme)
{
errors.Add(new ShaderError
{
message = $"Shader property constructor name '{constructorValue.name.lexeme}' does not match property type '{property.type.lexeme}'.",
line = constructorValue.name.line,
column = constructorValue.name.column
});
return false;
}
if (!s_propTypeInfo.TryGetValue(model.type, out var info))
{
errors.Add(new ShaderError
{
message = $"No constructor metadata registered for property type '{model.type}'.",
line = constructorValue.name.line,
column = constructorValue.name.column
});
return false;
}
// Count check
if (constructorValue.arguments == null)
{
errors.Add(new ShaderError
{
message = "Shader property constructor arguments are null.",
line = constructorValue.name.line,
column = constructorValue.name.column
});
return false;
}
if (constructorValue.arguments.Count != info.ArgCount)
{
errors.Add(new ShaderError
{
message = $"Shader property constructor for type '{property.type.lexeme}' expects {info.ArgCount} argument(s), but got {constructorValue.arguments.Count}.",
line = constructorValue.name.line,
column = constructorValue.name.column
});
return false;
}
// Type check (uniform requirement for all args)
var hasError = false;
for (var i = 0; i < constructorValue.arguments.Count; i++)
{
var arg = constructorValue.arguments[i];
if (!arg.Match(info.ArgTokenType))
{
errors.Add(new ShaderError
{
message = $"Shader property constructor argument {i} expects token kind '{info.ArgTokenType}', but got '{arg.type}'.",
line = arg.line,
column = arg.column
});
hasError = true;
}
}
if (hasError)
{
return false;
}
// Build default value if we have a builder (textures currently null / TODO)
if (info.Builder != null)
{
try
{
model.defaultValue = info.Builder(constructorValue.arguments, errors);
}
catch (Exception ex)
{
errors.Add(new ShaderError
{
message = $"Failed to construct default value for property '{property.name.lexeme}': {ex.Message}",
line = constructorValue.name.line,
column = constructorValue.name.column
});
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,116 @@
using System.Runtime.InteropServices;
namespace Ghost.Shader.Compiler.Parser;
internal class ShaderBlock : IBlockParser<ShaderSyntax, ShaderSemantics>
{
public static bool ShouldEnter(Token token)
{
return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.SHADER);
}
public static ShaderSyntax Parse(TokenStreamSlice stream)
{
var shader = new ShaderSyntax();
stream.Expect(TokenType.Keyword);
shader.name = stream.Expect(TokenType.StringLiteral);
stream.Expect(TokenType.LBrace);
var bodyStream = stream.Slice(stream.Remaining - 1);
while (bodyStream.TryPeek(out var nextToken))
{
if (PropertiesBlock.ShouldEnter(nextToken))
{
shader.properties = PropertiesBlock.Parse(bodyStream.SliceNextBlock());
}
else if (PipelineBlock.ShouldEnter(nextToken))
{
shader.pipeline = PipelineBlock.Parse(bodyStream.SliceNextBlock());
}
else if (PassBlock.ShouldEnter(nextToken))
{
shader.passes ??= new();
shader.passes.Add(PassBlock.Parse(bodyStream.SliceNextBlock()));
}
else if (nextToken.Match(TokenType.Identifier))
{
var func = ParseUtility.ParseFunction(ref bodyStream, TokenType.StringLiteral | TokenType.Number | TokenType.Identifier);
shader.functionCalls ??= new();
shader.functionCalls.Add(func);
}
else
{
throw new Exception($"Unexpected token '{nextToken}' in shader body.");
}
}
stream.Expect(TokenType.RBrace);
return shader;
}
public static ShaderSemantics? SemanticAnalysis(ShaderSyntax? syntax, List<ShaderError> errors)
{
if (syntax == null)
{
return null;
}
var shaderModel = new ShaderSemantics
{
name = syntax.name.lexeme,
properties = PropertiesBlock.SemanticAnalysis(syntax.properties, errors),
pipeline = PipelineBlock.SemanticAnalysis(syntax.pipeline, errors)
};
if (syntax.passes != null)
{
foreach (var passSyntax in syntax.passes)
{
var passModel = PassBlock.SemanticAnalysis(passSyntax, errors);
if (passModel != null)
{
shaderModel.passes ??= new();
shaderModel.passes.Add(passModel);
}
}
}
if (syntax.functionCalls != null)
{
foreach (var func in syntax.functionCalls)
{
switch (func.name.lexeme)
{
case TokenLexicon.KnownFunctions.FALLBACK:
if (func.arguments == null || func.arguments.Count != 1)
{
errors.Add(new ShaderError
{
message = "Fallback declaration requires exactly one arguments: (fallback shader name).",
line = func.name.line,
column = func.name.column
});
continue;
}
shaderModel.fallback = func.arguments[0].lexeme;
break;
default:
errors.Add(new ShaderError
{
message = $"Unknown function '{func.name.lexeme}' in shader.",
line = func.name.line,
column = func.name.column
});
break;
}
}
}
return shaderModel;
}
}

View File

@@ -0,0 +1,374 @@
using Ghost.Shader.Compiler.Parser;
using System.Collections.Generic;
using System.Text;
namespace Ghost.Shader.Compiler;
public struct ShaderError
{
public string message;
public int line;
public int column;
public readonly override string ToString()
{
return $"Error at {line}:{column} - {message}";
}
}
internal static class ShaderCompiler
{
private const string _GLOBAL_PROPERTY_FILE_NAME = "GlobalData.g.hlsl";
private const string _GENERATED_FILE_HEADER = "// Auto-generated shader file. Please do not edit this file directly.";
private struct ShaderInheritance
{
public ShaderSemantics? parent;
public List<ShaderInheritance>? children;
}
public static List<ShaderSyntax> ParseShaders(TokenStream stream)
{
var shaders = new List<ShaderSyntax>();
while (stream.TryPeek(out var nextToken))
{
if (ShaderBlock.ShouldEnter(nextToken))
{
var shader = ShaderBlock.Parse(stream.SliceNextBlock());
shaders.Add(shader);
}
else if (nextToken.Match(TokenType.EndOfFile))
{
stream.Consume();
}
else
{
throw new Exception($"Unexpected token '{nextToken.lexeme}' at top level. Expected 'shader' declaration.");
}
}
return shaders;
}
public static ShaderSemantics? SemanticAnalysis(ShaderSyntax syntax, out List<ShaderError> errors)
{
errors = new();
if (string.IsNullOrWhiteSpace(syntax.name.lexeme))
{
errors.Add(new ShaderError
{
message = "Shader name cannot be empty.",
line = syntax.name.line,
column = syntax.name.column
});
return null;
}
var shaderModel = ShaderBlock.SemanticAnalysis(syntax, errors);
return shaderModel;
}
private static List<ShaderSemantics>? TopologicalSort(ReadOnlySpan<ShaderSemantics> semantics)
{
var inDegrees = new Dictionary<string, int>();
var childrenMap = new Dictionary<string, List<string>>();
var semanticsMap = new Dictionary<string, ShaderSemantics>();
foreach (var s in semantics)
{
inDegrees[s.name] = 0;
childrenMap[s.name] = new List<string>();
semanticsMap[s.name] = s;
}
foreach (var s in semantics)
{
if (!string.IsNullOrEmpty(s.fallback) && semanticsMap.ContainsKey(s.fallback))
{
childrenMap[s.fallback].Add(s.name);
inDegrees[s.name]++;
}
}
var queue = new Queue<ShaderSemantics>();
foreach (var s in semantics)
{
if (inDegrees[s.name] == 0)
{
queue.Enqueue(s);
}
}
var sortedList = new List<ShaderSemantics>();
while (queue.Count > 0)
{
var current = queue.Dequeue();
sortedList.Add(current);
foreach (var childName in childrenMap[current.name])
{
inDegrees[childName]--;
if (inDegrees[childName] == 0)
{
queue.Enqueue(semanticsMap[childName]);
}
}
}
// If there's a cycle, the graph will not be fully traversed.
return sortedList.Count == semantics.Length ? sortedList : null;
}
private static string GetPassUniqueId(ShaderSemantics shader, PassSemantic pass)
{
//static ulong Fnv1a64(ReadOnlySpan<char> data)
//{
// const ulong offset = 14695981039346656037;
// const ulong prime = 1099511628211;
// var hash = offset;
// foreach (var b in data)
// {
// hash ^= b;
// hash *= prime;
// }
// return hash;
//}
//return $"{Fnv1a64(shader.name)}_{pass.name}";
return $"{shader.name}_{pass.name}";
}
private static PipelineDescriptor MeragePipeline(PipelineSemantic? semantic, PipelineDescriptor parent)
{
if (semantic == null)
{
return parent;
}
return new PipelineDescriptor
{
zTest = semantic.zTest ?? parent.zTest,
zWrite = semantic.zWrite ?? parent.zWrite,
cull = semantic.cull ?? parent.cull,
blend = semantic.blend ?? parent.blend,
colorMask = semantic.colorMask ?? parent.colorMask
};
}
private static List<PropertyDescriptor> MergeProperties(List<PropertySemantic>? semantics, List<PropertyDescriptor>? parent)
{
var result = new List<PropertyDescriptor>();
if (parent != null)
{
result.AddRange(parent);
}
if (semantics != null)
{
foreach (var prop in semantics)
{
if (prop.scope == PropertyScope.Local)
{
result.Add(new PropertyDescriptor
{
name = prop.name,
type = prop.type,
defaultValue = prop.defaultValue
});
}
}
}
return result.DistinctBy(p => p.name).ToList();
}
// TODO: Implement shader inheritance resolution, including property and pass merging.
// Currently, we just ignore inheritance.
public static ShaderDescriptor ResolveShader(ShaderSemantics semantics)
{
var descriptor = new ShaderDescriptor
{
name = semantics.name
};
var shaderGlobalProperties = semantics.properties?.Where(p => p.scope == PropertyScope.Global).Select(p => new PropertyDescriptor
{
name = p.name,
type = p.type,
defaultValue = p.defaultValue
}).ToList();
var shaderLocalProperties = semantics.properties?.Where(p => p.scope == PropertyScope.Local).Select(p => new PropertyDescriptor
{
name = p.name,
type = p.type,
defaultValue = p.defaultValue
}).ToList();
if (shaderGlobalProperties != null)
{
descriptor.globalProperties.AddRange(shaderGlobalProperties);
}
if (semantics.passes != null)
{
foreach (var pass in semantics.passes)
{
var localPipeline = MeragePipeline(pass.localPipeline, PipelineDescriptor.Default);
var localProperties = MergeProperties(pass.localProperties, shaderLocalProperties); // TODO: Merge with base shader properties if inheritance is implemented.
var fullPass = new FullPassDescriptor
{
uniqueIdentifier = GetPassUniqueId(semantics, pass),
vertexShader = pass.vertexShader,
pixelShader = pass.pixelShader,
localPipeline = localPipeline,
defines = pass.defines,
includes = pass.includes,
keywords = pass.keywords,
properties = localProperties
};
descriptor.passes.Add(fullPass);
}
}
return descriptor;
}
private static string ShaderPropertyTypeToHLSLType(ShaderPropertyType type)
{
return type switch
{
ShaderPropertyType.Float => "float",
ShaderPropertyType.Float2 => "float2",
ShaderPropertyType.Float3 => "float3",
ShaderPropertyType.Float4 => "float4",
ShaderPropertyType.Int => "int",
ShaderPropertyType.Int2 => "int2",
ShaderPropertyType.Int3 => "int3",
ShaderPropertyType.Int4 => "int4",
ShaderPropertyType.UInt => "uint",
ShaderPropertyType.UInt2 => "uint2",
ShaderPropertyType.UInt3 => "uint3",
ShaderPropertyType.UInt4 => "uint4",
ShaderPropertyType.Bool => "bool",
ShaderPropertyType.Bool2 => "bool2",
ShaderPropertyType.Bool3 => "bool3",
ShaderPropertyType.Bool4 => "bool4",
// NOTE: Textures here are bindless, represented as uint (descriptor index).
ShaderPropertyType.Texture2D => "TEXTURE2D_BINDLESS",
ShaderPropertyType.Texture3D => "TEXTURE3D_BINDLESS",
ShaderPropertyType.TextureCube => "TEXTURECUBE_BINDLESS",
ShaderPropertyType.Texture2DArray => "TEXTURE2D_ARRAY_BINDLESS",
ShaderPropertyType.TextureCubeArray => "TEXTURECUBE_ARRAY_BINDLESS",
_ => throw new ArgumentOutOfRangeException(nameof(type), $"Unsupported shader property type: {type}")
};
}
public static string CompilePass(IPassDescriptor descriptor, string targetDirectory)
{
if (descriptor is not FullPassDescriptor fullPass)
{
throw new NotSupportedException("Only full pass descriptors are supported for compilation.");
}
if (!Directory.Exists(targetDirectory))
{
throw new ArgumentException("Target directory does not exist.", nameof(targetDirectory));
}
var outputFileName = fullPass.uniqueIdentifier.Replace(' ', '_');
var outputFilePath = Path.Combine(targetDirectory, outputFileName + ".g.hlsl");
var outputDirectory = Path.GetDirectoryName(outputFilePath);
if (!Directory.Exists(outputDirectory))
{
Directory.CreateDirectory(outputDirectory!);
}
using var fileStream = File.CreateText(outputFilePath);
var fileDefine = outputFileName.Replace('/', '_').ToUpperInvariant() + "_G_HLSL";
var sb = new StringBuilder();
sb.AppendLine(_GENERATED_FILE_HEADER);
sb.AppendLine(@$"
#ifndef {fileDefine}
#define {fileDefine}
#include ""F:/csharp/GhostEngine/Ghost.Shader/BuiltIn/Common.hlsl""");
if (fullPass.properties != null)
{
sb.Append(@"
struct PerMaterialData
{");
foreach (var prop in fullPass.properties)
{
sb.Append($@"
{ShaderPropertyTypeToHLSLType(prop.type)} {prop.name};");
}
sb.Append(@"
};");
}
sb.AppendLine();
sb.AppendLine(@$"
#endif // {fileDefine}");
fileStream.Write(sb.ToString());
return outputFilePath;
}
public static void CompileShader(ShaderDescriptor descriptor, string targetDirectory)
{
if (!Directory.Exists(targetDirectory))
{
throw new ArgumentException("Target directory does not exist.", nameof(targetDirectory));
}
// Generate global property file.
if (descriptor.globalProperties.Count > 0)
{
var globalFilePath = Path.Combine(targetDirectory, _GLOBAL_PROPERTY_FILE_NAME);
using var globalFileStream = File.CreateText(globalFilePath);
var sb = new StringBuilder();
sb.AppendLine(_GENERATED_FILE_HEADER);
sb.Append(@"
#ifndef GLOBALDATA_G_HLSL
#define GLOBALDATA_G_HLSL
#include ""F:/csharp/GhostEngine/Ghost.Shader/BuiltIn/Common.hlsl""
struct GlobalData
{");
foreach (var prop in descriptor.globalProperties)
{
sb.Append($@"
{ShaderPropertyTypeToHLSLType(prop.type)} {prop.name};");
}
sb.AppendLine(@"
};
#endif // GLOBALDATA_G_HLSL");
globalFileStream.Write(sb.ToString());
}
// Compile each pass.
foreach (var pass in descriptor.passes)
{
CompilePass(pass, targetDirectory);
}
}
}

View File

@@ -0,0 +1,47 @@
using Ghost.Core.Graphics;
namespace Ghost.Shader.Compiler;
public enum PropertyScope
{
Global,
Local,
}
internal class PropertySemantic
{
public PropertyScope scope;
public ShaderPropertyType type;
public string name = string.Empty;
public object? defaultValue;
}
internal class PipelineSemantic
{
public ZTestOptions? zTest;
public ZWriteOptions? zWrite;
public CullOptions? cull;
public BlendOptions? blend;
public uint? colorMask;
}
internal class PassSemantic
{
public string name = string.Empty;
public ShaderEntryPoint vertexShader;
public ShaderEntryPoint pixelShader;
public List<string>? defines;
public List<string>? includes;
public List<KeywordsGroup>? keywords;
public List<PropertySemantic>? localProperties;
public PipelineSemantic? localPipeline;
}
internal class ShaderSemantics
{
public string name = string.Empty;
public string fallback = string.Empty;
public List<PropertySemantic>? properties;
public PipelineSemantic? pipeline;
public List<PassSemantic>? passes;
}

View File

@@ -0,0 +1,53 @@
namespace Ghost.Shader.Compiler;
internal struct FunctionCallDeclaration
{
public Token name;
public List<Token>? arguments;
}
internal struct PropertyDeclaration
{
public Token scope;
public Token type;
public Token name;
public FunctionCallDeclaration? propertyConstructor;
}
internal struct ValueDeclaration
{
public Token name;
public Token value;
}
internal class PropertiesSyntax
{
public List<PropertyDeclaration>? properties;
public List<FunctionCallDeclaration>? functionCalls;
}
internal class PipelineSyntax
{
public List<ValueDeclaration>? values;
public List<FunctionCallDeclaration>? functionCalls;
}
internal class PassSyntax
{
public Token name;
public PipelineSyntax? localPipeline;
public PropertiesSyntax? localProperties;
public List<Token>? defines;
public List<Token>? includes;
public List<FunctionCallDeclaration>? keywords;
public List<FunctionCallDeclaration>? functionCalls;
}
internal class ShaderSyntax
{
public Token name;
public PropertiesSyntax? properties;
public PipelineSyntax? pipeline;
public List<PassSyntax>? passes;
public List<FunctionCallDeclaration>? functionCalls;
}

View File

@@ -0,0 +1,238 @@
namespace Ghost.Shader.Compiler;
[Flags]
public enum TokenType
{
None = 0,
// Literals
Identifier = 1 << 0, // variable names, function names, etc.
Keyword = 1 << 1, // shader, properties, pipeline, pass, etc.
Number = 1 << 2, // numeric literals (123, 45.67, .5)
StringLiteral = 1 << 3, // "ForwardVS.hlsl"
// Operators and Punctuation
Equals = 1 << 4, // =
Semicolon = 1 << 5, // ;
Comma = 1 << 6, // ,
// Delimiters
LBrace = 1 << 7, // {
RBrace = 1 << 8, // }
LParen = 1 << 9, // (
RParen = 1 << 10, // )
// Special
EndOfFile = 1 << 11, // EOF
// Future extensions (commented out for now)
// LBracket = 1 << 12, // [
// RBracket = 1 << 13, // ]
// Dot = 1 << 14, // .
// Colon = 1 << 15, // :
// Plus = 1 << 16, // +
// Minus = 1 << 17, // -
// Star = 1 << 18, // *
// Slash = 1 << 19, // /
}
public readonly struct Token : IEquatable<Token>
{
public readonly TokenType type;
public readonly string lexeme;
public readonly int line;
public readonly int column;
public static Token Null => new Token(TokenType.None, string.Empty, -1, -1);
public Token(TokenType type, string lexeme, int line, int column)
{
this.type = type;
this.lexeme = lexeme;
this.line = line;
this.column = column;
}
public override readonly string ToString()
{
return $"{type}('{lexeme}') at {line}:{column}";
}
public bool Equals(Token other)
{
return type == other.type && lexeme == other.lexeme && line == other.line && column == other.column;
}
public override int GetHashCode()
{
return HashCode.Combine(type, lexeme, line, column);
}
public override bool Equals(object? obj)
{
return obj is Token token && Equals(token);
}
public static bool operator ==(Token left, Token right)
{
return left.Equals(right);
}
public static bool operator !=(Token left, Token right)
{
return !(left == right);
}
}
public static class TokenExtensions
{
public static bool Match(this Token token, TokenType type, string? lexeme = null)
{
if (!type.HasFlag(token.type))
{
return false;
}
if (!string.IsNullOrEmpty(lexeme) && token.lexeme != lexeme)
{
return false;
}
return true;
}
public static void Expect(this Token token, TokenType type, string? lexeme = null)
{
if (!token.Match(type, lexeme))
{
var expected = lexeme != null ? $"{type}('{lexeme}')" : type.ToString();
throw new Exception($"Unexpected token at line {token.line}, column {token.column}. Expected {expected}, got {token.type}('{token.lexeme}').");
}
}
}
internal static class TokenLexicon
{
public static class KnownKeywords
{
public const string SHADER = "shader";
public const string PROPERTIES = "properties";
public const string PIPELINE = "pipeline";
public const string PASS = "pass";
public const string DEFINES = "defines";
public const string KEYWORDS = "keywords";
public const string INCLUDES = "includes";
public const string GLOBAL = "global";
public const string LOCAL = "local";
}
public static class KnownPipelineProperties
{
public const string ZTEST = "ztest";
public const string ZWRITE = "zwrite";
public const string CULL = "cull";
public const string BLEND = "blend";
public const string COLORMASK = "color_mask";
}
public static class KnownFunctions
{
public const string VERTEX_SHADER = "vs";
public const string PIXEL_SHADER = "ps";
public const string MESH_SHADER = "ms";
public const string COMPUTE_SHADER = "cs";
public const string DYNAMIC = "dynamic";
public const string STATIC = "static";
public const string FALLBACK = "fallback";
}
public static class KnownTypes
{
// Basic types
public const string FLOAT = "float";
public const string FLOAT2 = "float2";
public const string FLOAT3 = "float3";
public const string FLOAT4 = "float4";
public const string INT = "int";
public const string INT2 = "int2";
public const string INT3 = "int3";
public const string INT4 = "int4";
public const string UINT = "uint";
public const string UINT2 = "uint2";
public const string UINT3 = "uint3";
public const string UINT4 = "uint4";
public const string BOOL = "bool";
public const string BOOL2 = "bool2";
public const string BOOL3 = "bool3";
public const string BOOL4 = "bool4";
// Texture types
public const string TEXTURE2D = "texture2d";
public const string TEXTURE2D_ARRAY = "texture2d_array";
public const string TEXTURE3D = "texture3d";
public const string TEXTURECUBE = "texturecube";
public const string TEXTURECUBE_ARRAY = "texturecube_array";
}
public static class KnownTextureValue
{
public const string WHITE = "white";
public const string BLACK = "black";
public const string GREY = "grey";
public const string NORMAL = "normal";
public const string TRANSPARENT = "transparent";
}
private static readonly HashSet<string> s_keywords = new()
{
KnownKeywords.SHADER,
KnownKeywords.PROPERTIES,
KnownKeywords.PIPELINE,
KnownKeywords.PASS,
KnownKeywords.DEFINES,
KnownKeywords.KEYWORDS,
KnownKeywords.INCLUDES,
KnownKeywords.GLOBAL,
KnownKeywords.LOCAL,
};
private static readonly HashSet<string> s_functions = new()
{
KnownFunctions.VERTEX_SHADER,
KnownFunctions.PIXEL_SHADER,
KnownFunctions.MESH_SHADER,
KnownFunctions.COMPUTE_SHADER,
KnownFunctions.DYNAMIC,
KnownFunctions.STATIC,
};
private static readonly HashSet<string> s_types = new()
{
KnownTypes.FLOAT, KnownTypes.FLOAT2, KnownTypes.FLOAT3, KnownTypes.FLOAT4,
KnownTypes.INT, KnownTypes.INT2, KnownTypes.INT3, KnownTypes.INT4,
KnownTypes.UINT, KnownTypes.UINT2, KnownTypes.UINT3, KnownTypes.UINT4,
KnownTypes.BOOL, KnownTypes.BOOL2, KnownTypes.BOOL3, KnownTypes.BOOL4,
KnownTypes.TEXTURE2D, KnownTypes.TEXTURE2D_ARRAY, KnownTypes.TEXTURE3D,
KnownTypes.TEXTURECUBE, KnownTypes.TEXTURECUBE_ARRAY,
};
private static readonly HashSet<string> s_textureDefaultValues = new()
{
KnownTextureValue.WHITE,
KnownTextureValue.BLACK,
KnownTextureValue.GREY,
KnownTextureValue.NORMAL,
KnownTextureValue.TRANSPARENT,
};
public static bool IsKeyword(string lexeme) => s_keywords.Contains(lexeme);
public static bool IsFunction(string lexeme) => s_functions.Contains(lexeme);
public static bool IsType(string lexeme) => s_types.Contains(lexeme);
public static bool IsTextureDefaultValue(string lexeme) => s_textureDefaultValues.Contains(lexeme);
}

Some files were not shown because too many files have changed in this diff Show More