Start of async asset loading

- Refactored content manager and asset loaders
- Asset placeholder
- Error asset
This commit is contained in:
Simon Lübeß
2023-03-18 18:59:30 +01:00
parent 7b6662cbc4
commit 50714976e9
11 changed files with 329 additions and 321 deletions
+8 -47
View File
@@ -48,54 +48,15 @@ class EffectAssetLoader : IAssetLoader //, IReloadingAssetLoader
Effect effect = new Effect(file, assetIdentifier, contentManager); Effect effect = new Effect(file, assetIdentifier, contentManager);
return effect; return effect;
}
/*StreamReader reader = scope .(file); public Asset GetPlaceholderAsset(Type assetType)
{
return default;
}
String text = scope .(); public Asset GetErrorAsset(Type assetType)
{
reader.ReadToEnd(text); return default;
MaterialFile materialFile = scope .();
var result = Bon.Deserialize<MaterialFile>(ref materialFile, text);
if (result case .Err)
{
Log.EngineLogger.Error("Failed to load material.");
return null;
// TODO: return error material
}
Effect fx = new Effect(materialFile.Effect);
Material material = new Material(fx);
for (let (slotName, textureIdentifier) in materialFile.Textures)
{
Texture texture = contentManager.LoadAsset(textureIdentifier) as Texture;
if (texture == null)
{
Log.EngineLogger.Error("Failed to load texture.");
// TODO: LoadAsset should return an error texture.
}
material.SetTexture(slotName, texture);
}
fx.ReleaseRef();
/*for (let (slotName, textureIdentifier) in materialFile.Variables)
{
if (texture == null)
{
Log.EngineLogger.Error("Failed to load texture.");
// TODO: LoadAsset should return an error texture.
}
material.SetVariable(slotName, );
}*/
return material; //ModelLoader.LoadMesh(file, subAsset.Value, 0);*/
} }
} }
+15 -50
View File
@@ -339,10 +339,9 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader
Log.EngineLogger.Error("Failed to load material."); Log.EngineLogger.Error("Failed to load material.");
Debug.SafeBreak(); Debug.SafeBreak();
return null; return null;
// TODO: return error material
} }
Effect fx = Content.GetAsset<Effect>(contentManager.LoadAsset(materialFile.Effect), contentManager);//new Effect(materialFile.Effect); Effect fx = Content.GetAsset<Effect>(contentManager.LoadAsset(materialFile.Effect, true), contentManager);
Material material = new Material(fx); Material material = new Material(fx);
@@ -353,7 +352,6 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader
if (texture.IsInvalid) if (texture.IsInvalid)
{ {
Log.EngineLogger.Error($"Failed to load texture \"{textureIdentifier}\"."); Log.EngineLogger.Error($"Failed to load texture \"{textureIdentifier}\".");
// TODO: LoadAsset should return an error texture.
} }
material.SetTexture(slotName, texture); material.SetTexture(slotName, texture);
@@ -377,7 +375,7 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader
material.SetVariable(slotName, value); material.SetVariable(slotName, value);
case .None: case .None:
default: default:
Log.EngineLogger.Error("Errorre"); Log.EngineLogger.Error($"Unknown variable type of variable {slotName}: {variableValue}");
} }
@@ -402,8 +400,6 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader
materialFile.Textures = new .(); materialFile.Textures = new .();
materialFile.Variables = new .(); materialFile.Variables = new .();
//material.SetTexture();
for (let (slotName, texture) in material.[Friend]_textures) for (let (slotName, texture) in material.[Friend]_textures)
{ {
Texture textureAsset = texture.Get(); Texture textureAsset = texture.Get();
@@ -419,7 +415,6 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader
for (let (name, arguments) in effect.[Friend]_variableDescriptions) for (let (name, arguments) in effect.[Friend]_variableDescriptions)
{ {
VariableValue variableValue = .None; VariableValue variableValue = .None;
//Object variantValue = null;
let variable = effect.Variables[name]; let variable = effect.Variables[name];
@@ -445,7 +440,6 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader
value = ColorRGBA.LinearToSRGB((ColorRGBA)value); value = ColorRGBA.LinearToSRGB((ColorRGBA)value);
variableValue = .ColorRGBA(value); variableValue = .ColorRGBA(value);
//variantValue = new box value;
} }
} }
else if (variable.Type == .Float && variable.Rows == 1) else if (variable.Type == .Float && variable.Rows == 1)
@@ -465,48 +459,6 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader
material.GetVariable<Vector4>(variable.Name, let value); material.GetVariable<Vector4>(variable.Name, let value);
variableValue = .Float4(value); variableValue = .Float4(value);
} }
/*bool hasMin = TryGetValue(arguments, "Min", var min);
bool hasMax = TryGetValue(arguments, "Max", var max);
for (int r < variable.Rows)
{
switch (variable.Columns)
{
case 1:
material.GetVariable<float>(variable.Name, var value);
float[1] minV = hasMin ? min.Get<float[1]>() : .(float.MinValue);
float[1] maxV = hasMax ? max.Get<float[1]>() : .(float.MaxValue);
if (ImGui.EditVector<1>(displayName, ref *(float[1]*)&value, .(), 0.1f, 100.0f, minV, maxV))
material.SetVariable(variable.Name, value);
case 2:
material.GetVariable<Vector2>(variable.Name, var value);
Vector2 minV = hasMin ? min.Get<Vector2>() : .(float.MinValue);
Vector2 maxV = hasMax ? max.Get<Vector2>() : .(float.MaxValue);
if (ImGui.EditVector2(displayName, ref value, .Zero, 0.1f, 100.0f, minV, maxV))
material.SetVariable(variable.Name, value);
case 3:
material.GetVariable<Vector3>(variable.Name, var value);
Vector3 minV = hasMin ? min.Get<Vector3>() : .(float.MinValue);
Vector3 maxV = hasMax ? max.Get<Vector3>() : .(float.MaxValue);
if (ImGui.EditVector3(displayName, ref value, .Zero, 0.1f, 100.0f, minV, maxV))
material.SetVariable(variable.Name, value);
case 4:
material.GetVariable<Vector4>(variable.Name, var value);
Vector4 minV = hasMin ? min.Get<Vector4>() : .(float.MinValue);
Vector4 maxV = hasMax ? max.Get<Vector4>() : .(float.MaxValue);
if (ImGui.EditVector4(displayName, ref value, .Zero, 0.1f, 100.0f, minV, maxV))
material.SetVariable(variable.Name, value);
}
}*/
} }
materialFile.Variables.Add(new String(name), variableValue); materialFile.Variables.Add(new String(name), variableValue);
@@ -523,4 +475,17 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader
return .Ok; return .Ok;
} }
Material _placeholder;
Material _error;
public Asset GetPlaceholderAsset(Type assetType)
{
return default;
}
public Asset GetErrorAsset(Type assetType)
{
return default;
}
} }
+12 -2
View File
@@ -44,8 +44,18 @@ class ModelAssetLoader : IAssetLoader //, IReloadingAssetLoader
public Asset LoadAsset(Stream file, AssetLoaderConfig config, StringView assetIdentifier, StringView? subAsset, IContentManager contentManager) public Asset LoadAsset(Stream file, AssetLoaderConfig config, StringView assetIdentifier, StringView? subAsset, IContentManager contentManager)
{ {
Log.EngineLogger.Assert(subAsset != null); //Log.EngineLogger.Assert(subAsset != null);
return ModelLoader.LoadMesh(file, subAsset.Value, 0); return ModelLoader.LoadMesh(file, subAsset ?? assetIdentifier, 0);
}
public Asset GetPlaceholderAsset(Type assetType)
{
return default;
}
public Asset GetErrorAsset(Type assetType)
{
return default;
} }
} }
+88 -69
View File
@@ -243,65 +243,19 @@ class EditorTextureAssetLoader : IAssetLoader//, IReloadingAssetLoader
case .PNG: case .PNG:
texture = LoadPng(data, config); texture = LoadPng(data, config);
case .Unknown: case .Unknown:
Runtime.FatalError("Unknown image format."); Log.EngineLogger.Error("Unknown texture format.");
texture = null;
} }
Log.EngineLogger.AssertDebug(texture != null); if (texture != null)
{
SetSampler(texture, config); SetSampler(texture, config);
texture.[Friend]Complete = true;
}
return texture; return texture;
} }
public void ReloadAsset(AssetFile assetFile, Stream data)
{
Texture reloadingTexture = assetFile.LoadedAsset as Texture;
if (reloadingTexture == null)
{
Log.EngineLogger.Error($"{nameof(Self)}: Requested reload of \"{assetFile.FilePath}\" but it's not a Texture!");
return;
}
EditorTextureAssetLoaderConfig config = assetFile.AssetConfig.Config as EditorTextureAssetLoaderConfig;
if (config == null)
{
Log.EngineLogger.Error($"{nameof(Self)}: Config of asset \"{assetFile.FilePath}\" doesn't have the correct type!");
return;
}
switch(GetTextureType(data))
{
case .DDS:
ReloadDds(reloadingTexture as Texture2D, data, config);
case .PNG:
ReloadPng(reloadingTexture as Texture2D, data, config);
case .Unknown:
Runtime.FatalError("Unknown image format.");
}
SetSampler(reloadingTexture, config);
}
private static void SetSampler(Texture texture, EditorTextureAssetLoaderConfig config)
{
using (SamplerState samplerState = SamplerStateManager.GetSampler(config.SamplerStateDescription))
{
texture.SamplerState = samplerState;
}
}
private static void ReloadPng(Texture2D reloadingTexture, Stream data, EditorTextureAssetLoaderConfig config)
{
Debug.Profiler.ProfileResourceFunction!();
using (Texture2D newTexture = LoadPng(data, config))
{
reloadingTexture.[Friend]SneakySwappyTexture(newTexture);
}
}
private static Texture2D LoadPng(Stream data, EditorTextureAssetLoaderConfig config) private static Texture2D LoadPng(Stream data, EditorTextureAssetLoaderConfig config)
{ {
Debug.Profiler.ProfileResourceFunction!(); Debug.Profiler.ProfileResourceFunction!();
@@ -313,40 +267,105 @@ class EditorTextureAssetLoader : IAssetLoader//, IReloadingAssetLoader
if (result case .Err(let err)) if (result case .Err(let err))
{ {
Log.EngineLogger.Error($"Failed to read data from stream. Texture: Error: {err}"); Log.EngineLogger.Error($"Failed to read data from stream. Texture: Error: {err}");
return null;
}
uint8* rawData = null;
defer
{
if (rawData != null)
LodePng.LodePng.Free(rawData);
} }
uint8* rawData = ?;
uint32 width = 0, height = 0; uint32 width = 0, height = 0;
uint32 errorCode = LodePng.LodePng.Decode32(&rawData, &width, &height, pngData.Ptr, (.)pngData.Count); {
Debug.Profiler.ProfileResourceScope!("LodePng.LodePng.Decode32");
uint32 errorCode = LodePng.LodePng.Decode32(&rawData, &width, &height, pngData.Ptr, (.)pngData.Count);
if (errorCode != 0)
{
Log.EngineLogger.Error($"Failed to decode PNG file {errorCode}.");
return null;
}
}
Log.EngineLogger.Assert(errorCode == 0, "Failed to load png File");
// TODO: load as SRGB because PNGs are usually not stored as linear
//Texture2DDesc desc = .(width, height, srgb? .R8G8B8A8_UNorm_SRGB : .R8G8B8A8_UNorm, 1, 1, .Immutable);
Texture2DDesc desc = .(width, height, config.IsSRGB ? .R8G8B8A8_UNorm_SRGB : .R8G8B8A8_UNorm, 1, 1, .Immutable); Texture2DDesc desc = .(width, height, config.IsSRGB ? .R8G8B8A8_UNorm_SRGB : .R8G8B8A8_UNorm, 1, 1, .Immutable);
Texture2D texture = new Texture2D(desc); Texture2D texture = new Texture2D(desc);
texture.SetData<Color>((.)rawData); texture.SetData<Color>((.)rawData);
// TODO: Generate mip maps // TODO: Generate mip maps
LodePng.LodePng.Free(rawData);
return texture; return texture;
} }
private static void ReloadDds(Texture2D reloadingTexture, Stream data, EditorTextureAssetLoaderConfig config)
{
using (Texture2D newTexture = new [Friend]Texture2D(data))
{
reloadingTexture.[Friend]SneakySwappyTexture(newTexture);
}
}
private static Texture LoadDds(Stream data, EditorTextureAssetLoaderConfig config) private static Texture LoadDds(Stream data, EditorTextureAssetLoaderConfig config)
{ {
// TODO: Move the loading of Dds files here.
Texture2D texture = new [Friend]Texture2D(data); Texture2D texture = new [Friend]Texture2D(data);
return texture; return texture;
} }
private static void SetSampler(Texture texture, EditorTextureAssetLoaderConfig config)
{
using (SamplerState samplerState = SamplerStateManager.GetSampler(config.SamplerStateDescription))
{
texture.SamplerState = samplerState;
}
}
private static Texture2D _placeholder2D;
private static Texture2D _error2D;
public Asset GetPlaceholderAsset(Type assetType)
{
switch (assetType)
{
case typeof(Texture2D):
fallthrough;
default:
if (_placeholder2D == null)
{
Texture2DDesc desc = .(1, 1, .R8G8B8A8_UNorm, 1, 1, .Immutable, .None);
_placeholder2D = new Texture2D(desc);
_placeholder2D.SamplerState = SamplerStateManager.PointWrap;
Color color = Color.Cyan;
_placeholder2D.SetData<Color>(&color);
Content.ManageAsset(_placeholder2D);
_placeholder2D.ReleaseRef();
_placeholder2D.[Friend]Complete = false;
}
return _placeholder2D;
}
}
public Asset GetErrorAsset(Type assetType)
{
switch (assetType)
{
case typeof(Texture2D):
fallthrough;
default:
if (_error2D == null)
{
Texture2DDesc desc = .(2, 2, .R8G8B8A8_UNorm, 1, 1, .Immutable, .None);
_error2D = new Texture2D(desc);
_error2D.SamplerState = SamplerStateManager.PointWrap;
Color[4] color = .(Color.HotPink, Color.Black, Color.Black, Color.HotPink);
_error2D.SetData<Color>(&color);
Content.ManageAsset(_error2D);
_error2D.ReleaseRef();
_placeholder2D.[Friend]Complete = true;
}
return _error2D;
}
}
} }
+184 -44
View File
@@ -8,6 +8,8 @@ using GlitchyEngine.Content;
using GlitchyEditor.Assets; using GlitchyEditor.Assets;
using GlitchyEngine; using GlitchyEngine;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using internal GlitchyEngine.Content.Asset;
namespace GlitchyEditor; namespace GlitchyEditor;
@@ -100,6 +102,8 @@ class EditorContentManager : IContentManager
public void Update() public void Update()
{ {
SwapInLoadedAssets();
if (!_reloadQueue.IsEmpty) if (!_reloadQueue.IsEmpty)
{ {
for (AssetHandle handle in _reloadQueue) for (AssetHandle handle in _reloadQueue)
@@ -112,6 +116,40 @@ class EditorContentManager : IContentManager
_assetHierarchy.Update(); _assetHierarchy.Update();
} }
/// Replaces placeholders with the loaded assets
private void SwapInLoadedAssets()
{
// Don't take the lock if we have nothing to do.
if (_finishedEntries.Count == 0)
return;
using (_finishedEntriesLock.Enter())
{
while (_finishedEntries.Count > 0)
{
let (placeholder, asset) = _finishedEntries[0];
delete placeholder.LoadingTask;
if (asset == null)
placeholder.PlaceholderType = .Error;
else
{
// TODO: I'm not sure whether AssetFiles are guaranteed to persist.
// Get the reference here because placeholder wont survive SwapAsset.
AssetFile file = placeholder.AssetFile;
file.[Friend]_loadedAsset = asset;
SwapAsset(placeholder, asset);
// SwapAsset increases RefCount, but this scope also holds a reference.
asset.ReleaseRef();
}
_finishedEntries.RemoveAtFast(0);
}
}
}
public IAssetLoader GetDefaultAssetLoader(StringView fileExtension) public IAssetLoader GetDefaultAssetLoader(StringView fileExtension)
{ {
if (_defaultAssetLoaders.TryGetValue(fileExtension, let value)) if (_defaultAssetLoaders.TryGetValue(fileExtension, let value))
@@ -131,9 +169,11 @@ class EditorContentManager : IContentManager
public void RegisterAssetLoader<T>() where T : new, class, IAssetLoader public void RegisterAssetLoader<T>() where T : new, class, IAssetLoader
{ {
//Log.EngineLogger.AssertDebug(!_assetLoaders.Any((l) => l.GetType() == typeof(T)), "Asset loader already registered."); // Log.EngineLogger.AssertDebug(!_assetLoaders.Any((l) => l.GetType() == typeof(T)), "Asset loader already registered.");
_assetLoaders.Add(new T()); T assetLoader = new T();
_assetLoaders.Add(assetLoader);
for (StringView ext in T.FileExtensions) for (StringView ext in T.FileExtensions)
_supportedExtensions.Add(new String(ext)); _supportedExtensions.Add(new String(ext));
@@ -196,13 +236,21 @@ class EditorContentManager : IContentManager
{ {
return _identiferToHandle.ContainsKey(identifier); return _identiferToHandle.ContainsKey(identifier);
} }
public Asset GetAsset(Type assetType, AssetHandle handle) public Asset GetAsset(Type assetType, AssetHandle handle)
{ {
Asset asset = null; Asset asset = null;
_handleToAsset.TryGetValue(handle, out asset); _handleToAsset.TryGetValue(handle, out asset);
if (var placeholder = asset as PlaceholderAsset)
{
if (placeholder.PlaceholderType == .Loading)
return placeholder.AssetLoader.GetPlaceholderAsset(assetType);
else if (placeholder.PlaceholderType == .Error)
return placeholder.AssetLoader.GetErrorAsset(assetType);
}
if (assetType == null) if (assetType == null)
{ {
return asset; return asset;
@@ -221,26 +269,26 @@ class EditorContentManager : IContentManager
private void ReloadAsset(AssetHandle handle) private void ReloadAsset(AssetHandle handle)
{ {
Asset asset = null; Debug.Profiler.ProfileResourceFunction!();
Asset oldAsset = null;
if (!_handleToAsset.TryGetValue(handle, out asset)) if (!_handleToAsset.TryGetValue(handle, out oldAsset))
{ {
Log.EngineLogger.Error("Can't reload! No asset exists for handle."); Log.EngineLogger.Error("Can't reload! No asset exists for handle.");
return; return;
} }
Log.EngineLogger.AssertDebug(asset != null); Log.EngineLogger.AssertDebug(oldAsset != null);
StringView oldIdentifier = asset.Identifier; StringView oldIdentifier = oldAsset.Identifier;
GetResourceAndSubassetName(oldIdentifier, let resourceName, let subassetName); GetResourceAndSubassetName(oldIdentifier, let resourceName, let subassetName);
String filePath = scope .(); String filePath = scope .();
GetResourceFilePath(resourceName, filePath); GetResourceFilePath(resourceName, filePath);
//filePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
Result<TreeNode<AssetNode>> resultNode = AssetHierarchy.GetNodeFromPath(filePath); Result<TreeNode<AssetNode>> resultNode = AssetHierarchy.GetNodeFromPath(filePath);
if (resultNode case .Err) if (resultNode case .Err)
@@ -255,35 +303,28 @@ class EditorContentManager : IContentManager
Stream stream = GetStream(filePath); Stream stream = GetStream(filePath);
// TODO: Add async loading!
Asset loadedAsset = assetLoader.LoadAsset(stream, file.AssetConfig.Config, resourceName, subassetName, this); Asset loadedAsset = assetLoader.LoadAsset(stream, file.AssetConfig.Config, resourceName, subassetName, this);
delete stream; delete stream;
if (loadedAsset == null) if (loadedAsset == null)
return; return;
loadedAsset.Identifier = oldIdentifier;
loadedAsset.[Friend]_handle = handle;
// Remove asset
_identiferToHandle.Remove(oldIdentifier);
Asset oldAsset = _handleToAsset[handle];
oldAsset.ReleaseRef();
_handleToAsset[handle] = loadedAsset;
_identiferToHandle.Add(loadedAsset.Identifier, handle);
file.[Friend]_loadedAsset = loadedAsset; file.[Friend]_loadedAsset = loadedAsset;
SwapAsset(oldAsset, loadedAsset);
// SwapAsset increases RefCount, but this scope also holds a reference.
loadedAsset.ReleaseRef();
} }
/// Returns the resource name and, if it exists, the subasset name. /// Returns the resource name and, if it exists, the subasset name.
private void GetResourceAndSubassetName(StringView identifier, out StringView resourceName, out StringView? subassetName) private void GetResourceAndSubassetName(StringView identifier, out StringView resourceName, out StringView? subassetName)
{ {
// Find subasset name
int poundIndex = identifier.IndexOf('#'); int poundIndex = identifier.IndexOf('#');
resourceName = poundIndex == -1 ? identifier : identifier.Substring(0, poundIndex); resourceName = (poundIndex != -1) ? identifier.Substring(0, poundIndex) : identifier;
subassetName = identifier.Substring(poundIndex + 1); subassetName = (poundIndex != -1) ? identifier.Substring(poundIndex + 1) : null;
} }
private void GetResourceFilePath(StringView resourceName, String filePath) private void GetResourceFilePath(StringView resourceName, String filePath)
@@ -293,25 +334,66 @@ class EditorContentManager : IContentManager
Path.Fixup(filePath); Path.Fixup(filePath);
} }
public AssetHandle LoadAsset(StringView identifier) private enum PlaceholderType
{ {
Loading,
Error
}
private class PlaceholderAsset : Asset
{
public AssetFile AssetFile {get; private set;}
public Task LoadingTask {get;set;}
public IAssetLoader AssetLoader {get; private set;}
public PlaceholderType PlaceholderType {get; set;}
public this(AssetFile assetFile, IAssetLoader assetLoader, PlaceholderType placeholderType)
{
AssetFile = assetFile;
AssetLoader = assetLoader;
PlaceholderType = placeholderType;
}
}
private append Monitor _finishedEntriesLock = .();
private append List<(PlaceholderAsset placeholder, Asset newAsset)> _finishedEntries = .();
private class MissingAsset : Asset {}
public class AssetIdentifier
{
public const char8 DirectorySeparatorChar = '/';
public static void Fixup(String assetIdentifier)
{
const String DotSeperator = $".{DirectorySeparatorChar}";
const String SeperatorDot = $"{DirectorySeparatorChar}.";
assetIdentifier.Replace('\\', DirectorySeparatorChar);
assetIdentifier.Replace(DotSeperator, "");
assetIdentifier.Replace(SeperatorDot, "");
if (assetIdentifier.StartsWith(DirectorySeparatorChar))
assetIdentifier.Remove(0, 1);
}
}
public AssetHandle LoadAsset(StringView identifier, bool blocking = false)
{
Debug.Profiler.ProfileResourceFunction!();
// Todo: How strict should we be on paths? // Todo: How strict should we be on paths?
String fixedIdentifier = scope String(identifier); String fixedIdentifier = scope String(identifier);
// TODO: Fixup is more of a filesystem thingy... we might want to use our own function AssetIdentifier.Fixup(fixedIdentifier);
Path.Fixup(fixedIdentifier);
if (_identiferToHandle.TryGetValue(fixedIdentifier, let asset)) if (_identiferToHandle.TryGetValue(fixedIdentifier, let asset))
{
return asset; return asset;
}
GetResourceAndSubassetName(fixedIdentifier, let resourceName, let subassetName); GetResourceAndSubassetName(fixedIdentifier, let resourceName, let subassetName);
String filePath = scope .(); String filePath = scope .();
GetResourceFilePath(resourceName, filePath); GetResourceFilePath(resourceName, filePath);
//filePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
Result<TreeNode<AssetNode>> resultNode = AssetHierarchy.GetNodeFromPath(filePath); Result<TreeNode<AssetNode>> resultNode = AssetHierarchy.GetNodeFromPath(filePath);
if (resultNode case .Err) if (resultNode case .Err)
@@ -327,14 +409,37 @@ class EditorContentManager : IContentManager
// TODO: what are we supposed to do if we don't find a loader? Sure not crash... // TODO: what are we supposed to do if we don't find a loader? Sure not crash...
Log.EngineLogger.AssertDebug(assetLoader != null); Log.EngineLogger.AssertDebug(assetLoader != null);
Stream stream = GetStream(filePath); Asset loadedAsset;
Asset loadedAsset = assetLoader.LoadAsset(stream, file.AssetConfig.Config, resourceName, subassetName, this); // TODO: Support lazy loading for all asset types
if (!(assetLoader is EditorTextureAssetLoader) || blocking)
delete stream; {
Stream stream = GetStream(filePath);
if (loadedAsset == null) loadedAsset = assetLoader.LoadAsset(stream, file.AssetConfig.Config, resourceName, subassetName, this);
return .Invalid;
delete stream;
if (loadedAsset == null)
return .Invalid;
}
else
{
PlaceholderAsset placeholder = new PlaceholderAsset(file, assetLoader, .Loading);
String filePath2 = new String(filePath);
String newResourceName = new String(resourceName);
String newSesourceName = subassetName == null ? null : new String(subassetName.Value);
placeholder.LoadingTask = new Task(new () => {
AsyncLoadAsset(placeholder, filePath2, assetLoader, file,
newResourceName, newSesourceName);
});
ThreadPool.QueueUserWorkItem(placeholder.LoadingTask);
loadedAsset = placeholder;
}
loadedAsset.Identifier = fixedIdentifier; loadedAsset.Identifier = fixedIdentifier;
AssetHandle handle = ManageAsset(loadedAsset); AssetHandle handle = ManageAsset(loadedAsset);
@@ -349,6 +454,25 @@ class EditorContentManager : IContentManager
return handle; return handle;
} }
private void AsyncLoadAsset(PlaceholderAsset placeholder, String filePath, IAssetLoader assetLoader, AssetFile file, String resourceName, String subassetName)
{
Debug.Profiler.ProfileResourceFunction!();
Stream stream = GetStream(filePath);
Asset loadedAsset = assetLoader.LoadAsset(stream, file.AssetConfig.Config, resourceName, subassetName, this);
delete stream;
delete filePath;
delete resourceName;
delete subassetName;
using (_finishedEntriesLock.Enter())
{
_finishedEntries.Add((placeholder, loadedAsset));
}
}
/// Gets the asset loader that has to be used for the given file. /// Gets the asset loader that has to be used for the given file.
IAssetLoader GetAssetLoader(AssetFile file) IAssetLoader GetAssetLoader(AssetFile file)
{ {
@@ -483,9 +607,10 @@ class EditorContentManager : IContentManager
AssetHandle handle = .(); AssetHandle handle = .();
// Generate until we find a unique key (shouldn't happen too often) // Generate until we find a unique key (shouldn't happen too often)
while (_handleToAsset.ContainsKey(handle)) while (handle.IsInvalid || _handleToAsset.ContainsKey(handle))
{ {
handle = .(); handle = .();
Log.EngineLogger.Trace("Handle was invalid or already taken.");
// TODO: perhaps test how often this happens. // TODO: perhaps test how often this happens.
// If this happens too often we could use a different random generator // If this happens too often we could use a different random generator
} }
@@ -500,9 +625,26 @@ class EditorContentManager : IContentManager
return handle; return handle;
} }
private void SwapAsset(Asset oldAsset, Asset newAsset)
{
newAsset.Identifier = oldAsset.Identifier;
newAsset._contentManager = this;
newAsset._handle = oldAsset.Handle;
_handleToAsset[oldAsset.Handle] = newAsset;
if (_identiferToHandle.ContainsKey(oldAsset.Identifier))
{
_identiferToHandle.Remove(oldAsset.Identifier);
_identiferToHandle.Add(newAsset.Identifier, newAsset.Handle);
}
newAsset.AddRef();
oldAsset.ReleaseRef();
}
public void UnmanageAsset(AssetHandle handle) public void UnmanageAsset(AssetHandle handle)
{ {
//Log.EngineLogger.AssertDebug(_handles.ContainsValue(handle), "Handle isn't managed by this content manager.");
Log.EngineLogger.AssertDebug(_handleToAsset.ContainsKey(handle), "Handle doesn't correspond to an asset."); Log.EngineLogger.AssertDebug(_handleToAsset.ContainsKey(handle), "Handle doesn't correspond to an asset.");
Asset asset = _handleToAsset[handle]; Asset asset = _handleToAsset[handle];
@@ -520,16 +662,14 @@ class EditorContentManager : IContentManager
/// Note: This will not release any assets. /// Note: This will not release any assets.
private void UnmanageAllAssets() private void UnmanageAllAssets()
{ {
for (let (_, assetHandle) in _identiferToHandle) for (let (handle, _) in _handleToAsset)
{ {
UnmanageAsset(assetHandle); UnmanageAsset(handle);
} }
} }
public void UpdateAssetIdentifier(Asset asset, StringView oldIdentifier, StringView newIdentifier) public void AssetMoved(Asset asset, StringView oldIdentifier, StringView newIdentifier)
{ {
// TODO: this is much harder with asste handles that are basically hashed identifiers!
Runtime.NotImplemented(); Runtime.NotImplemented();
if (oldIdentifier == newIdentifier) if (oldIdentifier == newIdentifier)
+1 -1
View File
@@ -25,7 +25,7 @@ namespace GlitchyEditor
public this(String texturePath, Vector2 iconSize) public this(String texturePath, Vector2 iconSize)
{ {
_texture = Content.LoadAsset(texturePath);//new Texture2D(texturePath); _texture = Content.LoadAsset(texturePath, null, true);
Vector2 pen = .(); Vector2 pen = .();
+5 -8
View File
@@ -8,7 +8,7 @@ using System.IO;
namespace GlitchyEngine.Content; namespace GlitchyEngine.Content;
[BonTarget] [BonTarget]
class Asset : RefCounter abstract class Asset : RefCounter
{ {
internal AssetHandle _handle = .Invalid; internal AssetHandle _handle = .Invalid;
@@ -22,15 +22,12 @@ class Asset : RefCounter
public StringView Identifier public StringView Identifier
{ {
get => _identifier; get => _identifier;
set internal set => _identifier.Set(value);
{
_contentManager?.UpdateAssetIdentifier(this, _identifier, value);
_identifier.Set(value);
// TODO: do we need to tell the content manager, that the name changed?
}
} }
/// If true the asset is completely loaded. If false it is only partially loaded (if at all).
public bool Complete { get; internal set; }
// TODO: do we need unmanaged assets? Probably not... // TODO: do we need unmanaged assets? Probably not...
/// Gets the content manager that manages this asset; or null if this asset isn't managed. /// Gets the content manager that manages this asset; or null if this asset isn't managed.
public IContentManager ContentManager => _contentManager; public IContentManager ContentManager => _contentManager;
+5 -5
View File
@@ -11,10 +11,10 @@ struct AssetHandle : IHashable
private UUID _uuid; private UUID _uuid;
/// Defines an asset that is invalid. /// Defines an asset that is invalid.
public const AssetHandle Invalid = .(UUID(0xAAAA'AAAA'AAAA'AAAA)); public const AssetHandle Invalid = .(UUID(0));
public bool IsValid => this == .Invalid; public bool IsValid => this != .Invalid;
public bool IsInvalid => !IsValid; public bool IsInvalid => this == .Invalid;
/// Create a new random AssetHandle /// Create a new random AssetHandle
public this() public this()
@@ -49,8 +49,8 @@ struct AssetHandle<T> where T : Asset
public const Self Invalid = .(); public const Self Invalid = .();
public bool IsValid => this == .Invalid; public bool IsValid => this != .Invalid;
public bool IsInvalid => !IsValid; public bool IsInvalid => this == .Invalid;
public this(AssetHandle handle, IContentManager contentManager = null) public this(AssetHandle handle, IContentManager contentManager = null)
{ {
+10 -13
View File
@@ -57,19 +57,25 @@ namespace GlitchyEngine.Content
/// @param contentManager The content manager used to load the asset. /// @param contentManager The content manager used to load the asset.
/// @returns The loaded asset. /// @returns The loaded asset.
Asset LoadAsset(Stream file, AssetLoaderConfig config, StringView assetIdentifier, StringView? subAsset, IContentManager contentManager); Asset LoadAsset(Stream file, AssetLoaderConfig config, StringView assetIdentifier, StringView? subAsset, IContentManager contentManager);
/// Returns the placeholder asset.
Asset GetPlaceholderAsset(Type assetType);
/// Returns the error asset.
Asset GetErrorAsset(Type assetType);
} }
static class Content static class Content
{ {
/// Loads the specified asset with the given contentManager or the current applications content manager. /// Loads the specified asset with the given contentManager or the current applications content manager.
public static AssetHandle LoadAsset(StringView assetIdentifier, IContentManager contentManager = null) public static AssetHandle LoadAsset(StringView assetIdentifier, IContentManager contentManager = null, bool blocking = false)
{ {
var contentManager; var contentManager;
if (contentManager == null) if (contentManager == null)
contentManager = Application.Get().ContentManager; contentManager = Application.Get().ContentManager;
AssetHandle handle = contentManager.LoadAsset(assetIdentifier); AssetHandle handle = contentManager.LoadAsset(assetIdentifier, blocking);
return handle; return handle;
} }
@@ -111,7 +117,7 @@ namespace GlitchyEngine.Content
interface IContentManager interface IContentManager
{ {
/// Loads the Asset with the given handle and returns the handle. /// Loads the Asset with the given handle and returns the handle.
AssetHandle LoadAsset(StringView assetIdentifier); AssetHandle LoadAsset(StringView assetIdentifier, bool blocking = false);
/// Returns the asset for the given handle or null, if it isn't loaded. /// Returns the asset for the given handle or null, if it isn't loaded.
Asset GetAsset(AssetHandle handle) Asset GetAsset(AssetHandle handle)
@@ -128,10 +134,6 @@ namespace GlitchyEngine.Content
/// The content manager will no longer manage the asset. /// The content manager will no longer manage the asset.
void UnmanageAsset(AssetHandle asset); void UnmanageAsset(AssetHandle asset);
// TODO: Maybe calling UnmanageAsset -> ManageAsset is enough....
/// Provides a method for the asset to tell its content manager that the identifer changed.
void UpdateAssetIdentifier(Asset asset, StringView oldIdentifier, StringView newIdentifier);
/// Returns a data stream for the given asset. /// Returns a data stream for the given asset.
Stream GetStream(StringView assetIdentifier); Stream GetStream(StringView assetIdentifier);
@@ -149,7 +151,7 @@ namespace GlitchyEngine.Content
Runtime.NotImplemented(); Runtime.NotImplemented();
} }
public AssetHandle LoadAsset(StringView assetIdentifier) public AssetHandle LoadAsset(StringView assetIdentifier, bool blocking = false)
{ {
Runtime.NotImplemented(); Runtime.NotImplemented();
} }
@@ -169,11 +171,6 @@ namespace GlitchyEngine.Content
Runtime.NotImplemented(); Runtime.NotImplemented();
} }
public void UpdateAssetIdentifier(Asset asset, StringView oldIdentifier, StringView newIdentifier)
{
Runtime.NotImplemented();
}
public Stream GetStream(StringView assetIdentifier) public Stream GetStream(StringView assetIdentifier)
{ {
Runtime.NotImplemented(); Runtime.NotImplemented();
@@ -101,7 +101,7 @@ namespace GlitchyEngine.Renderer
LoadDdsResourcePlatform(stream, ref nativeTexture); LoadDdsResourcePlatform(stream, ref nativeTexture);
let resType = nativeTexture.GetResourceType(); let resType = nativeTexture.GetResourceType();
Log.EngineLogger.Assert(resType == .Texture2D, scope $"The texture \"{_path}\" is not a 2D texture (it is {resType})."); Log.EngineLogger.Assert(resType == .Texture2D, scope $"The texture is not a 2D texture (it is {resType}).");
nativeTexture.GetDescription(out nativeDesc); nativeTexture.GetDescription(out nativeDesc);
} }
@@ -261,13 +261,6 @@ namespace GlitchyEngine.Renderer
{ {
return .(_nativeResourceView, _samplerState?.nativeSamplerState); return .(_nativeResourceView, _samplerState?.nativeSamplerState);
} }
protected override void PlatformSneakySwappyTexture(Texture2D otherTexture)
{
Swap!(nativeDesc, otherTexture.nativeDesc);
Swap!(nativeTexture, otherTexture.nativeTexture);
Swap!(_nativeResourceView, otherTexture._nativeResourceView);
}
} }
extension TextureCube extension TextureCube
-74
View File
@@ -63,19 +63,11 @@ namespace GlitchyEngine.Renderer
public class Texture2D : Texture public class Texture2D : Texture
{ {
protected String _path ~ delete _;
//public override extern uint32 Width {get;} //public override extern uint32 Width {get;}
//public override extern uint32 Height {get;} //public override extern uint32 Height {get;}
public override uint32 Depth => 1; public override uint32 Depth => 1;
//public override extern uint32 ArraySize {get;} //public override extern uint32 ArraySize {get;}
//public override extern uint32 MipLevels {get;} //public override extern uint32 MipLevels {get;}
private this(StringView path, bool pngSrgb = false)
{
_path = new String(path);
LoadTexture(pngSrgb);
}
// TODO: remove // TODO: remove
private this(Stream data) private this(Stream data)
@@ -83,72 +75,6 @@ namespace GlitchyEngine.Renderer
LoadDds(data); LoadDds(data);
} }
const String PngMagicWord = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A";
const String DdsMagicWord = "DDS ";
private void LoadTexture(bool pngSrgb)
{
Debug.Profiler.ProfileResourceFunction!();
Stream data = Application.Get().ContentManager.GetStream(_path);
defer delete data;
var readResult = data.Read<char8[8]>();
data.Position = 0;
char8[8] magicWord;
if (readResult case .Ok(out magicWord))
{
StringView strView = .(&magicWord, magicWord.Count);
if (strView.StartsWith(PngMagicWord))
{
LoadPng(data, pngSrgb);
}
else if (strView.StartsWith(DdsMagicWord))
{
LoadDds(data);
}
else
{
Runtime.FatalError("Unknown image format.");
}
}
}
protected void LoadPng(Stream stream, bool srgb)
{
Debug.Profiler.ProfileResourceFunction!();
uint8[] pngData = new:ScopedAlloc! uint8[stream.Length];
var result = stream.TryRead(pngData);
if (result case .Err(let err))
{
Log.EngineLogger.Error($"Failed to read data from stream. Texture: \"{_path}\", Error: {err}");
}
uint8* rawData = ?;
uint32 width = 0, height = 0;
uint32 errorCode = LodePng.LodePng.Decode32(&rawData, &width, &height, pngData.Ptr, (.)pngData.Count);
Debug.Assert(errorCode == 0, "Failed to load png File");
// TODO: load as SRGB because PNGs are usually not stored as linear
//Texture2DDesc desc = .(width, height, .R8G8B8A8_UNorm_SRGB, 1, 1, .Immutable);
Texture2DDesc desc = .(width, height, srgb? .R8G8B8A8_UNorm_SRGB : .R8G8B8A8_UNorm, 1, 1, .Immutable);
PrepareTexturePlatform(desc, false);
SetData<Color>((.)rawData);
LodePng.LodePng.Free(rawData);
}
protected void LoadDds(Stream stream) protected void LoadDds(Stream stream)
{ {
LoadDdsPlatform(stream); LoadDdsPlatform(stream);