mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 21:01:52 +00:00
Started rewriting shader compilation/processing
This commit is contained in:
@@ -0,0 +1,51 @@
|
|||||||
|
using Bon;
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using GlitchyEngine.Content;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace GlitchyEditor.Assets.Importers;
|
||||||
|
|
||||||
|
class ImportedShader : ImportedResource
|
||||||
|
{
|
||||||
|
private String _hlslCode = new .() ~ delete _;
|
||||||
|
|
||||||
|
public StringView HlslCode
|
||||||
|
{
|
||||||
|
get => _hlslCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public this(AssetIdentifier ownAssetIdentifier) : base(ownAssetIdentifier)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[BonTarget, BonPolyRegister]
|
||||||
|
class ShaderImporterConfig : AssetImporterConfig
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShaderImporter : IAssetImporter
|
||||||
|
{
|
||||||
|
private static readonly List<StringView> _fileExtensions = new .(){".hlsl"} ~ delete _;
|
||||||
|
|
||||||
|
public static List<StringView> FileExtensions => _fileExtensions;
|
||||||
|
|
||||||
|
public static Type ProcessedAssetType => typeof(ImportedShader);
|
||||||
|
|
||||||
|
public AssetImporterConfig CreateDefaultConfig()
|
||||||
|
{
|
||||||
|
return new AssetImporterConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result<ImportedResource> Import(StringView fullFileName, AssetIdentifier assetIdentifier, AssetConfig config)
|
||||||
|
{
|
||||||
|
ImportedShader shader = new ImportedShader(new AssetIdentifier(assetIdentifier));
|
||||||
|
|
||||||
|
Try!(File.ReadAllText(fullFileName, shader.[Friend]_hlslCode, true));
|
||||||
|
shader.[Friend]_hlslCode.Append('\n');
|
||||||
|
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,475 @@
|
|||||||
|
using System;
|
||||||
|
using DirectX.Common;
|
||||||
|
using GlitchyEngine;
|
||||||
|
using System.IO;
|
||||||
|
using DirectX.D3DCompiler;
|
||||||
|
using GlitchyEngine.Renderer;
|
||||||
|
using DirectX.D3D11Shader;
|
||||||
|
using System.Collections;
|
||||||
|
using GlitchyEngine.Content;
|
||||||
|
|
||||||
|
namespace GlitchyEditor.Assets.Processors;
|
||||||
|
|
||||||
|
using internal GlitchyEditor.Assets.Processors;
|
||||||
|
|
||||||
|
class ShaderDefineValue
|
||||||
|
{
|
||||||
|
private String _name ~ delete _;
|
||||||
|
private String _definition ~ delete _;
|
||||||
|
|
||||||
|
public StringView Name
|
||||||
|
{
|
||||||
|
get => _name;
|
||||||
|
set => String.NewOrSet!(_name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringView Definition
|
||||||
|
{
|
||||||
|
get => _definition;
|
||||||
|
set => String.NewOrSet!(_definition, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CompiledShader
|
||||||
|
{
|
||||||
|
private Dictionary<StringView, ReflectedConstantBuffer> _buffers = new .() ~ DeleteDictionaryAndValues!(_);
|
||||||
|
private Dictionary<StringView, ReflectedTexture> _textures = new .() ~ DeleteDictionaryAndValues!(_);
|
||||||
|
|
||||||
|
public void AddConstantBuffer(ReflectedConstantBuffer buffer)
|
||||||
|
{
|
||||||
|
_buffers.Add(buffer.Name, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddTexture(ReflectedTexture texture)
|
||||||
|
{
|
||||||
|
_textures.Add(texture.Name, texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReflectedConstantBuffer
|
||||||
|
{
|
||||||
|
private String _name ~ delete _;
|
||||||
|
|
||||||
|
public int Size;
|
||||||
|
|
||||||
|
public StringView Name
|
||||||
|
{
|
||||||
|
get => _name;
|
||||||
|
set => String.NewOrSet!(_name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<StringView, ReflectedConstantBufferVariable> _variables = new .() ~ DeleteDictionaryAndValues!(_);
|
||||||
|
|
||||||
|
public void AddVariable(ReflectedConstantBufferVariable vaiable)
|
||||||
|
{
|
||||||
|
_variables.Add(vaiable.Name, vaiable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint8[] RawData ~ delete _;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReflectedTexture
|
||||||
|
{
|
||||||
|
private String _name ~ delete _;
|
||||||
|
|
||||||
|
public uint32 BindPoint;
|
||||||
|
|
||||||
|
public TextureDimension TextureDimension;
|
||||||
|
|
||||||
|
public StringView Name
|
||||||
|
{
|
||||||
|
get => _name;
|
||||||
|
set => String.NewOrSet!(_name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public this(StringView name, uint32 bindPoint, TextureDimension textureDimension)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
BindPoint = bindPoint;
|
||||||
|
TextureDimension = textureDimension;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReflectedConstantBufferVariable
|
||||||
|
{
|
||||||
|
private String _name ~ delete _;
|
||||||
|
|
||||||
|
public StringView Name
|
||||||
|
{
|
||||||
|
get => _name;
|
||||||
|
set => String.NewOrSet!(_name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Offset;
|
||||||
|
public int SizeInBytes;
|
||||||
|
public bool IsUsed;
|
||||||
|
|
||||||
|
public GlitchyEngine.Renderer.ShaderVariableType ElementType;
|
||||||
|
public int Rows;
|
||||||
|
public int Columns;
|
||||||
|
|
||||||
|
public int ArraySize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Dx11
|
||||||
|
extension CompiledShader
|
||||||
|
{
|
||||||
|
internal ID3DBlob* _shaderBlob;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShaderCompiler
|
||||||
|
{
|
||||||
|
public static Result<CompiledShader> CompileAndReflectShader(StringView code, AssetIdentifier assetIdentifier, StringView entryPoint, StringView compileTarget, Span<ShaderDefineValue> defines)
|
||||||
|
{
|
||||||
|
defer {
|
||||||
|
if (@return case .Err)
|
||||||
|
delete shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
CompiledShader shader = new CompiledShader();
|
||||||
|
|
||||||
|
Try!(PlatformCompileShaderFromSource(code, assetIdentifier, entryPoint, compileTarget, defines, shader));
|
||||||
|
|
||||||
|
Try!(PlatformReflectShader(shader));
|
||||||
|
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static extern Result<void> PlatformCompileShaderFromSource(StringView code, AssetIdentifier fileName, StringView entryPoint, StringView compileTarget, Span<ShaderDefineValue> defines, CompiledShader outShader);
|
||||||
|
|
||||||
|
protected static extern Result<void> PlatformReflectShader(CompiledShader shader);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Dx11
|
||||||
|
extension ShaderCompiler
|
||||||
|
{
|
||||||
|
// TODO: This should be a setting per shader, really!
|
||||||
|
protected const ShaderCompileFlags DefaultCompileFlags = .EnableStrictness |
|
||||||
|
#if DEBUG
|
||||||
|
.Debug;
|
||||||
|
#else
|
||||||
|
.OptimizationLevel3;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected static override Result<void> PlatformCompileShaderFromSource(StringView code, AssetIdentifier fileName, StringView entryPoint, StringView compileTarget, Span<ShaderDefineValue> defines, CompiledShader outShader)
|
||||||
|
{
|
||||||
|
Debug.Profiler.ProfileResourceFunction!();
|
||||||
|
|
||||||
|
Log.EngineLogger.AssertDebug(outShader._shaderBlob == null);
|
||||||
|
|
||||||
|
ShaderMacro* nativeMacros = defines.Length == 0 ? null : new:ScopedAlloc! ShaderMacro[defines.Length]*;
|
||||||
|
|
||||||
|
for(int i < defines.Length)
|
||||||
|
{
|
||||||
|
nativeMacros[i].Name = defines[i].Name.ToScopedNativeWChar!::();
|
||||||
|
nativeMacros[i].Definition = defines[i].Definition.ToScopedNativeWChar!::();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Todo: sourceName, includes,
|
||||||
|
// Todo: variable shader target?
|
||||||
|
|
||||||
|
ID3DBlob* errorBlob = null;
|
||||||
|
|
||||||
|
String directory = scope .();
|
||||||
|
|
||||||
|
Path.GetDirectoryPath(fileName, directory);
|
||||||
|
|
||||||
|
using (let includer = Includer(directory))
|
||||||
|
{
|
||||||
|
var result = D3DCompiler.D3DCompile(code.Ptr, (.)code.Length, fileName.FullIdentifier.ToScopeCStr!(), nativeMacros, &includer, entryPoint.ToScopeCStr!(),
|
||||||
|
compileTarget.ToScopeCStr!(), DefaultCompileFlags /* Pass down compile flags? */, .None, &outShader._shaderBlob, &errorBlob);
|
||||||
|
|
||||||
|
if(result.Failed)
|
||||||
|
{
|
||||||
|
StringView str = StringView((char8*)errorBlob.GetBufferPointer(), (int)errorBlob.GetBufferSize());
|
||||||
|
Log.EngineLogger.Error($"Failed to compile Shader: Error Code({(int)result}): {result} | Error Message: {str}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.EngineLogger.Assert(outShader._shaderBlob != null, "Shader compilation failed.");
|
||||||
|
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override static Result<void> PlatformReflectShader(CompiledShader shader)
|
||||||
|
{
|
||||||
|
Debug.Profiler.ProfileResourceFunction!();
|
||||||
|
|
||||||
|
ID3D11ShaderReflection* reflection = null;
|
||||||
|
defer { reflection?.Release(); }
|
||||||
|
|
||||||
|
var reflectionResult = D3DCompiler.D3DReflect(shader._shaderBlob.GetBufferPointer(), shader._shaderBlob.GetBufferSize(), &reflection);
|
||||||
|
if (reflectionResult.Failed)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error($"Failed to reflect shader: ({(int)reflectionResult}) {reflectionResult}");
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
var getDescResult = reflection.GetDescription(let desc);
|
||||||
|
if (getDescResult.Failed)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error($"GetDescription failed: ({(int)getDescResult}) {getDescResult}");
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 resourceCount = desc.BoundResources;
|
||||||
|
for (uint32 i < resourceCount)
|
||||||
|
{
|
||||||
|
var res = reflection.GetResourceBindingDescription(i, let bindDesc);
|
||||||
|
|
||||||
|
if (res.Failed)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error($"Error({(int)res}) {res}: Failed to get resource binding desc for resource {i}");
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(bindDesc.Type)
|
||||||
|
{
|
||||||
|
case .ConstantBuffer:
|
||||||
|
var bufferReflection = reflection.GetConstantBufferByName(bindDesc.Name);
|
||||||
|
|
||||||
|
bufferReflection.GetDescription(let bufferDesc);
|
||||||
|
|
||||||
|
// ConstantBuffer
|
||||||
|
if(bufferDesc.Type == .D3D11_CT_CBUFFER)
|
||||||
|
{
|
||||||
|
ReflectedConstantBuffer cbuffer = Try!(ReflectConstantBuffer(bufferReflection));
|
||||||
|
shader.AddConstantBuffer(cbuffer);
|
||||||
|
}
|
||||||
|
case .Texture:
|
||||||
|
TextureDimension textureDimension;
|
||||||
|
|
||||||
|
switch (bindDesc.Dimension)
|
||||||
|
{
|
||||||
|
case .Texture1D:
|
||||||
|
textureDimension = .Texture1D;
|
||||||
|
case .Texture1DArray:
|
||||||
|
textureDimension = .Texture1DArray;
|
||||||
|
case .Texture2D:
|
||||||
|
textureDimension = .Texture2D;
|
||||||
|
case .Texture2DArray:
|
||||||
|
textureDimension = .Texture2DArray;
|
||||||
|
case .Texture3D:
|
||||||
|
textureDimension = .Texture3D;
|
||||||
|
case .TextureCube:
|
||||||
|
textureDimension = .TextureCube;
|
||||||
|
case .TextureCubeArray:
|
||||||
|
textureDimension = .TextureCubeArray;
|
||||||
|
default:
|
||||||
|
textureDimension = .Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
shader.AddTexture(new ReflectedTexture(StringView(bindDesc.Name), bindDesc.BindPoint, textureDimension));
|
||||||
|
case .Sampler:
|
||||||
|
// TODO: do we have to do something for samplers?
|
||||||
|
// i.e. can we get default values?
|
||||||
|
default:
|
||||||
|
Log.EngineLogger.Warning($"Unhandled shader resource type: \"{bindDesc.Type}\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Result<ReflectedConstantBuffer> ReflectConstantBuffer(ID3D11ShaderReflectionConstantBuffer* bufferReflection)
|
||||||
|
{
|
||||||
|
Debug.Profiler.ProfileResourceFunction!();
|
||||||
|
|
||||||
|
var buffer = new ReflectedConstantBuffer();
|
||||||
|
|
||||||
|
HResult result = bufferReflection.GetDescription(let bufferDescription);
|
||||||
|
Log.EngineLogger.Assert(result.Succeeded, scope $"Failed to get buffer description. Error({(int)result}): {result}");
|
||||||
|
|
||||||
|
Log.EngineLogger.AssertDebug(bufferDescription.Type == .D3D_CT_CBUFFER, "The buffer is not of type \"D3D_CT_CBUFFER\"");
|
||||||
|
|
||||||
|
buffer.Name = StringView(bufferDescription.Name);
|
||||||
|
buffer.Size = bufferDescription.Size;
|
||||||
|
buffer.RawData = new uint8[buffer.Size];
|
||||||
|
|
||||||
|
for(uint32 v = 0; v < bufferDescription.Variables; v++)
|
||||||
|
{
|
||||||
|
ID3D11ShaderReflectionVariable* variableReflection = bufferReflection.GetVariableByIndex(v);
|
||||||
|
|
||||||
|
if (ReflectConstantBufferVariable(buffer, variableReflection) case .Err)
|
||||||
|
{
|
||||||
|
delete buffer;
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Result<void> ReflectConstantBufferVariable(ReflectedConstantBuffer buffer, ID3D11ShaderReflectionVariable* variableReflection)
|
||||||
|
{
|
||||||
|
Debug.Profiler.ProfileResourceFunction!();
|
||||||
|
|
||||||
|
HResult getDescResult = variableReflection.GetDescription(let variableDescription);
|
||||||
|
if (getDescResult.Failed)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error($"Failed to get variable description. Error({(int)getDescResult}): {getDescResult}");
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
let variableType = variableReflection.GetVariableType();
|
||||||
|
let getTypeDescResult = variableType.GetDescription(let shaderTypeDescription);
|
||||||
|
if (getTypeDescResult.Failed)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error($"Failed to get variable description. Error({(int)getDescResult}): {getDescResult}");
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReflectedConstantBufferVariable variable = new ReflectedConstantBufferVariable();
|
||||||
|
variable.Name = StringView(variableDescription.Name);
|
||||||
|
|
||||||
|
variable.Offset = variableDescription.StartOffset;
|
||||||
|
variable.SizeInBytes = variableDescription.Size;
|
||||||
|
variable.IsUsed = variableDescription.uFlags.HasFlag(.Used);
|
||||||
|
|
||||||
|
variable.Columns = shaderTypeDescription.Columns;
|
||||||
|
variable.Rows = shaderTypeDescription.Rows;
|
||||||
|
variable.ArraySize = shaderTypeDescription.Elements;
|
||||||
|
|
||||||
|
switch(shaderTypeDescription.Type)
|
||||||
|
{
|
||||||
|
case .Bool:
|
||||||
|
variable.ElementType = .Bool;
|
||||||
|
case .Float:
|
||||||
|
variable.ElementType = .Float;
|
||||||
|
case .Int:
|
||||||
|
variable.ElementType = .Int;
|
||||||
|
case .UInt:
|
||||||
|
variable.ElementType = .UInt;
|
||||||
|
default:
|
||||||
|
Log.EngineLogger.Error($"Unhandled shader variable type: {shaderTypeDescription.Type}.");
|
||||||
|
delete variable;
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variableDescription.DefaultValue == null)
|
||||||
|
{
|
||||||
|
Internal.MemSet(&buffer.RawData[variable.Offset], 0, variable.SizeInBytes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Internal.MemCpy(&buffer.RawData[variable.Offset], variableDescription.DefaultValue, variable.SizeInBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.AddVariable(variable);
|
||||||
|
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Includer : ID3DInclude, IDisposable
|
||||||
|
{
|
||||||
|
private VTable _vTable;
|
||||||
|
|
||||||
|
private Dictionary<StringView, (String FileName, String FileContent)> _loadedFiles;
|
||||||
|
|
||||||
|
private String _parentFileDirectory;
|
||||||
|
|
||||||
|
public this(String parentFileDirectory)
|
||||||
|
{
|
||||||
|
_parentFileDirectory = parentFileDirectory;
|
||||||
|
_loadedFiles = new Dictionary<StringView, (String FileName, String FileContent)>();
|
||||||
|
|
||||||
|
_vTable.Open = => Open;
|
||||||
|
_vTable.Close = => Close;
|
||||||
|
|
||||||
|
_vt = &_vTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
for (let (key, value) in _loadedFiles)
|
||||||
|
{
|
||||||
|
delete value.FileName;
|
||||||
|
delete value.FileContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete _loadedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HResult Open(ID3DInclude* self, IncludeType includeType, char8* fileNamePtr, void* parentData, void** data, uint32* bytes)
|
||||||
|
{
|
||||||
|
Includer* includer = (.)self;
|
||||||
|
|
||||||
|
StringView fileName = StringView(fileNamePtr);
|
||||||
|
|
||||||
|
if (includer._loadedFiles.TryGetValue(fileName, let value))
|
||||||
|
{
|
||||||
|
*data = (void*)value.FileContent.Ptr;
|
||||||
|
*bytes = (uint32)value.FileContent.Length;
|
||||||
|
|
||||||
|
return .S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
String fullPath = scope .();
|
||||||
|
|
||||||
|
Path.Combine(fullPath, includer._parentFileDirectory, fileName);
|
||||||
|
|
||||||
|
if (!File.Exists(fullPath))
|
||||||
|
{
|
||||||
|
let treeResult = Editor.Instance.ContentManager.AssetHierarchy.GetNodeFromIdentifier(scope AssetIdentifier(fileName));
|
||||||
|
|
||||||
|
if (var assetNode = treeResult)
|
||||||
|
{
|
||||||
|
fullPath.Set(assetNode->Path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error($"Failed to include shader file \"{fileName}\"");
|
||||||
|
return .E_FILENOTFOUND;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream fileStream = Application.Instance.ContentManager.GetStream(fullPath);
|
||||||
|
|
||||||
|
if (fileStream == null)
|
||||||
|
{
|
||||||
|
fileStream = Application.Instance.ContentManager.GetStream(StringView(fileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileStream == null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
String fileContent = new String();
|
||||||
|
|
||||||
|
{
|
||||||
|
StreamReader reader = scope .(fileStream);
|
||||||
|
|
||||||
|
reader.ReadToEnd(fileContent);
|
||||||
|
|
||||||
|
String fileNameStr = new String(fileName);
|
||||||
|
|
||||||
|
includer._loadedFiles.Add(fileNameStr, (fileNameStr, fileContent));
|
||||||
|
}
|
||||||
|
|
||||||
|
delete fileStream;
|
||||||
|
|
||||||
|
*data = (void*)fileContent.Ptr;
|
||||||
|
*bytes = (uint32)fileContent.Length;
|
||||||
|
|
||||||
|
return .S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HResult Close(ID3DInclude* self, void** data)
|
||||||
|
{
|
||||||
|
Includer* includer = (.)self;
|
||||||
|
|
||||||
|
for (var v in includer._loadedFiles)
|
||||||
|
{
|
||||||
|
if (v.value.FileContent.Ptr == data)
|
||||||
|
{
|
||||||
|
includer._loadedFiles.Remove(v.key);
|
||||||
|
delete v.value.FileContent;
|
||||||
|
delete v.value.FileName;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .S_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,440 @@
|
|||||||
|
using GlitchyEditor.Assets.Importers;
|
||||||
|
using System;
|
||||||
|
using GlitchyEngine.Content;
|
||||||
|
using System.Collections;
|
||||||
|
using GlitchyEngine;
|
||||||
|
using GlitchyEngine.Math;
|
||||||
|
using GlitchyEngine.Renderer;
|
||||||
|
|
||||||
|
namespace GlitchyEditor.Assets.Processors;
|
||||||
|
|
||||||
|
class ProcessedShader : ProcessedResource
|
||||||
|
{
|
||||||
|
public override AssetType AssetType => .Shader;
|
||||||
|
|
||||||
|
public CompiledShader VertexShader ~ delete _;
|
||||||
|
public CompiledShader PixelShader ~ delete _;
|
||||||
|
|
||||||
|
public this(AssetIdentifier ownAssetIdentifier, AssetHandle assetHandle) : base(ownAssetIdentifier, assetHandle)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShaderVariable
|
||||||
|
{
|
||||||
|
private String _name ~ delete _;
|
||||||
|
|
||||||
|
private Dictionary<String, Variant> _parameters = new .() ~ {
|
||||||
|
for (var (entryKey, entry) in _)
|
||||||
|
{
|
||||||
|
delete entryKey;
|
||||||
|
entry.Dispose();
|
||||||
|
}
|
||||||
|
delete _;
|
||||||
|
};
|
||||||
|
|
||||||
|
public StringView Name
|
||||||
|
{
|
||||||
|
get => _name;
|
||||||
|
set => String.NewOrSet!(_name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<String, Variant> Parameters => _parameters;
|
||||||
|
|
||||||
|
public void AddParameter(StringView name, Variant value)
|
||||||
|
{
|
||||||
|
_parameters.Add(new String(name), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShaderProcessor : IAssetProcessor
|
||||||
|
{
|
||||||
|
public AssetProcessorConfig CreateDefaultConfig()
|
||||||
|
{
|
||||||
|
return new AssetProcessorConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Type ProcessedAssetType => typeof(ImportedShader);
|
||||||
|
|
||||||
|
public Result<void> Process(ImportedResource importedResource, AssetConfig config, List<ProcessedResource> outProcessedResources)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.AssertDebug(importedResource is ImportedShader);
|
||||||
|
|
||||||
|
Try!(ProcessShader(importedResource as ImportedShader, config, outProcessedResources));
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Result<void> ProcessShader(ImportedShader importedShader, AssetConfig config, List<ProcessedResource> outProcessedResources)
|
||||||
|
{
|
||||||
|
String vsName = scope String();
|
||||||
|
String psName = scope String();
|
||||||
|
|
||||||
|
Dictionary<StringView, ShaderVariable> variables = scope .();
|
||||||
|
Dictionary<String, String> engineBuffers = scope .();
|
||||||
|
|
||||||
|
defer
|
||||||
|
{
|
||||||
|
ClearDictionaryAndDeleteValues!(variables);
|
||||||
|
|
||||||
|
for (let (key, value) in engineBuffers)
|
||||||
|
{
|
||||||
|
delete key;
|
||||||
|
delete value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String code = new String(importedShader.HlslCode);
|
||||||
|
defer { delete code; }
|
||||||
|
|
||||||
|
Try!(ProcessFileContent(code, vsName, psName, variables, engineBuffers));
|
||||||
|
|
||||||
|
if (String.IsNullOrWhiteSpace(vsName) && String.IsNullOrWhiteSpace(psName))
|
||||||
|
{
|
||||||
|
// this is not an effect -> we don't need to compile it
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessedShader processedShader = new ProcessedShader(new AssetIdentifier(importedShader.AssetIdentifier), config.AssetHandle);
|
||||||
|
|
||||||
|
Try!(CompileAndReflect(vsName, psName, importedShader, code, processedShader));
|
||||||
|
|
||||||
|
Try!(MergeResources(processedShader));
|
||||||
|
|
||||||
|
outProcessedResources.Add(processedShader);
|
||||||
|
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Result<void> MergeResources(ProcessedShader processedShader)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Result<void> CompileAndReflect(StringView vsName, StringView psName, ImportedShader shader, StringView code, ProcessedShader processedShader)
|
||||||
|
{
|
||||||
|
if (!vsName.IsWhiteSpace)
|
||||||
|
{
|
||||||
|
processedShader.VertexShader = Try!(CompileAndReflectVertexShader(vsName, shader, code));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!psName.IsWhiteSpace)
|
||||||
|
{
|
||||||
|
processedShader.PixelShader = Try!(CompileAndReflectPixelShader(psName, shader, code));
|
||||||
|
}
|
||||||
|
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Result<CompiledShader> CompileAndReflectVertexShader(StringView vsName, ImportedShader importedShader, StringView code)
|
||||||
|
{
|
||||||
|
Debug.Profiler.ProfileResourceFunction!();
|
||||||
|
|
||||||
|
return ShaderCompiler.CompileAndReflectShader(code, importedShader.AssetIdentifier, vsName, "vs_5_0", .());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Result<CompiledShader> CompileAndReflectPixelShader(StringView vsName, ImportedShader importedShader, StringView code)
|
||||||
|
{
|
||||||
|
Debug.Profiler.ProfileResourceFunction!();
|
||||||
|
|
||||||
|
return ShaderCompiler.CompileAndReflectShader(code, importedShader.AssetIdentifier, vsName, "ps_5_0", .());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Result<void> ProcessFileContent(String fileContent, String outVsName, String outPsName, Dictionary<StringView, ShaderVariable> outVarDescs, Dictionary<String, String> outEngineBuffers)
|
||||||
|
{
|
||||||
|
Debug.Profiler.ProfileResourceFunction!();
|
||||||
|
|
||||||
|
Dictionary<StringView, StringView> arguments = scope .();
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if ((int Start, int End) value = GetNextPreprocessor(fileContent, index, let name, arguments..Clear()))
|
||||||
|
{
|
||||||
|
index = value.End;
|
||||||
|
|
||||||
|
switch(name)
|
||||||
|
{
|
||||||
|
case "Effect":
|
||||||
|
for (let (argName, argValue) in arguments)
|
||||||
|
{
|
||||||
|
switch(argName)
|
||||||
|
{
|
||||||
|
case "VS", "VertexShader":
|
||||||
|
outVsName.Append(argValue);
|
||||||
|
case "PS", "PixelShader":
|
||||||
|
outPsName.Append(argValue);
|
||||||
|
default:
|
||||||
|
Log.EngineLogger.Error($"Unknown parameter name \"{name}\".");
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "EditorVariable":
|
||||||
|
Try!(ProcessEditorVariables(arguments, outVarDescs));
|
||||||
|
case "EngineBuffer":
|
||||||
|
Try!(ProcessEngineBuffer(arguments, outEngineBuffers));
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
CommentLine(fileContent, value.Start);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CommentLine(StringView code, int commentPosition)
|
||||||
|
{
|
||||||
|
code[commentPosition] = '/';
|
||||||
|
code[commentPosition + 1] = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Result<(int Start, int End)> GetNextPreprocessor(StringView code, int startindex, out StringView name, Dictionary<StringView, StringView> arguments)
|
||||||
|
{
|
||||||
|
name = .();
|
||||||
|
|
||||||
|
int startOfLine;
|
||||||
|
int endOfLine;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
startOfLine = code.IndexOf("#pragma", startindex);
|
||||||
|
|
||||||
|
if (startOfLine == -1)
|
||||||
|
return .Err;
|
||||||
|
|
||||||
|
endOfLine = code.IndexOf('\n', startOfLine);
|
||||||
|
|
||||||
|
StringView line = (endOfLine != -1) ? code.Substring(startOfLine, endOfLine - startOfLine) : code.Substring(startOfLine);
|
||||||
|
|
||||||
|
// cut off the #pragma
|
||||||
|
line = line.Substring(7);
|
||||||
|
|
||||||
|
int lBracketIndex = line.IndexOf('[');
|
||||||
|
|
||||||
|
if (lBracketIndex == -1)
|
||||||
|
{
|
||||||
|
name = line..Trim();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
name = line.Substring(0, lBracketIndex);
|
||||||
|
name.Trim();
|
||||||
|
|
||||||
|
int rBracketIndex = line.IndexOf(']');
|
||||||
|
|
||||||
|
if (rBracketIndex == -1)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error($"Pragma is missing closing Bracket (\"{line}\")");
|
||||||
|
rBracketIndex = line.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringView argumentText = line.Substring(lBracketIndex + 1, rBracketIndex - lBracketIndex - 1);
|
||||||
|
|
||||||
|
for (StringView argument in argumentText.Split(';'))
|
||||||
|
{
|
||||||
|
int equalsIndex = argument.IndexOf('=');
|
||||||
|
|
||||||
|
StringView argumentName = .();
|
||||||
|
StringView argumentValue = .();
|
||||||
|
|
||||||
|
if (equalsIndex == -1)
|
||||||
|
{
|
||||||
|
argumentName = argument;
|
||||||
|
argumentName.Trim();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
argumentName = argument.Substring(0, equalsIndex);
|
||||||
|
argumentName.Trim();
|
||||||
|
|
||||||
|
argumentValue = argument.Substring(equalsIndex + 1);
|
||||||
|
argumentValue.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arguments.ContainsKey(argumentName))
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error($"Arguments \"{argumentName}\" already exists.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
arguments.Add(argumentName, argumentValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .Ok((startOfLine, endOfLine));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Result<void> ProcessEngineBuffer(Dictionary<StringView, StringView> arguments, Dictionary<String, String> outEngineBuffers)
|
||||||
|
{
|
||||||
|
String nameInEngine = null;
|
||||||
|
String nameInShader = null;
|
||||||
|
|
||||||
|
for (var (argName, argValue) in arguments)
|
||||||
|
{
|
||||||
|
if (argValue.StartsWith('"') && argValue.EndsWith('"'))
|
||||||
|
{
|
||||||
|
argValue = argValue[1...^2];
|
||||||
|
}
|
||||||
|
switch (argName)
|
||||||
|
{
|
||||||
|
case "Name":
|
||||||
|
nameInShader = new String(argValue);
|
||||||
|
case "Binding":
|
||||||
|
nameInEngine = new String(argValue);
|
||||||
|
default:
|
||||||
|
Log.EngineLogger.Error($"Unknown parameter for EngineBuffer: \"{argName}\".");
|
||||||
|
delete nameInEngine;
|
||||||
|
delete nameInShader;
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (String.IsNullOrWhiteSpace(nameInEngine) || String.IsNullOrWhiteSpace(nameInShader))
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error($"Name and Binding need to be defined.");
|
||||||
|
delete nameInEngine;
|
||||||
|
delete nameInShader;
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
outEngineBuffers.Add(nameInEngine, nameInShader);
|
||||||
|
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Result<void> ProcessEditorVariables(Dictionary<StringView, StringView> arguments, Dictionary<StringView, ShaderVariable> outVarDescs)
|
||||||
|
{
|
||||||
|
ShaderVariable variable = new .();
|
||||||
|
|
||||||
|
for (var (name, value) in arguments)
|
||||||
|
{
|
||||||
|
if (value.StartsWith('"') && value.EndsWith('"'))
|
||||||
|
{
|
||||||
|
value = value[1...^2];
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(name)
|
||||||
|
{
|
||||||
|
case "Name":
|
||||||
|
variable.Name = value;
|
||||||
|
case "Min", "Max":
|
||||||
|
if (Variant paramValue = ParseVariableValue(value))
|
||||||
|
variable.AddParameter(name, paramValue);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
delete variable;
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
Variant paramValue = Variant.Create(new String(value), true);
|
||||||
|
variable.AddParameter(name, paramValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variable.Name.IsWhiteSpace)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error("Failed to process shader: Missing argument \"Name\" int variable description.");
|
||||||
|
|
||||||
|
delete variable;
|
||||||
|
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
outVarDescs.Add(variable.Name, variable);
|
||||||
|
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Result<Variant> ParseVariableValue(StringView valueString)
|
||||||
|
{
|
||||||
|
if (valueString[0].IsDigit || valueString[0] == '-')
|
||||||
|
{
|
||||||
|
var valueString;
|
||||||
|
|
||||||
|
if (valueString.EndsWith('f'))
|
||||||
|
valueString.Length--;
|
||||||
|
|
||||||
|
float value = Try!(float.Parse(valueString));
|
||||||
|
|
||||||
|
return Variant.Create(value);
|
||||||
|
}
|
||||||
|
else if (valueString.StartsWith("float"))
|
||||||
|
{
|
||||||
|
int index = 5;
|
||||||
|
|
||||||
|
int numComponents = valueString[index++] - '0';
|
||||||
|
|
||||||
|
while (valueString[index] != '(')
|
||||||
|
{
|
||||||
|
if (!valueString[index].IsWhiteSpace)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error("Failed to process shader: Expected '('.");
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numComponents < 2 || numComponents > 4)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error($"Failed to process shader: Unsupported component count {numComponents}. Value must be between 2 and 4.");
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
float[] floats = scope float[numComponents];
|
||||||
|
|
||||||
|
for (int i < numComponents)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
char8 c = valueString[++index];
|
||||||
|
|
||||||
|
if (c.IsDigit || c == '.' || c == '-')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int start = index;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
char8 c = valueString[++index];
|
||||||
|
|
||||||
|
if (!c.IsDigit && c != '.')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int end = index;
|
||||||
|
|
||||||
|
StringView numberView = .(valueString, start, end - start);
|
||||||
|
|
||||||
|
var result = float.Parse(numberView);
|
||||||
|
|
||||||
|
if (result case .Ok(let value))
|
||||||
|
{
|
||||||
|
floats[i] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numComponents == 2)
|
||||||
|
return Variant.Create(*(float2*)floats.Ptr);
|
||||||
|
else if (numComponents == 3)
|
||||||
|
return Variant.Create(*(float3*)floats.Ptr);
|
||||||
|
else
|
||||||
|
return Variant.Create(*(float4*)floats.Ptr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error($"Failed to process shader: Unsupported variable value: \"{valueString}\".");
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,6 +43,10 @@ namespace GlitchyEditor
|
|||||||
|
|
||||||
_contentManager.RegisterAssetExporter<SpriteExporter>();
|
_contentManager.RegisterAssetExporter<SpriteExporter>();
|
||||||
|
|
||||||
|
_contentManager.RegisterAssetImporter<ShaderImporter>();
|
||||||
|
_contentManager.RegisterAssetProcessor<ShaderProcessor>();
|
||||||
|
//_contentManager.RegisterAssetExporter<ShaderEx>();
|
||||||
|
|
||||||
_contentManager.SetGlobalAssetCacheDirectory(".cache");
|
_contentManager.SetGlobalAssetCacheDirectory(".cache");
|
||||||
_contentManager.SetResourcesDirectory("Resources");
|
_contentManager.SetResourcesDirectory("Resources");
|
||||||
|
|
||||||
|
|||||||
@@ -4,5 +4,6 @@ enum AssetType : uint16
|
|||||||
{
|
{
|
||||||
Unknown,
|
Unknown,
|
||||||
Texture,
|
Texture,
|
||||||
Sprite
|
Sprite,
|
||||||
|
Shader
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user