feat(render): refactor pipeline & shader system for DX12 WG
Major refactor of render pipeline and shader system: - Replaced legacy shader properties with source generator and attribute-based HLSL struct generation. - Introduced ShaderPropertiesRegistry for runtime property layout/code registration. - Added modular IRenderPipeline, IRenderPipelineSettings, and IRenderPayload interfaces. - Implemented GhostRenderPipeline and ECS-driven GPUScene management. - Added experimental DirectX 12 Work Graph support. - Refactored shader compilation, variant hashing, and caching. - Updated APIs for consistency and improved codegen for registration. These changes modernize the rendering infrastructure for advanced features like work graphs and dynamic pipelines. BREAKING CHANGE: Shader DSL, pipeline, and property APIs have changed. Existing shaders and pipeline integrations must be updated.
This commit is contained in:
@@ -21,7 +21,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Misaki.HighPerformance" Version="1.0.7" />
|
||||
<PackageReference Include="Misaki.HighPerformance.Jobs" Version="1.5.8" />
|
||||
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.10">
|
||||
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.6.11">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -33,21 +33,14 @@ public struct KeywordsGroup
|
||||
public List<string> keywords;
|
||||
}
|
||||
|
||||
public struct PropertyDescriptor
|
||||
{
|
||||
public string name;
|
||||
public int offset;
|
||||
public int size;
|
||||
public ShaderPropertyType type;
|
||||
|
||||
public object? defaultValue;
|
||||
}
|
||||
|
||||
public struct PassDescriptor
|
||||
{
|
||||
public string identifier;
|
||||
public ShaderDescriptor shader;
|
||||
|
||||
public ulong identifier;
|
||||
public string name;
|
||||
|
||||
public string? hlsl;
|
||||
public ShaderEntryPoint taskShader;
|
||||
public ShaderEntryPoint meshShader;
|
||||
public ShaderEntryPoint pixelShader;
|
||||
@@ -55,54 +48,23 @@ public struct PassDescriptor
|
||||
public string[] includes;
|
||||
public KeywordsGroup[] keywords;
|
||||
public PipelineState localPipeline;
|
||||
public string? hlsl;
|
||||
}
|
||||
|
||||
public class ShaderDescriptor
|
||||
{
|
||||
public string name = string.Empty;
|
||||
public int cbufferSize;
|
||||
public PropertyDescriptor[] globalProperties = null!;
|
||||
public PropertyDescriptor[] properties = null!;
|
||||
public PassDescriptor[] passes = null!;
|
||||
public string? hlsl;
|
||||
public string propertiesCode = string.Empty;
|
||||
public uint propertyBufferSize;
|
||||
public PassDescriptor[] passes = Array.Empty<PassDescriptor>();
|
||||
}
|
||||
|
||||
public static class ShaderDescriptorExtensions
|
||||
public class ComputeShaderDescriptor
|
||||
{
|
||||
public static int GetSize(this ShaderPropertyType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
ShaderPropertyType.Float
|
||||
or ShaderPropertyType.Int
|
||||
or ShaderPropertyType.UInt
|
||||
or ShaderPropertyType.Bool => 4,
|
||||
|
||||
ShaderPropertyType.Float2
|
||||
or ShaderPropertyType.Int2
|
||||
or ShaderPropertyType.UInt2
|
||||
or ShaderPropertyType.Bool2 => 8,
|
||||
|
||||
ShaderPropertyType.Float3
|
||||
or ShaderPropertyType.Int3
|
||||
or ShaderPropertyType.UInt3
|
||||
or ShaderPropertyType.Bool3 => 12,
|
||||
|
||||
ShaderPropertyType.Float4
|
||||
or ShaderPropertyType.Int4
|
||||
or ShaderPropertyType.UInt4
|
||||
or ShaderPropertyType.Bool4 => 16,
|
||||
|
||||
ShaderPropertyType.Float4x4 => 64,
|
||||
|
||||
ShaderPropertyType.Texture2D
|
||||
or ShaderPropertyType.Texture3D
|
||||
or ShaderPropertyType.TextureCube
|
||||
or ShaderPropertyType.Texture2DArray
|
||||
or ShaderPropertyType.TextureCubeArray
|
||||
or ShaderPropertyType.Sampler => 4, // Bindless resource use uint32
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
public string name = string.Empty;
|
||||
public string propertiesCode = string.Empty;
|
||||
public uint propertyBufferSize;
|
||||
public ShaderEntryPoint entryPoint;
|
||||
public string[] defines = Array.Empty<string>();
|
||||
public string[] includes = Array.Empty<string>();
|
||||
public KeywordsGroup[] keywords = Array.Empty<KeywordsGroup>();
|
||||
}
|
||||
|
||||
41
src/Runtime/Ghost.Core/Graphics/ShaderPropertiesRegistry.cs
Normal file
41
src/Runtime/Ghost.Core/Graphics/ShaderPropertiesRegistry.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
namespace Ghost.Core.Graphics;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Struct)]
|
||||
public class GenerateShaderPropertyAttribute : Attribute
|
||||
{
|
||||
public GenerateShaderPropertyAttribute(string shaderName, string? name = null)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class GenerateAsHLSLTypeAttribute : Attribute
|
||||
{
|
||||
public GenerateAsHLSLTypeAttribute(string hlslTypeName)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
public struct ShaderPropertyInfo
|
||||
{
|
||||
public string shaderName;
|
||||
public string code;
|
||||
public uint size;
|
||||
}
|
||||
|
||||
public static class ShaderPropertiesRegistry
|
||||
{
|
||||
private static readonly Dictionary<string, ShaderPropertyInfo> s_nameToCode = new Dictionary<string, ShaderPropertyInfo>(StringComparer.Ordinal);
|
||||
|
||||
public static void Register(string name, string code, uint size)
|
||||
{
|
||||
s_nameToCode[name] = new ShaderPropertyInfo { shaderName = name, code = code, size = size };
|
||||
}
|
||||
|
||||
public static bool TryGetCode(string name, out ShaderPropertyInfo info)
|
||||
{
|
||||
return s_nameToCode.TryGetValue(name, out info);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -186,6 +186,9 @@ public readonly struct Key64<T> : IEquatable<Key64<T>>
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
|
||||
public static implicit operator ulong(Key64<T> key) => key.Value;
|
||||
public static implicit operator Key64<T>(ulong value) => new Key64<T>(value);
|
||||
}
|
||||
|
||||
public readonly struct Key128<T> : IEquatable<Key128<T>>
|
||||
@@ -239,4 +242,7 @@ public readonly struct Key128<T> : IEquatable<Key128<T>>
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
|
||||
public static implicit operator UInt128(Key128<T> key) => key.Value;
|
||||
public static implicit operator Key128<T>(UInt128 value) => new Key128<T>(value);
|
||||
}
|
||||
@@ -10,13 +10,13 @@ public static class Hash
|
||||
private const ulong _PRIME4 = 0x9e3779b97f4a7c15ul; // Golden Ratio
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Hash64(ulong a, ulong b)
|
||||
public static ulong Combine64(ulong a, ulong b)
|
||||
{
|
||||
return a ^ (b * _PRIME4 + (a << 6) + (a >> 2));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Hash64(ulong a, ulong b, ulong c)
|
||||
public static ulong Combine64(ulong a, ulong b, ulong c)
|
||||
{
|
||||
var h1 = a * _PRIME1;
|
||||
var h2 = b * _PRIME2;
|
||||
@@ -29,7 +29,7 @@ public static class Hash
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong Hash64(ulong a, ulong b, ulong c, ulong d)
|
||||
public static ulong Combine64(ulong a, ulong b, ulong c, ulong d)
|
||||
{
|
||||
var h1 = a * _PRIME1;
|
||||
var h2 = b * _PRIME2;
|
||||
|
||||
@@ -39,4 +39,4 @@ public sealed partial class EngineCore : IDisposable
|
||||
_renderSystem.Dispose();
|
||||
_jobScheduler.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
|
||||
<ProjectReference Include="..\Ghost.Entities\Ghost.Entities.csproj" />
|
||||
<!--<ProjectReference Include="..\Ghost.Generator\Ghost.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />-->
|
||||
<ProjectReference Include="..\Ghost.Generator\Ghost.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
<ProjectReference Include="..\Ghost.Graphics\Ghost.Graphics.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
111
src/Runtime/Ghost.Engine/RenderPipeline/GPUScene.cs
Normal file
111
src/Runtime/Ghost.Engine/RenderPipeline/GPUScene.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ghost.Engine.RenderPipeline;
|
||||
|
||||
internal unsafe class GPUScene : IDisposable
|
||||
{
|
||||
private readonly IResourceAllocator _resourceAllocator;
|
||||
private readonly IResourceDatabase _resourceDatabase;
|
||||
|
||||
private Handle<GPUBuffer> _sceneBuffer;
|
||||
private uint _instanceCount;
|
||||
private uint _capacity;
|
||||
|
||||
private uint _requiredResize;
|
||||
private bool _disposed;
|
||||
|
||||
internal GPUScene(IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase, uint initialCount)
|
||||
{
|
||||
_resourceAllocator = resourceAllocator;
|
||||
_resourceDatabase = resourceDatabase;
|
||||
|
||||
var bufferDesc = new BufferDesc
|
||||
{
|
||||
Size = initialCount * (ulong)sizeof(InstanceData),
|
||||
Stride = (uint)sizeof(InstanceData),
|
||||
Usage = BufferUsage.Structured | BufferUsage.UnorderedAccess | BufferUsage.ShaderResource,
|
||||
HeapType = HeapType.Default,
|
||||
};
|
||||
|
||||
_sceneBuffer = _resourceAllocator.CreateBuffer(in bufferDesc, "SceneBuffer");
|
||||
Debug.Assert(_sceneBuffer.IsValid, "Failed to create GPUScene buffer.");
|
||||
|
||||
_capacity = initialCount;
|
||||
}
|
||||
|
||||
~GPUScene()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
// NOTE: This is not thread safe.
|
||||
public void ResizeIfNeeded(ICommandBuffer cmd)
|
||||
{
|
||||
if (_requiredResize == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var newCapacity = _capacity * 2;
|
||||
newCapacity = Math.Max(newCapacity, _capacity + _requiredResize);
|
||||
|
||||
var newBufferDesc = new BufferDesc
|
||||
{
|
||||
Size = (ulong)newCapacity * (ulong)sizeof(InstanceData),
|
||||
Stride = (uint)sizeof(InstanceData),
|
||||
Usage = BufferUsage.Structured | BufferUsage.UnorderedAccess | BufferUsage.ShaderResource,
|
||||
HeapType = HeapType.Default,
|
||||
};
|
||||
|
||||
var newBuffer = _resourceAllocator.CreateBuffer(in newBufferDesc, "SceneBuffer_Resized");
|
||||
Debug.Assert(newBuffer.IsValid);
|
||||
|
||||
// Copy existing data to the new buffer
|
||||
cmd.CopyBuffer(newBuffer, _sceneBuffer, 0, 0, (ulong)_instanceCount * (ulong)sizeof(InstanceData));
|
||||
|
||||
// Replace old buffer with the new one
|
||||
_resourceDatabase.ReleaseResource(_sceneBuffer.AsResource());
|
||||
_sceneBuffer = newBuffer;
|
||||
_capacity = newCapacity;
|
||||
|
||||
_requiredResize = 0;
|
||||
}
|
||||
|
||||
public uint AddInstance()
|
||||
{
|
||||
if (Volatile.Read(ref _instanceCount) >= _capacity)
|
||||
{
|
||||
Interlocked.Increment(ref _requiredResize);
|
||||
}
|
||||
|
||||
var index = Interlocked.Increment(ref _instanceCount);
|
||||
return index;
|
||||
}
|
||||
|
||||
public uint RemoveInstance(uint index)
|
||||
{
|
||||
if (index < 0 || index >= _capacity)
|
||||
{
|
||||
return ~0u;
|
||||
}
|
||||
|
||||
// Return the last index. We will swap the last instance data with the removed index on gpu to keep the buffer compact.
|
||||
var last = Interlocked.Decrement(ref _instanceCount);
|
||||
return last;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_resourceDatabase.ReleaseResource(_sceneBuffer.AsResource());
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Ghost.Graphics;
|
||||
using Ghost.Graphics.Core;
|
||||
|
||||
namespace Ghost.Engine.RenderPipeline;
|
||||
|
||||
internal class GhostRenderPipeline : IRenderPipeline
|
||||
{
|
||||
private readonly RenderSystem _renderSystem;
|
||||
|
||||
private readonly GPUScene _gpuScene;
|
||||
|
||||
public GPUScene GPUScene => _gpuScene;
|
||||
|
||||
public GhostRenderPipeline(RenderSystem renderSystem)
|
||||
{
|
||||
_renderSystem = renderSystem;
|
||||
_gpuScene = new GPUScene(renderSystem.GraphicsEngine.ResourceAllocator, renderSystem.GraphicsEngine.ResourceDatabase, 102_400u); // 102.4k objects should be enough for now
|
||||
}
|
||||
|
||||
public void Render(RenderContext ctx, int frameIndex, IRenderPayload payload)
|
||||
{
|
||||
var ghostPayload = (GhostRenderPayload)payload;
|
||||
|
||||
var resourceManager = _renderSystem.ResourceManager;
|
||||
var resourceDatabase = _renderSystem.GraphicsEngine.ResourceDatabase;
|
||||
|
||||
foreach (ref readonly var request in ghostPayload.RenderRequests)
|
||||
{
|
||||
if (!RenderPipelineUtility.GetViewAndProjectionMatrices(_renderSystem, in request, out var view, out var projection, out var screenSize))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using Ghost.Engine.Components;
|
||||
using Ghost.Graphics;
|
||||
using Ghost.Graphics.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Ghost.Engine.RenderPipeline;
|
||||
|
||||
internal sealed class GhostRenderPayload : IRenderPayload
|
||||
{
|
||||
public struct AddInstanceRequest
|
||||
{
|
||||
public MeshInstance meshInstance;
|
||||
public float4x4 localToWorld;
|
||||
public uint instanceId;
|
||||
}
|
||||
|
||||
public struct RemoveInstanceRequest
|
||||
{
|
||||
public uint instanceId;
|
||||
public uint swapWithInstanceId;
|
||||
}
|
||||
|
||||
private readonly GhostRenderPipeline _renderPipeline;
|
||||
|
||||
private UnsafeList<RenderRequest> _renderRequests;
|
||||
|
||||
// TODO: Consider using a more efficient data structure for these queues, such as a lock-free ring buffer or a custom concurrent queue implementation.
|
||||
private readonly ConcurrentQueue<AddInstanceRequest> _addRequest;
|
||||
private readonly ConcurrentQueue<RemoveInstanceRequest> _removeRequest;
|
||||
|
||||
public ReadOnlySpan<RenderRequest> RenderRequests => _renderRequests;
|
||||
|
||||
public ConcurrentQueue<AddInstanceRequest> AddRequest => _addRequest;
|
||||
public ConcurrentQueue<RemoveInstanceRequest> RemoveRequest => _removeRequest;
|
||||
|
||||
public GhostRenderPayload(GhostRenderPipeline renderPipeline)
|
||||
{
|
||||
_renderPipeline = renderPipeline;
|
||||
|
||||
_renderRequests = new UnsafeList<RenderRequest>(4, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
|
||||
_addRequest = new ConcurrentQueue<AddInstanceRequest>();
|
||||
_removeRequest = new ConcurrentQueue<RemoveInstanceRequest>();
|
||||
}
|
||||
|
||||
// NOTE: This is not thread safe.
|
||||
public void AddRenderRequest(ref readonly RenderRequest renderRequest)
|
||||
{
|
||||
_renderRequests.Add(renderRequest);
|
||||
}
|
||||
|
||||
public uint AddInstance(float4x4 ltw, ref readonly MeshInstance meshInstance)
|
||||
{
|
||||
var index = _renderPipeline.GPUScene.AddInstance();
|
||||
_addRequest.Enqueue(new AddInstanceRequest { instanceId = index, localToWorld = ltw, meshInstance = meshInstance });
|
||||
return index;
|
||||
}
|
||||
|
||||
public void RemoveInstance(uint instanceId)
|
||||
{
|
||||
var swapWithInstanceId = _renderPipeline.GPUScene.RemoveInstance(instanceId);
|
||||
if (swapWithInstanceId != ~0u)
|
||||
{
|
||||
_removeRequest.Enqueue(new RemoveInstanceRequest { instanceId = instanceId, swapWithInstanceId = swapWithInstanceId });
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_renderRequests.Clear();
|
||||
_addRequest.Clear();
|
||||
_removeRequest.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_renderRequests.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
internal class GhostRenderPipelineSettings : IRenderPipelineSettings
|
||||
{
|
||||
public IRenderPipeline CreatePipeline(RenderSystem renderSystem)
|
||||
{
|
||||
return new GhostRenderPipeline(renderSystem);
|
||||
}
|
||||
|
||||
public IRenderPayload CreatePayload(RenderSystem renderSystem, IRenderPipeline _renderPipeline)
|
||||
{
|
||||
return new GhostRenderPayload((GhostRenderPipeline)_renderPipeline);
|
||||
}
|
||||
}
|
||||
52
src/Runtime/Ghost.Engine/Systems/AddGPUInstanceSystem.cs
Normal file
52
src/Runtime/Ghost.Engine/Systems/AddGPUInstanceSystem.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Engine.Components;
|
||||
using Ghost.Engine.RenderPipeline;
|
||||
using Ghost.Entities;
|
||||
using Ghost.Graphics;
|
||||
using Misaki.HighPerformance.Utilities;
|
||||
|
||||
namespace Ghost.Engine.Systems;
|
||||
|
||||
[RenderPipelineSystem<GhostRenderPipelineSettings>]
|
||||
internal class AddGPUInstanceSystem : SystemBase
|
||||
{
|
||||
private RenderSystem _renderSystem = null!;
|
||||
|
||||
private Identifier<EntityQuery> _meshInstanceQueryID;
|
||||
|
||||
protected override void OnInitialize(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
_renderSystem = systemAPI.World.GetService<RenderSystem>();
|
||||
|
||||
_meshInstanceQueryID = QueryBuilder.Create()
|
||||
.WithAll<MeshInstance, LocalToWorld>()
|
||||
.WithAbsent<GPUInstanceRef>()
|
||||
.Build(systemAPI.World, true);
|
||||
|
||||
RequireQueryForUpdate(_meshInstanceQueryID);
|
||||
}
|
||||
|
||||
protected override void OnUpdate(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
var payload = (GhostRenderPayload)_renderSystem.GetCurrentFramePayload();
|
||||
|
||||
ref var meshInstanceQuery = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_meshInstanceQueryID);
|
||||
|
||||
foreach (var chunk in meshInstanceQuery.GetChunkIterator())
|
||||
{
|
||||
var meshInstances = chunk.GetComponentData<MeshInstance>();
|
||||
var localToWorlds = chunk.GetComponentData<LocalToWorld>();
|
||||
var entities = chunk.GetEntities();
|
||||
|
||||
for (var i = 0; i < chunk.EntityCount; i++)
|
||||
{
|
||||
ref readonly var meshInstance = ref meshInstances.GetElementUnsafe(i);
|
||||
var localToWorld = localToWorlds.GetElementUnsafe(i);
|
||||
var entity = entities.GetElementUnsafe(i);
|
||||
|
||||
var index = payload.AddInstance(localToWorld.matrix, in meshInstance);
|
||||
systemAPI.World.EntityCommandBuffer.AddComponent(entity, new GPUInstanceRef { gpuSceneIndex = index });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src/Runtime/Ghost.Engine/Systems/RemoveGPUInstanceSystem.cs
Normal file
50
src/Runtime/Ghost.Engine/Systems/RemoveGPUInstanceSystem.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Engine.Components;
|
||||
using Ghost.Engine.RenderPipeline;
|
||||
using Ghost.Entities;
|
||||
using Ghost.Graphics;
|
||||
using Misaki.HighPerformance.Utilities;
|
||||
|
||||
namespace Ghost.Engine.Systems;
|
||||
|
||||
[RenderPipelineSystem<GhostRenderPipelineSettings>]
|
||||
internal class RemoveGPUInstanceSystem : SystemBase
|
||||
{
|
||||
private RenderSystem _renderSystem = null!;
|
||||
|
||||
private Identifier<EntityQuery> _gpuInstanceQueryID;
|
||||
|
||||
protected override void OnInitialize(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
_renderSystem = systemAPI.World.GetService<RenderSystem>();
|
||||
|
||||
_gpuInstanceQueryID = QueryBuilder.Create()
|
||||
.WithAll<GPUInstanceRef>()
|
||||
.WithAbsent<MeshInstance>()
|
||||
.Build(systemAPI.World, true);
|
||||
|
||||
RequireQueryForUpdate(_gpuInstanceQueryID);
|
||||
}
|
||||
|
||||
protected override void OnUpdate(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
var payload = (GhostRenderPayload)_renderSystem.GetCurrentFramePayload();
|
||||
|
||||
ref var gpuInstanceQuery = ref systemAPI.World.ComponentManager.GetEntityQueryReference(_gpuInstanceQueryID);
|
||||
|
||||
foreach (var chunk in gpuInstanceQuery.GetChunkIterator())
|
||||
{
|
||||
var gpuInstanceRefs = chunk.GetComponentData<GPUInstanceRef>();
|
||||
var entities = chunk.GetEntities();
|
||||
|
||||
for (var i = 0; i < chunk.EntityCount; i++)
|
||||
{
|
||||
var gpuInstance = gpuInstanceRefs.GetElementUnsafe(i);
|
||||
var entity = entities.GetElementUnsafe(i);
|
||||
|
||||
payload.RemoveInstance(gpuInstance.gpuSceneIndex);
|
||||
systemAPI.World.EntityCommandBuffer.RemoveComponent<GPUInstanceRef>(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using Ghost.Entities;
|
||||
using Ghost.Graphics.RenderPipeline;
|
||||
using Ghost.Graphics;
|
||||
|
||||
namespace Ghost.Engine.Systems;
|
||||
|
||||
@@ -17,20 +17,20 @@ public class RenderPipelineSystemAttribute<T> : RenderPipelineSystemAttribute
|
||||
|
||||
public static class RenderPipelineSystemRegistry
|
||||
{
|
||||
private static readonly Dictionary<Type, List<Func<ISystem>>> s_renderPipelineSystems = new();
|
||||
private static readonly Dictionary<nint, List<Func<ISystem>>> s_renderPipelineSystems = new();
|
||||
|
||||
public static void RegisterRenderPipelineSystem(Type settingsType, Func<ISystem> systemFactory)
|
||||
public static void RegisterRenderPipelineSystem(nint settingsType, Func<ISystem> systemFactory)
|
||||
{
|
||||
if (!s_renderPipelineSystems.TryGetValue(settingsType, out var systems))
|
||||
{
|
||||
systems = new List<Func<ISystem>>();
|
||||
systems = new List<Func<ISystem>>(4);
|
||||
s_renderPipelineSystems[settingsType] = systems;
|
||||
}
|
||||
|
||||
systems.Add(systemFactory);
|
||||
}
|
||||
|
||||
internal static IEnumerable<Func<ISystem>> GetRenderPipelineSystems(Type settingsType)
|
||||
internal static IEnumerable<Func<ISystem>> GetRenderPipelineSystems(nint settingsType)
|
||||
{
|
||||
if (s_renderPipelineSystems.TryGetValue(settingsType, out var systems))
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
@@ -24,7 +25,7 @@ public interface ISystem
|
||||
|
||||
public abstract class SystemBase : ISystem
|
||||
{
|
||||
private List<int>? _requiredQueries;
|
||||
private UnsafeList<int> _requiredQueries;
|
||||
|
||||
public World World
|
||||
{
|
||||
@@ -38,13 +39,14 @@ public abstract class SystemBase : ISystem
|
||||
|
||||
private bool ShouldUpdate()
|
||||
{
|
||||
if (_requiredQueries == null || _requiredQueries.Count == 0)
|
||||
if (!_requiredQueries.IsCreated || _requiredQueries.Count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var queryID in _requiredQueries)
|
||||
for (var i = 0; i < _requiredQueries.Count; i++)
|
||||
{
|
||||
var queryID = _requiredQueries[i];
|
||||
ref var query = ref World.ComponentManager.GetEntityQueryReference(new Identifier<EntityQuery>(queryID));
|
||||
if (query.CalculateEntityCount() == 0)
|
||||
{
|
||||
@@ -57,7 +59,11 @@ public abstract class SystemBase : ISystem
|
||||
|
||||
protected void RequireQueryForUpdate(Identifier<EntityQuery> queryID)
|
||||
{
|
||||
_requiredQueries ??= new List<int>(4);
|
||||
if (!_requiredQueries.IsCreated)
|
||||
{
|
||||
_requiredQueries = new UnsafeList<int>(4, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
|
||||
}
|
||||
|
||||
_requiredQueries.Add(queryID.Value);
|
||||
}
|
||||
|
||||
@@ -89,6 +95,7 @@ public abstract class SystemBase : ISystem
|
||||
|
||||
void ISystem.Cleanup(ref readonly SystemAPI systemAPI)
|
||||
{
|
||||
_requiredQueries.Dispose();
|
||||
OnCleanup(in systemAPI);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,30 +60,32 @@ namespace Ghost.Generator
|
||||
return;
|
||||
}
|
||||
|
||||
var name = $"g_component_registration";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append($@"
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
internal static class {name}
|
||||
{{
|
||||
[global::System.Runtime.CompilerServices.ModuleInitializer]
|
||||
public static void RegisterIComponentTypes()
|
||||
{{");
|
||||
|
||||
foreach (var symbol in components.Distinct(SymbolEqualityComparer.Default))
|
||||
{
|
||||
if (symbol is null) continue;
|
||||
if (symbol is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sb.Append($@"
|
||||
global::Ghost.Entities.ComponentRegistry.GetOrRegisterComponentID<{symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>();");
|
||||
sb.AppendLine($@" global::Ghost.Entities.ComponentRegistry.GetOrRegisterComponentID<{symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>();");
|
||||
}
|
||||
|
||||
sb.Append(@"
|
||||
}
|
||||
}");
|
||||
var typeName = $"g_component_registration";
|
||||
var code = $@"// <auto-generated/>
|
||||
|
||||
context.AddSource($"{name}.gen.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
internal static partial class {typeName}
|
||||
{{
|
||||
[global::System.Runtime.CompilerServices.ModuleInitializer]
|
||||
internal static void RegisterIComponentTypes()
|
||||
{{
|
||||
{sb}
|
||||
}}
|
||||
}}";
|
||||
|
||||
context.AddSource($"{typeName}.gen.cs", code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>14.0</LangVersion>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
215
src/Runtime/Ghost.Generator/ShaderPropertiesGenerator.cs
Normal file
215
src/Runtime/Ghost.Generator/ShaderPropertiesGenerator.cs
Normal file
@@ -0,0 +1,215 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Ghost.Generator
|
||||
{
|
||||
[Generator]
|
||||
internal class ShaderPropertiesGenerator : IIncrementalGenerator
|
||||
{
|
||||
private class ShaderStructInfo
|
||||
{
|
||||
public string ShaderName
|
||||
{
|
||||
set; get;
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public INamedTypeSymbol TypeSymbol
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
var shaderProperties = context.SyntaxProvider
|
||||
.ForAttributeWithMetadataName(
|
||||
"Ghost.Core.Graphics.GenerateShaderPropertyAttribute",
|
||||
(n, ct) => n is Microsoft.CodeAnalysis.CSharp.Syntax.StructDeclarationSyntax,
|
||||
(ctx, ct) =>
|
||||
{
|
||||
var structSymbol = (INamedTypeSymbol)ctx.TargetSymbol;
|
||||
|
||||
var attributeData = ctx.Attributes.FirstOrDefault(ad => ad.AttributeClass?.ToDisplayString() == "Ghost.Core.Graphics.GenerateShaderPropertyAttribute");
|
||||
if (attributeData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ShaderStructInfo
|
||||
{
|
||||
ShaderName = attributeData.ConstructorArguments[0].Value as string,
|
||||
Name = attributeData.ConstructorArguments[1].Value as string ?? structSymbol.Name,
|
||||
TypeSymbol = structSymbol
|
||||
};
|
||||
})
|
||||
.Where(x => x != null)
|
||||
.Collect();
|
||||
|
||||
context.RegisterSourceOutput(shaderProperties, GenerateHlslPropertyStruct);
|
||||
}
|
||||
|
||||
private void GenerateHlslPropertyStruct(SourceProductionContext context, ImmutableArray<ShaderStructInfo> array)
|
||||
{
|
||||
if (array.IsDefaultOrEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var codeBuilder = new StringBuilder();
|
||||
var registerBuilder = new StringBuilder();
|
||||
|
||||
foreach (var info in array)
|
||||
{
|
||||
if (string.IsNullOrEmpty(info.ShaderName))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor(
|
||||
"GSHADER001",
|
||||
"Invalid shader name",
|
||||
$"Shader name for struct '{info.TypeSymbol.Name}' cannot be null or empty.",
|
||||
"ShaderGeneration",
|
||||
DiagnosticSeverity.Error,
|
||||
isEnabledByDefault: true), info.TypeSymbol.Locations.FirstOrDefault()));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!info.TypeSymbol.IsUnmanagedType)
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor(
|
||||
"GSHADER002",
|
||||
"Unsupported struct type",
|
||||
$"Struct '{info.TypeSymbol.Name}' is not an unmanaged type and cannot be used for HLSL generation.",
|
||||
"ShaderGeneration",
|
||||
DiagnosticSeverity.Error,
|
||||
isEnabledByDefault: true), info.TypeSymbol.Locations.FirstOrDefault()));
|
||||
continue;
|
||||
}
|
||||
|
||||
var definedSymbol = $"__{info.Name.ToUpper()}_G_HLSL";
|
||||
|
||||
var fields = info.TypeSymbol.GetMembers().OfType<IFieldSymbol>();
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if (field.IsStatic || field.IsConst)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var hlslTypeAttribute = field.GetAttributes().FirstOrDefault(ad => ad.AttributeClass?.ToDisplayString() == "Ghost.Engine.Utilities.GenerateAsHLSLTypeAttribute");
|
||||
var hlslType = string.Empty;
|
||||
|
||||
if (hlslTypeAttribute == null)
|
||||
{
|
||||
switch (field.Type.SpecialType)
|
||||
{
|
||||
case SpecialType.System_Single:
|
||||
hlslType = "float";
|
||||
break;
|
||||
case SpecialType.System_Int32:
|
||||
hlslType = "int";
|
||||
break;
|
||||
case SpecialType.System_UInt32:
|
||||
hlslType = "uint";
|
||||
break;
|
||||
default:
|
||||
var typeName = field.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||
switch (typeName)
|
||||
{
|
||||
case "global::System.Numerics.Vector2":
|
||||
hlslType = "float2";
|
||||
break;
|
||||
case "global::System.Numerics.Vector3":
|
||||
hlslType = "float3";
|
||||
break;
|
||||
case "global::System.Numerics.Vector4":
|
||||
hlslType = "float4";
|
||||
break;
|
||||
case "global::System.Numerics.Matrix3x3":
|
||||
hlslType = "float3x3";
|
||||
break;
|
||||
case "global::System.Numerics.Matrix4x4":
|
||||
hlslType = "float4x4";
|
||||
break;
|
||||
case "global::System.Numerics.Quaternion" or "global::Misaki.HighPerformance.Mathematics.quaternion":
|
||||
hlslType = "float4";
|
||||
break;
|
||||
case var _ when typeName.StartsWith("global::Misaki.HighPerformance.Mathematics."):
|
||||
hlslType = typeName.Substring("global::Misaki.HighPerformance.Mathematics.".Length);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
hlslType = hlslTypeAttribute.ConstructorArguments[0].Value as string;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(hlslType))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor(
|
||||
"GSHADER003",
|
||||
"Unsupported field type",
|
||||
$"Field '{field.Name}' in struct '{info.TypeSymbol.Name}' has unsupported type '{field.Type.ToDisplayString()}' for HLSL generation.",
|
||||
"ShaderGeneration",
|
||||
DiagnosticSeverity.Error,
|
||||
isEnabledByDefault: true), field.Locations.FirstOrDefault()));
|
||||
continue;
|
||||
}
|
||||
|
||||
codeBuilder.AppendLine($" {hlslType} {field.Name};");
|
||||
}
|
||||
|
||||
var code = $@"// <auto-generated/>
|
||||
|
||||
namespace {info.TypeSymbol.ContainingNamespace.ToDisplayString()}
|
||||
{{
|
||||
[global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 4)]
|
||||
{info.TypeSymbol.DeclaredAccessibility.ToString().ToLower()} partial struct {info.TypeSymbol.Name}
|
||||
{{
|
||||
public const string HLSL_SOURCE = @""
|
||||
# ifndef {definedSymbol}
|
||||
# define {definedSymbol}
|
||||
struct {info.Name}
|
||||
{{
|
||||
{codeBuilder}
|
||||
}};
|
||||
# endif // {definedSymbol}"";
|
||||
}}
|
||||
}}";
|
||||
|
||||
context.AddSource($"{info.TypeSymbol.Name}_HLSL.gen.cs", code);
|
||||
codeBuilder.Clear();
|
||||
|
||||
var typeFullName = info.TypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||
registerBuilder.AppendLine($@" global::Ghost.Core.Graphics.ShaderPropertiesRegistry.Register(""{info.ShaderName}"", {typeFullName}.HLSL_SOURCE, (uint)sizeof({typeFullName}));");
|
||||
}
|
||||
|
||||
var registerTypeName = "g_shaderproperty_registeration";
|
||||
var registerCode = $@"// <auto-generated/>
|
||||
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
internal static partial class {registerTypeName}
|
||||
{{
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
[global::System.Runtime.CompilerServices.ModuleInitializer]
|
||||
internal unsafe static void RegisterShaderProperties()
|
||||
{{
|
||||
{registerBuilder}
|
||||
}}
|
||||
#endif
|
||||
}}";
|
||||
|
||||
context.AddSource($"{registerTypeName}.gen.cs", registerCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -442,7 +442,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
|
||||
null);
|
||||
}
|
||||
|
||||
public void BeginRenderPass(ReadOnlySpan<PassRenderTargetDesc> rtDescs, PassDepthStencilDesc depthDesc, bool allowUAVWrites = false)
|
||||
public void BeginRenderPass(ReadOnlySpan<PassRenderTargetDesc> rtDescs, ref readonly PassDepthStencilDesc depthDesc, bool allowUAVWrites = false)
|
||||
{
|
||||
AssertNotDisposed();
|
||||
ThrowIfNotRecording();
|
||||
@@ -786,6 +786,11 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
|
||||
pNativeObject->DrawInstanced(vertexCount, instanceCount, startVertex, startInstance);
|
||||
}
|
||||
|
||||
public void SetProgram(ref readonly SetProgramDesc desc)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
public void DrawIndexed(uint indexCount, uint instanceCount = 1, uint startIndex = 0, int baseVertex = 0, uint startInstance = 0)
|
||||
{
|
||||
AssertNotDisposed();
|
||||
@@ -836,7 +841,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void DispatchGraph()
|
||||
public void DispatchGraph(ref readonly DispatchGraphDesc desc)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -859,7 +864,7 @@ internal unsafe class D3D12CommandBuffer : D3D12Object<ID3D12GraphicsCommandList
|
||||
var resource = _resourceDatabase.GetResource(argumentBuffer.AsResource());
|
||||
var countResource = _resourceDatabase.GetResource(countBuffer.AsResource());
|
||||
|
||||
pNativeObject->ExecuteIndirect(((D3D12CommandSignature)commandSignature).NativeObject, 0,
|
||||
pNativeObject->ExecuteIndirect((ID3D12CommandSignature*)commandSignature.NativePointer, 0,
|
||||
resource, argumentOffset, countResource, countBufferOffset);
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,8 @@ internal unsafe class D3D12CommandSignature : D3D12Object<ID3D12CommandSignature
|
||||
return pCommandSignature;
|
||||
}
|
||||
|
||||
public IntPtr NativePointer => (IntPtr)NativeObject.Get();
|
||||
|
||||
public D3D12CommandSignature(D3D12RenderDevice device, D3D12PipelineLibrary pipelineLibrary, ref readonly CommandSignatureDesc desc, Key128<GraphicsPipeline> pipelineKey)
|
||||
: base(CreateCommandSignature(device, pipelineLibrary, in desc))
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Loader;
|
||||
using TerraFX.Interop.DirectX;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ internal unsafe class D3D12PipelineLibrary : D3D12Object<ID3D12PipelineLibrary1>
|
||||
return D3D12Utility.D3D12_DEPTH_STENCIL_DESC_CREATE(depthEnabled, writeEnabled, cmp);
|
||||
}
|
||||
|
||||
public Result<Key128<GraphicsPipeline>> CompilePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled)
|
||||
public Result<Key128<GraphicsPipeline>> CreatePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled)
|
||||
{
|
||||
static Result ValidatePassReflectionData(ref readonly GraphicsCompiledResult compiled)
|
||||
{
|
||||
|
||||
@@ -96,8 +96,6 @@ internal unsafe class D3D12RenderDevice : D3D12Object<ID3D12Device14>, IRenderDe
|
||||
|
||||
private FeatureSupport GetFeatureSupport()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
var support = FeatureSupport.None;
|
||||
|
||||
D3D12_FEATURE_DATA_D3D12_OPTIONS options = default;
|
||||
@@ -149,7 +147,7 @@ internal unsafe class D3D12RenderDevice : D3D12Object<ID3D12Device14>, IRenderDe
|
||||
D3D12_FEATURE_DATA_D3D12_OPTIONS21 options9 = default;
|
||||
if (pNativeObject->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS21, &options9, (uint)sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS8)).SUCCEEDED)
|
||||
{
|
||||
if (options9.WorkGraphsTier != D3D12_WORK_GRAPHS_TIER.D3D12_WORK_GRAPHS_TIER_NOT_SUPPORTED)
|
||||
if (options9.WorkGraphsTier != D3D12_WORK_GRAPHS_TIER_NOT_SUPPORTED)
|
||||
{
|
||||
support |= FeatureSupport.WorkGraphs;
|
||||
}
|
||||
|
||||
15
src/Runtime/Ghost.Graphics.D3D12/D3D12WorkGraphPipeline.cs
Normal file
15
src/Runtime/Ghost.Graphics.D3D12/D3D12WorkGraphPipeline.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using TerraFX.Interop.DirectX;
|
||||
|
||||
namespace Ghost.Graphics.D3D12;
|
||||
|
||||
internal class D3D12WorkGraphPipeline : IWorkGraphPipeline
|
||||
{
|
||||
private UniquePtr<ID3D12StateObject> _stateObject;
|
||||
private D3D12_PROGRAM_IDENTIFIER _programIdentifier;
|
||||
private D3D12_WORK_GRAPH_MEMORY_REQUIREMENTS _memoryRequirements;
|
||||
|
||||
private Handle<GPUResource> _backingBuffer;
|
||||
}
|
||||
@@ -6,7 +6,9 @@ using Misaki.HighPerformance.LowLevel;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.Utilities;
|
||||
using System.IO.Hashing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using TerraFX.Interop.DirectX;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
@@ -88,7 +90,7 @@ internal sealed partial class DXCShaderCompiler
|
||||
return argsArray;
|
||||
}
|
||||
|
||||
private static Result<string, Error> GetFinalShaderCode(string shaderPath, ReadOnlySpan<string> includes, string? injectedCode)
|
||||
private static Result<string, Error> BuildFinalShaderCode(string shaderPath, ReadOnlySpan<string> includes, string? injectedCode)
|
||||
{
|
||||
string shaderCode;
|
||||
if (shaderPath == "hlsl_block")
|
||||
@@ -110,7 +112,7 @@ internal sealed partial class DXCShaderCompiler
|
||||
shaderCode = File.ReadAllText(shaderPath);
|
||||
}
|
||||
|
||||
var sb = new System.Text.StringBuilder();
|
||||
var sb = new StringBuilder();
|
||||
foreach (var includePath in includes)
|
||||
{
|
||||
sb.AppendLine($"#include \"{includePath}\"");
|
||||
@@ -118,13 +120,13 @@ internal sealed partial class DXCShaderCompiler
|
||||
|
||||
if (!string.IsNullOrEmpty(injectedCode))
|
||||
{
|
||||
sb.AppendLine($"#line 1 \"hlsl_block\"");
|
||||
sb.AppendLine($"#line 0 \"injected_code\"");
|
||||
sb.AppendLine(injectedCode);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(shaderCode))
|
||||
{
|
||||
sb.AppendLine($"#line 1 \"{shaderPath}\"");
|
||||
sb.AppendLine($"#line 0 \"{shaderPath}\"");
|
||||
sb.AppendLine(shaderCode);
|
||||
}
|
||||
|
||||
@@ -155,7 +157,7 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler
|
||||
private UniquePtr<IDxcUtils> _utils;
|
||||
// NOTE: This is just a temporary cache for compiled shader code. We will implement a proper disk cache later.
|
||||
// TODO: This should be shader variant specific cache instead of pass specific.
|
||||
private readonly Dictionary<Key64<ShaderVariant>, GraphicsCompiledResult> _compiledResults;
|
||||
private readonly Dictionary<ulong, GraphicsCompiledResult> _compiledResults;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
@@ -173,7 +175,7 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler
|
||||
_compiler.Attach(pCompiler);
|
||||
_utils.Attach(pUtils);
|
||||
|
||||
_compiledResults = new Dictionary<Key64<ShaderVariant>, GraphicsCompiledResult>();
|
||||
_compiledResults = new Dictionary<ulong, GraphicsCompiledResult>();
|
||||
}
|
||||
|
||||
~DXCShaderCompiler()
|
||||
@@ -285,7 +287,7 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler
|
||||
|
||||
ThrowIfFailed(_utils.Get()->CreateDefaultIncludeHandler(includeHandler.GetAddressOf()));
|
||||
|
||||
var finalShaderCodeResult = GetFinalShaderCode(config.shaderPath, config.includes, config.injectedCode);
|
||||
var finalShaderCodeResult = BuildFinalShaderCode(config.shaderPath, config.includes, config.injectedCode);
|
||||
if (finalShaderCodeResult.IsFailure)
|
||||
{
|
||||
return Result.Failure(finalShaderCodeResult.Error);
|
||||
@@ -364,6 +366,7 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler
|
||||
{
|
||||
bytecode = bytecode,
|
||||
reflectionData = reflectionData,
|
||||
hashCode = XxHash64.HashToUInt64(bytecode)
|
||||
};
|
||||
}
|
||||
finally
|
||||
@@ -377,7 +380,7 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler
|
||||
|
||||
// TODO: This should be shader variant specific compile instead of pass specific.
|
||||
// TODO: Build final shader code in memory before compiling.
|
||||
public Result<GraphicsCompiledResult> CompilePass(ref readonly PassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, Key64<ShaderVariant> key)
|
||||
public Result<GraphicsCompiledResult> CompilePass(ref readonly PassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, ref readonly LocalKeywordSet keywords)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
@@ -386,6 +389,13 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler
|
||||
descriptor.defines?.CopyTo(fullDefines);
|
||||
additionalConfig.defines.CopyTo(fullDefines.AsSpan(defineCountInDescriptor));
|
||||
|
||||
var injectedCodeBuilder = new StringBuilder();
|
||||
injectedCodeBuilder.AppendLine(descriptor.shader.propertiesCode);
|
||||
injectedCodeBuilder.AppendLine(descriptor.hlsl);
|
||||
injectedCodeBuilder.AppendLine(additionalConfig.injectedCode);
|
||||
|
||||
var injectedCode = injectedCodeBuilder.ToString();
|
||||
|
||||
ShaderCompileResult tsResult = default;
|
||||
var tsEntry = descriptor.taskShader;
|
||||
if (tsEntry.IsCreated)
|
||||
@@ -396,7 +406,7 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler
|
||||
includes = descriptor.includes.AsSpan(),
|
||||
shaderPath = tsEntry.shader,
|
||||
entryPoint = tsEntry.entry,
|
||||
injectedCode = descriptor.hlsl + additionalConfig.injectedCode,
|
||||
injectedCode = injectedCode,
|
||||
stage = ShaderStage.TaskShader,
|
||||
tier = additionalConfig.tier,
|
||||
optimizeLevel = additionalConfig.optimizeLevel,
|
||||
@@ -422,7 +432,7 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler
|
||||
includes = descriptor.includes.AsSpan(),
|
||||
shaderPath = msEntry.shader,
|
||||
entryPoint = msEntry.entry,
|
||||
injectedCode = descriptor.hlsl + additionalConfig.injectedCode,
|
||||
injectedCode = injectedCode,
|
||||
stage = ShaderStage.MeshShader,
|
||||
tier = additionalConfig.tier,
|
||||
optimizeLevel = additionalConfig.optimizeLevel,
|
||||
@@ -452,7 +462,7 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler
|
||||
includes = descriptor.includes.AsSpan(),
|
||||
shaderPath = psEntry.shader,
|
||||
entryPoint = psEntry.entry,
|
||||
injectedCode = descriptor.hlsl + additionalConfig.injectedCode,
|
||||
injectedCode = injectedCode,
|
||||
stage = ShaderStage.PixelShader,
|
||||
tier = additionalConfig.tier,
|
||||
optimizeLevel = additionalConfig.optimizeLevel,
|
||||
@@ -479,7 +489,10 @@ internal sealed unsafe partial class DXCShaderCompiler : IShaderCompiler
|
||||
psResult = psResult,
|
||||
};
|
||||
|
||||
_compiledResults[key] = compiled;
|
||||
var passHash = RHIUtility.CreateShaderPassKey(descriptor.identifier, compiled.HashCode);
|
||||
var variantHash = RHIUtility.CreateShaderVariantKey(passHash, in keywords);
|
||||
|
||||
_compiledResults[variantHash] = compiled;
|
||||
return compiled;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using TerraFX.Interop.DirectX;
|
||||
using static TerraFX.Aliases.D3D12_Alias;
|
||||
|
||||
namespace Ghost.Graphics.D3D12.Experiment;
|
||||
|
||||
internal unsafe class D3D12SimpleWorkGraph : IDisposable
|
||||
{
|
||||
private readonly D3D12ResourceDatabase _resourceDatabase;
|
||||
private readonly D3D12ResourceAllocator _resourceAllocator;
|
||||
|
||||
private ID3D12StateObject* _stateObject;
|
||||
private ID3D12StateObjectProperties1* _stateObjectProperties;
|
||||
private ID3D12WorkGraphProperties* _workGraphProperties;
|
||||
|
||||
// Contains Opaque ID data used by the Runtime to identify the WG
|
||||
private D3D12_PROGRAM_IDENTIFIER _programIdentifier;
|
||||
|
||||
// Auto-allocated Backing Memory for WG Queues and Nodes state
|
||||
private Handle<GPUBuffer> _backingMemory;
|
||||
private D3D12_GPU_VIRTUAL_ADDRESS_RANGE _backingMemoryRange;
|
||||
|
||||
// First time dispatching a Work Graph requires INITIALIZE flag
|
||||
private bool _isInitialized;
|
||||
|
||||
public D3D12SimpleWorkGraph(
|
||||
D3D12RenderDevice device,
|
||||
D3D12ResourceDatabase resourceDatabase,
|
||||
D3D12ResourceAllocator resourceAllocator,
|
||||
ID3D12RootSignature* globalRootSignature,
|
||||
ReadOnlySpan<byte> bytecode,
|
||||
string programName)
|
||||
{
|
||||
_resourceDatabase = resourceDatabase;
|
||||
_resourceAllocator = resourceAllocator;
|
||||
|
||||
var subobjects = stackalloc D3D12_STATE_SUBOBJECT[3];
|
||||
|
||||
fixed (byte* pBytecode = bytecode)
|
||||
fixed (char* pProgramName = programName)
|
||||
{
|
||||
// 1. DXIL Library Subobject
|
||||
var dxilLibDesc = new D3D12_DXIL_LIBRARY_DESC
|
||||
{
|
||||
DXILLibrary = new D3D12_SHADER_BYTECODE
|
||||
{
|
||||
pShaderBytecode = pBytecode,
|
||||
BytecodeLength = (nuint)bytecode.Length
|
||||
},
|
||||
NumExports = 0,
|
||||
pExports = null
|
||||
};
|
||||
|
||||
subobjects[0].Type = D3D12_STATE_SUBOBJECT_TYPE_DXIL_LIBRARY;
|
||||
subobjects[0].pDesc = &dxilLibDesc;
|
||||
|
||||
// 2. Global Root Signature Subobject
|
||||
// This is shared among all Nodes. Perfect for passing your bindless Descriptor Heaps
|
||||
// and root constants (which hold your Buffer IDs and Sampler Indices).
|
||||
var rootSigDesc = new D3D12_GLOBAL_ROOT_SIGNATURE
|
||||
{
|
||||
pGlobalRootSignature = globalRootSignature
|
||||
};
|
||||
|
||||
subobjects[1].Type = D3D12_STATE_SUBOBJECT_TYPE_GLOBAL_ROOT_SIGNATURE;
|
||||
subobjects[1].pDesc = &rootSigDesc;
|
||||
|
||||
// 3. Work Graph Properties Subobject
|
||||
var workGraphDesc = new D3D12_WORK_GRAPH_DESC
|
||||
{
|
||||
ProgramName = pProgramName,
|
||||
Flags = D3D12_WORK_GRAPH_FLAG_INCLUDE_ALL_AVAILABLE_NODES
|
||||
};
|
||||
|
||||
subobjects[2].Type = D3D12_STATE_SUBOBJECT_TYPE_WORK_GRAPH;
|
||||
subobjects[2].pDesc = &workGraphDesc;
|
||||
|
||||
// Tie them to a state object descriptor
|
||||
var stateObjectDesc = new D3D12_STATE_OBJECT_DESC
|
||||
{
|
||||
Type = D3D12_STATE_OBJECT_TYPE_EXECUTABLE,
|
||||
NumSubobjects = 3,
|
||||
pSubobjects = subobjects
|
||||
};
|
||||
|
||||
// Build State Object!
|
||||
var pDevice = device.NativeObject.Get();
|
||||
ID3D12StateObject* pStateObject;
|
||||
ThrowIfFailed(pDevice->CreateStateObject(&stateObjectDesc, __uuidof<ID3D12StateObject>(), (void**)&pStateObject));
|
||||
_stateObject = pStateObject;
|
||||
|
||||
ID3D12StateObjectProperties1* pStateObjectProps;
|
||||
ThrowIfFailed(pStateObject->QueryInterface(__uuidof<ID3D12StateObjectProperties1>(), (void**)&pStateObjectProps));
|
||||
_stateObjectProperties = pStateObjectProps;
|
||||
|
||||
ID3D12WorkGraphProperties* pWorkGraphProps;
|
||||
ThrowIfFailed(pStateObject->QueryInterface(__uuidof<ID3D12WorkGraphProperties>(), (void**)&pWorkGraphProps));
|
||||
_workGraphProperties = pWorkGraphProps;
|
||||
|
||||
// Extract Program Identifier - we'll pass this via CommandList later.
|
||||
_programIdentifier = pStateObjectProps->GetProgramIdentifier(pProgramName);
|
||||
var workGraphIndex = pWorkGraphProps->GetWorkGraphIndex(pProgramName);
|
||||
|
||||
// Compute Backing Memory needed
|
||||
D3D12_WORK_GRAPH_MEMORY_REQUIREMENTS memReqs;
|
||||
pWorkGraphProps->GetWorkGraphMemoryRequirements(workGraphIndex, &memReqs);
|
||||
|
||||
// Allocate Backing Memory Buffer using your custom allocator!
|
||||
var backingMemoryDesc = new BufferDesc
|
||||
{
|
||||
Size = memReqs.MaxSizeInBytes,
|
||||
Usage = BufferUsage.UnorderedAccess, // Backing memory MUST be an Unordered Access resource.
|
||||
HeapType = HeapType.Default
|
||||
};
|
||||
|
||||
_backingMemory = _resourceAllocator.CreateBuffer(in backingMemoryDesc, $"{programName}_BackingMemory");
|
||||
var backingBufferResource = _resourceDatabase.GetResource(_backingMemory.AsResource());
|
||||
|
||||
_backingMemoryRange = new D3D12_GPU_VIRTUAL_ADDRESS_RANGE
|
||||
{
|
||||
StartAddress = backingBufferResource.Get()->GetGPUVirtualAddress(),
|
||||
SizeInBytes = memReqs.MaxSizeInBytes
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// You invoke this from outside passing records via CPU array to test immediately.
|
||||
public void DispatchGraph<TRecord>(D3D12CommandBuffer cmdBuffer, uint entryPointIndex, ReadOnlySpan<TRecord> records)
|
||||
where TRecord : unmanaged
|
||||
{
|
||||
var pCmdList = cmdBuffer.NativeObject.Get();
|
||||
|
||||
// 1. Prepare Program descriptors
|
||||
var setProgramDesc = new D3D12_SET_PROGRAM_DESC
|
||||
{
|
||||
Type = D3D12_PROGRAM_TYPE_WORK_GRAPH,
|
||||
WorkGraph = new D3D12_SET_WORK_GRAPH_DESC
|
||||
{
|
||||
ProgramIdentifier = _programIdentifier,
|
||||
Flags = _isInitialized ? D3D12_SET_WORK_GRAPH_FLAG_NONE : D3D12_SET_WORK_GRAPH_FLAG_INITIALIZE,
|
||||
BackingMemory = _backingMemoryRange,
|
||||
NodeLocalRootArgumentsTable = default // Ignored since we are pure bindless setup!
|
||||
}
|
||||
};
|
||||
|
||||
_isInitialized = true;
|
||||
|
||||
pCmdList->SetProgram(&setProgramDesc);
|
||||
|
||||
// 2. Execute! Map CPU inputs natively directly.
|
||||
fixed (TRecord* pRecords = records)
|
||||
{
|
||||
var cpuInput = new D3D12_NODE_CPU_INPUT
|
||||
{
|
||||
EntrypointIndex = entryPointIndex,
|
||||
NumRecords = (uint)records.Length,
|
||||
pRecords = pRecords,
|
||||
RecordStrideInBytes = (uint)sizeof(TRecord)
|
||||
};
|
||||
|
||||
var dispatchDesc = new D3D12_DISPATCH_GRAPH_DESC
|
||||
{
|
||||
Mode = D3D12_DISPATCH_MODE_NODE_CPU_INPUT,
|
||||
NodeCPUInput = cpuInput
|
||||
};
|
||||
|
||||
pCmdList->DispatchGraph(&dispatchDesc);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_backingMemory.IsValid)
|
||||
{
|
||||
_resourceDatabase.ReleaseResourceImmediately(_backingMemory.AsResource());
|
||||
_backingMemory = Handle<GPUBuffer>.Invalid;
|
||||
}
|
||||
|
||||
if (_workGraphProperties != null)
|
||||
{
|
||||
_workGraphProperties->Release();
|
||||
_workGraphProperties = null;
|
||||
}
|
||||
|
||||
if (_stateObjectProperties != null)
|
||||
{
|
||||
_stateObjectProperties->Release();
|
||||
_stateObjectProperties = null;
|
||||
}
|
||||
|
||||
if (_stateObject != null)
|
||||
{
|
||||
_stateObject->Release();
|
||||
_stateObject = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
#ifndef SIMPLE_WORKGRAPH_HLSL
|
||||
#define SIMPLE_WORKGRAPH_HLSL
|
||||
|
||||
#include "F:/csharp/GhostEngine/src/Runtime/Ghost.Graphics/Shaders/Includes/Common.hlsl"
|
||||
#include "F:/csharp/GhostEngine/src/Runtime/Ghost.Graphics/Shaders/Includes/Random.hlsl"
|
||||
|
||||
// The record types
|
||||
struct InitialRecord
|
||||
{
|
||||
uint seed;
|
||||
};
|
||||
|
||||
struct IncrementRecord
|
||||
{
|
||||
uint incrementValue;
|
||||
};
|
||||
|
||||
// Bindless root constants
|
||||
cbuffer RootConstants : register(b0)
|
||||
{
|
||||
uint counterBufferId;
|
||||
uint threshold;
|
||||
};
|
||||
|
||||
// Node: MainNode
|
||||
// Entry point for the Work Graph. Spawns threads matching the record count.
|
||||
[Shader("node")]
|
||||
[NodeIsProgramEntry]
|
||||
[NodeLaunch("thread")]
|
||||
void MainNode(
|
||||
uint dispatchThreadID : SV_DispatchThreadID,
|
||||
in InitialRecord record,
|
||||
[MaxRecords(1)] NodeOutput<IncrementRecord> IncrementNode) // Target the IncrementNode
|
||||
{
|
||||
// Generate a random number from JenkinsHash
|
||||
uint randValue = JenkinsHash(record.seed ^ dispatchThreadID);
|
||||
|
||||
// If the random number exceeds our threshold, launch the next node
|
||||
if (randValue > threshold)
|
||||
{
|
||||
ThreadNodeOutputRecords<IncrementRecord> outRecord = IncrementNode.GetThreadNodeOutputRecords(1);
|
||||
outRecord.Get().incrementValue = 1;
|
||||
outRecord.OutputComplete();
|
||||
}
|
||||
}
|
||||
|
||||
// Node: IncrementNode
|
||||
// Triggered dynamically from MainNode
|
||||
[Shader("node")]
|
||||
[NodeLaunch("thread")]
|
||||
void IncrementNode(
|
||||
uint dispatchThreadID : SV_DispatchThreadID,
|
||||
in IncrementRecord record)
|
||||
{
|
||||
// Retrieve our RWByteAddressBuffer generically through SM6.6 bindless descriptor heap
|
||||
RWByteAddressBuffer counterBuffer = ResourceDescriptorHeap[counterBufferId];
|
||||
|
||||
// Thread-safe atomic increment of our counter across all dispatched records
|
||||
uint originalValue;
|
||||
counterBuffer.InterlockedAdd(0, record.incrementValue, originalValue);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -473,7 +473,6 @@ public struct PassDepthStencilDesc
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public struct TextureSubresource
|
||||
{
|
||||
public uint MipLevel
|
||||
@@ -1135,6 +1134,131 @@ public ref struct CommandSignatureDesc
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe struct ProgramIdentifier
|
||||
{
|
||||
public void* pIdentifier;
|
||||
}
|
||||
|
||||
public unsafe struct NodeCPUInput
|
||||
{
|
||||
public uint entryPointIndex;
|
||||
public uint numRecords;
|
||||
public void* pRecords;
|
||||
public ulong recordStrideInBytes;
|
||||
}
|
||||
|
||||
public unsafe struct MultiNodeCPUInput
|
||||
{
|
||||
public uint numNodeInputs;
|
||||
public NodeCPUInput* pNodeInputs;
|
||||
public ulong nodeInputStrideInBytes;
|
||||
}
|
||||
|
||||
public struct DispatchGraphDesc
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
private struct __union
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public NodeCPUInput nodeCPUInput;
|
||||
[FieldOffset(0)]
|
||||
public ulong nodeGPUInput;
|
||||
[FieldOffset(0)]
|
||||
public MultiNodeCPUInput multiNodeCPUInput;
|
||||
[FieldOffset(0)]
|
||||
public ulong multiNodeGPUInput;
|
||||
}
|
||||
|
||||
private __union _input;
|
||||
|
||||
public GraphDispatchMode DispatchMode
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[UnscopedRef]
|
||||
public ref NodeCPUInput NodeCPUInput => ref _input.nodeCPUInput;
|
||||
[UnscopedRef]
|
||||
public ref ulong NodeGPUInput => ref _input.nodeGPUInput;
|
||||
[UnscopedRef]
|
||||
public ref MultiNodeCPUInput MultiNodeCPUInput => ref _input.multiNodeCPUInput;
|
||||
[UnscopedRef]
|
||||
public ref ulong MultiNodeGPUInput => ref _input.multiNodeGPUInput;
|
||||
}
|
||||
|
||||
public struct WorkGraphMemoryRequirements
|
||||
{
|
||||
public ulong MinSizeInBytes
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ulong MaxSizeInBytes
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public uint SizeGranularityInBytes
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public struct WorkGraphSubObjectDesc
|
||||
{
|
||||
public string ProgramName
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public bool IncludeAllAvailableNodes
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public struct SetProgramDesc
|
||||
{
|
||||
public ProgramIdentifier ProgramIdentifier
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public SetWorkGraphFlags Flags
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
|
||||
// D3D12_GPU_VIRTUAL_ADDRESS_RANGE
|
||||
public ulong BackingMemoryAddress
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ulong BackingMemorySize
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
|
||||
// D3D12_GPU_VIRTUAL_ADDRESS_RANGE_AND_STRIDE
|
||||
public ulong NodeLocalRootArgumentsTableAddress
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ulong NodeLocalRootArgumentsTableSizeInBytes
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ulong NodeLocalRootArgumentsTableStrideInBytes
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public struct SwapChainDesc
|
||||
{
|
||||
public uint Width
|
||||
@@ -1185,7 +1309,6 @@ public struct SwapChainTarget
|
||||
get; set;
|
||||
}
|
||||
|
||||
|
||||
public static SwapChainTarget FromWindowHandle(nint hwnd)
|
||||
{
|
||||
return new SwapChainTarget
|
||||
@@ -1213,7 +1336,6 @@ public enum SwapChainTargetType
|
||||
Composition
|
||||
}
|
||||
|
||||
|
||||
public enum BarrierType
|
||||
{
|
||||
Global,
|
||||
@@ -1503,4 +1625,19 @@ public enum IndirectArgumentType
|
||||
DispatchRays,
|
||||
DispatchMesh,
|
||||
IncrementingConstant,
|
||||
}
|
||||
|
||||
public enum GraphDispatchMode
|
||||
{
|
||||
CPUInput,
|
||||
GPUInput,
|
||||
MultiCPUInput,
|
||||
MultiGPUInput
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum SetWorkGraphFlags
|
||||
{
|
||||
None,
|
||||
Initialize
|
||||
}
|
||||
@@ -80,7 +80,7 @@ public interface ICommandBuffer : IRHIObject
|
||||
/// <param name="rtDescs">Render Target descriptions</param>
|
||||
/// <param name="depthDesc">Depth stencil description</param>
|
||||
/// <param name="allowUAVWrites">Whether UAV writes are allowed during the render pass</param>
|
||||
void BeginRenderPass(ReadOnlySpan<PassRenderTargetDesc> rtDescs, PassDepthStencilDesc depthDesc, bool allowUAVWrites = false);
|
||||
void BeginRenderPass(ReadOnlySpan<PassRenderTargetDesc> rtDescs, ref readonly PassDepthStencilDesc depthDesc, bool allowUAVWrites = false);
|
||||
|
||||
/// <summary>
|
||||
/// Ends the current render pass
|
||||
@@ -136,6 +136,8 @@ public interface ICommandBuffer : IRHIObject
|
||||
/// <param name="offsetIn32Bits">The Offset, in 32-bit values, from the start of the root parameter where the constants will be set.</param>
|
||||
void SetGraphicsRoot32Constants(uint rootIndex, ReadOnlySpan<uint> constantBuffer, uint offsetIn32Bits = 0);
|
||||
|
||||
void SetProgram(ref readonly SetProgramDesc desc);
|
||||
|
||||
/// <summary>
|
||||
/// Issues a non-indexed draw call.
|
||||
/// </summary>
|
||||
@@ -176,6 +178,8 @@ public interface ICommandBuffer : IRHIObject
|
||||
/// </summary>
|
||||
void DispatchRay();
|
||||
|
||||
void DispatchGraph(ref readonly DispatchGraphDesc desc);
|
||||
|
||||
/// <summary>
|
||||
/// Executes a sequence of GPU commands indirectly using the specified command signature and argument buffers.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
public interface ICommandSignature : IRHIObject;
|
||||
public unsafe interface ICommandSignature : IRHIObject
|
||||
{
|
||||
IntPtr NativePointer { get; }
|
||||
}
|
||||
@@ -6,5 +6,5 @@ public interface IPipelineLibrary : IDisposable
|
||||
{
|
||||
void SaveLibraryToDisk(string filePath);
|
||||
bool HasPipeline(Key128<GraphicsPipeline> key);
|
||||
Result<Key128<GraphicsPipeline>> CompilePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled);
|
||||
Result<Key128<GraphicsPipeline>> CreatePSO(ref readonly GraphicsPSODescriptor descriptor, ref readonly GraphicsCompiledResult compiled);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Core.Utilities;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
@@ -9,6 +10,7 @@ public struct ShaderCompileResult : IDisposable
|
||||
{
|
||||
public UnsafeArray<byte> bytecode;
|
||||
public ShaderReflectionData reflectionData;
|
||||
public ulong hashCode;
|
||||
|
||||
public readonly bool IsCreated => bytecode.IsCreated;
|
||||
|
||||
@@ -20,10 +22,25 @@ public struct ShaderCompileResult : IDisposable
|
||||
|
||||
public struct GraphicsCompiledResult : IDisposable
|
||||
{
|
||||
private ulong _hashCode;
|
||||
|
||||
public ShaderCompileResult tsResult;
|
||||
public ShaderCompileResult msResult;
|
||||
public ShaderCompileResult psResult;
|
||||
|
||||
public Key64<GraphicsCompiledResult> HashCode
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_hashCode == 0)
|
||||
{
|
||||
_hashCode = Hash.Combine64(tsResult.hashCode, msResult.hashCode, psResult.hashCode);
|
||||
}
|
||||
|
||||
return _hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
tsResult.Dispose();
|
||||
@@ -144,6 +161,6 @@ public readonly struct ShaderReflectionData
|
||||
public interface IShaderCompiler : IDisposable
|
||||
{
|
||||
Result<ShaderCompileResult> Compile(ref readonly ShaderCompilationConfig config, Allocator allocator);
|
||||
Result<GraphicsCompiledResult> CompilePass(ref readonly PassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, Key64<ShaderVariant> key);
|
||||
Result<GraphicsCompiledResult> CompilePass(ref readonly PassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, ref readonly LocalKeywordSet keywords);
|
||||
Result<GraphicsCompiledResult, Error> LoadCompiledCache(Key64<ShaderVariant> key);
|
||||
}
|
||||
|
||||
9
src/Runtime/Ghost.Graphics.RHI/IWorkGraphPipeline.cs
Normal file
9
src/Runtime/Ghost.Graphics.RHI/IWorkGraphPipeline.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Ghost.Graphics.RHI;
|
||||
|
||||
public interface IWorkGraphPipeline
|
||||
{
|
||||
}
|
||||
@@ -146,10 +146,9 @@ public static class RHIUtility
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Key64<ShaderPass> CreateShaderPassKey(string passID)
|
||||
public static Key64<ShaderPass> CreateShaderPassKey(ulong passID, ulong compiledHash)
|
||||
{
|
||||
var passIdSpan = passID.AsSpan();
|
||||
return new Key64<ShaderPass>(XxHash3.HashToUInt64(MemoryMarshal.AsBytes(passIdSpan)));
|
||||
return Hash.Combine64(passID, compiledHash);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -157,7 +156,7 @@ public static class RHIUtility
|
||||
{
|
||||
var passHash = passKey.Value;
|
||||
var keywordHash = keywords.GetHash64();
|
||||
return new Key64<ShaderVariant>(Hash.Hash64(passHash, keywordHash));
|
||||
return new Key64<ShaderVariant>(Hash.Combine64(passHash, keywordHash));
|
||||
}
|
||||
|
||||
public static unsafe Key128<GraphicsPipeline> CreateGraphicsPipelineKey(Key64<ShaderVariant> shaderVariantKey, PipelineState pipelineState, PassPipelineHash passKey)
|
||||
|
||||
@@ -111,17 +111,17 @@ public struct Material : IResourceReleasable
|
||||
};
|
||||
}
|
||||
|
||||
if (shader.CBufferSize != 0)
|
||||
if (shader.PropertyBufferSize != 0)
|
||||
{
|
||||
var desc = new BufferDesc
|
||||
{
|
||||
Size = shader.CBufferSize,
|
||||
Size = shader.PropertyBufferSize,
|
||||
Usage = BufferUsage.Raw | BufferUsage.ShaderResource,
|
||||
HeapType = HeapType.Default,
|
||||
};
|
||||
|
||||
var buffer = resourceAllocator.CreateBuffer(ref desc, "MaterialCBuffer");
|
||||
_cBufferCache = new CBufferCache(buffer, shader.CBufferSize);
|
||||
_cBufferCache = new CBufferCache(buffer, shader.PropertyBufferSize);
|
||||
}
|
||||
|
||||
return Error.None;
|
||||
|
||||
@@ -14,18 +14,18 @@ namespace Ghost.Graphics.Core;
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Meshlet
|
||||
{
|
||||
public SphereBounds boundingSphere; // 16 bytes
|
||||
public SphereBounds parentBoundingSphere; // 16 bytes
|
||||
public AABB boundingBox; // 24 bytes
|
||||
public uint vertexOffset; // offset into meshlet vertex index array
|
||||
public uint triangleOffset; // offset into packed triangle array
|
||||
public uint groupIndex; // owning group
|
||||
public float clusterError; // geometric error of this meshlet/cluster
|
||||
public float parentError; // geometric refinement error carried into runtime LOD tests
|
||||
public byte vertexCount; // max 64
|
||||
public byte triangleCount; // max 124
|
||||
public byte localMaterialIndex; // mesh-local material slot
|
||||
public byte lodLevel; // this meshlet's LOD level
|
||||
public SphereBounds boundingSphere; // 16 bytes
|
||||
public SphereBounds parentBoundingSphere; // 16 bytes
|
||||
public AABB boundingBox; // 24 bytes
|
||||
public uint vertexOffset; // offset into meshlet vertex index array
|
||||
public uint triangleOffset; // offset into packed triangle array
|
||||
public uint groupIndex; // owning group
|
||||
public float clusterError; // geometric error of this meshlet/cluster
|
||||
public float parentError; // geometric refinement error carried into runtime LOD tests
|
||||
public byte vertexCount; // max 64
|
||||
public byte triangleCount; // max 124
|
||||
public byte localMaterialIndex; // mesh-local material slot
|
||||
public byte lodLevel; // this meshlet's LOD level
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Core.Graphics;
|
||||
using Ghost.Core.Utilities;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
@@ -83,19 +84,20 @@ public partial struct Shader : IResourceReleasable
|
||||
// We can use a int array since the number and index of tags are fixed at compile time.
|
||||
|
||||
public readonly int PassCount => _shaderPasses.Count;
|
||||
public readonly uint CBufferSize => _cbufferSize;
|
||||
public readonly uint PropertyBufferSize => _cbufferSize;
|
||||
|
||||
internal Shader(ShaderDescriptor descriptor)
|
||||
internal Shader(ShaderDescriptor descriptor, ref readonly GraphicsCompiledResult compiledResult)
|
||||
{
|
||||
_cbufferSize = (uint)descriptor.cbufferSize;
|
||||
_cbufferSize = descriptor.propertyBufferSize;
|
||||
_shaderPasses = new UnsafeArray<ShaderPass>(descriptor.passes.Length, Allocator.Persistent);
|
||||
_passIDToLocal = new UnsafeHashMap<int, int>(descriptor.passes.Length, Allocator.Persistent);
|
||||
_keywordIDToLocal = new UnsafeHashMap<int, int>(32, Allocator.Persistent);
|
||||
|
||||
for (var i = 0; i < descriptor.passes.Length; i++)
|
||||
{
|
||||
var pass = descriptor.passes[i];
|
||||
var passKey = RHIUtility.CreateShaderPassKey(pass.identifier);
|
||||
ref readonly var pass = ref descriptor.passes[i];
|
||||
|
||||
var passKey = RHIUtility.CreateShaderPassKey(pass.identifier, compiledResult.HashCode);
|
||||
var keywords = default(LocalKeywordSet);
|
||||
|
||||
if (pass.keywords.Length > 0)
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ghost.Graphics;
|
||||
|
||||
public unsafe class GPUScene : IDisposable
|
||||
{
|
||||
private readonly IResourceAllocator _resourceAllocator;
|
||||
private readonly IResourceDatabase _resourceDatabase;
|
||||
|
||||
private Handle<GPUBuffer> _sceneBuffer;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
internal GPUScene(IResourceAllocator resourceAllocator, IResourceDatabase resourceDatabase, ulong initialCount)
|
||||
{
|
||||
_resourceAllocator = resourceAllocator;
|
||||
_resourceDatabase = resourceDatabase;
|
||||
|
||||
var bufferDesc = new BufferDesc
|
||||
{
|
||||
Size = initialCount * (ulong)sizeof(InstanceData),
|
||||
Stride = (uint)sizeof(InstanceData),
|
||||
Usage = BufferUsage.Structured | BufferUsage.UnorderedAccess | BufferUsage.ShaderResource,
|
||||
HeapType = HeapType.Default,
|
||||
};
|
||||
|
||||
_sceneBuffer = _resourceAllocator.CreateBuffer(in bufferDesc, "SceneBuffer");
|
||||
|
||||
Debug.Assert(_sceneBuffer.IsValid, "Failed to create GPUScene buffer.");
|
||||
}
|
||||
|
||||
~GPUScene()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_resourceDatabase.ReleaseResource(_sceneBuffer.AsResource());
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
131
src/Runtime/Ghost.Graphics/IRenderPipeline.cs
Normal file
131
src/Runtime/Ghost.Graphics/IRenderPipeline.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
|
||||
namespace Ghost.Graphics;
|
||||
|
||||
public interface IRenderPayload : IDisposable
|
||||
{
|
||||
ReadOnlySpan<RenderRequest> RenderRequests { get; }
|
||||
|
||||
void AddRenderRequest(ref readonly RenderRequest renderRequest);
|
||||
void Reset();
|
||||
}
|
||||
|
||||
public interface IRenderPipelineSettings
|
||||
{
|
||||
IRenderPipeline CreatePipeline(RenderSystem renderSystem);
|
||||
IRenderPayload CreatePayload(RenderSystem renderSystem, IRenderPipeline renderPipeline);
|
||||
}
|
||||
|
||||
public interface IRenderPipeline : IDisposable
|
||||
{
|
||||
void Render(RenderContext ctx, int frameIndex, IRenderPayload payload);
|
||||
}
|
||||
|
||||
|
||||
public static class RenderPipelineUtility
|
||||
{
|
||||
public static bool GetViewAndProjectionMatrices(RenderSystem renderSystem, ref readonly RenderRequest request, out float4x4 view, out float4x4 projection, out uint2 screenSize)
|
||||
{
|
||||
Handle<GPUTexture> rtHandle;
|
||||
if (request.swapChainIndex < 0)
|
||||
{
|
||||
rtHandle = request.colorTarget;
|
||||
}
|
||||
else if (renderSystem.SwapChainManager.TryGetSwapChain(request.swapChainIndex, out var swapChain))
|
||||
{
|
||||
rtHandle = swapChain.GetCurrentBackBuffer();
|
||||
}
|
||||
else
|
||||
{
|
||||
view = default;
|
||||
projection = default;
|
||||
screenSize = default;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var rtResult = renderSystem.GraphicsEngine.ResourceDatabase.GetResourceDescription(rtHandle.AsResource());
|
||||
if (rtResult.IsFailure)
|
||||
{
|
||||
view = default;
|
||||
projection = default;
|
||||
screenSize = default;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
screenSize = new uint2(rtResult.Value.TextureDescription.Width, rtResult.Value.TextureDescription.Height);
|
||||
var aspectScreen = (float)screenSize.x / screenSize.y;
|
||||
|
||||
view = math.inverse(request.view.localToWorld);
|
||||
|
||||
var vfov = 2.0f * math.atan(request.view.sensorSize.y / (2.0f * request.view.focalLength));
|
||||
var hfov = 2.0f * math.atan(request.view.sensorSize.x / (2.0f * request.view.focalLength));
|
||||
var aspectSensor = request.view.sensorSize.x / request.view.sensorSize.y;
|
||||
|
||||
float vfovF;
|
||||
switch (request.view.gateFit)
|
||||
{
|
||||
case GateFit.Vertical:
|
||||
vfovF = vfov;
|
||||
break;
|
||||
|
||||
case GateFit.Horizontal:
|
||||
// Adjust VFOV so that the sensor width fits the screen width
|
||||
var horizontalAspectBuffer = math.tan(hfov * 0.5f);
|
||||
vfovF = 2.0f * math.atan(horizontalAspectBuffer / aspectScreen);
|
||||
break;
|
||||
|
||||
case GateFit.Fill:
|
||||
if (aspectSensor > aspectScreen)
|
||||
{
|
||||
goto case GateFit.Vertical;
|
||||
}
|
||||
else
|
||||
{
|
||||
goto case GateFit.Horizontal;
|
||||
}
|
||||
|
||||
case GateFit.Overscan:
|
||||
if (aspectSensor > aspectScreen)
|
||||
{
|
||||
goto case GateFit.Horizontal;
|
||||
}
|
||||
else
|
||||
{
|
||||
goto case GateFit.Vertical;
|
||||
}
|
||||
default:
|
||||
vfovF = vfov;
|
||||
break;
|
||||
}
|
||||
|
||||
var m_11 = 1.0f / math.tan(vfovF * 0.5f);
|
||||
var m_00 = m_11 / aspectScreen;
|
||||
var m_22 = request.view.farClipPlane / (request.view.farClipPlane - request.view.nearClipPlane);
|
||||
var m_23 = -(request.view.farClipPlane * request.view.nearClipPlane) / (request.view.farClipPlane - request.view.nearClipPlane);
|
||||
|
||||
projection = new float4x4
|
||||
(
|
||||
m_00, 0, 0, 0,
|
||||
0, m_11, 0, 0,
|
||||
0, 0, m_22, m_23,
|
||||
0, 0, 1, 0
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (request.swapChainIndex >= 0)
|
||||
{
|
||||
renderSystem.SwapChainManager.ReleaseSwapChain(request.swapChainIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,7 +189,7 @@ internal sealed class RenderGraphContext : IUnsafeRenderContext
|
||||
};
|
||||
|
||||
var compiled = compiledCacheResult.Value;
|
||||
_pipelineLibrary.CompilePSO(in psoDes, in compiled).GetValueOrThrow();
|
||||
_pipelineLibrary.CreatePSO(in psoDes, in compiled).GetValueOrThrow();
|
||||
}
|
||||
|
||||
_activePerMaterialData = material._cBufferCache.GpuResource;
|
||||
|
||||
@@ -150,7 +150,7 @@ internal sealed class RenderGraphExecutor
|
||||
: AttachmentStoreOp.NoAccess,
|
||||
};
|
||||
|
||||
commandBuffer.BeginRenderPass(new Span<PassRenderTargetDesc>(pPassRTDescs, nativePass.colorAttachmentCount), depthDesc);
|
||||
commandBuffer.BeginRenderPass(new Span<PassRenderTargetDesc>(pPassRTDescs, nativePass.colorAttachmentCount), in depthDesc);
|
||||
|
||||
for (var i = 0; i < nativePass.colorAttachmentCount; i++)
|
||||
{
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
using Ghost.Graphics.Core;
|
||||
|
||||
namespace Ghost.Graphics.RenderPipeline;
|
||||
|
||||
public interface IRenderPayload : IDisposable
|
||||
{
|
||||
void Reset();
|
||||
}
|
||||
|
||||
public interface IRenderPipelineSettings
|
||||
{
|
||||
IRenderPipeline CreatePipeline(RenderSystem renderSystem);
|
||||
IRenderPayload CreatePayload(RenderSystem renderSystem);
|
||||
}
|
||||
|
||||
public interface IRenderPipeline : IDisposable
|
||||
{
|
||||
void Render(RenderContext ctx, int frameIndex, IRenderPayload payload);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.D3D12;
|
||||
using Ghost.Graphics.RenderPipeline;
|
||||
using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.Mathematics;
|
||||
using System.Collections.Concurrent;
|
||||
@@ -130,7 +129,7 @@ public class RenderSystem : IDisposable
|
||||
_renderPipeline = _renderPipelineSettings.CreatePipeline(this);
|
||||
for (var i = 0; i < _frameResources.Length; i++)
|
||||
{
|
||||
_frameResources[i].RenderPayload = _renderPipelineSettings.CreatePayload(this);
|
||||
_frameResources[i].RenderPayload = _renderPipelineSettings.CreatePayload(this, _renderPipeline);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,7 +192,7 @@ public class RenderSystem : IDisposable
|
||||
_renderPipeline = _renderPipelineSettings.CreatePipeline(this);
|
||||
for (var i = 0; i < _frameResources.Length; i++)
|
||||
{
|
||||
_frameResources[i].RenderPayload = _renderPipelineSettings.CreatePayload(this);
|
||||
_frameResources[i].RenderPayload = _renderPipelineSettings.CreatePayload(this, _renderPipeline);
|
||||
}
|
||||
|
||||
_isRunning = false;
|
||||
|
||||
@@ -5,6 +5,7 @@ using Ghost.Graphics.RHI;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ghost.Graphics;
|
||||
|
||||
@@ -141,11 +142,11 @@ public sealed partial class ResourceManager : IDisposable
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="Identifier{Shader}"/> representing the newly created shader.</returns>
|
||||
/// <param name="descriptor">The viewGroup containing the shader's properties and passes.</param>
|
||||
public Identifier<Shader> CreateGraphicsShader(ShaderDescriptor descriptor)
|
||||
public Identifier<Shader> CreateGraphicsShader(ShaderDescriptor descriptor, ref readonly GraphicsCompiledResult compiledResult)
|
||||
{
|
||||
Debug.Assert(!_disposed);
|
||||
|
||||
var shader = new Shader(descriptor);
|
||||
var shader = new Shader(descriptor, in compiledResult);
|
||||
|
||||
var id = _shaders.Count;
|
||||
_shaders.Add(shader);
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
shader "MyShader/Standard"
|
||||
{
|
||||
properties
|
||||
{
|
||||
//float4 color = { 1, 1, 1, 1 };
|
||||
//tex2d texture1 = { black };
|
||||
//tex2d texture2 = { white };
|
||||
//tex2d texture3 = { grey };
|
||||
//tex2d texture4 = { normal };
|
||||
//sampler tex_sampler;
|
||||
}
|
||||
|
||||
pass "Forward"
|
||||
{
|
||||
pipeline
|
||||
|
||||
Reference in New Issue
Block a user