New material loading pipeline almost working

This commit is contained in:
Simon Lübeß
2025-01-31 13:18:49 +01:00
parent 2f0505241f
commit acfb052137
11 changed files with 419 additions and 81 deletions
@@ -0,0 +1,96 @@
using System;
using System.IO;
using GlitchyEngine.Renderer;
using GlitchyEditor.Assets.Processors;
using GlitchyEngine;
using GlitchyEditor.Assets.Importers;
using GlitchyEngine.Content;
using System.Diagnostics;
namespace GlitchyEditor.Assets.Exporters;
class MaterialExporter : IAssetExporter
{
public static AssetType ExportedAssetType => .Material;
public AssetExporterConfig CreateDefaultConfig()
{
return new AssetExporterConfig();
}
public Result<void> Export(Stream stream, ProcessedResource processedResource, AssetConfig config)
{
Log.EngineLogger.AssertDebug(processedResource is ProcessedMaterial);
ProcessedMaterial processedMaterial = (.)processedResource;
Compiler.Assert(sizeof(AssetHandle) == 8);
/*
File Format:
Effect Asset Handle (8 byte)
Texture Count (uint16)
Textures
{
Texture Handle (8 byte)
Texture Name Length (int16)
Texture Name Data...
}
Buffer Count (uint16)
Buffers
{
Buffer Size (int64)
Buffer Name Length (int16)
Buffer Name Data...
Raw Buffer Data...
}
*/
Try!(stream.Write((AssetHandle)processedMaterial.EffectHandle));
Try!(WriteTextures(stream, processedMaterial));
Try!(WriteBuffers(stream, processedMaterial));
return .Ok;
}
private Result<void> WriteTextures(Stream stream, ProcessedMaterial processedMaterial)
{
Log.EngineLogger.Assert(processedMaterial.Textures.Count < int16.MaxValue);
Try!(stream.Write((uint16)processedMaterial.Textures.Count));
for (let (textureName, textureHandle) in processedMaterial.Textures)
{
Try!(stream.Write((AssetHandle)textureHandle));
Log.EngineLogger.Assert(textureName.Length < int16.MaxValue);
Try!(stream.Write((int16)textureName.Length));
Try!(stream.Write(textureName));
}
return .Ok;
}
private Result<void> WriteBuffers(Stream stream, ProcessedMaterial processedMaterial)
{
Log.EngineLogger.Assert(processedMaterial.Buffers.Count < int16.MaxValue);
Try!(stream.Write((uint16)processedMaterial.Buffers.Count));
for (let (bufferName, bufferData) in processedMaterial.Buffers)
{
Try!(stream.Write((int64)bufferData.Count));
Log.EngineLogger.Assert(bufferName.Length < int16.MaxValue);
Try!(stream.Write((int16)bufferName.Length));
Try!(stream.Write(bufferName));
Try!(stream.Write(Span<uint8>(bufferData)));
}
return .Ok;
}
}
@@ -30,20 +30,20 @@ class ShaderExporter : IAssetExporter
Textures
{
Texture Dimension (1 byte)
Vertex Shader Bind Point (int32, 4 bytes)
Pixel Shader Bind Point (int32, 4 bytes)
Texture Name Length (16 bytes)
Vertex Shader Bind Point (int32, 4 bytes) # TODO: ASSUME same bind point for all shader stages?
Pixel Shader Bind Point (int32, 4 bytes) # TODO: ASSUME same bind point for all shader stages?
Texture Name Length (uint16)
Texture Name Data...
}
Buffer Count (uint16)
Buffers
{
Buffer Size (int64)
Vertex Shader Bind Point (int32, 4 bytes)
Pixel Shader Bind Point (int32, 4 bytes)
Buffer Name Length (16 bytes)
Vertex Shader Bind Point (int32, 4 bytes) # TODO: ASSUME same bind point for all shader stages?
Pixel Shader Bind Point (int32, 4 bytes) # TODO: ASSUME same bind point for all shader stages?
Buffer Name Length (uint16)
Buffer Name Data...
Engine Buffer Name Length (16 bytes)
Engine Buffer Name Length (uint16)
Engine Buffer Name Data...
Variable Count (uint16)
Variables
@@ -55,7 +55,7 @@ class ShaderExporter : IAssetExporter
Rows (uint8)
Columns (uint8)
ArraySize (uint64)
Name Length (16 bytes)
Name Length (uint16)
Name Data...
}
RawData...
@@ -5,60 +5,44 @@ using GlitchyEngine.Renderer;
using Bon;
using GlitchyEngine.Math;
using System.IO;
using Bon.Integrated;
using GlitchyEngine;
namespace GlitchyEditor.Assets.Importers;
[BonTarget]
class NewMaterialFile : ImportedResource
class MaterialVariable
{
public AssetHandle<Effect> Effect;
public ShaderVariableType ElementType;
public int MatrixColumns;
public int MatrixRows;
public int ArrayElements;
public Dictionary<String, AssetHandle<Texture>> Textures = new .() ~ DeleteDictionaryAndKeys!(_);
public Dictionary<String, MaterialVariableValue> Constants = new .() ~ DeleteDictionaryAndKeys!(_);
public uint8[] RawData ~ delete _;
public this(AssetIdentifier ownAssetIdentifier) : base(ownAssetIdentifier)
public this()
{
}
}
public this(ShaderVariableType elementType, int matrixColumns, int matrixRows, int arrayElements)
{
ElementType = elementType;
MatrixColumns = matrixColumns;
MatrixRows = matrixRows;
ArrayElements = arrayElements;
[BonTarget]
public enum MaterialVariableValue
{
case bool(bool Value);
case bool2(bool2 Value);
case bool3(bool3 Value);
case bool4(bool4 Value);
case int(int Value);
case int2(int2 Value);
case int3(int3 Value);
case int4(int4 Value);
case uint(uint Value);
case uint2(uint2 Value);
case uint3(uint3 Value);
case uint4(uint4 Value);
case Float(float Value);
case Float2(float2 Value);
case Float3(float3 Value);
case Float4(float4 Value);
case Half(half Value);
case Half2(half2 Value);
case Half3(half3 Value);
case Half4(half4 Value);
case ColorRGB(ColorRGB Value);
case ColorRGBA(ColorRGBA Value);
case None;
RawData = new uint8[elementType.ElementSizeInBytes() * matrixColumns * matrixRows * arrayElements];
}
/*static this()
static this()
{
gBonEnv.typeHandlers.Add(typeof(Self),
((.)new => VariableValueSerialize, (.)new => VariableValueDeserialize));
}
static void VariableValueSerialize(BonWriter writer, ValueView value, BonEnvironment env)
static void VariableValueSerialize(BonWriter writer, ValueView value, BonEnvironment env, SerializeValueState state)
{
Log.EngineLogger.Assert(value.type == typeof(Self));
/*Log.EngineLogger.Assert(value.type == typeof(Self));
let variableValue = value.Get<Self>();
@@ -69,15 +53,152 @@ public enum MaterialVariableValue
Serialize.Value(writer, nameof(MaterialFile.Textures), materialFile.Textures, env);
}*/
}
static Result<void> VariableValueDeserialize(BonReader reader, ValueView val, BonEnvironment env, DeserializeValueState state)
{
MaterialVariable output = val.Get<MaterialVariable>();
StringView typeName = Try!(reader.EnumName());
// If we don't find a digit, we assume entire type name is element type and rows string ends up being an empty string.
int firstDigitIndex = typeName.Length;
for (char8 c in typeName)
{
if (c.IsDigit)
{
firstDigitIndex = @c.Index;
}
}
static Result<void> VariableValueDeserialize(BonReader reader, ValueView val, BonEnvironment env)
Result<ShaderVariableType> elementTypeResult = Enum.Parse<ShaderVariableType>(typeName.Substring(0, firstDigitIndex), true);
if (elementTypeResult case .Err)
{
return .Ok;
Deserialize.Error!("Unknown element type.", reader);
}
int xIndex = typeName.LastIndexOf('x');
StringView rowsString = null;
StringView columnsString = null;
if (xIndex != -1)
{
rowsString = typeName.Substring(firstDigitIndex ..< xIndex);
columnsString = typeName.Substring(xIndex + 1);
}
else
{
rowsString = typeName.Substring(firstDigitIndex);
}
Result<int, Int.ParseError> rowsResult = int.Parse(rowsString);
if (rowsResult case .Err(let error))
{
switch (error)
{
case .NoValue:
rowsResult = 1;
case .Overflow:
Deserialize.Error!("Integer overflow in row count.", reader);
case .InvalidChar:
Deserialize.Error!("Invalid character in row count.", reader);
default:
return .Err;
}
}
Result<int, Int.ParseError> columnsResult = int.Parse(columnsString);
if (columnsResult case .Err(let error))
{
switch (error)
{
case .NoValue:
columnsResult = 1;
case .Overflow:
Deserialize.Error!("Integer overflow in column count.", reader);
case .InvalidChar:
Deserialize.Error!("Invalid character in column count.", reader);
default:
return .Err;
}
}
// TODO: Array
/*if (reader.[Friend]Check('['))
{
}*/
output.ElementType = elementTypeResult.Get();
output.MatrixRows = rowsResult.Get();
output.MatrixColumns = columnsResult.Get();
output.ArrayElements = 1;
int elementSize = output.ElementType.ElementSizeInBytes();
int elementCount = output.MatrixColumns * output.MatrixRows;
output.RawData = new uint8[elementSize * elementCount * output.ArrayElements];
Try!(reader.ObjectBlock());
for (int currentElementIndex < elementCount)
{
switch (output.ElementType)
{
case .Float:
StringView valueString = Try!(reader.Floating());
float* rawFloats = (float*)output.RawData.Ptr;
rawFloats[currentElementIndex] = Try!(float.Parse(valueString));
case .Bool:
// TODO: I'm pretty sure this is wrong, bools in c buffers are usually 32 bit
bool value = Try!(reader.Bool());
((bool*)&output.RawData)[currentElementIndex] = value;
case .Int:
StringView valueString = Try!(reader.Integer());
((int32*)&output.RawData)[currentElementIndex] = Try!(int32.Parse(valueString));
case .UInt:
StringView valueString = Try!(reader.Integer());
((uint32*)&output.RawData)[currentElementIndex] = Try!(uint32.Parse(valueString));
}
if (reader.ObjectHasMore())
{
Try!(reader.EntryEnd());
}
else
{
Log.EngineLogger.Warning("Vector/Matrix doesn't contain enough elements.");
break;
}
}
Try!(reader.ObjectBlockEnd());
return .Ok;
}
}
[BonTarget]
class NewMaterialFile : ImportedResource
{
public AssetHandle<Effect> Effect;
public Dictionary<String, AssetHandle<Texture>> Textures = new .() ~ DeleteDictionaryAndKeys!(_);
public Dictionary<String, MaterialVariable> Constants = new .() ~ DeleteDictionaryAndKeysAndValues!(_);
public this(AssetIdentifier ownAssetIdentifier) : base(ownAssetIdentifier)
{
}
}
class MaterialImporter: IAssetImporter
{
@@ -144,7 +144,7 @@ class MaterialAssetPropertiesEditor : AssetPropertiesEditor
{
if (previewType.Get<String>().Equals("Color", .InvariantCultureIgnoreCase))
{
Log.EngineLogger.AssertDebug(variable.Type == .Float && variable.Rows == 1);
Log.EngineLogger.AssertDebug(variable.ElementType == .Float && variable.Rows == 1);
if (variable.Columns == 3)
{
@@ -173,7 +173,7 @@ class MaterialAssetPropertiesEditor : AssetPropertiesEditor
}
else if (previewType.Get<String>().Equals("ColorHDR", .InvariantCultureIgnoreCase))
{
Log.EngineLogger.AssertDebug(variable.Type == .Float && variable.Rows == 1);
Log.EngineLogger.AssertDebug(variable.ElementType == .Float && variable.Rows == 1);
if (variable.Columns == 3)
{
@@ -203,7 +203,7 @@ class MaterialAssetPropertiesEditor : AssetPropertiesEditor
}
//ImGui.ColorEdit3("", null, .HDR | .Float)
else if (variable.Type == .Float && variable.Rows == 1)
else if (variable.ElementType == .Float && variable.Rows == 1)
{
bool hasMin = TryGetValue(arguments, "Min", var min);
bool hasMax = TryGetValue(arguments, "Max", var max);
@@ -495,7 +495,7 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader
if (hasPreviewType && previewType.Get<String>() == "Color")
{
Log.EngineLogger.AssertDebug(variable.Type == .Float && variable.Rows == 1);
Log.EngineLogger.AssertDebug(variable.ElementType == .Float && variable.Rows == 1);
if (variable.Columns == 3)
{
@@ -515,7 +515,7 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader
variableValue = .ColorRGBA(value);
}
}
else if (variable.Type == .Float && variable.Rows == 1)
else if (variable.ElementType == .Float && variable.Rows == 1)
{
switch (variable.Columns)
{
@@ -4,26 +4,42 @@ using GlitchyEditor.Assets.Importers;
using GlitchyEngine.Renderer;
using GlitchyEngine;
using GlitchyEngine.Content;
using System.Diagnostics;
namespace GlitchyEditor.Assets.Processors;
class ProcessedMaterial : ProcessedResource
{
public AssetHandle EffectHandle;
private Dictionary<String, uint8[]> _bufferData = new .() ~ DeleteDictionaryAndKeysAndValues!(_);
private Dictionary<String, AssetHandle> _textures = new .() ~ DeleteDictionaryAndKeys!(_);
public override AssetType AssetType => .Material;
public Dictionary<String, uint8[]> Buffers => _bufferData;
public Dictionary<String, AssetHandle> Textures => _textures;
public this(AssetIdentifier ownAssetIdentifier, AssetHandle assetHandle) : base(ownAssetIdentifier, assetHandle)
{
}
public uint8[] CloneBuffer(ConstantBuffer cBuffer)
public Span<uint8> CloneBuffer(String name, ConstantBuffer cBuffer)
{
String name = new String(cBuffer.Name);
//uint8[] data = cBuffer.
String copyName = new String(name);
uint8[] data = new uint8[cBuffer.RawData.Length];
cBuffer.RawData.CopyTo(data);
return null;
_bufferData.Add(copyName, data);
return data;
}
public void AddTexture(StringView name, AssetHandle textureHandle)
{
String copyName = new String(name);
_textures.Add(copyName, textureHandle);
}
}
@@ -47,22 +63,67 @@ class MaterialProcessor : IAssetProcessor
Effect effect = Content.GetAsset<Effect>(importedMaterial.Effect, blocking: true);
if (effect == null)
// TODO: Don't error out, use the error-effect
return .Err;
ProcessedMaterial processedMaterial = new .(new AssetIdentifier(importedMaterial.AssetIdentifier), config.AssetHandle);
processedMaterial.EffectHandle = importedMaterial.Effect;
for (let buffer in effect.Buffers)
{
// TODO: Skip engine buffers
if (buffer.Buffer == null)
continue;
ConstantBuffer cBuffer = buffer.Buffer as ConstantBuffer;
if (cBuffer == null)
{
Log.EngineLogger.Error("The buffers of a material must be ConstantBuffers.");
continue;
// TODO: Why even allow any kind of Buffer for Materials? Will this make sense later if we can create and bind Buffers from C#?
}
processedMaterial.CloneBuffer(cBuffer);
Span<uint8> data = processedMaterial.CloneBuffer(buffer.Name, cBuffer);
for (BufferVariable effectVariable in cBuffer.Variables)
{
if (importedMaterial.Constants.TryGetValue(effectVariable.Name, let materialValue))
{
if (effectVariable.ElementType != materialValue.ElementType)
{
Log.EngineLogger.Error("Element type doesn't match between material and effect.");
continue;
}
// Create buffers
int effectVariableSize = effectVariable.Rows * effectVariable.Columns * Math.Max(1, effectVariable.ArrayElements) * effectVariable.ElementType.ElementSizeInBytes();
return default;
if (effectVariableSize != materialValue.RawData.Count)
{
Log.EngineLogger.Warning("Warning, matrix size in matrix and effect doesn't match. Truncating value.");
}
int bytesToCopy = Math.Min(effectVariableSize, materialValue.RawData.Count);
Debug.Assert(effectVariable.Offset + bytesToCopy <= data.Length, "Material value goes out of bounds ");
Internal.MemCpy(data.Ptr + effectVariable.Offset, materialValue.RawData.Ptr, bytesToCopy);
}
}
}
for (let (textureName, textureBinding) in effect.Textures)
{
if (importedMaterial.Textures.TryGetValue(textureName, let textureHandle))
{
processedMaterial.AddTexture(textureName, textureHandle);
}
}
outProcessedResources.Add(processedMaterial);
return .Ok;
}
}
+4 -4
View File
@@ -29,10 +29,6 @@ namespace GlitchyEditor
_contentManager.SetAsDefaultAssetLoader<ModelAssetLoader>(".glb", ".gltf");
_contentManager.SetAssetPropertiesEditor<ModelAssetLoader>(=> ModelAssetPropertiesEditor.Factory);
_contentManager.RegisterAssetLoader<MaterialAssetLoader>();
_contentManager.SetAsDefaultAssetLoader<MaterialAssetLoader>(".mat");
_contentManager.SetAssetPropertiesEditor<MaterialAssetLoader>(=> MaterialAssetPropertiesEditor.Factory);
_contentManager.RegisterAssetImporter<TextureImporter>();
_contentManager.RegisterAssetProcessor<TextureProcessor>();
_contentManager.RegisterAssetExporter<TextureExporter>();
@@ -43,6 +39,10 @@ namespace GlitchyEditor
_contentManager.RegisterAssetProcessor<ShaderProcessor>();
_contentManager.RegisterAssetExporter<ShaderExporter>();
_contentManager.RegisterAssetImporter<MaterialImporter>();
_contentManager.RegisterAssetProcessor<MaterialProcessor>();
_contentManager.RegisterAssetExporter<MaterialExporter>();
_contentManager.SetGlobalAssetCacheDirectory(".cache");
_contentManager.SetResourcesDirectory("Resources");
@@ -946,6 +946,7 @@ class EditorContentManager : IContentManager
TextureLoader textureLoader = new .() ~ delete _;
SpriteLoader spriteLoader = new .() ~ delete _;
ShaderLoader shaderLoader = new .() ~ delete _;
MaterialLoader materialLoader = new .() ~ delete _;
private IProcessedAssetLoader GetLoader(AssetType assetType)
{
@@ -957,6 +958,8 @@ class EditorContentManager : IContentManager
return spriteLoader;
case .Shader:
return shaderLoader;
case .Material:
return materialLoader;
default:
return null;
}
@@ -0,0 +1,53 @@
using System;
using System.Collections;
using System.IO;
using GlitchyEngine.Renderer;
namespace GlitchyEngine.Content.Loaders;
class MaterialLoader : IProcessedAssetLoader
{
public Result<Asset> Load(Stream stream)
{
Compiler.Assert(sizeof(AssetHandle) == 8);
AssetHandle<Effect> effectHandle = Try!(stream.Read<AssetHandle>());
Effect effect = Content.GetAsset<Effect>(effectHandle, blocking: true);
Material material = new Material(effect);
uint16 textureCount = Try!(stream.Read<uint16>());
for (int i < textureCount)
{
AssetHandle<Texture> textureHandle = Try!(stream.Read<AssetHandle>());
int16 textureNameLength = Try!(stream.Read<int16>());
String textureName = scope String(textureNameLength);
stream.ReadStrSized32(textureNameLength, textureName);
material.SetTexture(textureName, textureHandle);
}
uint16 bufferCount = Try!(stream.Read<uint16>());
for (int i < bufferCount)
{
int64 bufferDataSize = Try!(stream.Read<int64>());
int16 textureNameLength = Try!(stream.Read<int16>());
String textureName = scope String(textureNameLength);
stream.ReadStrSized32(textureNameLength, textureName);
uint8[] data = new:ScopedAlloc! uint8[bufferDataSize];
Try!(stream.TryRead(data));
// TODO: Somehow get buffer data into material
}
material.SetVariable("AlbedoColor", GlitchyEngine.Math.float4(1.0f, 0, 0, 1.0f));
return material;
}
}
+14 -11
View File
@@ -10,20 +10,20 @@ namespace GlitchyEngine.Renderer
private String _name ~ delete _;
private ShaderVariableType _type;
private ShaderVariableType _elementType;
internal uint32 _columns;
internal uint32 _rows;
private uint32 _offset;
internal uint32 _sizeInBytes;
// Number of elements in the array
internal uint32 _elements;
internal uint32 _arrayElements;
private bool _isUsed;
public ConstantBuffer ConstantBuffer => _constantBuffer;
public ShaderVariableType Type => _type;
public ShaderVariableType ElementType => _elementType;
public String Name => _name;
@@ -31,6 +31,9 @@ namespace GlitchyEngine.Renderer
public uint32 Columns => _columns;
public uint32 Rows => _rows;
public uint32 ArrayElements => _arrayElements;
public uint32 Offset => _offset;
/**
* Gets a pointer to the start of the variable in the constant buffers backing data.
@@ -38,16 +41,16 @@ namespace GlitchyEngine.Renderer
[Inline]
internal uint8* firstByte => _constantBuffer.rawData.CArray() + _offset;
public this(StringView name, ConstantBuffer constantBuffer, ShaderVariableType type, uint32 columns, uint32 rows, uint32 offset, uint32 sizeInBytes, uint32 elements, bool isUsed)
public this(StringView name, ConstantBuffer constantBuffer, ShaderVariableType type, uint32 columns, uint32 rows, uint32 offset, uint32 sizeInBytes, uint32 arrayElements, bool isUsed)
{
_name = new String(name);
_constantBuffer = constantBuffer; // Only hold a weak reference. This variable has to die with the buffer
_type = type;
_elementType = type;
_columns = columns;
_rows = rows;
_offset = offset;
_sizeInBytes = sizeInBytes;
_elements = elements;
_arrayElements = arrayElements;
_isUsed = isUsed;
}
@@ -64,8 +67,8 @@ namespace GlitchyEngine.Renderer
#endif
#if GE_SHADER_VAR_TYPE_MISMATCH_IS_ERROR
if (type != _type)
Log.EngineLogger.Assert(false, scope $"The types do not match: Expected \"{_type}\" but Received \"{type}\" instead. Variable: \"{_name}\" of buffer: \"{_constantBuffer.Name}\"");
if (type != _elementType)
Log.EngineLogger.Assert(false, scope $"The types do not match: Expected \"{_elementType}\" but Received \"{type}\" instead. Variable: \"{_name}\" of buffer: \"{_constantBuffer.Name}\"");
#elif GE_SHADER_VAR_TYPE_MISMATCH_IS_WARNING
if (type != _type)
Log.EngineLogger.Warning($"The types do not match: Expected \"{_type}\" but Received \"{type}\" instead. Variable: \"{_name}\" of buffer: \"{_constantBuffer.Name}\"");
@@ -174,7 +177,7 @@ namespace GlitchyEngine.Renderer
// TODO: assert length
Internal.MemCpy(firstByte, value.Ptr, sizeof(Matrix4x3) * Math.Min(value.Count, _elements));
Internal.MemCpy(firstByte, value.Ptr, sizeof(Matrix4x3) * Math.Min(value.Count, _arrayElements));
}
public void SetData(Matrix3x3 value)
@@ -192,7 +195,7 @@ namespace GlitchyEngine.Renderer
// TODO: assert length
for(int i < Math.Min(value.Count, _elements))
for(int i < Math.Min(value.Count, _arrayElements))
{
((Matrix4x3*)firstByte)[i] = Matrix4x3(value[i]);
}
@@ -206,7 +209,7 @@ namespace GlitchyEngine.Renderer
// TODO: assert length
Internal.MemCpy(firstByte, value.Ptr, sizeof(Matrix) * Math.Min(value.Count, _elements));
Internal.MemCpy(firstByte, value.Ptr, sizeof(Matrix) * Math.Min(value.Count, _arrayElements));
}
// Todo: add all the other SetData-Methods
@@ -78,6 +78,7 @@ namespace GlitchyEngine.Renderer
switch (this)
{
case .Bool:
// TODO: I'm pretty sure this is wrong and should be 4 as well!
return 1;
case .Float:
return 4;
+4 -4
View File
@@ -93,9 +93,9 @@ public class Material : Asset
{
if (_variables.TryGetValue(newKey, let oldEntry))
{
if (oldEntry.Variable.Type == newValue.Variable.Type)
if (oldEntry.Variable.ElementType == newValue.Variable.ElementType)
{
int elementSize = newValue.Variable.Type.ElementSizeInBytes();
int elementSize = newValue.Variable.ElementType.ElementSizeInBytes();
for (int r = 0; r < Math.Min(oldEntry.Variable.Rows, newValue.Variable.Rows); r++)
for (int c = 0; c < Math.Min(oldEntry.Variable.Columns, newValue.Variable.Columns); c++)
@@ -259,7 +259,7 @@ public class Material : Asset
{
entry.Variable.EnsureTypeMatch<Matrix3x3>();
int count = Math.Min(values.Count, entry.Variable._elements);
int count = Math.Min(values.Count, entry.Variable._arrayElements);
for(int i < count)
{
@@ -281,7 +281,7 @@ public class Material : Asset
{
entry.Variable.EnsureTypeMatch<Matrix>();
Internal.MemCpy(RawPointer!<Matrix>(entry.Offset), values.Ptr, sizeof(Matrix) * Math.Min(values.Count, entry.Variable._elements));
Internal.MemCpy(RawPointer!<Matrix>(entry.Offset), values.Ptr, sizeof(Matrix) * Math.Min(values.Count, entry.Variable._arrayElements));
}
else
{