forked from Misaki/GhostEngine
Refactor shader system: arrays, keywords, property syntax
Major refactor of shader compiler and related systems:
- Switch ShaderDescriptor/PassDescriptor to arrays; remove IPassDescriptor
- Rewrite keywords block parser/semantic analysis for flexible syntax
- Change property initializers to brace syntax `{ ... }`
- Simplify TokenStream API (remove ref index params)
- Make GetBindlessIndex return uint (~0u for not found)
- Update shader compilation and variant logic for new descriptors
- Update test shader syntax to match new property/keyword formats
- Add AGENTS.md agent development guide
- Add Antlr4 dependency to Ghost.DSL
- Miscellaneous code style and error handling improvements
This commit is contained in:
297
AGENTS.md
Normal file
297
AGENTS.md
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
# GhostEngine - Agent Development Guide
|
||||||
|
|
||||||
|
This guide provides essential information for AI coding agents working on the GhostEngine codebase.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
- **Type**: Game Engine
|
||||||
|
- **Language**: C#
|
||||||
|
- **Target Framework**: .NET 10.0
|
||||||
|
- **Special Features**: ECS architecture, D3D12 rendering, AOT compilation, WinUI 3 editor
|
||||||
|
- **Platform**: Windows (net10.0-windows10.0.22621.0 for editor projects)
|
||||||
|
|
||||||
|
## Build Commands
|
||||||
|
|
||||||
|
### Build Entire Solution
|
||||||
|
```bash
|
||||||
|
dotnet build GhostEngine.slnx
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Specific Project
|
||||||
|
```bash
|
||||||
|
dotnet build Ghost.Entities/Ghost.Entities.csproj
|
||||||
|
dotnet build Ghost.Editor/Ghost.Editor.csproj
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build with Configuration
|
||||||
|
```bash
|
||||||
|
dotnet build GhostEngine.slnx -c Release
|
||||||
|
dotnet build GhostEngine.slnx -c Debug
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clean Build
|
||||||
|
```bash
|
||||||
|
dotnet clean GhostEngine.slnx
|
||||||
|
dotnet build GhostEngine.slnx
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Commands
|
||||||
|
|
||||||
|
### Run All Tests (Custom Framework)
|
||||||
|
Tests use a custom test framework (not xUnit/NUnit/MSTest). Each test project is an executable.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run entity tests
|
||||||
|
dotnet run --project Ghost.Entities.Test/Ghost.Entities.Test.csproj
|
||||||
|
|
||||||
|
# Run shader tests
|
||||||
|
dotnet run --project Ghost.Shader.Test/Ghost.Shader.Test.csproj
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run Single Test
|
||||||
|
Tests implement `ITest` interface. To run a specific test, modify the test project's `Program.cs`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// In Ghost.Entities.Test/Program.cs
|
||||||
|
TestRunner.Run<EntityQueryTest>(); // Run specific test
|
||||||
|
TestRunner.Run<EntityQueryTest>(10); // Run with 10 iterations
|
||||||
|
```
|
||||||
|
|
||||||
|
### Visual Tests (Graphics)
|
||||||
|
Graphics tests use WinUI 3 and require running as packaged apps:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet run --project Ghost.Graphics.Test/Ghost.Graphics.Test.csproj
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code Style Guidelines
|
||||||
|
|
||||||
|
### Formatting (from .editorconfig)
|
||||||
|
|
||||||
|
- **Braces**: Allman style - all opening braces on new lines
|
||||||
|
- **Line Length**: Max 400 characters (very permissive)
|
||||||
|
- **Single-line statements**: Preserved (allowed)
|
||||||
|
- **Single-line blocks**: Preserved (allowed)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Correct brace style
|
||||||
|
public void Method()
|
||||||
|
{
|
||||||
|
if (condition)
|
||||||
|
{
|
||||||
|
DoSomething();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Imports
|
||||||
|
|
||||||
|
- **System directives**: NOT sorted first (dotnet_sort_system_directives_first = false)
|
||||||
|
- **No grouping**: Import directives not separated by blank lines
|
||||||
|
- **Order**: Organize by project convention, not alphabetically
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using Ghost.Core;
|
||||||
|
using Ghost.Entities;
|
||||||
|
using Misaki.HighPerformance.Collections;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using TerraFX.Interop.DirectX;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Types and Nullability
|
||||||
|
|
||||||
|
- **Nullable**: Enabled for all projects
|
||||||
|
- **Implicit usings**: Enabled
|
||||||
|
- **Unsafe code**: Allowed in most projects (AllowUnsafeBlocks = True)
|
||||||
|
- **Primary constructors**: NOT preferred (csharp_style_prefer_primary_constructors = false)
|
||||||
|
|
||||||
|
### Naming Conventions
|
||||||
|
|
||||||
|
- **Classes/Interfaces**: PascalCase (`EntityManager`, `ICommandBuffer`)
|
||||||
|
- **Methods**: PascalCase (`CreateEntity`, `GetComponent`)
|
||||||
|
- **Properties**: PascalCase (`IsSuccess`, `Value`)
|
||||||
|
- **Fields (private)**: Camel case with underscore prefix (`_entityLocations`, `_world`)
|
||||||
|
- **Fields (public/internal)**: Camel case, no prefix for struct fields (`archetypeID`, `chunkIndex`)
|
||||||
|
- **Type parameters**: Single letter or PascalCase (`T`, `TComponent`)
|
||||||
|
- **Constants**: PascalCase (no SCREAMING_SNAKE_CASE)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class EntityManager
|
||||||
|
{
|
||||||
|
private readonly World _world; // Private field
|
||||||
|
private UnsafeSlotMap<EntityLocation> _entityLocations;
|
||||||
|
|
||||||
|
public World World => _world; // Property
|
||||||
|
|
||||||
|
public Entity CreateEntity() { } // Method
|
||||||
|
}
|
||||||
|
|
||||||
|
internal struct EntityLocation // Struct
|
||||||
|
{
|
||||||
|
public int archetypeID; // Public struct field
|
||||||
|
public int chunkIndex;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
**Use Result Types** - Railway-oriented programming pattern:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Custom result types defined in Ghost.Core
|
||||||
|
public ErrorStatus DoOperation()
|
||||||
|
{
|
||||||
|
return ErrorStatus.None; // or ErrorStatus.NotFound, etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result<T> GetValue()
|
||||||
|
{
|
||||||
|
if (success)
|
||||||
|
return Result<T>.Success(value);
|
||||||
|
else
|
||||||
|
return Result<T>.Failure("Error message");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result<T, ErrorStatus> GetValueWithStatus()
|
||||||
|
{
|
||||||
|
if (success)
|
||||||
|
return value; // Implicit conversion
|
||||||
|
else
|
||||||
|
return ErrorStatus.NotFound; // Implicit conversion
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extension methods for checking results
|
||||||
|
result.ThrowIfFailed();
|
||||||
|
var value = result.GetValueOrThrow();
|
||||||
|
var value = result.GetValueOrDefault(defaultValue);
|
||||||
|
if (result.TryGetValue(out var value)) { }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Error Status Values**: None, NotFound, InvalidArgument, InvalidState, InternalError, PermissionDenied, NotSupported, OutOfMemory, Timeout, Cancelled, UnknownError
|
||||||
|
|
||||||
|
### Memory and Performance
|
||||||
|
|
||||||
|
- **Use unsafe code** when needed for performance-critical paths
|
||||||
|
- **Span<T> and stackalloc**: Prefer for temporary allocations
|
||||||
|
- **ref returns**: Use for zero-copy access to internal data
|
||||||
|
- **Allocator patterns**: Use `Allocator.Persistent` for long-lived allocations
|
||||||
|
- **AllocationManager**: Create stack scopes for temporary allocations
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Stack allocation pattern
|
||||||
|
var entities = (Span<Entity>)stackalloc Entity[1];
|
||||||
|
|
||||||
|
// Using allocation scope
|
||||||
|
using var scope = AllocationManager.CreateStackScope();
|
||||||
|
var batchDestroy = new UnsafeList<EntityLocation>(entities.Length, scope.AllocationHandle);
|
||||||
|
|
||||||
|
// Ref returns for zero-copy access
|
||||||
|
public ref T GetSingleton<T>() where T : unmanaged, IComponent
|
||||||
|
{
|
||||||
|
var ptr = GetSingleton(ComponentTypeID<T>.Value);
|
||||||
|
return ref *(T*)ptr;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type Safety Patterns
|
||||||
|
|
||||||
|
**Strongly-typed identifiers**:
|
||||||
|
```csharp
|
||||||
|
Identifier<IComponent> componentID;
|
||||||
|
Identifier<Archetype> archetypeID;
|
||||||
|
Handle<T> resourceHandle;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Generic constraints**:
|
||||||
|
```csharp
|
||||||
|
public void Method<T>() where T : unmanaged, IComponent
|
||||||
|
public void Method<T, E>() where E : struct, Enum
|
||||||
|
```
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- **XML comments**: Required for public APIs
|
||||||
|
- **Summary tags**: Describe what, not how
|
||||||
|
- **Remarks**: Add for complex behavior, thread-safety warnings, structural changes
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
/// <summary>
|
||||||
|
/// Create an entity with specified components.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="set">A set of component space IDs to add to the entities.</param>
|
||||||
|
/// <returns>The created entity.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method causes structural changes and is not thread-safe.
|
||||||
|
/// Use <see cref="EntityCommandBuffer"/> to defer changes.
|
||||||
|
/// </remarks>
|
||||||
|
public Entity CreateEntity(ComponentSet set) { }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Patterns
|
||||||
|
|
||||||
|
**ECS Component Registration**:
|
||||||
|
```csharp
|
||||||
|
// Type-safe component ID
|
||||||
|
ComponentTypeID<Transform>.Value
|
||||||
|
|
||||||
|
// Component sets for archetypes
|
||||||
|
var set = new ComponentSet(ComponentTypeID<Transform>.Value, ComponentTypeID<Velocity>.Value);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Disposal Pattern**:
|
||||||
|
```csharp
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
~MyClass()
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed) return;
|
||||||
|
|
||||||
|
// Cleanup code
|
||||||
|
_disposed = true;
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Debug-only validation**:
|
||||||
|
```csharp
|
||||||
|
#if DEBUG || GHOST_EDITOR
|
||||||
|
if (!_isSuccess)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Error: {_message}");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture Notes
|
||||||
|
|
||||||
|
### Entity Component System (ECS)
|
||||||
|
- Archetype-based storage (similar to Unity DOTS)
|
||||||
|
- Component data stored in chunks
|
||||||
|
- Queries use bitset signatures for fast matching
|
||||||
|
- Structural changes move entities between archetypes
|
||||||
|
|
||||||
|
### Graphics (D3D12)
|
||||||
|
- Hardware abstraction via `ICommandBuffer`
|
||||||
|
- Resource lifetime managed via handles
|
||||||
|
- Pipeline state objects (PSO) cached in library
|
||||||
|
- Native interop via TerraFX.Interop
|
||||||
|
|
||||||
|
### Custom Dependencies
|
||||||
|
- `Misaki.HighPerformance.*`: High-performance collections and utilities
|
||||||
|
- `TerraFX.Interop.*`: Native Windows/DirectX interop
|
||||||
|
- Custom source generators in `Ghost.Generator`
|
||||||
|
|
||||||
|
## Important Rules
|
||||||
|
|
||||||
|
1. **Never disable nullable warnings** - fix the root cause
|
||||||
|
2. **Use Result types** instead of throwing exceptions for expected failures
|
||||||
|
3. **Document thread-safety** in XML comments for public APIs
|
||||||
|
4. **AllowUnsafeBlocks** is enabled - use unsafe code when it improves performance
|
||||||
|
5. **Avoid collection expressions/initializers** (disabled in .editorconfig)
|
||||||
|
6. **Prefer explicit over implicit** - clarity over brevity
|
||||||
|
7. **Test changes** by running the appropriate test project executable
|
||||||
@@ -33,19 +33,6 @@ public struct KeywordsGroup
|
|||||||
public List<string> keywords;
|
public List<string> keywords;
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IPassDescriptor
|
|
||||||
{
|
|
||||||
public string Identifier
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct PropertyDescriptor
|
public struct PropertyDescriptor
|
||||||
{
|
{
|
||||||
public ShaderPropertyType type;
|
public ShaderPropertyType type;
|
||||||
@@ -53,30 +40,27 @@ public struct PropertyDescriptor
|
|||||||
public object? defaultValue;
|
public object? defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PassDescriptor : IPassDescriptor
|
public struct PassDescriptor
|
||||||
{
|
{
|
||||||
public string uniqueIdentifier = string.Empty;
|
public string identifier;
|
||||||
public string name = string.Empty;
|
public string name;
|
||||||
|
|
||||||
public ShaderEntryPoint taskShader;
|
public ShaderEntryPoint taskShader;
|
||||||
public ShaderEntryPoint meshShader;
|
public ShaderEntryPoint meshShader;
|
||||||
public ShaderEntryPoint pixelShader;
|
public ShaderEntryPoint pixelShader;
|
||||||
public List<string>? defines;
|
public string[] defines;
|
||||||
public List<string>? includes;
|
public string[] includes;
|
||||||
public List<KeywordsGroup>? keywords;
|
public KeywordsGroup[] keywords;
|
||||||
public PipelineState localPipeline;
|
public PipelineState localPipeline;
|
||||||
|
|
||||||
public string Identifier => uniqueIdentifier;
|
|
||||||
public string Name => name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ShaderDescriptor
|
public class ShaderDescriptor
|
||||||
{
|
{
|
||||||
public string name = string.Empty;
|
public string name = string.Empty;
|
||||||
public uint cbufferSize;
|
public uint cbufferSize;
|
||||||
public List<PropertyDescriptor> globalProperties = new();
|
public PropertyDescriptor[] globalProperties = null!;
|
||||||
public List<PropertyDescriptor> properties = new();
|
public PropertyDescriptor[] properties = null!;
|
||||||
public List<IPassDescriptor> passes = new();
|
public PassDescriptor[] passes = null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ShaderDescriptorExtensions
|
public static class ShaderDescriptorExtensions
|
||||||
|
|||||||
@@ -6,6 +6,14 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Antlr4.CodeGenerator" Version="4.6.6">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Antlr4.Runtime.Standard" Version="4.13.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../Ghost.Core/Ghost.Core.csproj" />
|
<ProjectReference Include="../Ghost.Core/Ghost.Core.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -145,8 +145,13 @@ internal static class DSLShaderCompiler
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static uint CalculateCBufferSize(List<PropertyDescriptor> properties)
|
private static uint CalculateCBufferSize(ReadOnlySpan<PropertyDescriptor> properties)
|
||||||
{
|
{
|
||||||
|
if (properties.IsEmpty)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
var currentOffset = 0u;
|
var currentOffset = 0u;
|
||||||
|
|
||||||
foreach (var prop in properties)
|
foreach (var prop in properties)
|
||||||
@@ -180,7 +185,7 @@ internal static class DSLShaderCompiler
|
|||||||
name = p.name,
|
name = p.name,
|
||||||
type = p.type,
|
type = p.type,
|
||||||
defaultValue = p.defaultValue
|
defaultValue = p.defaultValue
|
||||||
}).ToList();
|
}).ToArray();
|
||||||
|
|
||||||
var shaderLocalProperties = semantics.properties?
|
var shaderLocalProperties = semantics.properties?
|
||||||
.Where(p => p.scope == PropertyScope.Local)
|
.Where(p => p.scope == PropertyScope.Local)
|
||||||
@@ -189,41 +194,37 @@ internal static class DSLShaderCompiler
|
|||||||
name = p.name,
|
name = p.name,
|
||||||
type = p.type,
|
type = p.type,
|
||||||
defaultValue = p.defaultValue
|
defaultValue = p.defaultValue
|
||||||
}).ToList();
|
}).ToArray();
|
||||||
|
|
||||||
if (shaderGlobalProperties != null)
|
descriptor.globalProperties = shaderGlobalProperties ?? Array.Empty<PropertyDescriptor>();
|
||||||
{
|
descriptor.properties = shaderLocalProperties ?? Array.Empty<PropertyDescriptor>();
|
||||||
descriptor.globalProperties ??= new List<PropertyDescriptor>();
|
descriptor.cbufferSize = CalculateCBufferSize(descriptor.properties);
|
||||||
descriptor.globalProperties.AddRange(shaderGlobalProperties);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shaderLocalProperties != null)
|
|
||||||
{
|
|
||||||
descriptor.properties ??= new List<PropertyDescriptor>();
|
|
||||||
descriptor.properties.AddRange(shaderLocalProperties);
|
|
||||||
descriptor.cbufferSize = CalculateCBufferSize(descriptor.properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (semantics.passes != null)
|
if (semantics.passes != null)
|
||||||
{
|
{
|
||||||
foreach (var pass in semantics.passes)
|
descriptor.passes = new PassDescriptor[semantics.passes.Count];
|
||||||
|
for (int i = 0; i < semantics.passes.Count; i++)
|
||||||
{
|
{
|
||||||
|
var pass = semantics.passes[i];
|
||||||
var localPipeline = MeragePipeline(pass.localPipeline, PipelineState.Default);
|
var localPipeline = MeragePipeline(pass.localPipeline, PipelineState.Default);
|
||||||
var fullPass = new PassDescriptor
|
descriptor.passes[i] = new PassDescriptor
|
||||||
{
|
{
|
||||||
uniqueIdentifier = GetPassUniqueId(semantics, pass),
|
identifier = GetPassUniqueId(semantics, pass),
|
||||||
name = pass.name,
|
name = pass.name,
|
||||||
taskShader = pass.taskShader,
|
taskShader = pass.taskShader,
|
||||||
meshShader = pass.meshShader,
|
meshShader = pass.meshShader,
|
||||||
pixelShader = pass.pixelShader,
|
pixelShader = pass.pixelShader,
|
||||||
localPipeline = localPipeline,
|
localPipeline = localPipeline,
|
||||||
defines = pass.defines,
|
defines = pass.defines?.ToArray() ?? Array.Empty<string>(),
|
||||||
keywords = pass.keywords,
|
includes = pass.includes?.ToArray() ?? Array.Empty<string>(),
|
||||||
|
keywords = pass.keywords?.ToArray() ?? Array.Empty<KeywordsGroup>()
|
||||||
};
|
};
|
||||||
|
|
||||||
descriptor.passes.Add(fullPass);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
descriptor.passes = Array.Empty<PassDescriptor>();
|
||||||
|
}
|
||||||
|
|
||||||
return descriptor;
|
return descriptor;
|
||||||
}
|
}
|
||||||
@@ -237,6 +238,11 @@ internal static class DSLShaderCompiler
|
|||||||
var lexer = new Lexer(source);
|
var lexer = new Lexer(source);
|
||||||
var stream = new TokenStream(lexer.Tokenize());
|
var stream = new TokenStream(lexer.Tokenize());
|
||||||
var shaderInfo = ParseShaders(stream);
|
var shaderInfo = ParseShaders(stream);
|
||||||
|
if (shaderInfo.Count == 0)
|
||||||
|
{
|
||||||
|
return Result.Failure("No shader found in the provided file.");
|
||||||
|
}
|
||||||
|
|
||||||
var model = SemanticAnalysis(shaderInfo[0], out var errors);
|
var model = SemanticAnalysis(shaderInfo[0], out var errors);
|
||||||
|
|
||||||
if (errors.Count != 0 || model == null)
|
if (errors.Count != 0 || model == null)
|
||||||
@@ -263,14 +269,21 @@ internal static class DSLShaderCompiler
|
|||||||
return Result.Failure("Failed to generate pass files: " + generatedResult.Message);
|
return Result.Failure("Failed to generate pass files: " + generatedResult.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var pass in desc.passes)
|
foreach (ref var pass in desc.passes.AsSpan())
|
||||||
{
|
{
|
||||||
if (pass is PassDescriptor fullPass)
|
if (pass.includes == null)
|
||||||
{
|
{
|
||||||
fullPass.includes ??= new List<string>();
|
pass.includes = new string[2];
|
||||||
fullPass.includes.Add(globalPropResult.Value);
|
|
||||||
fullPass.includes.Add(generatedResult.Value);
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Array.Resize(ref pass.includes, pass.includes.Length + 2);
|
||||||
|
// Shift existing includes to make room for the two new includes at the front.
|
||||||
|
pass.includes.AsSpan(0, pass.includes.Length - 2).CopyTo(pass.includes.AsSpan(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
pass.includes[0] = globalPropResult.Value;
|
||||||
|
pass.includes[1] = generatedResult.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return desc;
|
return desc;
|
||||||
@@ -360,7 +373,7 @@ struct PerMaterialData
|
|||||||
return outputFilePath;
|
return outputFilePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Result<string> GenerateGlobalProperties(List<PropertyDescriptor> globalProperties, string targetDirectory)
|
public static Result<string> GenerateGlobalProperties(ReadOnlySpan<PropertyDescriptor> globalProperties, string targetDirectory)
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(targetDirectory))
|
if (!Directory.Exists(targetDirectory))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ internal class PassSemantic
|
|||||||
public ShaderEntryPoint meshShader;
|
public ShaderEntryPoint meshShader;
|
||||||
public ShaderEntryPoint pixelShader;
|
public ShaderEntryPoint pixelShader;
|
||||||
public List<string>? defines;
|
public List<string>? defines;
|
||||||
|
public List<string>? includes;
|
||||||
public List<KeywordsGroup>? keywords;
|
public List<KeywordsGroup>? keywords;
|
||||||
public PipelineSemantic? localPipeline;
|
public PipelineSemantic? localPipeline;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ internal struct PropertyDeclaration
|
|||||||
public Token scope;
|
public Token scope;
|
||||||
public Token type;
|
public Token type;
|
||||||
public Token name;
|
public Token name;
|
||||||
public FunctionCallDeclaration? propertyConstructor;
|
public List<Token>? propertyInitializer;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal struct ValueDeclaration
|
internal struct ValueDeclaration
|
||||||
@@ -44,7 +44,7 @@ internal class PassSyntax
|
|||||||
public HlslDeclaration? hlsl;
|
public HlslDeclaration? hlsl;
|
||||||
public List<Token>? defines;
|
public List<Token>? defines;
|
||||||
public List<Token>? includes;
|
public List<Token>? includes;
|
||||||
public List<FunctionCallDeclaration>? keywords;
|
public List<List<Token>>? keywords;
|
||||||
public List<FunctionCallDeclaration>? functionCalls;
|
public List<FunctionCallDeclaration>? functionCalls;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,26 +2,42 @@ using Ghost.Core.Graphics;
|
|||||||
|
|
||||||
namespace Ghost.DSL.ShaderCompiler.Parser;
|
namespace Ghost.DSL.ShaderCompiler.Parser;
|
||||||
|
|
||||||
internal class KeywordsBlock : IBlockParser<List<FunctionCallDeclaration>, List<KeywordsGroup>>
|
internal class KeywordsBlock : IBlockParser<List<List<Token>>, List<KeywordsGroup>>
|
||||||
{
|
{
|
||||||
public static bool ShouldEnter(Token token)
|
public static bool ShouldEnter(Token token)
|
||||||
{
|
{
|
||||||
return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.KEYWORDS);
|
return token.Match(TokenType.Keyword, TokenLexicon.KnownKeywords.KEYWORDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<FunctionCallDeclaration> Parse(TokenStreamSlice stream)
|
public static List<List<Token>> Parse(TokenStreamSlice stream)
|
||||||
{
|
{
|
||||||
stream.Expect(TokenType.Keyword);
|
stream.Expect(TokenType.Keyword);
|
||||||
stream.Expect(TokenType.LBrace);
|
stream.Expect(TokenType.LBrace);
|
||||||
|
|
||||||
var keywords = new List<FunctionCallDeclaration>();
|
var keywords = new List<List<Token>>();
|
||||||
|
|
||||||
var bodyStream = stream.Slice(stream.Remaining - 1);
|
var bodyStream = stream.Slice(stream.Remaining - 1);
|
||||||
while (bodyStream.HasMore)
|
while (bodyStream.HasMore)
|
||||||
{
|
{
|
||||||
var keywordToken = bodyStream.Expect(TokenType.Identifier);
|
var keys = new List<Token>();
|
||||||
var args = ParseUtility.ParseFunctionArguments(ref bodyStream, TokenType.Identifier);
|
while (!bodyStream.Match(TokenType.Semicolon))
|
||||||
keywords.Add(new FunctionCallDeclaration { name = keywordToken, arguments = args });
|
{
|
||||||
|
var expectType = TokenType.Identifier;
|
||||||
|
if (keys.Count == 0)
|
||||||
|
{
|
||||||
|
expectType |= TokenType.Keyword;
|
||||||
|
}
|
||||||
|
|
||||||
|
var argument = bodyStream.Expect(expectType);
|
||||||
|
keys.Add(argument);
|
||||||
|
|
||||||
|
if (bodyStream.Match(TokenType.Comma))
|
||||||
|
{
|
||||||
|
bodyStream.Consume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keywords.Add(keys);
|
||||||
bodyStream.Expect(TokenType.Semicolon);
|
bodyStream.Expect(TokenType.Semicolon);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +46,7 @@ internal class KeywordsBlock : IBlockParser<List<FunctionCallDeclaration>, List<
|
|||||||
return keywords;
|
return keywords;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<KeywordsGroup>? SemanticAnalysis(List<FunctionCallDeclaration>? syntax, List<DSLShaderError> errors)
|
public static List<KeywordsGroup>? SemanticAnalysis(List<List<Token>>? syntax, List<DSLShaderError> errors)
|
||||||
{
|
{
|
||||||
if (syntax == null)
|
if (syntax == null)
|
||||||
{
|
{
|
||||||
@@ -38,42 +54,42 @@ internal class KeywordsBlock : IBlockParser<List<FunctionCallDeclaration>, List<
|
|||||||
}
|
}
|
||||||
|
|
||||||
var keywords = new List<KeywordsGroup>(syntax.Count);
|
var keywords = new List<KeywordsGroup>(syntax.Count);
|
||||||
foreach (var keyword in syntax)
|
foreach (var keys in syntax)
|
||||||
{
|
{
|
||||||
if (keyword.arguments == null || keyword.arguments.Count == 0)
|
if (keys.Count == 0)
|
||||||
{
|
{
|
||||||
errors.Add(new DSLShaderError
|
|
||||||
{
|
|
||||||
message = $"Function '{keyword.name.lexeme}' must have at least one argument.",
|
|
||||||
line = keyword.name.line,
|
|
||||||
column = keyword.name.column
|
|
||||||
});
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var group = new KeywordsGroup();
|
var group = new KeywordsGroup();
|
||||||
switch (keyword.name.lexeme)
|
group.space = keys[0].lexeme switch
|
||||||
{
|
{
|
||||||
case TokenLexicon.KnownFunctions.LOCAL:
|
TokenLexicon.KnownFunctions.LOCAL => KeywordSpace.Local,
|
||||||
group.space = KeywordSpace.Local;
|
TokenLexicon.KnownFunctions.GLOBAL => KeywordSpace.Global,
|
||||||
break;
|
_ => KeywordSpace.Local
|
||||||
case TokenLexicon.KnownFunctions.GLOBAL:
|
};
|
||||||
group.space = KeywordSpace.Global;
|
|
||||||
break;
|
for (var i = 0; i < keys.Count; i++)
|
||||||
default:
|
{
|
||||||
|
var token = keys[i];
|
||||||
|
if (i == 0 && token.type == TokenType.Keyword)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.type != TokenType.Identifier)
|
||||||
|
{
|
||||||
errors.Add(new DSLShaderError
|
errors.Add(new DSLShaderError
|
||||||
{
|
{
|
||||||
message = $"Unknown function name '{keyword.name.lexeme}'.",
|
message = $"Invalid keyword '{token.lexeme}' in keywords block.",
|
||||||
line = keyword.name.line,
|
line = token.line,
|
||||||
column = keyword.name.column
|
column = token.column
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var arg in keyword.arguments)
|
group.keywords ??= new List<string>(keys.Count);
|
||||||
{
|
group.keywords.Add(token.lexeme);
|
||||||
group.keywords ??= new List<string>(keyword.arguments.Count);
|
|
||||||
group.keywords.Add(arg.lexeme);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
keywords.Add(group);
|
keywords.Add(group);
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ internal class PassBlock : IBlockParser<PassSyntax, PassSemantic>
|
|||||||
{
|
{
|
||||||
name = syntax.name.lexeme,
|
name = syntax.name.lexeme,
|
||||||
defines = DefinesBlock.SemanticAnalysis(syntax.defines, errors),
|
defines = DefinesBlock.SemanticAnalysis(syntax.defines, errors),
|
||||||
|
includes = IncludesBlock.SemanticAnalysis(syntax.includes, errors),
|
||||||
keywords = KeywordsBlock.SemanticAnalysis(syntax.keywords, errors),
|
keywords = KeywordsBlock.SemanticAnalysis(syntax.keywords, errors),
|
||||||
localPipeline = PipelineBlock.SemanticAnalysis(syntax.localPipeline, errors),
|
localPipeline = PipelineBlock.SemanticAnalysis(syntax.localPipeline, errors),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -218,14 +218,18 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
|
|||||||
{
|
{
|
||||||
case TokenType.Equals:
|
case TokenType.Equals:
|
||||||
{
|
{
|
||||||
var constructorTypeToken = bodyStream.Expect(TokenType.Identifier);
|
bodyStream.Expect(TokenType.LBrace);
|
||||||
var args = ParseUtility.ParseFunctionArguments(ref bodyStream, TokenType.Identifier | TokenType.Number);
|
while (!bodyStream.Match(TokenType.RBrace))
|
||||||
shaderProperty.propertyConstructor = new FunctionCallDeclaration
|
|
||||||
{
|
{
|
||||||
name = constructorTypeToken,
|
var token = bodyStream.Consume();
|
||||||
arguments = args
|
if (!token.Match(TokenType.Comma))
|
||||||
};
|
{
|
||||||
|
shaderProperty.propertyInitializer ??= new List<Token>();
|
||||||
|
shaderProperty.propertyInitializer.Add(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyStream.Expect(TokenType.RBrace);
|
||||||
bodyStream.Expect(TokenType.Semicolon);
|
bodyStream.Expect(TokenType.Semicolon);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -282,9 +286,9 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (property.propertyConstructor != null)
|
if (property.propertyInitializer != null)
|
||||||
{
|
{
|
||||||
flowControl = ValidatePropertyConstructor(errors, property, model);
|
flowControl = ValidatePropertyInitializer(errors, property, model);
|
||||||
if (!flowControl)
|
if (!flowControl)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -346,14 +350,14 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ValidatePropertyConstructor(List<DSLShaderError> errors, PropertyDeclaration property, PropertySemantic model)
|
private static bool ValidatePropertyInitializer(List<DSLShaderError> errors, PropertyDeclaration property, PropertySemantic model)
|
||||||
{
|
{
|
||||||
var constructor = property.propertyConstructor;
|
var initializer = property.propertyInitializer;
|
||||||
if (!constructor.HasValue)
|
if (initializer == null)
|
||||||
{
|
{
|
||||||
errors.Add(new DSLShaderError
|
errors.Add(new DSLShaderError
|
||||||
{
|
{
|
||||||
message = "Shader property constructor is null.",
|
message = "Shader property initializer is null.",
|
||||||
line = property.name.line,
|
line = property.name.line,
|
||||||
column = property.name.column
|
column = property.name.column
|
||||||
});
|
});
|
||||||
@@ -361,63 +365,25 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var constructorValue = constructor.Value;
|
|
||||||
if (string.IsNullOrWhiteSpace(constructorValue.name.lexeme))
|
|
||||||
{
|
|
||||||
errors.Add(new DSLShaderError
|
|
||||||
{
|
|
||||||
message = "Shader property constructor has an empty name.",
|
|
||||||
line = constructorValue.name.line,
|
|
||||||
column = constructorValue.name.column
|
|
||||||
});
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (constructorValue.name.lexeme != property.type.lexeme)
|
|
||||||
{
|
|
||||||
errors.Add(new DSLShaderError
|
|
||||||
{
|
|
||||||
message = $"Shader property constructor name '{constructorValue.name.lexeme}' does not match property type '{property.type.lexeme}'.",
|
|
||||||
line = constructorValue.name.line,
|
|
||||||
column = constructorValue.name.column
|
|
||||||
});
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!s_propTypeInfo.TryGetValue(model.type, out var info))
|
if (!s_propTypeInfo.TryGetValue(model.type, out var info))
|
||||||
{
|
{
|
||||||
errors.Add(new DSLShaderError
|
errors.Add(new DSLShaderError
|
||||||
{
|
{
|
||||||
message = $"No constructor metadata registered for property type '{model.type}'.",
|
message = $"No initializer metadata registered for property type '{model.type}'.",
|
||||||
line = constructorValue.name.line,
|
line = property.name.line,
|
||||||
column = constructorValue.name.column
|
column = property.name.column
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count check
|
if (initializer.Count != info.ArgCount)
|
||||||
if (constructorValue.arguments == null)
|
|
||||||
{
|
{
|
||||||
errors.Add(new DSLShaderError
|
errors.Add(new DSLShaderError
|
||||||
{
|
{
|
||||||
message = "Shader property constructor arguments are null.",
|
message = $"Shader property constructor for type '{property.type.lexeme}' expects {info.ArgCount} argument(s), but got {initializer.Count}.",
|
||||||
line = constructorValue.name.line,
|
line = property.name.line,
|
||||||
column = constructorValue.name.column
|
column = property.name.column
|
||||||
});
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (constructorValue.arguments.Count != info.ArgCount)
|
|
||||||
{
|
|
||||||
errors.Add(new DSLShaderError
|
|
||||||
{
|
|
||||||
message = $"Shader property constructor for type '{property.type.lexeme}' expects {info.ArgCount} argument(s), but got {constructorValue.arguments.Count}.",
|
|
||||||
line = constructorValue.name.line,
|
|
||||||
column = constructorValue.name.column
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -425,9 +391,9 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
|
|||||||
|
|
||||||
// Type check (uniform requirement for all args)
|
// Type check (uniform requirement for all args)
|
||||||
var hasError = false;
|
var hasError = false;
|
||||||
for (var i = 0; i < constructorValue.arguments.Count; i++)
|
for (var i = 0; i < initializer.Count; i++)
|
||||||
{
|
{
|
||||||
var arg = constructorValue.arguments[i];
|
var arg = initializer[i];
|
||||||
if (!arg.Match(info.ArgTokenType))
|
if (!arg.Match(info.ArgTokenType))
|
||||||
{
|
{
|
||||||
errors.Add(new DSLShaderError
|
errors.Add(new DSLShaderError
|
||||||
@@ -451,15 +417,15 @@ internal class PropertiesBlock : IBlockParser<PropertiesSyntax, List<PropertySem
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
model.defaultValue = info.Builder(constructorValue.arguments, errors);
|
model.defaultValue = info.Builder(initializer, errors);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
errors.Add(new DSLShaderError
|
errors.Add(new DSLShaderError
|
||||||
{
|
{
|
||||||
message = $"Failed to construct default value for property '{property.name.lexeme}': {ex.Message}",
|
message = $"Failed to construct default value for property '{property.name.lexeme}': {ex.Message}",
|
||||||
line = constructorValue.name.line,
|
line = property.name.line,
|
||||||
column = constructorValue.name.column
|
column = property.name.column
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -2,12 +2,17 @@ namespace Ghost.DSL.ShaderCompiler;
|
|||||||
|
|
||||||
internal static class TokenStreamImple
|
internal static class TokenStreamImple
|
||||||
{
|
{
|
||||||
public static Token Peek(ReadOnlySpan<Token> tokens, ref int index, int length)
|
public static Token Peek(ReadOnlySpan<Token> tokens, int index, int length)
|
||||||
{
|
{
|
||||||
return index + length < tokens.Length ? tokens[index + length] : throw new InvalidOperationException("No more tokens available");
|
if (index + length < tokens.Length)
|
||||||
|
{
|
||||||
|
return tokens[index + length];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IndexOutOfRangeException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryPeek(ReadOnlySpan<Token> tokens, ref int index, int length, out Token token)
|
public static bool TryPeek(ReadOnlySpan<Token> tokens, int index, int length, out Token token)
|
||||||
{
|
{
|
||||||
if (index + length < tokens.Length)
|
if (index + length < tokens.Length)
|
||||||
{
|
{
|
||||||
@@ -31,29 +36,33 @@ internal static class TokenStreamImple
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Token Consume(ReadOnlySpan<Token> tokens, ref int index)
|
public static Token Consume(ReadOnlySpan<Token> tokens, ref int index, int count = 1)
|
||||||
{
|
{
|
||||||
return index < tokens.Length ? tokens[index++] : throw new InvalidOperationException("No more tokens available");
|
if (index + count <= tokens.Length)
|
||||||
|
{
|
||||||
|
index += count;
|
||||||
|
return tokens[index - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IndexOutOfRangeException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool Match(ReadOnlySpan<Token> tokens, ref int index, TokenType type, string? lexeme)
|
public static bool Match(ReadOnlySpan<Token> tokens, int index, TokenType type, string? lexeme)
|
||||||
{
|
{
|
||||||
var t = Peek(tokens, ref index, 0);
|
var t = Peek(tokens, index, 0);
|
||||||
if (!t.Match(type, lexeme))
|
if (!t.Match(type, lexeme))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//index++;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int MatchMany(ReadOnlySpan<Token> tokens, ref int index, TokenType type, string? lexeme)
|
public static int MatchMany(ReadOnlySpan<Token> tokens, int index, TokenType type, string? lexeme)
|
||||||
{
|
{
|
||||||
var count = 0;
|
var count = 0;
|
||||||
while (TryPeek(tokens, ref index, 0, out var t) && t.Match(type, lexeme))
|
while (TryPeek(tokens, index, 0, out var t) && t.Match(type, lexeme))
|
||||||
{
|
{
|
||||||
index++;
|
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +71,7 @@ internal static class TokenStreamImple
|
|||||||
|
|
||||||
public static Token Expect(ReadOnlySpan<Token> tokens, ref int index, TokenType type, string? lexeme)
|
public static Token Expect(ReadOnlySpan<Token> tokens, ref int index, TokenType type, string? lexeme)
|
||||||
{
|
{
|
||||||
if (!TryPeek(tokens, ref index, 0, out var t))
|
if (!TryPeek(tokens, index, 0, out var t))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Expected token but reached end of stream");
|
throw new InvalidOperationException("Expected token but reached end of stream");
|
||||||
}
|
}
|
||||||
@@ -110,17 +119,17 @@ internal class TokenStream
|
|||||||
|
|
||||||
public Token Peek(int length = 0)
|
public Token Peek(int length = 0)
|
||||||
{
|
{
|
||||||
return TokenStreamImple.Peek(_tokens, ref _index, length);
|
return TokenStreamImple.Peek(_tokens, _index, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryPeek(out Token token)
|
public bool TryPeek(out Token token)
|
||||||
{
|
{
|
||||||
return TokenStreamImple.TryPeek(_tokens, ref _index, 0, out token);
|
return TokenStreamImple.TryPeek(_tokens, _index, 0, out token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryPeek(int length, out Token token)
|
public bool TryPeek(int length, out Token token)
|
||||||
{
|
{
|
||||||
return TokenStreamImple.TryPeek(_tokens, ref _index, length, out token);
|
return TokenStreamImple.TryPeek(_tokens, _index, length, out token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryConsume(out Token token)
|
public bool TryConsume(out Token token)
|
||||||
@@ -128,19 +137,19 @@ internal class TokenStream
|
|||||||
return TokenStreamImple.TryConsume(_tokens, ref _index, out token);
|
return TokenStreamImple.TryConsume(_tokens, ref _index, out token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Token Consume()
|
public Token Consume(int count = 1)
|
||||||
{
|
{
|
||||||
return TokenStreamImple.Consume(_tokens, ref _index);
|
return TokenStreamImple.Consume(_tokens, ref _index, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Match(TokenType type, string? lexeme = null)
|
public bool Match(TokenType type, string? lexeme = null)
|
||||||
{
|
{
|
||||||
return TokenStreamImple.Match(_tokens, ref _index, type, lexeme);
|
return TokenStreamImple.Match(_tokens, _index, type, lexeme);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int MatchMany(TokenType type, string? lexeme = null)
|
public int MatchMany(TokenType type, string? lexeme = null)
|
||||||
{
|
{
|
||||||
return TokenStreamImple.MatchMany(_tokens, ref _index, type, lexeme);
|
return TokenStreamImple.MatchMany(_tokens, _index, type, lexeme);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Token Expect(TokenType type, string? lexeme = null)
|
public Token Expect(TokenType type, string? lexeme = null)
|
||||||
@@ -224,17 +233,17 @@ internal ref struct TokenStreamSlice
|
|||||||
|
|
||||||
public Token Peek(int length = 0)
|
public Token Peek(int length = 0)
|
||||||
{
|
{
|
||||||
return TokenStreamImple.Peek(_tokens, ref _index, length);
|
return TokenStreamImple.Peek(_tokens, _index, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryPeek(out Token token)
|
public bool TryPeek(out Token token)
|
||||||
{
|
{
|
||||||
return TokenStreamImple.TryPeek(_tokens, ref _index, 0, out token);
|
return TokenStreamImple.TryPeek(_tokens, _index, 0, out token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryPeek(int length, out Token token)
|
public bool TryPeek(int length, out Token token)
|
||||||
{
|
{
|
||||||
return TokenStreamImple.TryPeek(_tokens, ref _index, length, out token);
|
return TokenStreamImple.TryPeek(_tokens, _index, length, out token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryConsume(out Token token)
|
public bool TryConsume(out Token token)
|
||||||
@@ -242,19 +251,19 @@ internal ref struct TokenStreamSlice
|
|||||||
return TokenStreamImple.TryConsume(_tokens, ref _index, out token);
|
return TokenStreamImple.TryConsume(_tokens, ref _index, out token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Token Consume()
|
public Token Consume(int count = 1)
|
||||||
{
|
{
|
||||||
return TokenStreamImple.Consume(_tokens, ref _index);
|
return TokenStreamImple.Consume(_tokens, ref _index, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Match(TokenType type, string? lexeme = null)
|
public bool Match(TokenType type, string? lexeme = null)
|
||||||
{
|
{
|
||||||
return TokenStreamImple.Match(_tokens, ref _index, type, lexeme);
|
return TokenStreamImple.Match(_tokens, _index, type, lexeme);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int MatchMany(TokenType type, string? lexeme = null)
|
public int MatchMany(TokenType type, string? lexeme = null)
|
||||||
{
|
{
|
||||||
return TokenStreamImple.MatchMany(_tokens, ref _index, type, lexeme);
|
return TokenStreamImple.MatchMany(_tokens, _index, type, lexeme);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Token Expect(TokenType type, string? lexeme = null)
|
public Token Expect(TokenType type, string? lexeme = null)
|
||||||
|
|||||||
@@ -144,6 +144,6 @@ public readonly struct ShaderReflectionData
|
|||||||
public interface IShaderCompiler : IDisposable
|
public interface IShaderCompiler : IDisposable
|
||||||
{
|
{
|
||||||
Result<ShaderCompileResult> Compile(ref readonly ShaderCompilationConfig config, Allocator allocator);
|
Result<ShaderCompileResult> Compile(ref readonly ShaderCompilationConfig config, Allocator allocator);
|
||||||
Result<GraphicsCompiledResult> CompilePass(IPassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, Key64<ShaderVariant> key);
|
Result<GraphicsCompiledResult> CompilePass(ref readonly PassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, Key64<ShaderVariant> key);
|
||||||
Result<GraphicsCompiledResult, ErrorStatus> LoadCompiledCache(Key64<ShaderVariant> key);
|
Result<GraphicsCompiledResult, ErrorStatus> LoadCompiledCache(Key64<ShaderVariant> key);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -367,26 +367,23 @@ internal sealed unsafe partial class DxcShaderCompiler : IShaderCompiler
|
|||||||
|
|
||||||
// TODO: This should be shader variant specific compile instead of pass specific.
|
// TODO: This should be shader variant specific compile instead of pass specific.
|
||||||
// TODO: Build final shader code in memory before compiling.
|
// TODO: Build final shader code in memory before compiling.
|
||||||
public Result<GraphicsCompiledResult> CompilePass(IPassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, Key64<ShaderVariant> key)
|
public Result<GraphicsCompiledResult> CompilePass(ref readonly PassDescriptor descriptor, ref readonly ShaderCompilationConfig additionalConfig, Key64<ShaderVariant> key)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
|
|
||||||
if (descriptor is not PassDescriptor fullDescriptor)
|
var defineCountInDescriptor = descriptor.defines?.Length ?? 0;
|
||||||
{
|
var fullDefines = new string[defineCountInDescriptor + additionalConfig.defines.Length];
|
||||||
return Result.Failure("FullPassDescriptor expected.");
|
descriptor.defines?.CopyTo(fullDefines);
|
||||||
}
|
additionalConfig.defines.CopyTo(fullDefines.AsSpan(defineCountInDescriptor));
|
||||||
|
|
||||||
var fullDefines = fullDescriptor.defines ?? new List<string>();
|
|
||||||
fullDefines.AddRange(additionalConfig.defines);
|
|
||||||
|
|
||||||
ShaderCompileResult tsResult = default;
|
ShaderCompileResult tsResult = default;
|
||||||
var tsEntry = fullDescriptor.taskShader;
|
var tsEntry = descriptor.taskShader;
|
||||||
if (tsEntry.IsCreated)
|
if (tsEntry.IsCreated)
|
||||||
{
|
{
|
||||||
var config = new ShaderCompilationConfig
|
var config = new ShaderCompilationConfig
|
||||||
{
|
{
|
||||||
defines = fullDefines.AsSpan(),
|
defines = fullDefines.AsSpan(),
|
||||||
includes = fullDescriptor.includes.AsSpan(),
|
includes = descriptor.includes.AsSpan(),
|
||||||
shaderPath = tsEntry.shader,
|
shaderPath = tsEntry.shader,
|
||||||
entryPoint = tsEntry.entry,
|
entryPoint = tsEntry.entry,
|
||||||
stage = ShaderStage.TaskShader,
|
stage = ShaderStage.TaskShader,
|
||||||
@@ -405,13 +402,13 @@ internal sealed unsafe partial class DxcShaderCompiler : IShaderCompiler
|
|||||||
}
|
}
|
||||||
|
|
||||||
ShaderCompileResult msResult;
|
ShaderCompileResult msResult;
|
||||||
var msEntry = fullDescriptor.meshShader;
|
var msEntry = descriptor.meshShader;
|
||||||
if (msEntry.IsCreated)
|
if (msEntry.IsCreated)
|
||||||
{
|
{
|
||||||
var config = new ShaderCompilationConfig
|
var config = new ShaderCompilationConfig
|
||||||
{
|
{
|
||||||
defines = fullDefines.AsSpan(),
|
defines = fullDefines.AsSpan(),
|
||||||
includes = fullDescriptor.includes.AsSpan(),
|
includes = descriptor.includes.AsSpan(),
|
||||||
shaderPath = msEntry.shader,
|
shaderPath = msEntry.shader,
|
||||||
entryPoint = msEntry.entry,
|
entryPoint = msEntry.entry,
|
||||||
stage = ShaderStage.MeshShader,
|
stage = ShaderStage.MeshShader,
|
||||||
@@ -434,13 +431,13 @@ internal sealed unsafe partial class DxcShaderCompiler : IShaderCompiler
|
|||||||
}
|
}
|
||||||
|
|
||||||
ShaderCompileResult psResult;
|
ShaderCompileResult psResult;
|
||||||
var psEntry = fullDescriptor.pixelShader;
|
var psEntry = descriptor.pixelShader;
|
||||||
if (psEntry.IsCreated)
|
if (psEntry.IsCreated)
|
||||||
{
|
{
|
||||||
var config = new ShaderCompilationConfig
|
var config = new ShaderCompilationConfig
|
||||||
{
|
{
|
||||||
defines = fullDefines.AsSpan(),
|
defines = fullDefines.AsSpan(),
|
||||||
includes = fullDescriptor.includes.AsSpan(),
|
includes = descriptor.includes.AsSpan(),
|
||||||
shaderPath = psEntry.shader,
|
shaderPath = psEntry.shader,
|
||||||
entryPoint = psEntry.entry,
|
entryPoint = psEntry.entry,
|
||||||
stage = ShaderStage.PixelShader,
|
stage = ShaderStage.PixelShader,
|
||||||
|
|||||||
@@ -116,8 +116,8 @@ public readonly unsafe ref struct RenderingContext
|
|||||||
localToWorld = localToWorld,
|
localToWorld = localToWorld,
|
||||||
worldBoundsMin = meshData.BoundingBox.Min,
|
worldBoundsMin = meshData.BoundingBox.Min,
|
||||||
worldBoundsMax = meshData.BoundingBox.Max,
|
worldBoundsMax = meshData.BoundingBox.Max,
|
||||||
vertexBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.VertexBuffer.AsResource()).GetValueOrThrow(),
|
vertexBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.VertexBuffer.AsResource()),
|
||||||
indexBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.IndexBuffer.AsResource()).GetValueOrThrow(),
|
indexBuffer = _engine.ResourceDatabase.GetBindlessIndex(meshData.IndexBuffer.AsResource()),
|
||||||
};
|
};
|
||||||
|
|
||||||
var bufferHandle = meshData.ObjectDataBuffer.AsResource();
|
var bufferHandle = meshData.ObjectDataBuffer.AsResource();
|
||||||
@@ -214,8 +214,8 @@ public readonly unsafe ref struct RenderingContext
|
|||||||
|
|
||||||
var data = new PushConstantsData
|
var data = new PushConstantsData
|
||||||
{
|
{
|
||||||
objectIndex = _engine.ResourceDatabase.GetBindlessIndex(meshRef.ObjectDataBuffer.AsResource()).GetValueOrThrow(),
|
objectIndex = _engine.ResourceDatabase.GetBindlessIndex(meshRef.ObjectDataBuffer.AsResource()),
|
||||||
materialIndex = _engine.ResourceDatabase.GetBindlessIndex(materialRef._cBufferCache.GpuResource.AsResource()).GetValueOrThrow(),
|
materialIndex = _engine.ResourceDatabase.GetBindlessIndex(materialRef._cBufferCache.GpuResource.AsResource()),
|
||||||
};
|
};
|
||||||
|
|
||||||
var pushConstantSpan = new ReadOnlySpan<uint>(&data, sizeof(PushConstantsData) / sizeof(uint));
|
var pushConstantSpan = new ReadOnlySpan<uint>(&data, sizeof(PushConstantsData) / sizeof(uint));
|
||||||
|
|||||||
@@ -102,30 +102,23 @@ public partial struct Shader : IResourceReleasable
|
|||||||
internal Shader(ShaderDescriptor descriptor)
|
internal Shader(ShaderDescriptor descriptor)
|
||||||
{
|
{
|
||||||
_cbufferSize = descriptor.cbufferSize;
|
_cbufferSize = descriptor.cbufferSize;
|
||||||
_shaderPasses = new UnsafeArray<ShaderPass>(descriptor.passes.Count, Allocator.Persistent);
|
_shaderPasses = new UnsafeArray<ShaderPass>(descriptor.passes.Length, Allocator.Persistent);
|
||||||
_passIDToLocal = new UnsafeHashMap<int, int>(descriptor.passes.Count, Allocator.Persistent);
|
_passIDToLocal = new UnsafeHashMap<int, int>(descriptor.passes.Length, Allocator.Persistent);
|
||||||
_keywordIDToLocal = new UnsafeHashMap<int, int>(32, Allocator.Persistent);
|
_keywordIDToLocal = new UnsafeHashMap<int, int>(32, Allocator.Persistent);
|
||||||
|
|
||||||
for (var i = 0; i < descriptor.passes.Count; i++)
|
for (var i = 0; i < descriptor.passes.Length; i++)
|
||||||
{
|
{
|
||||||
var pass = descriptor.passes[i];
|
var pass = descriptor.passes[i];
|
||||||
|
var passKey = RHIUtility.CreateShaderPassKey(pass.identifier);
|
||||||
// TODO: Handle inherited passes
|
|
||||||
if (pass is not PassDescriptor fullPass)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var passKey = RHIUtility.CreateShaderPassKey(pass.Identifier);
|
|
||||||
var keywords = default(LocalKeywordSet);
|
var keywords = default(LocalKeywordSet);
|
||||||
|
|
||||||
if (fullPass.keywords != null && fullPass.keywords.Count > 0)
|
if (pass.keywords.Length > 0)
|
||||||
{
|
{
|
||||||
var localKeywordIndex = 0;
|
var localKeywordIndex = 0;
|
||||||
|
|
||||||
for (var j = 0; j < fullPass.keywords.Count; j++)
|
for (var j = 0; j < pass.keywords.Length; j++)
|
||||||
{
|
{
|
||||||
var group = fullPass.keywords[j];
|
var group = pass.keywords[j];
|
||||||
if (group.keywords == null)
|
if (group.keywords == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -150,11 +143,11 @@ public partial struct Shader : IResourceReleasable
|
|||||||
_shaderPasses[i] = new ShaderPass
|
_shaderPasses[i] = new ShaderPass
|
||||||
{
|
{
|
||||||
Key = passKey,
|
Key = passKey,
|
||||||
DeafaultState = fullPass.localPipeline,
|
DeafaultState = pass.localPipeline,
|
||||||
KeywordIDs = keywords,
|
KeywordIDs = keywords,
|
||||||
};
|
};
|
||||||
|
|
||||||
_passIDToLocal[GetPassID(pass.Name)] = (ushort)i;
|
_passIDToLocal[GetPassID(pass.name)] = (ushort)i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -244,14 +244,14 @@ internal class D3D12ResourceDatabase : IResourceDatabase
|
|||||||
return r.Value.desc;
|
return r.Value.desc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<uint, ErrorStatus> GetBindlessIndex(Handle<GPUResource> handle)
|
public uint GetBindlessIndex(Handle<GPUResource> handle)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
|
|
||||||
ref var info = ref GetResourceRecord(handle, out var exist);
|
ref var info = ref GetResourceRecord(handle, out var exist);
|
||||||
if (!exist || !info.Allocated)
|
if (!exist || !info.Allocated)
|
||||||
{
|
{
|
||||||
return ErrorStatus.NotFound;
|
return ~0u;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (uint)info.viewGroup.srv.Value;
|
return (uint)info.viewGroup.srv.Value;
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ public interface IResourceDatabase : IDisposable
|
|||||||
/// Retrieves the bindless index associated with the specified GPU resource handle.
|
/// Retrieves the bindless index associated with the specified GPU resource handle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="handle">A handle to the GPU resource for which to obtain the bindless index. Must reference a valid, currently registered resource.</param>
|
/// <param name="handle">A handle to the GPU resource for which to obtain the bindless index. Must reference a valid, currently registered resource.</param>
|
||||||
/// <returns>The bindless index corresponding to the specified GPU resource handle. -1 if the resource does not support bindless access or is not found.</returns>
|
/// <returns>The bindless index corresponding to the specified GPU resource handle. ~0 if the resource does not support bindless access or is not found.</returns>
|
||||||
Result<uint, ErrorStatus> GetBindlessIndex(Handle<GPUResource> handle);
|
uint GetBindlessIndex(Handle<GPUResource> handle);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the name of the GPU resource associated with the specified handle.
|
/// Retrieves the name of the GPU resource associated with the specified handle.
|
||||||
|
|||||||
@@ -48,22 +48,31 @@ internal class MeshRenderPass : IRenderPass
|
|||||||
"C:/Users/Misaki/Downloads/Im/yande.re 1134666 blue_archive nakamasa_ichika sugarhigh.jpg"
|
"C:/Users/Misaki/Downloads/Im/yande.re 1134666 blue_archive nakamasa_ichika sugarhigh.jpg"
|
||||||
];
|
];
|
||||||
|
|
||||||
private static IEnumerable<List<string>> GetAllVariantCombination(List<KeywordsGroup> keywordsGroups)
|
private static IEnumerable<ReadOnlyMemory<string>> GetAllVariantCombination(KeywordsGroup[] keywordsGroups)
|
||||||
{
|
{
|
||||||
if (keywordsGroups.Count == 0)
|
if (keywordsGroups.Length == 0)
|
||||||
{
|
{
|
||||||
yield return [];
|
yield return ReadOnlyMemory<string>.Empty;
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var firstGroup = keywordsGroups[0];
|
var firstGroup = keywordsGroups[0];
|
||||||
var remainingGroups = keywordsGroups.Skip(1).ToList();
|
var remainingGroups = keywordsGroups[1..];
|
||||||
|
|
||||||
|
foreach (var combination in GetAllVariantCombination(remainingGroups))
|
||||||
|
{
|
||||||
|
yield return combination;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var keyword in firstGroup.keywords)
|
foreach (var keyword in firstGroup.keywords)
|
||||||
{
|
{
|
||||||
foreach (var combination in GetAllVariantCombination(remainingGroups))
|
foreach (var combination in GetAllVariantCombination(remainingGroups))
|
||||||
{
|
{
|
||||||
combination.Insert(0, keyword);
|
var array = new string[combination.Length + 1];
|
||||||
yield return combination;
|
array[0] = keyword;
|
||||||
|
combination.Span.CopyTo(array.AsSpan(1));
|
||||||
|
|
||||||
|
yield return array;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,15 +84,9 @@ internal class MeshRenderPass : IRenderPass
|
|||||||
_shader = ctx.ResourceAllocator.CreateGraphicsShader(shaderDescriptor);
|
_shader = ctx.ResourceAllocator.CreateGraphicsShader(shaderDescriptor);
|
||||||
_material = ctx.ResourceAllocator.CreateMaterial(_shader);
|
_material = ctx.ResourceAllocator.CreateMaterial(_shader);
|
||||||
|
|
||||||
for (var i = 0; i < shaderDescriptor.passes.Count; i++)
|
for (var i = 0; i < shaderDescriptor.passes.Length; i++)
|
||||||
{
|
{
|
||||||
var pass = shaderDescriptor.passes[i];
|
ref var pass = ref shaderDescriptor.passes[i];
|
||||||
|
|
||||||
if (pass is not PassDescriptor fullPass)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var config = new ShaderCompilationConfig
|
var config = new ShaderCompilationConfig
|
||||||
{
|
{
|
||||||
optimizeLevel = CompilerOptimizeLevel.O3,
|
optimizeLevel = CompilerOptimizeLevel.O3,
|
||||||
@@ -94,25 +97,25 @@ internal class MeshRenderPass : IRenderPass
|
|||||||
// TODO: Ideally, in editor mode, we compile a single variant when it's needed during rendering. Before the compilation is done, we fallback to a special "compilation in progress" shader.
|
// TODO: Ideally, in editor mode, we compile a single variant when it's needed during rendering. Before the compilation is done, we fallback to a special "compilation in progress" shader.
|
||||||
// During the build process, we can precompile all the variants and store them in the cache for fast loading in runtime.
|
// During the build process, we can precompile all the variants and store them in the cache for fast loading in runtime.
|
||||||
// After the compilation, we should store the compiled result in the disk cache even in editor mode. This allows us to avoid recompiling the same variant, same code hash and same version) multiple times.
|
// After the compilation, we should store the compiled result in the disk cache even in editor mode. This allows us to avoid recompiling the same variant, same code hash and same version) multiple times.
|
||||||
if (fullPass.keywords == null)
|
if (pass.keywords.Length == 0)
|
||||||
{
|
{
|
||||||
var emptyKeywords = new LocalKeywordSet();
|
var emptyKeywords = new LocalKeywordSet();
|
||||||
var variantKey = RHIUtility.CreateShaderVariantKey(
|
var variantKey = RHIUtility.CreateShaderVariantKey(
|
||||||
RHIUtility.CreateShaderPassKey(pass.Identifier),
|
RHIUtility.CreateShaderPassKey(pass.identifier),
|
||||||
in emptyKeywords);
|
in emptyKeywords);
|
||||||
|
|
||||||
ctx.ShaderCompiler.CompilePass(pass, in config, variantKey).GetValueOrThrow();
|
ctx.ShaderCompiler.CompilePass(in pass, in config, variantKey).GetValueOrThrow();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ref var shaderRef = ref ctx.ResourceDatabase.GetShaderReference(_shader);
|
ref var shaderRef = ref ctx.ResourceDatabase.GetShaderReference(_shader);
|
||||||
|
|
||||||
foreach (var keyGroup in GetAllVariantCombination(fullPass.keywords))
|
foreach (var keyGroup in GetAllVariantCombination(pass.keywords))
|
||||||
{
|
{
|
||||||
config.defines = keyGroup.AsSpan();
|
config.defines = keyGroup.Span;
|
||||||
var keywordsSet = new LocalKeywordSet();
|
var keywordsSet = new LocalKeywordSet();
|
||||||
|
|
||||||
foreach (var key in keyGroup)
|
foreach (var key in keyGroup.Span)
|
||||||
{
|
{
|
||||||
var localIndex = shaderRef.GetLocalKeywordIndex(Shader.GetKeywordID(key));
|
var localIndex = shaderRef.GetLocalKeywordIndex(Shader.GetKeywordID(key));
|
||||||
if (localIndex == -1)
|
if (localIndex == -1)
|
||||||
@@ -124,10 +127,10 @@ internal class MeshRenderPass : IRenderPass
|
|||||||
}
|
}
|
||||||
|
|
||||||
var variantKey = RHIUtility.CreateShaderVariantKey(
|
var variantKey = RHIUtility.CreateShaderVariantKey(
|
||||||
RHIUtility.CreateShaderPassKey(pass.Identifier),
|
RHIUtility.CreateShaderPassKey(pass.identifier),
|
||||||
in keywordsSet);
|
in keywordsSet);
|
||||||
|
|
||||||
ctx.ShaderCompiler.CompilePass(pass, in config, variantKey).GetValueOrThrow();
|
ctx.ShaderCompiler.CompilePass(in pass, in config, variantKey).GetValueOrThrow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,10 +175,10 @@ internal class MeshRenderPass : IRenderPass
|
|||||||
var matProps = new ShaderProperties_MyShader_Standard
|
var matProps = new ShaderProperties_MyShader_Standard
|
||||||
{
|
{
|
||||||
color = new float4(1.0f, 1.0f, 1.0f, 1.0f),
|
color = new float4(1.0f, 1.0f, 1.0f, 1.0f),
|
||||||
texture1 = ctx.ResourceDatabase.GetBindlessIndex(_textures[0].AsResource()).GetValueOrThrow(),
|
texture1 = ctx.ResourceDatabase.GetBindlessIndex(_textures[0].AsResource()),
|
||||||
texture2 = ctx.ResourceDatabase.GetBindlessIndex(_textures[1].AsResource()).GetValueOrThrow(),
|
texture2 = ctx.ResourceDatabase.GetBindlessIndex(_textures[1].AsResource()),
|
||||||
texture3 = ctx.ResourceDatabase.GetBindlessIndex(_textures[2].AsResource()).GetValueOrThrow(),
|
texture3 = ctx.ResourceDatabase.GetBindlessIndex(_textures[2].AsResource()),
|
||||||
texture4 = ctx.ResourceDatabase.GetBindlessIndex(_textures[3].AsResource()).GetValueOrThrow(),
|
texture4 = ctx.ResourceDatabase.GetBindlessIndex(_textures[3].AsResource()),
|
||||||
tex_sampler = (uint)sampler.Value,
|
tex_sampler = (uint)sampler.Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ shader "MyShader/Standard"
|
|||||||
{
|
{
|
||||||
properties
|
properties
|
||||||
{
|
{
|
||||||
float4 color = float4(1, 1, 1, 1);
|
float4 color = { 1, 1, 1, 1 };
|
||||||
tex2d texture1 = tex2d(black);
|
tex2d texture1 = { black };
|
||||||
tex2d texture2 = tex2d(white);
|
tex2d texture2 = { white };
|
||||||
tex2d texture3 = tex2d(grey);
|
tex2d texture3 = { grey };
|
||||||
tex2d texture4 = tex2d(normal);
|
tex2d texture4 = { normal };
|
||||||
sampler tex_sampler;
|
sampler tex_sampler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,7 +21,21 @@ shader "MyShader/Standard"
|
|||||||
color_mask = all;
|
color_mask = all;
|
||||||
}
|
}
|
||||||
|
|
||||||
ms("F:/csharp/GhostEngine/Ghost.Graphics/RenderPasses/ShaderCode.hlsl", "MSMain");
|
keywords
|
||||||
ps("F:/csharp/GhostEngine/Ghost.Graphics/RenderPasses/ShaderCode.hlsl", "PSMain");
|
{
|
||||||
|
local TEST_KEYWORD, TEST_KEYWORD2;
|
||||||
|
local TEST_KEYWORD3;
|
||||||
|
}
|
||||||
|
|
||||||
|
hlsl
|
||||||
|
{
|
||||||
|
float Test()
|
||||||
|
{
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh "F:/csharp/GhostEngine/Ghost.Graphics/RenderPasses/ShaderCode.hlsl" : "MSMain";
|
||||||
|
pixel "F:/csharp/GhostEngine/Ghost.Graphics/RenderPasses/ShaderCode.hlsl" : "PSMain";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user