Compare commits
4 Commits
b42398bbce
...
1cc65e8218
| Author | SHA1 | Date | |
|---|---|---|---|
| 1cc65e8218 | |||
| d0076c852f | |||
| ba8694ed0c | |||
| 80e820a858 |
8
src/Directory.Build.props
Normal file
8
src/Directory.Build.props
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<Project>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)' == 'Debug_Editor'">
|
||||||
|
<DefineConstants>$(DefineConstants);DEBUG;GHOST_EDITOR;MHP_ENABLE_SAFETY_CHECKS;</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)' == 'Release_Editor'">
|
||||||
|
<DefineConstants>$(DefineConstants);GHOST_EDITOR;MHP_ENABLE_SAFETY_CHECKS;</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ public static class DSLShaderCompiler
|
|||||||
var pass = semantics.passes![i];
|
var pass = semantics.passes![i];
|
||||||
var localPipeline = MeragePipeline(pass.localPipeline, PipelineState.Default);
|
var localPipeline = MeragePipeline(pass.localPipeline, PipelineState.Default);
|
||||||
|
|
||||||
var result = BuildFinalShaderCode(pass.amplificationShader.shaderPath, pass.includes.AsSpan(), pass.hlsl, propertyInfo.code);
|
var result = BuildFinalShaderCode(pass.amplificationShader.shaderPath, pass.includes.AsSpan(), pass.hlsl, propertyInfo.Code);
|
||||||
if (result.IsFailure)
|
if (result.IsFailure)
|
||||||
{
|
{
|
||||||
return Result.Failure($"Failed to build shader code for pass '{pass.name}': {result.Message}");
|
return Result.Failure($"Failed to build shader code for pass '{pass.name}': {result.Message}");
|
||||||
@@ -109,7 +109,7 @@ public static class DSLShaderCompiler
|
|||||||
|
|
||||||
var amplificationShaderCode = new ShaderCode { code = result.Value, entryPoint = pass.amplificationShader.entry };
|
var amplificationShaderCode = new ShaderCode { code = result.Value, entryPoint = pass.amplificationShader.entry };
|
||||||
|
|
||||||
result = BuildFinalShaderCode(pass.meshShader.shaderPath, pass.includes.AsSpan(), pass.hlsl, propertyInfo.code);
|
result = BuildFinalShaderCode(pass.meshShader.shaderPath, pass.includes.AsSpan(), pass.hlsl, propertyInfo.Code);
|
||||||
if (result.IsFailure)
|
if (result.IsFailure)
|
||||||
{
|
{
|
||||||
return Result.Failure($"Failed to build shader code for pass '{pass.name}': {result.Message}");
|
return Result.Failure($"Failed to build shader code for pass '{pass.name}': {result.Message}");
|
||||||
@@ -117,7 +117,7 @@ public static class DSLShaderCompiler
|
|||||||
|
|
||||||
var meshShaderCode = new ShaderCode { code = result.Value, entryPoint = pass.meshShader.entry };
|
var meshShaderCode = new ShaderCode { code = result.Value, entryPoint = pass.meshShader.entry };
|
||||||
|
|
||||||
result = BuildFinalShaderCode(pass.pixelShader.shaderPath, pass.includes.AsSpan(), pass.hlsl, propertyInfo.code);
|
result = BuildFinalShaderCode(pass.pixelShader.shaderPath, pass.includes.AsSpan(), pass.hlsl, propertyInfo.Code);
|
||||||
if (result.IsFailure)
|
if (result.IsFailure)
|
||||||
{
|
{
|
||||||
return Result.Failure($"Failed to build shader code for pass '{pass.name}': {result.Message}");
|
return Result.Failure($"Failed to build shader code for pass '{pass.name}': {result.Message}");
|
||||||
@@ -142,7 +142,7 @@ public static class DSLShaderCompiler
|
|||||||
var descriptor = new GraphicsShaderDescriptor
|
var descriptor = new GraphicsShaderDescriptor
|
||||||
{
|
{
|
||||||
Name = semantics.name,
|
Name = semantics.name,
|
||||||
PropertyBufferSize = propertyInfo.size,
|
PropertyBufferSize = propertyInfo.Size,
|
||||||
|
|
||||||
ShaderModel = semantics.shaderModel,
|
ShaderModel = semantics.shaderModel,
|
||||||
Passes = passes
|
Passes = passes
|
||||||
@@ -302,7 +302,7 @@ public static class DSLShaderCompiler
|
|||||||
var shaderCodes = new ShaderCode[semantics.entryPoints.Count];
|
var shaderCodes = new ShaderCode[semantics.entryPoints.Count];
|
||||||
for (var i = 0; i < shaderCodes.Length; i++)
|
for (var i = 0; i < shaderCodes.Length; i++)
|
||||||
{
|
{
|
||||||
var result = BuildFinalShaderCode(semantics.entryPoints[i].shaderPath, semantics.includes.AsSpan(), semantics.hlsl, propertyInfo.code);
|
var result = BuildFinalShaderCode(semantics.entryPoints[i].shaderPath, semantics.includes.AsSpan(), semantics.hlsl, propertyInfo.Code);
|
||||||
if (result.IsFailure)
|
if (result.IsFailure)
|
||||||
{
|
{
|
||||||
return Result.Failure($"Failed to build shader code for entry point '{semantics.entryPoints[i].entry}': {result.Message}");
|
return Result.Failure($"Failed to build shader code for entry point '{semantics.entryPoints[i].entry}': {result.Message}");
|
||||||
@@ -314,7 +314,7 @@ public static class DSLShaderCompiler
|
|||||||
return new ComputeShaderDescriptor
|
return new ComputeShaderDescriptor
|
||||||
{
|
{
|
||||||
Name = semantics.name,
|
Name = semantics.name,
|
||||||
PropertyBufferSize = propertyInfo.size,
|
PropertyBufferSize = propertyInfo.Size,
|
||||||
ShaderModel = semantics.shaderModel,
|
ShaderModel = semantics.shaderModel,
|
||||||
ShaderCodes = shaderCodes,
|
ShaderCodes = shaderCodes,
|
||||||
Defines = semantics.defines?.ToArray() ?? Array.Empty<string>(),
|
Defines = semantics.defines?.ToArray() ?? Array.Empty<string>(),
|
||||||
|
|||||||
@@ -10,8 +10,7 @@
|
|||||||
<SupportedOSPlatformVersion>10.0.20348.0</SupportedOSPlatformVersion>
|
<SupportedOSPlatformVersion>10.0.20348.0</SupportedOSPlatformVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
<!-- in .net 10, field keyword is not preview anymore, but we are still waiting roslyn team to update their code analyzer packages -->
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
<langversion>preview</langversion>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Remove="Assets\MeshNode.cs" />
|
<Content Remove="Assets\MeshNode.cs" />
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ public sealed partial class AssetCatalog
|
|||||||
);
|
);
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_assets_path ON assets(source_path);
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_assets_path ON assets(source_path);
|
||||||
CREATE INDEX IF NOT EXISTS idx_assets_parent ON assets(parent_guid);
|
CREATE INDEX IF NOT EXISTS idx_assets_parent ON assets(parent_guid);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_assets_type_id ON assets(asset_type_id);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS dependencies (
|
CREATE TABLE IF NOT EXISTS dependencies (
|
||||||
from_guid BLOB(16) NOT NULL REFERENCES assets(guid) ON DELETE CASCADE,
|
from_guid BLOB(16) NOT NULL REFERENCES assets(guid) ON DELETE CASCADE,
|
||||||
@@ -272,7 +273,7 @@ public sealed partial class AssetCatalog
|
|||||||
{
|
{
|
||||||
using var connection = OpenConnection();
|
using var connection = OpenConnection();
|
||||||
using var cmd = connection.CreateCommand();
|
using var cmd = connection.CreateCommand();
|
||||||
|
|
||||||
cmd.CommandText = SqlEnumerate;
|
cmd.CommandText = SqlEnumerate;
|
||||||
using var reader = cmd.ExecuteReader();
|
using var reader = cmd.ExecuteReader();
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
@@ -281,6 +282,33 @@ public sealed partial class AssetCatalog
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Guid> EnumerateByTypes(params Guid[] assetTypeIds)
|
||||||
|
{
|
||||||
|
if (assetTypeIds.Length == 0)
|
||||||
|
{
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var connection = OpenConnection();
|
||||||
|
using var cmd = connection.CreateCommand();
|
||||||
|
|
||||||
|
var parameterNames = new List<string>(assetTypeIds.Length);
|
||||||
|
for (int i = 0; i < assetTypeIds.Length; i++)
|
||||||
|
{
|
||||||
|
string paramName = $"@typeId{i}";
|
||||||
|
parameterNames.Add(paramName);
|
||||||
|
cmd.Parameters.AddWithValue(paramName, assetTypeIds[i].ToByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.CommandText = $"SELECT guid FROM assets WHERE asset_type_id IN ({string.Join(", ", parameterNames)})";
|
||||||
|
|
||||||
|
using var reader = cmd.ExecuteReader();
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
yield return new Guid((byte[])reader[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public List<SubAssetInfo> GetSubAssets(Guid parentGuid)
|
public List<SubAssetInfo> GetSubAssets(Guid parentGuid)
|
||||||
{
|
{
|
||||||
using var connection = OpenConnection();
|
using var connection = OpenConnection();
|
||||||
|
|||||||
@@ -0,0 +1,361 @@
|
|||||||
|
using Ghost.Core;
|
||||||
|
using Ghost.Core.Graphics;
|
||||||
|
using Ghost.Editor.Core.Assets;
|
||||||
|
using Ghost.Editor.Core.Contracts;
|
||||||
|
using Ghost.Editor.Core.Utilities;
|
||||||
|
using Ghost.Engine;
|
||||||
|
using Ghost.Graphics.Core;
|
||||||
|
using Ghost.Graphics.RHI;
|
||||||
|
using Ghost.Graphics.Services;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Ghost.Editor.Core.Services;
|
||||||
|
|
||||||
|
internal sealed class EditorShaderCompilerBridge : IShaderCompilationBridge
|
||||||
|
{
|
||||||
|
private readonly IAssetRegistry _assetRegistry;
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private readonly IShaderCompiler _compiler;
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<ulong, Guid> _shaderIdToAssetId = new();
|
||||||
|
private readonly ConcurrentDictionary<Guid, Dictionary<int, string>[]> _assetKeywordMappings = new();
|
||||||
|
private Task? _shaderDictionaryPopulated;
|
||||||
|
|
||||||
|
public event Action<Key64<ShaderVariant>, ulong>? OnShaderVariantCompiled;
|
||||||
|
|
||||||
|
public EditorShaderCompilerBridge(IAssetRegistry assetRegistry, IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
_assetRegistry = assetRegistry;
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
_compiler = new DXCShaderCompiler();
|
||||||
|
|
||||||
|
_assetRegistry.OnAssetImported += OnAssetImported;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAssetImported(object? sender, Guid guid)
|
||||||
|
{
|
||||||
|
var path = _assetRegistry.GetAssetPath(guid);
|
||||||
|
if (path != null && (path.EndsWith(".gshdr") || path.EndsWith(".gcomp")))
|
||||||
|
{
|
||||||
|
var result = _assetRegistry.LoadAssetAsync(guid).AsTask().Result;
|
||||||
|
if (result.IsSuccess)
|
||||||
|
{
|
||||||
|
var nameHash = ExtractNameHash(result.Value);
|
||||||
|
if (nameHash != 0)
|
||||||
|
{
|
||||||
|
_shaderIdToAssetId[nameHash] = guid;
|
||||||
|
BuildKeywordMappings(result.Value, guid);
|
||||||
|
|
||||||
|
var engineCore = _serviceProvider.GetService<EngineCore>();
|
||||||
|
if (engineCore != null)
|
||||||
|
{
|
||||||
|
var shaderLibrary = engineCore.RenderSystem.ShaderLibrary;
|
||||||
|
var pipelineLibrary = engineCore.RenderSystem.GraphicsEngine.PipelineLibrary;
|
||||||
|
shaderLibrary.InvalidateShaderCache(nameHash, pipelineLibrary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static ulong ExtractNameHash(IAsset asset)
|
||||||
|
{
|
||||||
|
if (asset is GraphicsShaderAsset graphicsAsset)
|
||||||
|
{
|
||||||
|
return RHIUtility.GetShaderID(graphicsAsset.Descriptor.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (asset is ComputeShaderAsset computeAsset)
|
||||||
|
{
|
||||||
|
return RHIUtility.GetShaderID(computeAsset.Descriptor.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task EnsureShaderDictionaryPopulatedAsync()
|
||||||
|
{
|
||||||
|
var existing = Volatile.Read(ref _shaderDictionaryPopulated);
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
var original = Interlocked.CompareExchange(ref _shaderDictionaryPopulated, tcs.Task, null);
|
||||||
|
if (original != null)
|
||||||
|
{
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var catalog = _assetRegistry.GetAssetCatalog();
|
||||||
|
var assetGuids = catalog.EnumerateByTypes(typeof(GraphicsShaderAsset).GUID, typeof(ComputeShaderAsset).GUID);
|
||||||
|
|
||||||
|
foreach (var assetGuid in assetGuids)
|
||||||
|
{
|
||||||
|
var result = await _assetRegistry.LoadAssetAsync(assetGuid);
|
||||||
|
if (result.IsSuccess)
|
||||||
|
{
|
||||||
|
var nameHash = ExtractNameHash(result.Value);
|
||||||
|
if (nameHash != 0)
|
||||||
|
{
|
||||||
|
_shaderIdToAssetId[nameHash] = assetGuid;
|
||||||
|
BuildKeywordMappings(result.Value, assetGuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tcs.SetResult();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
tcs.SetException(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return tcs.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildKeywordMappings(IAsset asset, Guid assetId)
|
||||||
|
{
|
||||||
|
if (asset is GraphicsShaderAsset graphicsAsset)
|
||||||
|
{
|
||||||
|
var passes = graphicsAsset.Descriptor.Passes;
|
||||||
|
var mappings = new Dictionary<int, string>[passes.Length];
|
||||||
|
for (var i = 0; i < passes.Length; i++)
|
||||||
|
{
|
||||||
|
mappings[i] = BuildKeywordMappingFromGroups(passes[i].keywords);
|
||||||
|
}
|
||||||
|
|
||||||
|
_assetKeywordMappings[assetId] = mappings;
|
||||||
|
}
|
||||||
|
else if (asset is ComputeShaderAsset computeAsset)
|
||||||
|
{
|
||||||
|
var entryCount = computeAsset.Descriptor.ShaderCodes.Length;
|
||||||
|
var mappings = new Dictionary<int, string>[entryCount];
|
||||||
|
var sharedMapping = BuildKeywordMappingFromGroups(computeAsset.Descriptor.Keywords);
|
||||||
|
for (var i = 0; i < entryCount; i++)
|
||||||
|
{
|
||||||
|
mappings[i] = sharedMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
_assetKeywordMappings[assetId] = mappings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<int, string> BuildKeywordMappingFromGroups(KeywordsGroup[] groups)
|
||||||
|
{
|
||||||
|
var mapping = new Dictionary<int, string>();
|
||||||
|
var localIndex = 0;
|
||||||
|
|
||||||
|
foreach (var group in groups)
|
||||||
|
{
|
||||||
|
if (group.keywords == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group.space != KeywordSpace.Local)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var kw in group.keywords)
|
||||||
|
{
|
||||||
|
mapping[localIndex++] = kw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string[] BuildVariantDefines(LocalKeywordSet keywordMask, Dictionary<int, string>? keywordMapping)
|
||||||
|
{
|
||||||
|
if (keywordMapping == null || keywordMapping.Count == 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var defines = new List<string>(keywordMapping.Count);
|
||||||
|
foreach (var (localIndex, keywordName) in keywordMapping)
|
||||||
|
{
|
||||||
|
if (keywordMask.IsKeywordEnabled(localIndex))
|
||||||
|
{
|
||||||
|
defines.Add(keywordName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defines.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ReadOnlySpan<string> CombineDefines(ReadOnlySpan<string> staticDefines, ReadOnlySpan<string> variantDefines)
|
||||||
|
{
|
||||||
|
if (variantDefines.Length == 0)
|
||||||
|
{
|
||||||
|
return staticDefines;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (staticDefines.Length == 0)
|
||||||
|
{
|
||||||
|
return variantDefines;
|
||||||
|
}
|
||||||
|
|
||||||
|
var combined = new string[staticDefines.Length + variantDefines.Length];
|
||||||
|
staticDefines.CopyTo(combined);
|
||||||
|
variantDefines.CopyTo(combined.AsSpan(staticDefines.Length));
|
||||||
|
return combined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestCompilation(ulong shaderId, int passIndex, Key64<ShaderVariant> variantKey, LocalKeywordSet keywordMask)
|
||||||
|
{
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await EnsureShaderDictionaryPopulatedAsync();
|
||||||
|
|
||||||
|
if (!_shaderIdToAssetId.TryGetValue(shaderId, out var assetId))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var assetResult = await _assetRegistry.LoadAssetAsync(assetId);
|
||||||
|
if (assetResult.IsFailure)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary<int, string>? keywordMapping = null;
|
||||||
|
if (_assetKeywordMappings.TryGetValue(assetId, out var mappings) && passIndex < mappings.Length)
|
||||||
|
{
|
||||||
|
keywordMapping = mappings[passIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assetResult.Value is GraphicsShaderAsset graphicsAsset)
|
||||||
|
{
|
||||||
|
var pass = graphicsAsset.Descriptor.Passes[passIndex];
|
||||||
|
await CompileGraphicsPassAsync(shaderId, passIndex, variantKey, keywordMask, pass, graphicsAsset.Descriptor.ShaderModel, keywordMapping);
|
||||||
|
}
|
||||||
|
else if (assetResult.Value is ComputeShaderAsset computeAsset)
|
||||||
|
{
|
||||||
|
await CompileComputePassAsync(shaderId, passIndex, variantKey, keywordMask, computeAsset.Descriptor, passIndex, keywordMapping);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe Task CompileGraphicsPassAsync(ulong shaderId, int passIndex, Key64<ShaderVariant> variantKey, LocalKeywordSet keywordMask, PassDescriptor descriptor, ShaderModel shaderModel, Dictionary<int, string>? keywordMapping)
|
||||||
|
{
|
||||||
|
var variantDefines = BuildVariantDefines(keywordMask, keywordMapping);
|
||||||
|
|
||||||
|
var additionalConfig = new ShaderCompilationConfig
|
||||||
|
{
|
||||||
|
defines = variantDefines,
|
||||||
|
model = shaderModel,
|
||||||
|
optimizeLevel = CompilerOptimizeLevel.O3,
|
||||||
|
options = CompilerOption.None
|
||||||
|
};
|
||||||
|
|
||||||
|
var compileResult = _compiler.CompileShaderPass(ref descriptor, ref additionalConfig, AllocationHandle.Persistent);
|
||||||
|
if (compileResult.IsFailure)
|
||||||
|
{
|
||||||
|
Ghost.Core.Logger.Error($"Failed to compile graphics shader {shaderId}: {compileResult.Message}");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
var engineCore = _serviceProvider.GetService<EngineCore>();
|
||||||
|
if (engineCore == null)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var compiled = compileResult.Value;
|
||||||
|
|
||||||
|
var stageCount = 0;
|
||||||
|
if (compiled.asResult.IsCreated) stageCount++;
|
||||||
|
if (compiled.msResult.IsCreated) stageCount++;
|
||||||
|
if (compiled.psResult.IsCreated) stageCount++;
|
||||||
|
|
||||||
|
var byteCodes = stackalloc ShaderByteCode[stageCount];
|
||||||
|
var idx = 0;
|
||||||
|
if (compiled.asResult.IsCreated)
|
||||||
|
{
|
||||||
|
byteCodes[idx++] = new ShaderByteCode { pCode = (byte*)compiled.asResult.GetUnsafePtr(), size = (ulong)compiled.asResult.Length };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compiled.msResult.IsCreated)
|
||||||
|
{
|
||||||
|
byteCodes[idx++] = new ShaderByteCode { pCode = (byte*)compiled.msResult.GetUnsafePtr(), size = (ulong)compiled.msResult.Length };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compiled.psResult.IsCreated)
|
||||||
|
{
|
||||||
|
byteCodes[idx++] = new ShaderByteCode { pCode = (byte*)compiled.psResult.GetUnsafePtr(), size = (ulong)compiled.psResult.Length };
|
||||||
|
}
|
||||||
|
|
||||||
|
var shaderLibrary = engineCore.RenderSystem.ShaderLibrary;
|
||||||
|
shaderLibrary.CacheCompiledResult(shaderId, passIndex, variantKey, new ReadOnlySpan<ShaderByteCode>(byteCodes, stageCount));
|
||||||
|
|
||||||
|
var (compiledHash, _) = shaderLibrary.GetCompiledHash(shaderId, passIndex, variantKey);
|
||||||
|
OnShaderVariantCompiled?.Invoke(variantKey, compiledHash);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe Task CompileComputePassAsync(ulong shaderId, int passIndex, Key64<ShaderVariant> variantKey, LocalKeywordSet keywordMask, ComputeShaderDescriptor descriptor, int entryIndex, Dictionary<int, string>? keywordMapping)
|
||||||
|
{
|
||||||
|
var variantDefines = BuildVariantDefines(keywordMask, keywordMapping);
|
||||||
|
var fullDefines = CombineDefines(descriptor.Defines, variantDefines);
|
||||||
|
|
||||||
|
var code = descriptor.ShaderCodes[entryIndex];
|
||||||
|
var config = new ShaderCompilationConfig
|
||||||
|
{
|
||||||
|
shaderCode = code.code,
|
||||||
|
entryPoint = code.entryPoint,
|
||||||
|
stage = ShaderStage.ComputeShader,
|
||||||
|
defines = fullDefines,
|
||||||
|
model = descriptor.ShaderModel,
|
||||||
|
optimizeLevel = CompilerOptimizeLevel.O3,
|
||||||
|
options = CompilerOption.None
|
||||||
|
};
|
||||||
|
|
||||||
|
var compileResult = _compiler.Compile(ref config, AllocationHandle.Persistent);
|
||||||
|
if (compileResult.IsFailure)
|
||||||
|
{
|
||||||
|
Ghost.Core.Logger.Error($"Failed to compile compute shader {shaderId}: {compileResult.Message}");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
var engineCore = _serviceProvider.GetService<EngineCore>();
|
||||||
|
if (engineCore == null)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var bytecodeArray = compileResult.Value;
|
||||||
|
|
||||||
|
var byteCode = new ShaderByteCode
|
||||||
|
{
|
||||||
|
pCode = (byte*)bytecodeArray.GetUnsafePtr(),
|
||||||
|
size = (ulong)bytecodeArray.Length
|
||||||
|
};
|
||||||
|
|
||||||
|
var shaderLibrary = engineCore.RenderSystem.ShaderLibrary;
|
||||||
|
shaderLibrary.CacheCompiledResult(shaderId, passIndex, variantKey, new ReadOnlySpan<ShaderByteCode>(ref byteCode));
|
||||||
|
|
||||||
|
var (compiledHash, _) = shaderLibrary.GetCompiledHash(shaderId, passIndex, variantKey);
|
||||||
|
OnShaderVariantCompiled?.Invoke(variantKey, compiledHash);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_assetRegistry.OnAssetImported -= OnAssetImported;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ using Ghost.Editor.ViewModels.Controls;
|
|||||||
using Ghost.Editor.ViewModels.Windows;
|
using Ghost.Editor.ViewModels.Windows;
|
||||||
using Ghost.Editor.Views.Windows;
|
using Ghost.Editor.Views.Windows;
|
||||||
using Ghost.Engine;
|
using Ghost.Engine;
|
||||||
|
using Ghost.Graphics.RHI;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.UI.Dispatching;
|
using Microsoft.UI.Dispatching;
|
||||||
@@ -66,6 +67,7 @@ public partial class App : Application
|
|||||||
services.AddSingleton<IPreviewService, PreviewService>();
|
services.AddSingleton<IPreviewService, PreviewService>();
|
||||||
services.AddSingleton<IAssetRegistry, AssetRegistry>();
|
services.AddSingleton<IAssetRegistry, AssetRegistry>();
|
||||||
services.AddSingleton<IContentProvider, EditorContentProvider>();
|
services.AddSingleton<IContentProvider, EditorContentProvider>();
|
||||||
|
services.AddSingleton<IShaderCompilationBridge, EditorShaderCompilerBridge>();
|
||||||
|
|
||||||
services.AddSingleton<EngineCore>();
|
services.AddSingleton<EngineCore>();
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,7 @@
|
|||||||
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
|
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
|
||||||
<UseWinUI>true</UseWinUI>
|
<UseWinUI>true</UseWinUI>
|
||||||
<EnableMsixTooling>true</EnableMsixTooling>
|
<EnableMsixTooling>true</EnableMsixTooling>
|
||||||
<!-- in .net 10, field keyword is not preview anymore, but we are still waiting roslyn team to update their code analyzer packages -->
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
<langversion>preview</langversion>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="Views\Controls\Hierarchy.xaml" />
|
<None Remove="Views\Controls\Hierarchy.xaml" />
|
||||||
@@ -217,15 +216,18 @@
|
|||||||
|
|
||||||
<!-- Publish Properties -->
|
<!-- Publish Properties -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
<PublishReadyToRun Condition="'$(Configuration)'=='Debug_Editor'">False</PublishReadyToRun>
|
||||||
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||||
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
|
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<SupportedOSPlatformVersion>10.0.20348.0</SupportedOSPlatformVersion>
|
<SupportedOSPlatformVersion>10.0.20348.0</SupportedOSPlatformVersion>
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
<PublishAot>False</PublishAot>
|
|
||||||
<PublishTrimmed>False</PublishTrimmed>
|
|
||||||
<RootNamespace>Ghost.Editor</RootNamespace>
|
<RootNamespace>Ghost.Editor</RootNamespace>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_Editor|x64'">
|
||||||
|
<Optimize>True</Optimize>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_Editor|ARM64'">
|
||||||
|
<Optimize>True</Optimize>
|
||||||
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
<Solution>
|
<Solution>
|
||||||
<Configurations>
|
<Configurations>
|
||||||
|
<BuildType Name="Debug" />
|
||||||
|
<BuildType Name="Debug_Editor" />
|
||||||
|
<BuildType Name="Release" />
|
||||||
|
<BuildType Name="Release_Editor" />
|
||||||
<Platform Name="ARM64" />
|
<Platform Name="ARM64" />
|
||||||
<Platform Name="x64" />
|
<Platform Name="x64" />
|
||||||
<Platform Name="x86" />
|
<Platform Name="x86" />
|
||||||
@@ -9,12 +13,18 @@
|
|||||||
<Project Path="Editor/Ghost.Editor.Core/Ghost.Editor.Core.csproj">
|
<Project Path="Editor/Ghost.Editor.Core/Ghost.Editor.Core.csproj">
|
||||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||||
<Platform Solution="*|x64" Project="x64" />
|
<Platform Solution="*|x64" Project="x64" />
|
||||||
<Platform Solution="*|x86" Project="x86" />
|
<Platform Solution="Debug_Editor|x86" Project="x64" />
|
||||||
|
<Platform Solution="Debug|x86" Project="x86" />
|
||||||
|
<Platform Solution="Release_Editor|x86" Project="x64" />
|
||||||
|
<Platform Solution="Release|x86" Project="x86" />
|
||||||
</Project>
|
</Project>
|
||||||
<Project Path="Editor/Ghost.Editor/Ghost.Editor.csproj">
|
<Project Path="Editor/Ghost.Editor/Ghost.Editor.csproj">
|
||||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||||
<Platform Solution="*|x64" Project="x64" />
|
<Platform Solution="*|x64" Project="x64" />
|
||||||
<Platform Solution="*|x86" Project="x86" />
|
<Platform Solution="Debug_Editor|x86" Project="x64" />
|
||||||
|
<Platform Solution="Debug|x86" Project="x86" />
|
||||||
|
<Platform Solution="Release_Editor|x86" Project="x64" />
|
||||||
|
<Platform Solution="Release|x86" Project="x86" />
|
||||||
<Deploy />
|
<Deploy />
|
||||||
</Project>
|
</Project>
|
||||||
</Folder>
|
</Folder>
|
||||||
@@ -45,12 +55,13 @@
|
|||||||
</Project>
|
</Project>
|
||||||
<Project Path="Test/Ghost.MicroTest/Ghost.MicroTest.csproj" Id="8c8ffa4b-e1e4-46a1-9221-7b508a109edd" />
|
<Project Path="Test/Ghost.MicroTest/Ghost.MicroTest.csproj" Id="8c8ffa4b-e1e4-46a1-9221-7b508a109edd" />
|
||||||
<Project Path="Test/Ghost.Shader.Test/Ghost.Shader.Test.csproj" />
|
<Project Path="Test/Ghost.Shader.Test/Ghost.Shader.Test.csproj" />
|
||||||
<Project Path="Test/Ghost.Test.Core/Ghost.Test.Core.csproj" />
|
<Project Path="Test/Ghost.TestCore/Ghost.TestCore.csproj" />
|
||||||
<Project Path="Test/Ghost.UnitTest/Ghost.UnitTest.csproj" Id="4da45668-456b-4dcc-acd8-6bfe154e6837">
|
<Project Path="Test/Ghost.UnitTest/Ghost.UnitTest.csproj" Id="4da45668-456b-4dcc-acd8-6bfe154e6837">
|
||||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||||
<Platform Solution="*|x64" Project="x64" />
|
<Platform Solution="*|x64" Project="x64" />
|
||||||
<Platform Solution="*|x86" Project="x86" />
|
<Platform Solution="*|x86" Project="x86" />
|
||||||
<Deploy />
|
<Deploy Solution="Debug|*" />
|
||||||
|
<Deploy Solution="Release|*" />
|
||||||
</Project>
|
</Project>
|
||||||
</Folder>
|
</Folder>
|
||||||
<Folder Name="/Tools/">
|
<Folder Name="/Tools/">
|
||||||
|
|||||||
@@ -5,15 +5,7 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
|
||||||
<DefineConstants>$(DefineConstants);MHP_ENABLE_SAFETY_CHECKS;MHP_ENABLE_MIMALLOC;MHP_FASTMATH</DefineConstants>
|
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
|
||||||
<IsTrimmable>True</IsTrimmable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
|
||||||
<DefineConstants>$(DefineConstants);MHP_ENABLE_MIMALLOC;MHP_FASTMATH</DefineConstants>
|
<DefineConstants>$(DefineConstants);MHP_ENABLE_MIMALLOC;MHP_FASTMATH</DefineConstants>
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
<IsTrimmable>True</IsTrimmable>
|
<IsTrimmable>True</IsTrimmable>
|
||||||
|
|||||||
36
src/Runtime/Ghost.Core/Graphics/BindlessHandles.cs
Normal file
36
src/Runtime/Ghost.Core/Graphics/BindlessHandles.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ghost.Core.Graphics;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public readonly struct Texture2DHandle
|
||||||
|
{
|
||||||
|
public readonly uint DescriptorIndex;
|
||||||
|
|
||||||
|
public Texture2DHandle(uint descriptorIndex)
|
||||||
|
{
|
||||||
|
DescriptorIndex = descriptorIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public readonly struct Texture3DHandle
|
||||||
|
{
|
||||||
|
public readonly uint DescriptorIndex;
|
||||||
|
|
||||||
|
public Texture3DHandle(uint descriptorIndex)
|
||||||
|
{
|
||||||
|
DescriptorIndex = descriptorIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public readonly struct BufferHandle
|
||||||
|
{
|
||||||
|
public readonly uint DescriptorIndex;
|
||||||
|
|
||||||
|
public BufferHandle(uint descriptorIndex)
|
||||||
|
{
|
||||||
|
DescriptorIndex = descriptorIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,69 @@
|
|||||||
namespace Ghost.Core.Graphics;
|
namespace Ghost.Core.Graphics;
|
||||||
|
|
||||||
|
public enum ShaderPropertyType
|
||||||
|
{
|
||||||
|
Unknown,
|
||||||
|
Float,
|
||||||
|
Float2,
|
||||||
|
Float3,
|
||||||
|
Float4,
|
||||||
|
Int,
|
||||||
|
UInt,
|
||||||
|
Float4x4,
|
||||||
|
Texture2D,
|
||||||
|
Texture3D,
|
||||||
|
Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct ShaderPropertyFieldInfo
|
||||||
|
{
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get; init;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShaderPropertyType Type
|
||||||
|
{
|
||||||
|
get; init;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Offset
|
||||||
|
{
|
||||||
|
get; init;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if DEBUG || GHOST_EDITOR
|
#if DEBUG || GHOST_EDITOR
|
||||||
public struct ShaderPropertyInfo
|
public struct ShaderPropertyInfo
|
||||||
{
|
{
|
||||||
public string shaderName;
|
public string ShaderName
|
||||||
public string code;
|
{
|
||||||
public uint size;
|
get; init;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Code
|
||||||
|
{
|
||||||
|
get; init;
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint Size
|
||||||
|
{
|
||||||
|
get; init;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShaderPropertyFieldInfo[] Fields
|
||||||
|
{
|
||||||
|
get; init;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ShaderPropertiesRegistry
|
public static class ShaderPropertiesRegistry
|
||||||
{
|
{
|
||||||
private static readonly Dictionary<string, ShaderPropertyInfo> s_nameToCode = new Dictionary<string, ShaderPropertyInfo>(StringComparer.Ordinal);
|
private static readonly Dictionary<string, ShaderPropertyInfo> s_nameToCode = new Dictionary<string, ShaderPropertyInfo>(StringComparer.Ordinal);
|
||||||
|
|
||||||
public static void Register(string name, string code, uint size)
|
public static void Register(string name, string code, uint size, ShaderPropertyFieldInfo[] fields)
|
||||||
{
|
{
|
||||||
s_nameToCode[name] = new ShaderPropertyInfo { shaderName = name, code = code, size = size };
|
s_nameToCode[name] = new ShaderPropertyInfo { ShaderName = name, Code = code, Size = size, Fields = fields };
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryGetInfo(string name, out ShaderPropertyInfo info)
|
public static bool TryGetInfo(string name, out ShaderPropertyInfo info)
|
||||||
|
|||||||
@@ -123,22 +123,19 @@ public unsafe partial struct TempJobAllocator : IAllocator
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public int AdvanceFrame()
|
public void AdvanceFrame()
|
||||||
{
|
{
|
||||||
var allocations = Interlocked.Exchange(ref _allocationsPerFrame[_currentFrameIndex], 0);
|
|
||||||
|
|
||||||
_currentFrameCount++;
|
_currentFrameCount++;
|
||||||
_currentFrameIndex = _currentFrameCount % _FRAME_LATENCY;
|
_currentFrameIndex = _currentFrameCount % _FRAME_LATENCY;
|
||||||
|
|
||||||
(_pArena + _currentFrameIndex)->Reset();
|
(_pArena + _currentFrameIndex)->Reset();
|
||||||
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
if (_allocationsPerFrame[_currentFrameIndex] != 0)
|
var allocations = Interlocked.Exchange(ref _allocationsPerFrame[_currentFrameIndex], 0);
|
||||||
|
if (allocations != 0)
|
||||||
{
|
{
|
||||||
Logger.Error($"TempJobAllocator: Detected {_allocationsPerFrame[_currentFrameIndex]} leaked allocations from frame {_currentFrameCount - _FRAME_LATENCY}.");
|
Logger.Error($"TempJobAllocator: Detected {allocations} leaked allocations from frame {_currentFrameCount - _FRAME_LATENCY}.");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return allocations;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,11 +3,56 @@ using Ghost.Graphics;
|
|||||||
using Ghost.Graphics.Core;
|
using Ghost.Graphics.Core;
|
||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using Ghost.Graphics.Utilities;
|
using Ghost.Graphics.Utilities;
|
||||||
|
using Misaki.HighPerformance.Mathematics;
|
||||||
using Misaki.HighPerformance.Mathematics.Geometry;
|
using Misaki.HighPerformance.Mathematics.Geometry;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ghost.Engine;
|
namespace Ghost.Engine;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
internal struct MeshContentHeader
|
||||||
|
{
|
||||||
|
public const uint MAGIC = 0x48534D47; // GMSH
|
||||||
|
public const uint VERSION = 1;
|
||||||
|
|
||||||
|
public uint magic;
|
||||||
|
public uint version;
|
||||||
|
|
||||||
|
public uint vertexCount;
|
||||||
|
public uint indexCount;
|
||||||
|
public uint materialPartCount;
|
||||||
|
public uint meshletCount;
|
||||||
|
public uint meshletGroupCount;
|
||||||
|
public uint meshletHierarchyNodeCount;
|
||||||
|
public uint meshletVertexCount;
|
||||||
|
public uint meshletTriangleCount;
|
||||||
|
public uint materialSlotCount;
|
||||||
|
public uint lodLevelCount;
|
||||||
|
|
||||||
|
public float3 boundsMin;
|
||||||
|
public float3 boundsMax;
|
||||||
|
|
||||||
|
public ulong vertexOffset;
|
||||||
|
public ulong indexOffset;
|
||||||
|
public ulong materialPartOffset;
|
||||||
|
public ulong meshletOffset;
|
||||||
|
public ulong meshletGroupOffset;
|
||||||
|
public ulong meshletHierarchyNodeOffset;
|
||||||
|
public ulong meshletVertexOffset;
|
||||||
|
public ulong meshletTriangleOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
internal struct MeshContentMaterialPart
|
||||||
|
{
|
||||||
|
public int materialIndex;
|
||||||
|
public int indexStart;
|
||||||
|
public int indexCount;
|
||||||
|
public int vertexStart;
|
||||||
|
public int vertexCount;
|
||||||
|
}
|
||||||
|
|
||||||
internal unsafe partial class AssetEntry
|
internal unsafe partial class AssetEntry
|
||||||
{
|
{
|
||||||
private sealed unsafe class MeshParsedData
|
private sealed unsafe class MeshParsedData
|
||||||
@@ -225,6 +270,7 @@ internal unsafe partial class AssetEntry
|
|||||||
private void OnMeshUploadComplete(ResourceStreamingContext context)
|
private void OnMeshUploadComplete(ResourceStreamingContext context)
|
||||||
{
|
{
|
||||||
var (oldHandle, newHandle) = GetStorage<(Handle<Mesh>, Handle<Mesh>)>();
|
var (oldHandle, newHandle) = GetStorage<(Handle<Mesh>, Handle<Mesh>)>();
|
||||||
|
// FIX: Do not reaplce the mesh, replace the underlying buffers and data instead because we are using persistent gpu scene. Replacing the mesh does not update the gpu scene at all.
|
||||||
var actualHandle = context.ResourceManager.ReplaceMesh(oldHandle, newHandle);
|
var actualHandle = context.ResourceManager.ReplaceMesh(oldHandle, newHandle);
|
||||||
if (actualHandle.IsInvalid)
|
if (actualHandle.IsInvalid)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
using Ghost.Core.Graphics;
|
||||||
using Ghost.Engine.RenderPipeline;
|
using Ghost.Engine.RenderPipeline;
|
||||||
using Ghost.Graphics;
|
using Ghost.Graphics;
|
||||||
|
using Ghost.Graphics.RHI;
|
||||||
using Misaki.HighPerformance.Jobs;
|
using Misaki.HighPerformance.Jobs;
|
||||||
|
using Misaki.HighPerformance.Mathematics;
|
||||||
|
|
||||||
namespace Ghost.Engine;
|
namespace Ghost.Engine;
|
||||||
|
|
||||||
@@ -23,7 +26,7 @@ public sealed partial class EngineCore : IDisposable
|
|||||||
internal RenderSystem RenderSystem => _renderSystem;
|
internal RenderSystem RenderSystem => _renderSystem;
|
||||||
internal AssetManager AssetManager => _assetManager;
|
internal AssetManager AssetManager => _assetManager;
|
||||||
|
|
||||||
public EngineCore(IContentProvider contentProvider)
|
public EngineCore(IContentProvider contentProvider, IShaderCompilationBridge? shaderCompilationBridge = null)
|
||||||
{
|
{
|
||||||
_contentProvider = contentProvider;
|
_contentProvider = contentProvider;
|
||||||
|
|
||||||
@@ -44,6 +47,7 @@ public sealed partial class EngineCore : IDisposable
|
|||||||
InitialRenderPipelineSettings = new GhostRenderPipelineSettings(),
|
InitialRenderPipelineSettings = new GhostRenderPipelineSettings(),
|
||||||
ResourceStreamingProcessor = _streamingProcessor,
|
ResourceStreamingProcessor = _streamingProcessor,
|
||||||
ShaderCacheDirectory = "ShaderCache",
|
ShaderCacheDirectory = "ShaderCache",
|
||||||
|
ShaderCompilationBridge = shaderCompilationBridge,
|
||||||
};
|
};
|
||||||
|
|
||||||
_renderSystem = new RenderSystem(renderingDesc);
|
_renderSystem = new RenderSystem(renderingDesc);
|
||||||
@@ -57,3 +61,11 @@ public sealed partial class EngineCore : IDisposable
|
|||||||
_jobScheduler.Dispose();
|
_jobScheduler.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GenerateShaderProperty("TestShader")]
|
||||||
|
public partial struct TestShaderProperty
|
||||||
|
{
|
||||||
|
public Texture2DHandle texture;
|
||||||
|
public uint someValue;
|
||||||
|
public float3 otherValue;
|
||||||
|
}
|
||||||
@@ -5,14 +5,7 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
|
||||||
<IsTrimmable>True</IsTrimmable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
<IsTrimmable>True</IsTrimmable>
|
<IsTrimmable>True</IsTrimmable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
using Misaki.HighPerformance.Mathematics;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ghost.Engine;
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
public struct MeshContentHeader
|
|
||||||
{
|
|
||||||
public const uint MAGIC = 0x48534D47; // GMSH
|
|
||||||
public const uint VERSION = 1;
|
|
||||||
|
|
||||||
public uint magic;
|
|
||||||
public uint version;
|
|
||||||
|
|
||||||
public uint vertexCount;
|
|
||||||
public uint indexCount;
|
|
||||||
public uint materialPartCount;
|
|
||||||
public uint meshletCount;
|
|
||||||
public uint meshletGroupCount;
|
|
||||||
public uint meshletHierarchyNodeCount;
|
|
||||||
public uint meshletVertexCount;
|
|
||||||
public uint meshletTriangleCount;
|
|
||||||
public uint materialSlotCount;
|
|
||||||
public uint lodLevelCount;
|
|
||||||
|
|
||||||
public float3 boundsMin;
|
|
||||||
public float3 boundsMax;
|
|
||||||
|
|
||||||
public ulong vertexOffset;
|
|
||||||
public ulong indexOffset;
|
|
||||||
public ulong materialPartOffset;
|
|
||||||
public ulong meshletOffset;
|
|
||||||
public ulong meshletGroupOffset;
|
|
||||||
public ulong meshletHierarchyNodeOffset;
|
|
||||||
public ulong meshletVertexOffset;
|
|
||||||
public ulong meshletTriangleOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
public struct MeshContentMaterialPart
|
|
||||||
{
|
|
||||||
public int materialIndex;
|
|
||||||
public int indexStart;
|
|
||||||
public int indexCount;
|
|
||||||
public int vertexStart;
|
|
||||||
public int vertexCount;
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Ghost.Engine;
|
|
||||||
|
|
||||||
internal class TestSetup : IDisposable
|
|
||||||
{
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using Ghost.Core;
|
|||||||
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 System.Diagnostics;
|
|
||||||
|
|
||||||
namespace Ghost.Entities;
|
namespace Ghost.Entities;
|
||||||
|
|
||||||
|
|||||||
@@ -86,15 +86,18 @@ internal class AssetHandlerRegistrationGenerator : IIncrementalGenerator
|
|||||||
var registerTypeName = "g_assethandler_registeration";
|
var registerTypeName = "g_assethandler_registeration";
|
||||||
var code = $@"// <auto-generated />
|
var code = $@"// <auto-generated />
|
||||||
|
|
||||||
|
#if GHOST_EDITOR
|
||||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||||
internal static partial class {registerTypeName}
|
internal static partial class {registerTypeName}
|
||||||
{{
|
{{
|
||||||
|
|
||||||
[global::System.Runtime.CompilerServices.ModuleInitializer]
|
[global::System.Runtime.CompilerServices.ModuleInitializer]
|
||||||
internal static void RegisterAssetHandlers()
|
internal static void RegisterAssetHandlers()
|
||||||
{{
|
{{
|
||||||
{sb}
|
{sb}
|
||||||
}}
|
}}
|
||||||
}}";
|
}}
|
||||||
|
#endif";
|
||||||
|
|
||||||
context.AddSource($"{registerTypeName}.gen.cs", code);
|
context.AddSource($"{registerTypeName}.gen.cs", code);
|
||||||
}
|
}
|
||||||
@@ -156,6 +159,7 @@ internal class IAssetSettingsRegistrationGenerator : IIncrementalGenerator
|
|||||||
var registerTypeName = "g_iassetsettings_registeration";
|
var registerTypeName = "g_iassetsettings_registeration";
|
||||||
var code = $@"// <auto-generated />
|
var code = $@"// <auto-generated />
|
||||||
|
|
||||||
|
#if GHOST_EDITOR
|
||||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||||
internal static partial class {registerTypeName}
|
internal static partial class {registerTypeName}
|
||||||
{{
|
{{
|
||||||
@@ -164,7 +168,8 @@ internal static partial class {registerTypeName}
|
|||||||
{{
|
{{
|
||||||
{sb}
|
{sb}
|
||||||
}}
|
}}
|
||||||
}}";
|
}}
|
||||||
|
#endif";
|
||||||
|
|
||||||
context.AddSource($"{registerTypeName}.gen.cs", code);
|
context.AddSource($"{registerTypeName}.gen.cs", code);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,229 +0,0 @@
|
|||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Ghost.Generator
|
|
||||||
{
|
|
||||||
[Generator]
|
|
||||||
public class ComponentSerializationGenerator : IIncrementalGenerator
|
|
||||||
{
|
|
||||||
private string GetJsonWriteCall(ITypeSymbol type, string fieldName)
|
|
||||||
{
|
|
||||||
switch (type.SpecialType)
|
|
||||||
{
|
|
||||||
case SpecialType.System_Byte:
|
|
||||||
case SpecialType.System_SByte:
|
|
||||||
case SpecialType.System_Int16:
|
|
||||||
case SpecialType.System_Int32:
|
|
||||||
case SpecialType.System_Int64:
|
|
||||||
case SpecialType.System_Single:
|
|
||||||
case SpecialType.System_Double:
|
|
||||||
case SpecialType.System_Decimal:
|
|
||||||
case SpecialType.System_UInt64:
|
|
||||||
case SpecialType.System_UInt16:
|
|
||||||
case SpecialType.System_UInt32:
|
|
||||||
case SpecialType.System_IntPtr:
|
|
||||||
case SpecialType.System_UIntPtr:
|
|
||||||
return $@"writer.WriteNumber(""{fieldName}"", value.{fieldName});";
|
|
||||||
case SpecialType.System_Boolean:
|
|
||||||
return $@"writer.WriteBoolean(""{fieldName}"", value.{fieldName});";
|
|
||||||
case SpecialType.System_Char:
|
|
||||||
return $@"writer.WriteString(""{fieldName}"", [value.{fieldName}]);";
|
|
||||||
case SpecialType.System_String:
|
|
||||||
return $@"writer.WriteString(""{fieldName}"", value.{fieldName});";
|
|
||||||
}
|
|
||||||
|
|
||||||
return $@"writer.WritePropertyName(""{fieldName}""); global::System.Text.Json.JsonSerializer.Serialize(writer, value.{fieldName}, options);";
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetJsonReadCall(ITypeSymbol type)
|
|
||||||
{
|
|
||||||
switch (type.SpecialType)
|
|
||||||
{
|
|
||||||
case SpecialType.System_Byte: return "reader.GetByte()";
|
|
||||||
case SpecialType.System_SByte: return "reader.GetSByte()";
|
|
||||||
case SpecialType.System_Int16: return "reader.GetInt16()";
|
|
||||||
case SpecialType.System_Int32: return "reader.GetInt32()";
|
|
||||||
case SpecialType.System_Int64: return "reader.GetInt64()";
|
|
||||||
case SpecialType.System_Single: return "reader.GetSingle()";
|
|
||||||
case SpecialType.System_Double: return "reader.GetDouble()";
|
|
||||||
case SpecialType.System_Decimal: return "reader.GetDecimal()";
|
|
||||||
case SpecialType.System_UInt16: return "reader.GetUInt16()";
|
|
||||||
case SpecialType.System_UInt32: return "reader.GetUInt32()";
|
|
||||||
case SpecialType.System_UInt64: return "reader.GetUInt64()";
|
|
||||||
// Note: the size of IntPtr and UIntPtr varies by platform, we use Int64/UInt64 to ensure compatibility
|
|
||||||
case SpecialType.System_IntPtr: return "reader.GetInt64()";
|
|
||||||
case SpecialType.System_UIntPtr: return "reader.GetUInt64()";
|
|
||||||
case SpecialType.System_Boolean: return "reader.GetBoolean()";
|
|
||||||
case SpecialType.System_Char: return "reader.GetString()[0]";
|
|
||||||
case SpecialType.System_String: return "reader.GetString()";
|
|
||||||
}
|
|
||||||
|
|
||||||
return $"global::System.Text.Json.JsonSerializer.Deserialize<{type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>(ref reader, options)";
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GenerateJsonSerializer(SourceProductionContext context, ImmutableArray<INamedTypeSymbol> symbols)
|
|
||||||
{
|
|
||||||
var sbWrites = new StringBuilder();
|
|
||||||
var sbReads = new StringBuilder();
|
|
||||||
|
|
||||||
foreach (var symbol in symbols)
|
|
||||||
{
|
|
||||||
var namespaceName = symbol.ContainingNamespace.ToDisplayString();
|
|
||||||
var structName = symbol.Name;
|
|
||||||
var fullTypeName = symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
|
||||||
|
|
||||||
// 1. Build Field Logic (Same as before)
|
|
||||||
sbWrites.Clear();
|
|
||||||
sbReads.Clear();
|
|
||||||
|
|
||||||
var fields = symbol.GetMembers()
|
|
||||||
.OfType<IFieldSymbol>()
|
|
||||||
.Where(f => !f.IsStatic && f.DeclaredAccessibility == Accessibility.Public);
|
|
||||||
|
|
||||||
foreach (var field in fields)
|
|
||||||
{
|
|
||||||
// Note: GetJsonWriteCall returns a string ending with ";"
|
|
||||||
var writeCall = GetJsonWriteCall(field.Type, field.Name);
|
|
||||||
if (writeCall != null)
|
|
||||||
{
|
|
||||||
sbWrites.Append(" "); // Indentation
|
|
||||||
sbWrites.AppendLine(writeCall);
|
|
||||||
}
|
|
||||||
|
|
||||||
var readCall = GetJsonReadCall(field.Type);
|
|
||||||
if (readCall != null)
|
|
||||||
{
|
|
||||||
// Note the double quotes ""{field.Name}"" for the case string
|
|
||||||
sbReads.Append(" "); // Indentation
|
|
||||||
sbReads.AppendLine($@"case ""{field.Name}"": result.{field.Name} = {readCall}; break;");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. The Main Template using $@
|
|
||||||
// Watch the double braces {{ }} and double quotes "" ""
|
|
||||||
var sourceCode = $@"// <auto-generated/>
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
namespace {namespaceName}
|
|
||||||
{{
|
|
||||||
public unsafe static class {structName}_Serializer
|
|
||||||
{{
|
|
||||||
[global::System.Runtime.CompilerServices.ModuleInitializer]
|
|
||||||
internal static void Init()
|
|
||||||
{{
|
|
||||||
var id = Ghost.Entities.ComponentTypeID<{fullTypeName}>.Value;
|
|
||||||
Ghost.Engine.IO.ComponentSerializerRegistry.Register(id, SerializeBinaryUnsafe, SerializeJsonUnsafe);
|
|
||||||
}}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// BINARY (Fast Path)
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
[global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static unsafe void SerializeBinaryUnsafe(global::System.IO.BinaryWriter writer, void* value)
|
|
||||||
{{
|
|
||||||
writer.Write(new global::System.ReadOnlySpan<byte>(value, sizeof({fullTypeName})));
|
|
||||||
}}
|
|
||||||
|
|
||||||
[global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static void SerializeBinary(this global::System.IO.BinaryWriter writer, ref {fullTypeName} value)
|
|
||||||
{{
|
|
||||||
unsafe {{ writer.Write(new global::System.ReadOnlySpan<byte>(global::System.Runtime.CompilerServices.Unsafe.AsPointer(ref value), sizeof({fullTypeName}))); }}
|
|
||||||
}}
|
|
||||||
|
|
||||||
[global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static void DeserializeBinary(this global::System.IO.BinaryReader reader, ref {fullTypeName} value)
|
|
||||||
{{
|
|
||||||
unsafe {{ reader.Read(new global::System.Span<byte>(global::System.Runtime.CompilerServices.Unsafe.AsPointer(ref value), sizeof({fullTypeName}))); }}
|
|
||||||
}}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// JSON WRITE
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
public static unsafe void SerializeJsonUnsafe(System.Text.Json.Utf8JsonWriter writer, void* ptr, System.Text.Json.JsonSerializerOptions? options)
|
|
||||||
{{
|
|
||||||
SerializeJson(writer, ref *( {fullTypeName}*)ptr, options);
|
|
||||||
}}
|
|
||||||
|
|
||||||
public static void SerializeJson(this global::System.Text.Json.Utf8JsonWriter writer, ref {fullTypeName} value, global::System.Text.Json.JsonSerializerOptions? options)
|
|
||||||
{{
|
|
||||||
writer.WriteStartObject();
|
|
||||||
{sbWrites}
|
|
||||||
writer.WriteEndObject();
|
|
||||||
}}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
// JSON READ
|
|
||||||
// ---------------------------------------------------------
|
|
||||||
public static {fullTypeName} DeserializeJson(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Text.Json.JsonSerializerOptions? options)
|
|
||||||
{{
|
|
||||||
var result = default({fullTypeName});
|
|
||||||
|
|
||||||
if (reader.TokenType != global::System.Text.Json.JsonTokenType.StartObject) throw new global::System.Text.Json.JsonException();
|
|
||||||
|
|
||||||
while (reader.Read())
|
|
||||||
{{
|
|
||||||
if (reader.TokenType == global::System.Text.Json.JsonTokenType.EndObject) return result;
|
|
||||||
if (reader.TokenType != global::System.Text.Json.JsonTokenType.PropertyName) throw new global::System.Text.Json.JsonException();
|
|
||||||
|
|
||||||
var propName = reader.GetString();
|
|
||||||
reader.Read();
|
|
||||||
|
|
||||||
switch (propName)
|
|
||||||
{{
|
|
||||||
{sbReads}
|
|
||||||
default: reader.Skip(); break;
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
return result;
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
}}";
|
|
||||||
|
|
||||||
context.AddSource($"{structName}.Serializer.gen.cs", sourceCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
|
|
||||||
var componentCandidates = context.SyntaxProvider
|
|
||||||
.CreateSyntaxProvider(
|
|
||||||
predicate: (syntaxNode, _) => syntaxNode is StructDeclarationSyntax,
|
|
||||||
transform: (ctx, _) =>
|
|
||||||
{
|
|
||||||
var structSyntax = (StructDeclarationSyntax)ctx.Node;
|
|
||||||
|
|
||||||
if (!(ctx.SemanticModel.GetDeclaredSymbol(structSyntax) is INamedTypeSymbol symbol))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var compilation = ctx.SemanticModel.Compilation;
|
|
||||||
var iComponentSymbol = compilation.GetTypeByMetadataName("Ghost.Entities.IComponent");
|
|
||||||
|
|
||||||
if (iComponentSymbol == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var iface in symbol.AllInterfaces)
|
|
||||||
{
|
|
||||||
if (SymbolEqualityComparer.Default.Equals(iface, iComponentSymbol))
|
|
||||||
{
|
|
||||||
return symbol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
.Where(symbol => symbol != null)
|
|
||||||
.Collect();
|
|
||||||
|
|
||||||
context.RegisterSourceOutput(componentCandidates, GenerateJsonSerializer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<LangVersion>14.0</LangVersion>
|
<LangVersion>14.0</LangVersion>
|
||||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||||
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -90,9 +90,13 @@ namespace Ghost.Generator
|
|||||||
isEnabledByDefault: true), info.TypeSymbol.Locations.FirstOrDefault()));
|
isEnabledByDefault: true), info.TypeSymbol.Locations.FirstOrDefault()));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var definedSymbol = $"__{info.Name.ToUpper()}_G_HLSL";
|
var definedSymbol = $"__{info.Name.ToUpper()}_G_HLSL";
|
||||||
|
|
||||||
|
var fieldsBuilder = new StringBuilder();
|
||||||
|
fieldsBuilder.AppendLine($" public static readonly global::Ghost.Core.Graphics.ShaderPropertyFieldInfo[] ReflectionData = new global::Ghost.Core.Graphics.ShaderPropertyFieldInfo[]");
|
||||||
|
fieldsBuilder.AppendLine(" {");
|
||||||
|
|
||||||
var fields = info.TypeSymbol.GetMembers().OfType<IFieldSymbol>();
|
var fields = info.TypeSymbol.GetMembers().OfType<IFieldSymbol>();
|
||||||
foreach (var field in fields)
|
foreach (var field in fields)
|
||||||
{
|
{
|
||||||
@@ -103,6 +107,7 @@ namespace Ghost.Generator
|
|||||||
|
|
||||||
var hlslTypeAttribute = field.GetAttributes().FirstOrDefault(ad => ad.AttributeClass?.ToDisplayString() == "Ghost.Engine.Utilities.GenerateAsHLSLTypeAttribute");
|
var hlslTypeAttribute = field.GetAttributes().FirstOrDefault(ad => ad.AttributeClass?.ToDisplayString() == "Ghost.Engine.Utilities.GenerateAsHLSLTypeAttribute");
|
||||||
var hlslType = string.Empty;
|
var hlslType = string.Empty;
|
||||||
|
var shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.Unknown";
|
||||||
|
|
||||||
if (hlslTypeAttribute == null)
|
if (hlslTypeAttribute == null)
|
||||||
{
|
{
|
||||||
@@ -110,34 +115,51 @@ namespace Ghost.Generator
|
|||||||
{
|
{
|
||||||
case SpecialType.System_Single:
|
case SpecialType.System_Single:
|
||||||
hlslType = "float";
|
hlslType = "float";
|
||||||
|
shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.Float";
|
||||||
break;
|
break;
|
||||||
case SpecialType.System_Int32:
|
case SpecialType.System_Int32:
|
||||||
hlslType = "int";
|
hlslType = "int";
|
||||||
|
shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.Int";
|
||||||
break;
|
break;
|
||||||
case SpecialType.System_UInt32:
|
case SpecialType.System_UInt32:
|
||||||
hlslType = "uint";
|
hlslType = "uint";
|
||||||
|
shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.UInt";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
var typeName = field.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
var typeName = field.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||||
switch (typeName)
|
switch (typeName)
|
||||||
{
|
{
|
||||||
case "global::System.Numerics.Vector2":
|
case "global::System.Numerics.Vector2" or "global::Misaki.HighPerformance.Mathematics.float2":
|
||||||
hlslType = "float2";
|
hlslType = "float2";
|
||||||
|
shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.Float2";
|
||||||
break;
|
break;
|
||||||
case "global::System.Numerics.Vector3":
|
case "global::System.Numerics.Vector3" or "global::Misaki.HighPerformance.Mathematics.float3":
|
||||||
hlslType = "float3";
|
hlslType = "float3";
|
||||||
|
shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.Float3";
|
||||||
break;
|
break;
|
||||||
case "global::System.Numerics.Vector4":
|
case "global::System.Numerics.Vector4" or "global::Misaki.HighPerformance.Mathematics.float4":
|
||||||
hlslType = "float4";
|
hlslType = "float4";
|
||||||
|
shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.Float4";
|
||||||
break;
|
break;
|
||||||
case "global::System.Numerics.Matrix3x3":
|
case "global::System.Numerics.Matrix4x4" or "global::Misaki.HighPerformance.Mathematics.float4x4":
|
||||||
hlslType = "float3x3";
|
|
||||||
break;
|
|
||||||
case "global::System.Numerics.Matrix4x4":
|
|
||||||
hlslType = "float4x4";
|
hlslType = "float4x4";
|
||||||
|
shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.Float4x4";
|
||||||
break;
|
break;
|
||||||
case "global::System.Numerics.Quaternion" or "global::Misaki.HighPerformance.Mathematics.quaternion":
|
case "global::System.Numerics.Quaternion" or "global::Misaki.HighPerformance.Mathematics.quaternion":
|
||||||
hlslType = "float4";
|
hlslType = "float4";
|
||||||
|
shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.Float4";
|
||||||
|
break;
|
||||||
|
case "global::Ghost.Core.Graphics.Texture2DHandle":
|
||||||
|
hlslType = "uint";
|
||||||
|
shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.Texture2D";
|
||||||
|
break;
|
||||||
|
case "global::Ghost.Core.Graphics.Texture3DHandle":
|
||||||
|
hlslType = "uint";
|
||||||
|
shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.Texture3D";
|
||||||
|
break;
|
||||||
|
case "global::Ghost.Core.Graphics.BufferHandle":
|
||||||
|
hlslType = "uint";
|
||||||
|
shaderPropType = "global::Ghost.Core.Graphics.ShaderPropertyType.Buffer";
|
||||||
break;
|
break;
|
||||||
case var _ when typeName.StartsWith("global::Misaki.HighPerformance.Mathematics."):
|
case var _ when typeName.StartsWith("global::Misaki.HighPerformance.Mathematics."):
|
||||||
hlslType = typeName.Substring("global::Misaki.HighPerformance.Mathematics.".Length);
|
hlslType = typeName.Substring("global::Misaki.HighPerformance.Mathematics.".Length);
|
||||||
@@ -167,8 +189,12 @@ namespace Ghost.Generator
|
|||||||
}
|
}
|
||||||
|
|
||||||
codeBuilder.AppendLine($" {hlslType} {field.Name};");
|
codeBuilder.AppendLine($" {hlslType} {field.Name};");
|
||||||
|
|
||||||
|
fieldsBuilder.AppendLine($" new global::Ghost.Core.Graphics.ShaderPropertyFieldInfo {{ Name = \"{field.Name}\", Type = {shaderPropType}, Offset = (int)global::System.Runtime.InteropServices.Marshal.OffsetOf<{info.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>(\"{field.Name}\") }},");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fieldsBuilder.AppendLine(" };");
|
||||||
|
|
||||||
var code = $@"// <auto-generated/>
|
var code = $@"// <auto-generated/>
|
||||||
|
|
||||||
namespace {info.TypeSymbol.ContainingNamespace.ToDisplayString()}
|
namespace {info.TypeSymbol.ContainingNamespace.ToDisplayString()}
|
||||||
@@ -176,7 +202,7 @@ namespace {info.TypeSymbol.ContainingNamespace.ToDisplayString()}
|
|||||||
[global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 4)]
|
[global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 4)]
|
||||||
{info.TypeSymbol.DeclaredAccessibility.ToString().ToLower()} partial struct {info.TypeSymbol.Name}
|
{info.TypeSymbol.DeclaredAccessibility.ToString().ToLower()} partial struct {info.TypeSymbol.Name}
|
||||||
{{
|
{{
|
||||||
#if DEBUG || GHOST_EDITOR
|
#if GHOST_EDITOR
|
||||||
public const string HLSL_SOURCE = @""
|
public const string HLSL_SOURCE = @""
|
||||||
# ifndef {definedSymbol}
|
# ifndef {definedSymbol}
|
||||||
# define {definedSymbol}
|
# define {definedSymbol}
|
||||||
@@ -185,31 +211,33 @@ struct {info.Name}
|
|||||||
{codeBuilder}
|
{codeBuilder}
|
||||||
}};
|
}};
|
||||||
# endif // {definedSymbol}"";
|
# endif // {definedSymbol}"";
|
||||||
}}
|
|
||||||
|
{fieldsBuilder}
|
||||||
#endif
|
#endif
|
||||||
|
}}
|
||||||
}}";
|
}}";
|
||||||
|
|
||||||
context.AddSource($"{info.TypeSymbol.Name}_HLSL.gen.cs", code);
|
context.AddSource($"{info.TypeSymbol.Name}_HLSL.gen.cs", code);
|
||||||
codeBuilder.Clear();
|
codeBuilder.Clear();
|
||||||
|
|
||||||
var typeFullName = info.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
var typeFullName = info.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||||
registerBuilder.AppendLine($@" global::Ghost.Core.Graphics.ShaderPropertiesRegistry.Register(""{info.ShaderName}"", {typeFullName}.HLSL_SOURCE, (uint)sizeof({typeFullName}));");
|
registerBuilder.AppendLine($@" global::Ghost.Core.Graphics.ShaderPropertiesRegistry.Register(""{info.ShaderName}"", {typeFullName}.HLSL_SOURCE, (uint)sizeof({typeFullName}), {typeFullName}.ReflectionData);");
|
||||||
}
|
}
|
||||||
|
|
||||||
var registerTypeName = "g_shaderproperty_registeration";
|
var registerTypeName = "g_shaderproperty_registeration";
|
||||||
var registerCode = $@"// <auto-generated/>
|
var registerCode = $@"// <auto-generated/>
|
||||||
|
|
||||||
|
#if GHOST_EDITOR
|
||||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||||
internal static partial class {registerTypeName}
|
internal static partial class {registerTypeName}
|
||||||
{{
|
{{
|
||||||
#if DEBUG || GHOST_EDITOR
|
|
||||||
[global::System.Runtime.CompilerServices.ModuleInitializer]
|
[global::System.Runtime.CompilerServices.ModuleInitializer]
|
||||||
internal unsafe static void RegisterShaderProperties()
|
internal unsafe static void RegisterShaderProperties()
|
||||||
{{
|
{{
|
||||||
{registerBuilder}
|
{registerBuilder}
|
||||||
}}
|
}}
|
||||||
#endif
|
}}
|
||||||
}}";
|
#endif";
|
||||||
|
|
||||||
context.AddSource($"{registerTypeName}.gen.cs", registerCode);
|
context.AddSource($"{registerTypeName}.gen.cs", registerCode);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
|||||||
|
|
||||||
_cpuFrame = cpuFrame;
|
_cpuFrame = cpuFrame;
|
||||||
_resourceDatabase.BeginFrame(cpuFrame);
|
_resourceDatabase.BeginFrame(cpuFrame);
|
||||||
|
_pipelineLibrary.BeginFrame(cpuFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EndFrame(ulong gpuFrame)
|
public void EndFrame(ulong gpuFrame)
|
||||||
@@ -151,6 +152,7 @@ internal class D3D12GraphicsEngine : IGraphicsEngine
|
|||||||
Logger.DebugAssert(!_disposed);
|
Logger.DebugAssert(!_disposed);
|
||||||
|
|
||||||
_resourceDatabase.EndFrame(gpuFrame);
|
_resourceDatabase.EndFrame(gpuFrame);
|
||||||
|
_pipelineLibrary.EndFrame(gpuFrame);
|
||||||
|
|
||||||
while (_commandBufferReturnQueue.TryPeek(out var entry) && entry.returnFrame < gpuFrame)
|
while (_commandBufferReturnQueue.TryPeek(out var entry) && entry.returnFrame < gpuFrame)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ internal struct D3D12PipelineState : IDisposable
|
|||||||
{
|
{
|
||||||
public UniquePtr<ID3D12PipelineState> pso;
|
public UniquePtr<ID3D12PipelineState> pso;
|
||||||
public Key64<ShaderVariant> shaderVariant;
|
public Key64<ShaderVariant> shaderVariant;
|
||||||
|
public ulong contentHash;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
@@ -33,6 +34,17 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
|||||||
|
|
||||||
private UnsafeHashMap<UInt128, D3D12PipelineState> _pipelineCache;
|
private UnsafeHashMap<UInt128, D3D12PipelineState> _pipelineCache;
|
||||||
|
|
||||||
|
private struct StalePipeline
|
||||||
|
{
|
||||||
|
public UInt128 pipelineKey;
|
||||||
|
public D3D12PipelineState pso;
|
||||||
|
public ulong frameAdded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UnsafeList<StalePipeline> _stalePipelines;
|
||||||
|
private ulong _currentCpuFrame;
|
||||||
|
private ulong _completedGpuFrame;
|
||||||
|
|
||||||
public ID3D12RootSignature* DefaultRootSignature => _defaultRootSignature.Get();
|
public ID3D12RootSignature* DefaultRootSignature => _defaultRootSignature.Get();
|
||||||
|
|
||||||
private static ID3D12PipelineLibrary1* CreateLibrary(D3D12RenderDevice device, string? filePath)
|
private static ID3D12PipelineLibrary1* CreateLibrary(D3D12RenderDevice device, string? filePath)
|
||||||
@@ -61,6 +73,7 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
|||||||
_device = device;
|
_device = device;
|
||||||
|
|
||||||
_pipelineCache = new UnsafeHashMap<UInt128, D3D12PipelineState>(32, AllocationHandle.Persistent);
|
_pipelineCache = new UnsafeHashMap<UInt128, D3D12PipelineState>(32, AllocationHandle.Persistent);
|
||||||
|
_stalePipelines = new UnsafeList<StalePipeline>(16, AllocationHandle.Persistent);
|
||||||
|
|
||||||
CreateDefaultRootSignature().ThrowIfFailed();
|
CreateDefaultRootSignature().ThrowIfFailed();
|
||||||
}
|
}
|
||||||
@@ -145,7 +158,7 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
|||||||
return D3D12Utility.D3D12_DEPTH_STENCIL_DESC_CREATE(depthEnabled, writeEnabled, cmp);
|
return D3D12Utility.D3D12_DEPTH_STENCIL_DESC_CREATE(depthEnabled, writeEnabled, cmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result CreatePSO(Key64<ShaderVariant> shaderVariantKey, UInt128 pipelineKey, D3D12_PIPELINE_STATE_STREAM_DESC* pStreamDesc)
|
private Result CreatePSO(ulong contentHash, Key64<ShaderVariant> shaderVariantKey, UInt128 pipelineKey, D3D12_PIPELINE_STATE_STREAM_DESC* pStreamDesc)
|
||||||
{
|
{
|
||||||
ID3D12PipelineState* pPipelineState = default;
|
ID3D12PipelineState* pPipelineState = default;
|
||||||
|
|
||||||
@@ -171,6 +184,7 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
|||||||
|
|
||||||
D3D12PipelineState pso = default;
|
D3D12PipelineState pso = default;
|
||||||
pso.shaderVariant = shaderVariantKey;
|
pso.shaderVariant = shaderVariantKey;
|
||||||
|
pso.contentHash = contentHash;
|
||||||
pso.pso.Attach(pPipelineState);
|
pso.pso.Attach(pPipelineState);
|
||||||
|
|
||||||
_pipelineCache[pipelineKey] = pso;
|
_pipelineCache[pipelineKey] = pso;
|
||||||
@@ -187,7 +201,7 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
|||||||
}
|
}
|
||||||
|
|
||||||
var passAttachmentKey = new PassAttachmentHash(desc.RtvFormats, desc.DsvFormat);
|
var passAttachmentKey = new PassAttachmentHash(desc.RtvFormats, desc.DsvFormat);
|
||||||
var pipelineKey = RHIUtility.CreateGraphicsPipelineKey(desc.VariantKey, desc.PipelineOption, passAttachmentKey);
|
var pipelineKey = RHIUtility.CreateGraphicsPipelineKey(desc.CompiledHash, desc.PipelineOption, passAttachmentKey);
|
||||||
|
|
||||||
if (!_pipelineCache.ContainsKey(pipelineKey))
|
if (!_pipelineCache.ContainsKey(pipelineKey))
|
||||||
{
|
{
|
||||||
@@ -243,7 +257,7 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
|||||||
SizeInBytes = (nuint)sizeof(CD3DX12_PIPELINE_MESH_STATE_STREAM)
|
SizeInBytes = (nuint)sizeof(CD3DX12_PIPELINE_MESH_STATE_STREAM)
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = CreatePSO(desc.VariantKey, pipelineKey, &streamDesc);
|
var result = CreatePSO(desc.CompiledHash, desc.VariantKey, pipelineKey, &streamDesc);
|
||||||
if (result.IsFailure)
|
if (result.IsFailure)
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
@@ -258,7 +272,7 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
|||||||
{
|
{
|
||||||
AssertNotDisposed();
|
AssertNotDisposed();
|
||||||
|
|
||||||
var pipelineKey = RHIUtility.CreateComputePipelineKey(desc.VariantKey);
|
var pipelineKey = RHIUtility.CreateComputePipelineKey(desc.CompiledHash);
|
||||||
if (!_pipelineCache.ContainsKey(pipelineKey))
|
if (!_pipelineCache.ContainsKey(pipelineKey))
|
||||||
{
|
{
|
||||||
fixed (byte* pCSByteCode = desc.CsCode)
|
fixed (byte* pCSByteCode = desc.CsCode)
|
||||||
@@ -272,7 +286,7 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
|||||||
SizeInBytes = (nuint)sizeof(CD3DX12_PIPELINE_STATE_STREAM_CS)
|
SizeInBytes = (nuint)sizeof(CD3DX12_PIPELINE_STATE_STREAM_CS)
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = CreatePSO(desc.VariantKey, pipelineKey, &streamDesc);
|
var result = CreatePSO(desc.CompiledHash, desc.VariantKey, pipelineKey, &streamDesc);
|
||||||
if (result.IsFailure)
|
if (result.IsFailure)
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
@@ -300,6 +314,55 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
|||||||
return Error.NotFound;
|
return Error.NotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void BeginFrame(ulong cpuFrame)
|
||||||
|
{
|
||||||
|
_currentCpuFrame = cpuFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EndFrame(ulong gpuFrame)
|
||||||
|
{
|
||||||
|
_completedGpuFrame = gpuFrame;
|
||||||
|
|
||||||
|
// Process stale pipelines and dispose them if they are no longer in flight
|
||||||
|
for (int i = _stalePipelines.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var stale = _stalePipelines[i];
|
||||||
|
if (_completedGpuFrame >= stale.frameAdded)
|
||||||
|
{
|
||||||
|
stale.pso.Dispose();
|
||||||
|
_stalePipelines.RemoveAtSwapBack(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EvictStalePipelines(ulong oldContentHash)
|
||||||
|
{
|
||||||
|
// Find all pipelines with matching oldContentHash
|
||||||
|
using var keysToRemove = new UnsafeList<UInt128>(8, AllocationHandle.Temp);
|
||||||
|
|
||||||
|
foreach (var kvp in _pipelineCache)
|
||||||
|
{
|
||||||
|
if (kvp.Value.contentHash == oldContentHash)
|
||||||
|
{
|
||||||
|
keysToRemove.Add(kvp.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var key in keysToRemove)
|
||||||
|
{
|
||||||
|
if (_pipelineCache.TryGetValue(key, out var pso))
|
||||||
|
{
|
||||||
|
_stalePipelines.Add(new StalePipeline
|
||||||
|
{
|
||||||
|
pipelineKey = key,
|
||||||
|
pso = pso,
|
||||||
|
frameAdded = _currentCpuFrame
|
||||||
|
});
|
||||||
|
_pipelineCache.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
foreach (var kvp in _pipelineCache)
|
foreach (var kvp in _pipelineCache)
|
||||||
@@ -308,6 +371,7 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
|||||||
}
|
}
|
||||||
|
|
||||||
_pipelineCache.Dispose();
|
_pipelineCache.Dispose();
|
||||||
|
_stalePipelines.Dispose();
|
||||||
_defaultRootSignature.Dispose();
|
_defaultRootSignature.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,9 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<PublishAot>true</PublishAot>
|
<PublishAot>true</PublishAot>
|
||||||
</PropertyGroup>
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
|
<IsTrimmable>True</IsTrimmable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ public readonly struct ShaderPass
|
|||||||
get; init;
|
get; init;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LocalKeywordSet KeywordIDs
|
public LocalKeywordSet DefinedKeywords
|
||||||
{
|
{
|
||||||
get; init;
|
get; init;
|
||||||
}
|
}
|
||||||
@@ -219,7 +219,7 @@ public readonly struct PassAttachmentHash : IEquatable<PassAttachmentHash>
|
|||||||
|
|
||||||
public ref struct GraphicsPSODesc
|
public ref struct GraphicsPSODesc
|
||||||
{
|
{
|
||||||
public UInt128 CompiledHash
|
public ulong CompiledHash
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
@@ -262,7 +262,7 @@ public ref struct GraphicsPSODesc
|
|||||||
|
|
||||||
public ref struct ComputePSODesc
|
public ref struct ComputePSODesc
|
||||||
{
|
{
|
||||||
public UInt128 CompiledHash
|
public ulong CompiledHash
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,9 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
|
<IsTrimmable>True</IsTrimmable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -9,4 +9,8 @@ public interface IPipelineLibrary : IDisposable
|
|||||||
bool HasPipelineStateObject(UInt128 key);
|
bool HasPipelineStateObject(UInt128 key);
|
||||||
Result<Key128<PipelineState>> CreateGraphicsPipeline(ref readonly GraphicsPSODesc desc);
|
Result<Key128<PipelineState>> CreateGraphicsPipeline(ref readonly GraphicsPSODesc desc);
|
||||||
Result<Key128<PipelineState>> CreateComputePipeline(ref readonly ComputePSODesc desc);
|
Result<Key128<PipelineState>> CreateComputePipeline(ref readonly ComputePSODesc desc);
|
||||||
|
|
||||||
|
void BeginFrame(ulong cpuFrame);
|
||||||
|
void EndFrame(ulong gpuFrame);
|
||||||
|
void EvictStalePipelines(ulong oldContentHash);
|
||||||
}
|
}
|
||||||
|
|||||||
17
src/Runtime/Ghost.Graphics.RHI/IShaderCompilationBridge.cs
Normal file
17
src/Runtime/Ghost.Graphics.RHI/IShaderCompilationBridge.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using Ghost.Core;
|
||||||
|
|
||||||
|
namespace Ghost.Graphics.RHI;
|
||||||
|
|
||||||
|
public interface IShaderCompilationBridge : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Request the bridge to recompile a shader variant or handle cache misses.
|
||||||
|
/// This is typically called by the ShaderLibrary when a variant hash is not found.
|
||||||
|
/// </summary>
|
||||||
|
void RequestCompilation(ulong shaderId, int passIndex, Key64<ShaderVariant> variantKey, LocalKeywordSet keywordMask);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event triggered when a shader variant has been successfully compiled and updated.
|
||||||
|
/// </summary>
|
||||||
|
event Action<Key64<ShaderVariant>, ulong> OnShaderVariantCompiled;
|
||||||
|
}
|
||||||
@@ -379,7 +379,7 @@ public readonly unsafe ref struct RenderContext
|
|||||||
var variantKey = RHIUtility.CreateShaderVariantKey(entryHash, in keywordSet);
|
var variantKey = RHIUtility.CreateShaderVariantKey(entryHash, in keywordSet);
|
||||||
|
|
||||||
// TODO: Refactor this into a helper method.
|
// TODO: Refactor this into a helper method.
|
||||||
var (compiledHash, error) = ShaderLibrary.GetCompiledHash(variantKey);
|
var (compiledHash, error) = ShaderLibrary.GetCompiledHash(shader.UniqueID, entryIndex, variantKey, keywordSet);
|
||||||
if (error.IsFailure)
|
if (error.IsFailure)
|
||||||
{
|
{
|
||||||
// TODO: Fallback to an error material.
|
// TODO: Fallback to an error material.
|
||||||
@@ -391,12 +391,11 @@ public readonly unsafe ref struct RenderContext
|
|||||||
|
|
||||||
if (!PipelineLibrary.HasPipelineStateObject(pipelineKey))
|
if (!PipelineLibrary.HasPipelineStateObject(pipelineKey))
|
||||||
{
|
{
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
var compiledCacheResult = ShaderLibrary.GetCompiledCache(shader.UniqueID, entryIndex);
|
||||||
var compiledCacheResult = ShaderLibrary.GetCompiledCache(shader.UniqueID, entryIndex, scope.AllocationHandle);
|
|
||||||
if (compiledCacheResult.IsFailure)
|
if (compiledCacheResult.IsFailure)
|
||||||
{
|
{
|
||||||
// TODO: Fallback to a checkerboard shader.
|
Logger.Warning($"Failed to load compiled shader cache for compute pipeline {pipelineKey}. Skipping compute dispatch.");
|
||||||
throw new InvalidOperationException("Failed to load compiled shader cache for pipeline state object creation.");
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var cache = compiledCacheResult.Value;
|
var cache = compiledCacheResult.Value;
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ public partial struct Shader : IResourceReleasable
|
|||||||
{
|
{
|
||||||
Key = RHIUtility.GetPassID(_nameHash, i),
|
Key = RHIUtility.GetPassID(_nameHash, i),
|
||||||
DefaultState = pass.localPipeline,
|
DefaultState = pass.localPipeline,
|
||||||
KeywordIDs = keywords,
|
DefinedKeywords = keywords,
|
||||||
};
|
};
|
||||||
|
|
||||||
_passIDToLocal[GetPassID(pass.name)] = (ushort)i;
|
_passIDToLocal[GetPassID(pass.name)] = (ushort)i;
|
||||||
|
|||||||
@@ -5,16 +5,17 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
|
<IsTrimmable>True</IsTrimmable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<DebugType>embedded</DebugType>
|
<DebugType>embedded</DebugType>
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_Editor|AnyCPU'">
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<DebugType>embedded</DebugType>
|
||||||
<IsTrimmable>True</IsTrimmable>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
namespace Ghost.Graphics;
|
|
||||||
|
|
||||||
public interface IShaderCompilationBridge
|
|
||||||
{
|
|
||||||
bool TryGetBytecode(ulong manifestKey, out ReadOnlyMemory<byte> bytecode);
|
|
||||||
bool IsCompiling(ulong manifestKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: For testing only.
|
|
||||||
internal sealed class NullShaderCompilationBridge : IShaderCompilationBridge
|
|
||||||
{
|
|
||||||
public bool TryGetBytecode(ulong manifestKey, out ReadOnlyMemory<byte> bytecode)
|
|
||||||
{
|
|
||||||
bytecode = default;
|
|
||||||
return false; // Always fall through to ShaderLibrary's disk cache
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsCompiling(ulong manifestKey) => false;
|
|
||||||
}
|
|
||||||
@@ -169,10 +169,10 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
|||||||
var materialPipeline = material.GetPassPipelineOverride(material.ActivePassIndex);
|
var materialPipeline = material.GetPassPipelineOverride(material.ActivePassIndex);
|
||||||
|
|
||||||
// Mask out the keywords that are not used in this pass.
|
// Mask out the keywords that are not used in this pass.
|
||||||
var variantMask = material._keywordMask & pass.KeywordIDs;
|
var variantMask = material._keywordMask & pass.DefinedKeywords;
|
||||||
var variantKey = RHIUtility.CreateShaderVariantKey(pass.Key, in variantMask);
|
var variantKey = RHIUtility.CreateShaderVariantKey(pass.Key, in variantMask);
|
||||||
|
|
||||||
var (compiledHash, error) = _shaderLibrary.GetCompiledHash(variantKey);
|
var (compiledHash, error) = _shaderLibrary.GetCompiledHash(shader.UniqueID, material.ActivePassIndex, variantKey, variantMask);
|
||||||
if (error.IsFailure)
|
if (error.IsFailure)
|
||||||
{
|
{
|
||||||
// TODO: Fallback to a default shader or show an error material.
|
// TODO: Fallback to a default shader or show an error material.
|
||||||
@@ -183,11 +183,11 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
|||||||
|
|
||||||
if (!_pipelineLibrary.HasPipelineStateObject(pipelineKey))
|
if (!_pipelineLibrary.HasPipelineStateObject(pipelineKey))
|
||||||
{
|
{
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
var compiledCacheResult = _shaderLibrary.GetCompiledCache(shader.UniqueID, material.ActivePassIndex);
|
||||||
var compiledCacheResult = _shaderLibrary.GetCompiledCache(shader.UniqueID, material.ActivePassIndex, scope.AllocationHandle);
|
|
||||||
if (compiledCacheResult.IsFailure)
|
if (compiledCacheResult.IsFailure)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Failed to load compiled shader cache for pipeline state object creation.");
|
Logger.Warning($"Failed to load compiled shader cache for graphics pipeline {pipelineKey}. Skipping draw call.");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var cache = compiledCacheResult.Value;
|
var cache = compiledCacheResult.Value;
|
||||||
@@ -277,7 +277,7 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
|||||||
var keywordSet = new LocalKeywordSet(); // TODO: Support keywords in compute shader.
|
var keywordSet = new LocalKeywordSet(); // TODO: Support keywords in compute shader.
|
||||||
var variantKey = RHIUtility.CreateShaderVariantKey(entryHash, in keywordSet);
|
var variantKey = RHIUtility.CreateShaderVariantKey(entryHash, in keywordSet);
|
||||||
|
|
||||||
var (compiledHash, error) = _shaderLibrary.GetCompiledHash(variantKey);
|
var (compiledHash, error) = _shaderLibrary.GetCompiledHash(shader.UniqueID, entryIndex, variantKey, keywordSet);
|
||||||
if (error.IsFailure)
|
if (error.IsFailure)
|
||||||
{
|
{
|
||||||
// TODO: Fallback to a default shader or show an error material.
|
// TODO: Fallback to a default shader or show an error material.
|
||||||
@@ -288,11 +288,11 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
|||||||
|
|
||||||
if (!_pipelineLibrary.HasPipelineStateObject(pipelineKey))
|
if (!_pipelineLibrary.HasPipelineStateObject(pipelineKey))
|
||||||
{
|
{
|
||||||
using var scope = AllocationManager.CreateStackScope();
|
var compiledCacheResult = _shaderLibrary.GetCompiledCache(shader.UniqueID, entryIndex);
|
||||||
var compiledCacheResult = _shaderLibrary.GetCompiledCache(shader.UniqueID, entryIndex, scope.AllocationHandle);
|
|
||||||
if (compiledCacheResult.IsFailure)
|
if (compiledCacheResult.IsFailure)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Failed to load compiled shader cache for pipeline state object creation.");
|
Logger.Warning($"Failed to load compiled shader cache for compute pipeline {pipelineKey}. Skipping compute dispatch.");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var cache = compiledCacheResult.Value;
|
var cache = compiledCacheResult.Value;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Ghost.Core.Utilities;
|
|||||||
using Ghost.Graphics.RHI;
|
using Ghost.Graphics.RHI;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
using System.IO.Hashing;
|
using System.IO.Hashing;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
@@ -80,6 +81,16 @@ internal unsafe class ShaderLibrary : IDisposable
|
|||||||
|
|
||||||
_cacheDirectory = cacheDirectory;
|
_cacheDirectory = cacheDirectory;
|
||||||
_shaderCompilationBridge = shaderCompilationBridge;
|
_shaderCompilationBridge = shaderCompilationBridge;
|
||||||
|
|
||||||
|
if (_shaderCompilationBridge != null)
|
||||||
|
{
|
||||||
|
_shaderCompilationBridge.OnShaderVariantCompiled += OnVariantCompiled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnVariantCompiled(Key64<ShaderVariant> variantKey, ulong newCompiledHash)
|
||||||
|
{
|
||||||
|
_variantToCompiledHash[variantKey] = newCompiledHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetShaderCacheFilePath(ulong hash)
|
private string GetShaderCacheFilePath(ulong hash)
|
||||||
@@ -117,14 +128,17 @@ internal unsafe class ShaderLibrary : IDisposable
|
|||||||
};
|
};
|
||||||
|
|
||||||
var offsets = stackalloc ulong[byteCodes.Length];
|
var offsets = stackalloc ulong[byteCodes.Length];
|
||||||
var offset = (ulong)(sizeof(CacheHeader) + (sizeof(ulong) * byteCodes.Length));
|
var offset = (nuint)(sizeof(CacheHeader) + (sizeof(ulong) * byteCodes.Length));
|
||||||
for (var i = 0; i < byteCodes.Length; i++)
|
for (var i = 0; i < byteCodes.Length; i++)
|
||||||
{
|
{
|
||||||
offsets[i] = offset;
|
offsets[i] = offset;
|
||||||
offset += byteCodes[i].size;
|
offset += (nuint)byteCodes[i].size;
|
||||||
}
|
}
|
||||||
|
|
||||||
var data = new MemoryBlock((nuint)offset, 8, AllocationHandle.Persistent);
|
var alignment = Math.Max(Math.Max(MemoryUtility.AlignOf<CacheHeader>(), MemoryUtility.AlignOf<ulong>()), 8);
|
||||||
|
offset = MemoryUtility.AlignUp(offset, alignment);
|
||||||
|
|
||||||
|
var data = new MemoryBlock(offset, alignment, AllocationHandle.Persistent);
|
||||||
var writer = new SpanWriter(data.AsSpan<byte>());
|
var writer = new SpanWriter(data.AsSpan<byte>());
|
||||||
|
|
||||||
writer.Write(header);
|
writer.Write(header);
|
||||||
@@ -141,7 +155,18 @@ internal unsafe class ShaderLibrary : IDisposable
|
|||||||
writer.WriteSpan(src);
|
writer.WriteSpan(src);
|
||||||
}
|
}
|
||||||
|
|
||||||
var codeHash = XxHash64.HashToUInt64(data.AsSpan<byte>());
|
// Compute hash from bytecode only
|
||||||
|
var codeHash = 0UL;
|
||||||
|
if (byteCodes.Length > 0)
|
||||||
|
{
|
||||||
|
ulong totalBytecodeSize = 0;
|
||||||
|
for (int i = 0; i < byteCodes.Length; i++) totalBytecodeSize += byteCodes[i].size;
|
||||||
|
|
||||||
|
// We skip the header and offsets at the beginning of the MemoryBlock
|
||||||
|
var bytecodeSpan = data.AsSpan<byte>().Slice((int)(sizeof(CacheHeader) + (sizeof(ulong) * byteCodes.Length)), (int)totalBytecodeSize);
|
||||||
|
codeHash = XxHash64.HashToUInt64(bytecodeSpan);
|
||||||
|
}
|
||||||
|
|
||||||
_variantToCompiledHash[variantKey] = codeHash;
|
_variantToCompiledHash[variantKey] = codeHash;
|
||||||
|
|
||||||
ref var entry = ref _inMemoryCache.GetValueRefOrAddDefault(id, out var exists);
|
ref var entry = ref _inMemoryCache.GetValueRefOrAddDefault(id, out var exists);
|
||||||
@@ -149,16 +174,15 @@ internal unsafe class ShaderLibrary : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public Result<ShaderCache, Error> GetCompiledCache(ulong id, int index, AllocationHandle allocationHandle)
|
public Result<ShaderCache, Error> GetCompiledCache(ulong id, int index)
|
||||||
{
|
{
|
||||||
if (_inMemoryCache.TryGetValue(id, out var entry))
|
if (_inMemoryCache.TryGetValue(id, out var entry))
|
||||||
{
|
{
|
||||||
if (index < entry.cache.Length)
|
if (index < entry.cache.Length)
|
||||||
{
|
{
|
||||||
var shaderCache = entry.cache[index];
|
var shaderCache = entry.cache[index];
|
||||||
var result = new MemoryBlock(shaderCache.byteCode.Size, shaderCache.byteCode.Alignment, allocationHandle);
|
var result = new MemoryBlock(shaderCache.byteCode.GetUnsafePtr(), (uint)shaderCache.byteCode.Size);
|
||||||
|
|
||||||
result.CopyFrom(shaderCache.byteCode.AsSpan<byte>());
|
|
||||||
return new ShaderCache
|
return new ShaderCache
|
||||||
{
|
{
|
||||||
byteCode = result,
|
byteCode = result,
|
||||||
@@ -171,16 +195,38 @@ internal unsafe class ShaderLibrary : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public Result<ulong, Error> GetCompiledHash(Key64<ShaderVariant> variantKey)
|
public Result<ulong, Error> GetCompiledHash(ulong id, int passIndex, Key64<ShaderVariant> variantKey, LocalKeywordSet keywordMask = default)
|
||||||
{
|
{
|
||||||
if (_variantToCompiledHash.TryGetValue(variantKey, out var compiledHash))
|
if (_variantToCompiledHash.TryGetValue(variantKey, out var compiledHash))
|
||||||
{
|
{
|
||||||
return compiledHash;
|
return compiledHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_shaderCompilationBridge?.RequestCompilation(id, passIndex, variantKey, keywordMask);
|
||||||
return Error.NotFound;
|
return Error.NotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void InvalidateShaderCache(ulong id, IPipelineLibrary pipelineLibrary)
|
||||||
|
{
|
||||||
|
if (_inMemoryCache.TryGetValue(id, out var entry))
|
||||||
|
{
|
||||||
|
for (int i = 0; i < entry.cache.Length; i++)
|
||||||
|
{
|
||||||
|
if (entry.cache[i].compiledHash != 0)
|
||||||
|
{
|
||||||
|
pipelineLibrary.EvictStalePipelines(entry.cache[i].compiledHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entry.Dispose();
|
||||||
|
_inMemoryCache.Remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait, what about _variantToCompiledHash?
|
||||||
|
// It maps variantKey -> compiledHash. Since we don't have a way to find variantKeys for this shader id,
|
||||||
|
// it will just linger as garbage. But it's small (8 bytes + 8 bytes).
|
||||||
|
// A proper fix would require _inMemoryCache to store the variantKeys, but for now we ignore it.
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
foreach (var kvp in _inMemoryCache)
|
foreach (var kvp in _inMemoryCache)
|
||||||
@@ -191,6 +237,11 @@ internal unsafe class ShaderLibrary : IDisposable
|
|||||||
_inMemoryCache.Dispose();
|
_inMemoryCache.Dispose();
|
||||||
_variantToCompiledHash.Dispose();
|
_variantToCompiledHash.Dispose();
|
||||||
|
|
||||||
|
if (_shaderCompilationBridge != null)
|
||||||
|
{
|
||||||
|
_shaderCompilationBridge.OnShaderVariantCompiled -= OnVariantCompiled;
|
||||||
|
}
|
||||||
|
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Ghost.Test.Core;
|
using Ghost.TestCore;
|
||||||
using Misaki.HighPerformance.Jobs;
|
using Misaki.HighPerformance.Jobs;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.Mathematics;
|
using Misaki.HighPerformance.Mathematics;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -15,7 +16,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Runtime\Ghost.Entities\Ghost.Entities.csproj" />
|
<ProjectReference Include="..\..\Runtime\Ghost.Entities\Ghost.Entities.csproj" />
|
||||||
<ProjectReference Include="..\..\Test\Ghost.Test.Core\Ghost.Test.Core.csproj" />
|
<ProjectReference Include="..\..\Test\Ghost.TestCore\Ghost.TestCore.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Ghost.Test.Core;
|
using Ghost.TestCore;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.Mathematics;
|
using Misaki.HighPerformance.Mathematics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Ghost.Test.Core;
|
using Ghost.TestCore;
|
||||||
|
|
||||||
namespace Ghost.Entities.Test;
|
namespace Ghost.Entities.Test;
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
<UseWinUI>true</UseWinUI>
|
<UseWinUI>true</UseWinUI>
|
||||||
<EnableMsixTooling>true</EnableMsixTooling>
|
<EnableMsixTooling>true</EnableMsixTooling>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -50,7 +51,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Runtime\Ghost.Engine\Ghost.Engine.csproj" />
|
<ProjectReference Include="..\..\Runtime\Ghost.Engine\Ghost.Engine.csproj" />
|
||||||
<ProjectReference Include="..\..\Test\Ghost.Test.Core\Ghost.Test.Core.csproj" />
|
<ProjectReference Include="..\..\Test\Ghost.TestCore\Ghost.TestCore.csproj" />
|
||||||
<ProjectReference Include="..\..\Runtime\Ghost.Graphics\Ghost.Graphics.csproj" />
|
<ProjectReference Include="..\..\Runtime\Ghost.Graphics\Ghost.Graphics.csproj" />
|
||||||
<ProjectReference Include="..\..\Editor\Ghost.DSL\Ghost.DSL.csproj" />
|
<ProjectReference Include="..\..\Editor\Ghost.DSL\Ghost.DSL.csproj" />
|
||||||
<ProjectReference Include="..\..\ThridParty\Ghost.Ufbx\Ghost.Ufbx.csproj" />
|
<ProjectReference Include="..\..\ThridParty\Ghost.Ufbx\Ghost.Ufbx.csproj" />
|
||||||
@@ -68,6 +69,7 @@
|
|||||||
<!-- Publish Properties -->
|
<!-- Publish Properties -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||||
|
<PublishReadyToRun Condition="'$(Configuration)'=='Debug_Editor'">False</PublishReadyToRun>
|
||||||
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||||
<SupportedOSPlatformVersion>10.0.20348.0</SupportedOSPlatformVersion>
|
<SupportedOSPlatformVersion>10.0.20348.0</SupportedOSPlatformVersion>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
@@ -76,10 +78,19 @@
|
|||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
|
||||||
<DebugType>embedded</DebugType>
|
<DebugType>embedded</DebugType>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_Editor|x86'">
|
||||||
|
<DebugType>embedded</DebugType>
|
||||||
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
<DebugType>embedded</DebugType>
|
<DebugType>embedded</DebugType>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_Editor|x64'">
|
||||||
|
<DebugType>embedded</DebugType>
|
||||||
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||||
<DebugType>embedded</DebugType>
|
<DebugType>embedded</DebugType>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_Editor|ARM64'">
|
||||||
|
<DebugType>embedded</DebugType>
|
||||||
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
171
src/Test/Ghost.MicroTest/DXCBindingTest.cs
Normal file
171
src/Test/Ghost.MicroTest/DXCBindingTest.cs
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
using Ghost.DXC;
|
||||||
|
using Ghost.TestCore;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using static Ghost.DXC.UUID;
|
||||||
|
|
||||||
|
namespace Ghost.MicroTest;
|
||||||
|
|
||||||
|
internal unsafe class DXCBindingTest : ITest
|
||||||
|
{
|
||||||
|
private static ReadOnlySpan<byte> ShaderCode => @"
|
||||||
|
struct VSInput
|
||||||
|
{
|
||||||
|
float3 position : POSITION;
|
||||||
|
float3 normal : NORMAL;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PSInput
|
||||||
|
{
|
||||||
|
float4 position : SV_POSITION;
|
||||||
|
float3 normal : NORMAL;
|
||||||
|
};
|
||||||
|
|
||||||
|
PSInput main(VSInput input)
|
||||||
|
{
|
||||||
|
PSInput output;
|
||||||
|
output.position = float4(input.position, 1.0);
|
||||||
|
output.normal = input.normal;
|
||||||
|
return output;
|
||||||
|
}"u8;
|
||||||
|
|
||||||
|
private IDxcCompiler3* _compiler;
|
||||||
|
private IDxcUtils* _utils;
|
||||||
|
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
IDxcCompiler3* pCompiler = default;
|
||||||
|
IDxcUtils* pUtils = default;
|
||||||
|
var hr = Api.DxcCreateInstance((Guid*)Unsafe.AsPointer(in Api.CLSID_DxcCompiler), __uuidof(pCompiler), (void**)&pCompiler);
|
||||||
|
if (hr < 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Failed to create DXC compiler instance. HRESULT: 0x{hr:X8}");
|
||||||
|
}
|
||||||
|
|
||||||
|
hr = Api.DxcCreateInstance((Guid*)Unsafe.AsPointer(in Api.CLSID_DxcUtils), __uuidof(pUtils), (void**)&pUtils);
|
||||||
|
if (hr < 0)
|
||||||
|
{
|
||||||
|
pCompiler->Release();
|
||||||
|
throw new InvalidOperationException($"Failed to create DXC utils instance. HRESULT: 0x{hr:X8}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_compiler = pCompiler;
|
||||||
|
_utils = pUtils;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<string> GetCompilerArguments()
|
||||||
|
{
|
||||||
|
return new List<string>
|
||||||
|
{
|
||||||
|
"-T", "vs_6_6", // Target profile (vs_6_6 for vertex shader)
|
||||||
|
"-E", "main", // Entry point
|
||||||
|
"-HV", "2021", // HLSL version 2021
|
||||||
|
"-enable-16bit-types" // Enable 16-bit types
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Run()
|
||||||
|
{
|
||||||
|
IDxcIncludeHandler* includeHandler = default;
|
||||||
|
IDxcBlobEncoding* sourceBlob = default;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var hr = _utils->CreateDefaultIncludeHandler(&includeHandler);
|
||||||
|
Assert.AreEqual(0, hr, $"Failed to create default include handler. HRESULT: 0x{hr:X8}");
|
||||||
|
|
||||||
|
fixed (byte* pCode = ShaderCode)
|
||||||
|
{
|
||||||
|
hr = _utils->CreateBlobFromPinned(pCode, (uint)ShaderCode.Length, Api.DXC_CP_UTF8, &sourceBlob);
|
||||||
|
Assert.AreEqual(0, hr, $"Failed to create blob from shader code. HRESULT: 0x{hr:X8}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var argsArray = GetCompilerArguments();
|
||||||
|
var argPtrs = stackalloc char*[argsArray.Count];
|
||||||
|
for (var i = 0; i < argsArray.Count; i++)
|
||||||
|
{
|
||||||
|
argPtrs[i] = (char*)Marshal.StringToHGlobalUni(argsArray[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
IDxcResult* result = default;
|
||||||
|
IDxcBlob* bytecodeBlob = default;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Compile shader
|
||||||
|
var buffer = new DxcBuffer
|
||||||
|
{
|
||||||
|
Ptr = sourceBlob->GetBufferPointer(),
|
||||||
|
Size = sourceBlob->GetBufferSize(),
|
||||||
|
Encoding = Api.DXC_CP_UTF8
|
||||||
|
};
|
||||||
|
|
||||||
|
hr = _compiler->Compile(&buffer, argPtrs, (uint)argsArray.Count, includeHandler, __uuidof(result), (void**)&result);
|
||||||
|
Assert.AreEqual(0, hr, $"Failed to compile shader. HRESULT: 0x{hr:X8}");
|
||||||
|
|
||||||
|
// Check compilation result
|
||||||
|
int hrStatus;
|
||||||
|
result->GetStatus(&hrStatus);
|
||||||
|
if (hrStatus < 0)
|
||||||
|
{
|
||||||
|
// Get error messages
|
||||||
|
IDxcBlobEncoding* pErrorBlob = default;
|
||||||
|
result->GetErrorBuffer(&pErrorBlob);
|
||||||
|
|
||||||
|
if (pErrorBlob != null)
|
||||||
|
{
|
||||||
|
var errorMessage = Marshal.PtrToStringUTF8((IntPtr)pErrorBlob->GetBufferPointer());
|
||||||
|
pErrorBlob->Release();
|
||||||
|
|
||||||
|
throw new InvalidOperationException($"DXC shader compilation failed:\n{errorMessage}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("DXC shader compilation failed with unknown error.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get compiled bytecode
|
||||||
|
hr = result->GetResult(&bytecodeBlob);
|
||||||
|
Assert.AreEqual(0, hr, $"Failed to get compiled shader bytecode. HRESULT: 0x{hr:X8}");
|
||||||
|
|
||||||
|
var bytecodeSize = bytecodeBlob->GetBufferSize();
|
||||||
|
Assert.IsTrue(bytecodeSize > 0, "Compiled shader bytecode is empty.");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
result->Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytecodeBlob != null)
|
||||||
|
{
|
||||||
|
bytecodeBlob->Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < argsArray.Count; i++)
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal((nint)argPtrs[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (includeHandler != null)
|
||||||
|
{
|
||||||
|
includeHandler->Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceBlob != null)
|
||||||
|
{
|
||||||
|
sourceBlob->Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Cleanup()
|
||||||
|
{
|
||||||
|
_compiler->Release();
|
||||||
|
_utils->Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<PublishAot>True</PublishAot>
|
<PublishAot>True</PublishAot>
|
||||||
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -15,7 +16,8 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Runtime\Ghost.Core\Ghost.Core.csproj" />
|
<ProjectReference Include="..\..\Runtime\Ghost.Core\Ghost.Core.csproj" />
|
||||||
<ProjectReference Include="..\..\Test\Ghost.Test.Core\Ghost.Test.Core.csproj" />
|
<ProjectReference Include="..\Ghost.TestCore\Ghost.TestCore.csproj" />
|
||||||
|
<ProjectReference Include="..\..\ThridParty\Ghost.DXC\Ghost.DXC.csproj" />
|
||||||
<ProjectReference Include="..\..\ThridParty\Ghost.Nvtt\Ghost.Nvtt.csproj" />
|
<ProjectReference Include="..\..\ThridParty\Ghost.Nvtt\Ghost.Nvtt.csproj" />
|
||||||
<ProjectReference Include="..\..\ThridParty\Ghost.StbI\Ghost.StbI.csproj" />
|
<ProjectReference Include="..\..\ThridParty\Ghost.StbI\Ghost.StbI.csproj" />
|
||||||
<ProjectReference Include="..\..\ThridParty\Ghost.Ufbx\Ghost.Ufbx.csproj" />
|
<ProjectReference Include="..\..\ThridParty\Ghost.Ufbx\Ghost.Ufbx.csproj" />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using Ghost.Nvtt;
|
using Ghost.Nvtt;
|
||||||
using Ghost.Test.Core;
|
using Ghost.TestCore;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Ghost.MicroTest;
|
using Ghost.MicroTest;
|
||||||
using Ghost.Test.Core;
|
using Ghost.TestCore;
|
||||||
|
|
||||||
TestRunner.Run<UfbxBindingTest>();
|
TestRunner.Run<DXCBindingTest>();
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
using Ghost.StbI;
|
using Ghost.StbI;
|
||||||
using Ghost.Test.Core;
|
using Ghost.TestCore;
|
||||||
|
|
||||||
namespace Ghost.MicroTest;
|
namespace Ghost.MicroTest;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Ghost.Test.Core;
|
using Ghost.TestCore;
|
||||||
using Ghost.Ufbx;
|
using Ghost.Ufbx;
|
||||||
|
|
||||||
namespace Ghost.MicroTest;
|
namespace Ghost.MicroTest;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<PublishAot>True</PublishAot>
|
<PublishAot>True</PublishAot>
|
||||||
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
namespace Ghost.Test.Core;
|
|
||||||
|
|
||||||
public class TestRunner
|
|
||||||
{
|
|
||||||
public static void Run<T>()
|
|
||||||
where T : ITest, new()
|
|
||||||
{
|
|
||||||
var test = new T();
|
|
||||||
test.Setup();
|
|
||||||
test.Run();
|
|
||||||
test.Cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Run<T>(int iteration)
|
|
||||||
where T : ITest, new()
|
|
||||||
{
|
|
||||||
var test = new T();
|
|
||||||
test.Setup();
|
|
||||||
|
|
||||||
iteration = iteration < 1 ? 1 : iteration;
|
|
||||||
for (var i = 0; i < iteration; i++)
|
|
||||||
{
|
|
||||||
test.Run();
|
|
||||||
}
|
|
||||||
|
|
||||||
test.Cleanup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
33
src/Test/Ghost.TestCore/Assert.cs
Normal file
33
src/Test/Ghost.TestCore/Assert.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
namespace Ghost.TestCore;
|
||||||
|
|
||||||
|
public static class Assert
|
||||||
|
{
|
||||||
|
public static void AreEqual<T>(T expected, T actual, string message = "")
|
||||||
|
{
|
||||||
|
if (!EqualityComparer<T>.Default.Equals(expected, actual))
|
||||||
|
{
|
||||||
|
throw new AssertFailedException($"Assert.AreEqual failed. Expected: {expected}, Actual: {actual}. {message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void IsTrue(bool condition, string message = "")
|
||||||
|
{
|
||||||
|
if (!condition)
|
||||||
|
{
|
||||||
|
throw new AssertFailedException($"Assert.IsTrue failed. {message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void IsFalse(bool condition, string message = "")
|
||||||
|
{
|
||||||
|
if (condition)
|
||||||
|
{
|
||||||
|
throw new AssertFailedException($"Assert.IsFalse failed. {message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Fail(string message = "")
|
||||||
|
{
|
||||||
|
throw new AssertFailedException($"Assert.Fail: {message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/Test/Ghost.TestCore/AssertFailedException.cs
Normal file
17
src/Test/Ghost.TestCore/AssertFailedException.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
namespace Ghost.TestCore;
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
internal class AssertFailedException : Exception
|
||||||
|
{
|
||||||
|
public AssertFailedException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public AssertFailedException(string? message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public AssertFailedException(string? message, Exception? innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Ghost.Test.Core;
|
namespace Ghost.TestCore;
|
||||||
|
|
||||||
public interface ITest
|
public interface ITest
|
||||||
{
|
{
|
||||||
49
src/Test/Ghost.TestCore/TestRunner.cs
Normal file
49
src/Test/Ghost.TestCore/TestRunner.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
namespace Ghost.TestCore;
|
||||||
|
|
||||||
|
public class TestRunner
|
||||||
|
{
|
||||||
|
public static void Run<T>()
|
||||||
|
where T : ITest, new()
|
||||||
|
{
|
||||||
|
var test = new T();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
test.Setup();
|
||||||
|
test.Run();
|
||||||
|
test.Cleanup();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Test failed with exception: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Test completed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Run<T>(int iteration)
|
||||||
|
where T : ITest, new()
|
||||||
|
{
|
||||||
|
var test = new T();
|
||||||
|
var i = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
test.Setup();
|
||||||
|
|
||||||
|
iteration = iteration < 1 ? 1 : iteration;
|
||||||
|
for (i = 0; i < iteration; i++)
|
||||||
|
{
|
||||||
|
test.Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
test.Cleanup();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Test failed at iteration {i} with exception: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"Test completed after {iteration} iterations.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,7 +46,7 @@ public class ImportCoordinatorTests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task TestImportCoordinator_BasicImport()
|
public async Task TestImportCoordinator_BasicImport()
|
||||||
{
|
{
|
||||||
using var catalog = new AssetCatalog(_dbPath);
|
var catalog = new AssetCatalog(_dbPath);
|
||||||
using var coordinator = new ImportCoordinator(catalog);
|
using var coordinator = new ImportCoordinator(catalog);
|
||||||
|
|
||||||
var assetGuid = Guid.NewGuid();
|
var assetGuid = Guid.NewGuid();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<Platforms>x64;x86;ARM64</Platforms>
|
<Platforms>x64;x86;ARM64</Platforms>
|
||||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
166
src/Test/Ghost.UnitTest/Graphics/ShaderLibraryTest.cs
Normal file
166
src/Test/Ghost.UnitTest/Graphics/ShaderLibraryTest.cs
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
using Ghost.Core;
|
||||||
|
using Ghost.Core.Graphics;
|
||||||
|
using Ghost.Graphics.RHI;
|
||||||
|
using Ghost.Graphics.Services;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
|
||||||
|
namespace Ghost.UnitTest.Graphics;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class ShaderLibraryTest
|
||||||
|
{
|
||||||
|
private class MockPipelineLibrary : IPipelineLibrary
|
||||||
|
{
|
||||||
|
public List<ulong> EvictedHashes { get; } = new();
|
||||||
|
|
||||||
|
public Result<Key128<PipelineState>> CreateComputePipeline(ref readonly ComputePSODesc desc) => Result<Key128<PipelineState>>.Failure();
|
||||||
|
public Result<Key128<PipelineState>> CreateGraphicsPipeline(ref readonly GraphicsPSODesc desc) => Result<Key128<PipelineState>>.Failure();
|
||||||
|
|
||||||
|
public void EvictStalePipelines(ulong compiledHash)
|
||||||
|
{
|
||||||
|
EvictedHashes.Add(compiledHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasPipelineStateObject(UInt128 key) => false;
|
||||||
|
public void SaveLibraryToDisk(string filePath) { }
|
||||||
|
public void BeginFrame(ulong submittedFrame) { }
|
||||||
|
public void EndFrame(ulong completedFrame) { }
|
||||||
|
public void Dispose() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MockShaderCompilationBridge : IShaderCompilationBridge
|
||||||
|
{
|
||||||
|
public List<(ulong id, int passIndex, Key64<ShaderVariant> variantKey, LocalKeywordSet keywordMask)> Requests { get; } = new();
|
||||||
|
public event Action<Key64<ShaderVariant>, ulong>? OnShaderVariantCompiled;
|
||||||
|
|
||||||
|
public void RequestCompilation(ulong shaderId, int passIndex, Key64<ShaderVariant> variantKey, LocalKeywordSet keywordMask)
|
||||||
|
{
|
||||||
|
Requests.Add((shaderId, passIndex, variantKey, keywordMask));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TriggerCompiled(Key64<ShaderVariant> variantKey, ulong newHash)
|
||||||
|
{
|
||||||
|
OnShaderVariantCompiled?.Invoke(variantKey, newHash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestInitialize]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
AllocationManager.Initialize(AllocationManagerDesc.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCleanup]
|
||||||
|
public void Cleanup()
|
||||||
|
{
|
||||||
|
AllocationManager.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public unsafe void TestInvalidateShaderCache_EvictsPipelinesAndClearsCache()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
using var shaderLibrary = new ShaderLibrary(null, "TestShaderCache");
|
||||||
|
var mockPipelineLibrary = new MockPipelineLibrary();
|
||||||
|
|
||||||
|
ulong testShaderId = 12345;
|
||||||
|
var testPassIndex = 0;
|
||||||
|
var variantKey = new Key64<ShaderVariant>(999);
|
||||||
|
|
||||||
|
// Create some dummy bytecode to cache
|
||||||
|
var fakeData = new byte[] { 1, 2, 3, 4 };
|
||||||
|
var expectedHash = 0UL;
|
||||||
|
|
||||||
|
fixed (byte* pData = fakeData)
|
||||||
|
{
|
||||||
|
var byteCode = new ShaderByteCode
|
||||||
|
{
|
||||||
|
pCode = pData,
|
||||||
|
size = (ulong)fakeData.Length
|
||||||
|
};
|
||||||
|
|
||||||
|
var byteCodes = new Span<ShaderByteCode>(ref byteCode);
|
||||||
|
|
||||||
|
// Compute hash that should be generated (only bytecode)
|
||||||
|
var dataSpan = new ReadOnlySpan<byte>(byteCode.pCode, (int)byteCode.size);
|
||||||
|
expectedHash = System.IO.Hashing.XxHash64.HashToUInt64(dataSpan);
|
||||||
|
|
||||||
|
// Act: Cache it
|
||||||
|
shaderLibrary.CacheCompiledResult(testShaderId, testPassIndex, variantKey, byteCodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify it was cached successfully
|
||||||
|
var cachedResult = shaderLibrary.GetCompiledCache(testShaderId, testPassIndex);
|
||||||
|
Assert.IsTrue(cachedResult.IsSuccess, "Shader should be cached");
|
||||||
|
Assert.AreEqual(expectedHash, cachedResult.Value.compiledHash);
|
||||||
|
|
||||||
|
// Act: Invalidate
|
||||||
|
shaderLibrary.InvalidateShaderCache(testShaderId, mockPipelineLibrary);
|
||||||
|
|
||||||
|
// Assert: EvictStalePipelines should be called
|
||||||
|
Assert.HasCount(1, mockPipelineLibrary.EvictedHashes);
|
||||||
|
Assert.AreEqual(expectedHash, mockPipelineLibrary.EvictedHashes[0]);
|
||||||
|
|
||||||
|
// Assert: Cache should be cleared
|
||||||
|
var cachedResultAfter = shaderLibrary.GetCompiledCache(testShaderId, testPassIndex);
|
||||||
|
Assert.IsFalse(cachedResultAfter.IsSuccess, "Cache should be invalidated");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestGetCompiledHash_TriggersCompilationRequest()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockBridge = new MockShaderCompilationBridge();
|
||||||
|
using var shaderLibrary = new ShaderLibrary(mockBridge, "TestShaderCache");
|
||||||
|
var testShaderId = 555UL;
|
||||||
|
var passIndex = 1;
|
||||||
|
var variantKey = new Key64<ShaderVariant>(777);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = shaderLibrary.GetCompiledHash(testShaderId, passIndex, variantKey);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsFalse(result.IsSuccess);
|
||||||
|
Assert.AreEqual(Error.NotFound, result.Error);
|
||||||
|
Assert.HasCount(1, mockBridge.Requests);
|
||||||
|
Assert.AreEqual(testShaderId, mockBridge.Requests[0].id);
|
||||||
|
Assert.AreEqual(passIndex, mockBridge.Requests[0].passIndex);
|
||||||
|
Assert.AreEqual(variantKey, mockBridge.Requests[0].variantKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestOnVariantCompiled_UpdatesHashCache()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var mockBridge = new MockShaderCompilationBridge();
|
||||||
|
using var shaderLibrary = new ShaderLibrary(mockBridge, "TestShaderCache");
|
||||||
|
var variantKey = new Key64<ShaderVariant>(123);
|
||||||
|
ulong newHash = 0xABCDE;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
mockBridge.TriggerCompiled(variantKey, newHash);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var result = shaderLibrary.GetCompiledHash(0, 0, variantKey);
|
||||||
|
Assert.IsTrue(result.IsSuccess);
|
||||||
|
Assert.AreEqual(newHash, result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestGetCompiledCache_HandlesIndexOutOfBounds()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
using var shaderLibrary = new ShaderLibrary(null, "TestShaderCache");
|
||||||
|
ulong testShaderId = 111;
|
||||||
|
var scope = AllocationManager.CreateStackScope();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = shaderLibrary.GetCompiledCache(testShaderId, 99);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsFalse(result.IsSuccess);
|
||||||
|
Assert.AreEqual(Error.NotFound, result.Error);
|
||||||
|
|
||||||
|
scope.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,12 +5,17 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_Editor|AnyCPU'">
|
||||||
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -4,12 +4,17 @@
|
|||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_Editor|AnyCPU'">
|
||||||
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -5,12 +5,17 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_Editor|AnyCPU'">
|
||||||
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -5,12 +5,17 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_Editor|AnyCPU'">
|
||||||
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -5,12 +5,17 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_Editor|AnyCPU'">
|
||||||
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -5,12 +5,17 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_Editor|AnyCPU'">
|
||||||
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<Configurations>Debug;Release;Debug_Editor;Release_Editor</Configurations>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
BIN
src/build_log.txt
Normal file
BIN
src/build_log.txt
Normal file
Binary file not shown.
34
src/test_generator.cs
Normal file
34
src/test_generator.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
class Program {
|
||||||
|
static void Main() {
|
||||||
|
string definedSymbol = "__FOO_HLSL__";
|
||||||
|
string infoName = "Foo";
|
||||||
|
string codeBuilder = " float4 myColor;\n";
|
||||||
|
string fieldsBuilder = " // fields";
|
||||||
|
var code = $$"""
|
||||||
|
// <auto-generated/>
|
||||||
|
|
||||||
|
namespace MyNamespace
|
||||||
|
{
|
||||||
|
[global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 4)]
|
||||||
|
public partial struct {{infoName}}
|
||||||
|
{
|
||||||
|
#if GHOST_EDITOR
|
||||||
|
public const string HLSL_SOURCE = @"
|
||||||
|
#ifndef {{definedSymbol}}
|
||||||
|
#define {{definedSymbol}}
|
||||||
|
struct {{infoName}}
|
||||||
|
{
|
||||||
|
{{codeBuilder}}
|
||||||
|
};
|
||||||
|
#endif // {{definedSymbol}}";
|
||||||
|
|
||||||
|
{{fieldsBuilder}}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Console.WriteLine(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user