From 50714976e95a4d5c32b1067c7ee537d243ee1089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Sat, 18 Mar 2023 18:47:36 +0100 Subject: [PATCH] Start of async asset loading - Refactored content manager and asset loaders - Asset placeholder - Error asset --- GlitchyEditor/src/Assets/EffectAssetLoader.bf | 55 +---- .../src/Assets/MaterialAssetLoader.bf | 65 ++--- GlitchyEditor/src/Assets/ModelAssetLoader.bf | 14 +- .../src/Assets/TextureAssetLoader.bf | 157 ++++++------ GlitchyEditor/src/EditorContentManager.bf | 228 ++++++++++++++---- GlitchyEditor/src/EditorIcons.bf | 2 +- GlitchyEngine/src/Content/Asset.bf | 13 +- GlitchyEngine/src/Content/AssetHandle.bf | 10 +- GlitchyEngine/src/Content/ContentManager.bf | 23 +- .../src/Platform/DX11/Renderer/Dx11Texture.bf | 9 +- GlitchyEngine/src/Renderer/Texture.bf | 74 ------ 11 files changed, 329 insertions(+), 321 deletions(-) diff --git a/GlitchyEditor/src/Assets/EffectAssetLoader.bf b/GlitchyEditor/src/Assets/EffectAssetLoader.bf index 4d21fd1..8878780 100644 --- a/GlitchyEditor/src/Assets/EffectAssetLoader.bf +++ b/GlitchyEditor/src/Assets/EffectAssetLoader.bf @@ -48,54 +48,15 @@ class EffectAssetLoader : IAssetLoader //, IReloadingAssetLoader Effect effect = new Effect(file, assetIdentifier, contentManager); return effect; + } - /*StreamReader reader = scope .(file); + public Asset GetPlaceholderAsset(Type assetType) + { + return default; + } - String text = scope .(); - - reader.ReadToEnd(text); - - MaterialFile materialFile = scope .(); - - var result = Bon.Deserialize(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);*/ + public Asset GetErrorAsset(Type assetType) + { + return default; } } \ No newline at end of file diff --git a/GlitchyEditor/src/Assets/MaterialAssetLoader.bf b/GlitchyEditor/src/Assets/MaterialAssetLoader.bf index a87c73b..f4fe990 100644 --- a/GlitchyEditor/src/Assets/MaterialAssetLoader.bf +++ b/GlitchyEditor/src/Assets/MaterialAssetLoader.bf @@ -339,10 +339,9 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader Log.EngineLogger.Error("Failed to load material."); Debug.SafeBreak(); return null; - // TODO: return error material } - Effect fx = Content.GetAsset(contentManager.LoadAsset(materialFile.Effect), contentManager);//new Effect(materialFile.Effect); + Effect fx = Content.GetAsset(contentManager.LoadAsset(materialFile.Effect, true), contentManager); Material material = new Material(fx); @@ -353,7 +352,6 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader if (texture.IsInvalid) { Log.EngineLogger.Error($"Failed to load texture \"{textureIdentifier}\"."); - // TODO: LoadAsset should return an error texture. } material.SetTexture(slotName, texture); @@ -377,7 +375,7 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader material.SetVariable(slotName, value); case .None: 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.Variables = new .(); - //material.SetTexture(); - for (let (slotName, texture) in material.[Friend]_textures) { Texture textureAsset = texture.Get(); @@ -419,7 +415,6 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader for (let (name, arguments) in effect.[Friend]_variableDescriptions) { VariableValue variableValue = .None; - //Object variantValue = null; let variable = effect.Variables[name]; @@ -445,7 +440,6 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader value = ColorRGBA.LinearToSRGB((ColorRGBA)value); variableValue = .ColorRGBA(value); - //variantValue = new box value; } } else if (variable.Type == .Float && variable.Rows == 1) @@ -465,48 +459,6 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader material.GetVariable(variable.Name, let 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(variable.Name, var value); - - float[1] minV = hasMin ? min.Get() : .(float.MinValue); - float[1] maxV = hasMax ? max.Get() : .(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(variable.Name, var value); - - Vector2 minV = hasMin ? min.Get() : .(float.MinValue); - Vector2 maxV = hasMax ? max.Get() : .(float.MaxValue); - - if (ImGui.EditVector2(displayName, ref value, .Zero, 0.1f, 100.0f, minV, maxV)) - material.SetVariable(variable.Name, value); - case 3: - material.GetVariable(variable.Name, var value); - - Vector3 minV = hasMin ? min.Get() : .(float.MinValue); - Vector3 maxV = hasMax ? max.Get() : .(float.MaxValue); - - if (ImGui.EditVector3(displayName, ref value, .Zero, 0.1f, 100.0f, minV, maxV)) - material.SetVariable(variable.Name, value); - case 4: - material.GetVariable(variable.Name, var value); - - Vector4 minV = hasMin ? min.Get() : .(float.MinValue); - Vector4 maxV = hasMax ? max.Get() : .(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); @@ -523,4 +475,17 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader return .Ok; } + + Material _placeholder; + Material _error; + + public Asset GetPlaceholderAsset(Type assetType) + { + return default; + } + + public Asset GetErrorAsset(Type assetType) + { + return default; + } } \ No newline at end of file diff --git a/GlitchyEditor/src/Assets/ModelAssetLoader.bf b/GlitchyEditor/src/Assets/ModelAssetLoader.bf index 00d4ba3..41cc8cd 100644 --- a/GlitchyEditor/src/Assets/ModelAssetLoader.bf +++ b/GlitchyEditor/src/Assets/ModelAssetLoader.bf @@ -44,8 +44,18 @@ class ModelAssetLoader : IAssetLoader //, IReloadingAssetLoader 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; } } \ No newline at end of file diff --git a/GlitchyEditor/src/Assets/TextureAssetLoader.bf b/GlitchyEditor/src/Assets/TextureAssetLoader.bf index dff5855..cf88063 100644 --- a/GlitchyEditor/src/Assets/TextureAssetLoader.bf +++ b/GlitchyEditor/src/Assets/TextureAssetLoader.bf @@ -243,65 +243,19 @@ class EditorTextureAssetLoader : IAssetLoader//, IReloadingAssetLoader case .PNG: texture = LoadPng(data, config); case .Unknown: - Runtime.FatalError("Unknown image format."); + Log.EngineLogger.Error("Unknown texture format."); + texture = null; } - Log.EngineLogger.AssertDebug(texture != null); - - SetSampler(texture, config); + if (texture != null) + { + SetSampler(texture, config); + texture.[Friend]Complete = true; + } 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) { Debug.Profiler.ProfileResourceFunction!(); @@ -313,40 +267,105 @@ class EditorTextureAssetLoader : IAssetLoader//, IReloadingAssetLoader if (result case .Err(let 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 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); Texture2D texture = new Texture2D(desc); texture.SetData((.)rawData); // TODO: Generate mip maps - LodePng.LodePng.Free(rawData); - 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) { + // TODO: Move the loading of Dds files here. Texture2D texture = new [Friend]Texture2D(data); 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); + + 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); + + Content.ManageAsset(_error2D); + _error2D.ReleaseRef(); + + _placeholder2D.[Friend]Complete = true; + } + + return _error2D; + } + } } diff --git a/GlitchyEditor/src/EditorContentManager.bf b/GlitchyEditor/src/EditorContentManager.bf index eceafb6..3ccbc29 100644 --- a/GlitchyEditor/src/EditorContentManager.bf +++ b/GlitchyEditor/src/EditorContentManager.bf @@ -8,6 +8,8 @@ using GlitchyEngine.Content; using GlitchyEditor.Assets; using GlitchyEngine; using System.Linq; +using System.Threading.Tasks; +using internal GlitchyEngine.Content.Asset; namespace GlitchyEditor; @@ -100,6 +102,8 @@ class EditorContentManager : IContentManager public void Update() { + SwapInLoadedAssets(); + if (!_reloadQueue.IsEmpty) { for (AssetHandle handle in _reloadQueue) @@ -112,6 +116,40 @@ class EditorContentManager : IContentManager _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) { if (_defaultAssetLoaders.TryGetValue(fileExtension, let value)) @@ -131,9 +169,11 @@ class EditorContentManager : IContentManager public void RegisterAssetLoader() 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) _supportedExtensions.Add(new String(ext)); @@ -196,13 +236,21 @@ class EditorContentManager : IContentManager { return _identiferToHandle.ContainsKey(identifier); } - + public Asset GetAsset(Type assetType, AssetHandle handle) { Asset asset = null; _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) { return asset; @@ -221,26 +269,26 @@ class EditorContentManager : IContentManager 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."); 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); String filePath = scope .(); GetResourceFilePath(resourceName, filePath); - //filePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); - Result> resultNode = AssetHierarchy.GetNodeFromPath(filePath); if (resultNode case .Err) @@ -255,35 +303,28 @@ class EditorContentManager : IContentManager Stream stream = GetStream(filePath); + // TODO: Add async loading! Asset loadedAsset = assetLoader.LoadAsset(stream, file.AssetConfig.Config, resourceName, subassetName, this); delete stream; if (loadedAsset == null) 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; + + 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. private void GetResourceAndSubassetName(StringView identifier, out StringView resourceName, out StringView? subassetName) { - // Find subasset name int poundIndex = identifier.IndexOf('#'); - resourceName = poundIndex == -1 ? identifier : identifier.Substring(0, poundIndex); - subassetName = identifier.Substring(poundIndex + 1); + resourceName = (poundIndex != -1) ? identifier.Substring(0, poundIndex) : identifier; + subassetName = (poundIndex != -1) ? identifier.Substring(poundIndex + 1) : null; } private void GetResourceFilePath(StringView resourceName, String filePath) @@ -293,25 +334,66 @@ class EditorContentManager : IContentManager 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? String fixedIdentifier = scope String(identifier); - // TODO: Fixup is more of a filesystem thingy... we might want to use our own function - Path.Fixup(fixedIdentifier); + AssetIdentifier.Fixup(fixedIdentifier); if (_identiferToHandle.TryGetValue(fixedIdentifier, let asset)) - { return asset; - } GetResourceAndSubassetName(fixedIdentifier, let resourceName, let subassetName); String filePath = scope .(); GetResourceFilePath(resourceName, filePath); - //filePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); - Result> resultNode = AssetHierarchy.GetNodeFromPath(filePath); 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... Log.EngineLogger.AssertDebug(assetLoader != null); - Stream stream = GetStream(filePath); + Asset loadedAsset; - Asset loadedAsset = assetLoader.LoadAsset(stream, file.AssetConfig.Config, resourceName, subassetName, this); - - delete stream; + // TODO: Support lazy loading for all asset types + if (!(assetLoader is EditorTextureAssetLoader) || blocking) + { + Stream stream = GetStream(filePath); - if (loadedAsset == null) - return .Invalid; + loadedAsset = assetLoader.LoadAsset(stream, file.AssetConfig.Config, resourceName, subassetName, this); + + 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; AssetHandle handle = ManageAsset(loadedAsset); @@ -349,6 +454,25 @@ class EditorContentManager : IContentManager 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. IAssetLoader GetAssetLoader(AssetFile file) { @@ -483,9 +607,10 @@ class EditorContentManager : IContentManager AssetHandle handle = .(); // Generate until we find a unique key (shouldn't happen too often) - while (_handleToAsset.ContainsKey(handle)) + while (handle.IsInvalid || _handleToAsset.ContainsKey(handle)) { handle = .(); + Log.EngineLogger.Trace("Handle was invalid or already taken."); // TODO: perhaps test how often this happens. // If this happens too often we could use a different random generator } @@ -500,9 +625,26 @@ class EditorContentManager : IContentManager 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) { - //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."); Asset asset = _handleToAsset[handle]; @@ -520,16 +662,14 @@ class EditorContentManager : IContentManager /// Note: This will not release any assets. 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(); if (oldIdentifier == newIdentifier) diff --git a/GlitchyEditor/src/EditorIcons.bf b/GlitchyEditor/src/EditorIcons.bf index 34fc764..a550f62 100644 --- a/GlitchyEditor/src/EditorIcons.bf +++ b/GlitchyEditor/src/EditorIcons.bf @@ -25,7 +25,7 @@ namespace GlitchyEditor public this(String texturePath, Vector2 iconSize) { - _texture = Content.LoadAsset(texturePath);//new Texture2D(texturePath); + _texture = Content.LoadAsset(texturePath, null, true); Vector2 pen = .(); diff --git a/GlitchyEngine/src/Content/Asset.bf b/GlitchyEngine/src/Content/Asset.bf index 3e57a0f..02c5913 100644 --- a/GlitchyEngine/src/Content/Asset.bf +++ b/GlitchyEngine/src/Content/Asset.bf @@ -8,7 +8,7 @@ using System.IO; namespace GlitchyEngine.Content; [BonTarget] -class Asset : RefCounter +abstract class Asset : RefCounter { internal AssetHandle _handle = .Invalid; @@ -22,15 +22,12 @@ class Asset : RefCounter public StringView Identifier { get => _identifier; - set - { - _contentManager?.UpdateAssetIdentifier(this, _identifier, value); - - _identifier.Set(value); - // TODO: do we need to tell the content manager, that the name changed? - } + internal set => _identifier.Set(value); } + /// 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... /// Gets the content manager that manages this asset; or null if this asset isn't managed. public IContentManager ContentManager => _contentManager; diff --git a/GlitchyEngine/src/Content/AssetHandle.bf b/GlitchyEngine/src/Content/AssetHandle.bf index 3903f46..03694ac 100644 --- a/GlitchyEngine/src/Content/AssetHandle.bf +++ b/GlitchyEngine/src/Content/AssetHandle.bf @@ -11,10 +11,10 @@ struct AssetHandle : IHashable private UUID _uuid; /// 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 IsInvalid => !IsValid; + public bool IsValid => this != .Invalid; + public bool IsInvalid => this == .Invalid; /// Create a new random AssetHandle public this() @@ -49,8 +49,8 @@ struct AssetHandle where T : Asset public const Self Invalid = .(); - public bool IsValid => this == .Invalid; - public bool IsInvalid => !IsValid; + public bool IsValid => this != .Invalid; + public bool IsInvalid => this == .Invalid; public this(AssetHandle handle, IContentManager contentManager = null) { diff --git a/GlitchyEngine/src/Content/ContentManager.bf b/GlitchyEngine/src/Content/ContentManager.bf index 1850662..16c16ad 100644 --- a/GlitchyEngine/src/Content/ContentManager.bf +++ b/GlitchyEngine/src/Content/ContentManager.bf @@ -57,19 +57,25 @@ namespace GlitchyEngine.Content /// @param contentManager The content manager used to load the asset. /// @returns The loaded asset. 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 { /// 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; if (contentManager == null) contentManager = Application.Get().ContentManager; - AssetHandle handle = contentManager.LoadAsset(assetIdentifier); + AssetHandle handle = contentManager.LoadAsset(assetIdentifier, blocking); return handle; } @@ -111,7 +117,7 @@ namespace GlitchyEngine.Content interface IContentManager { /// 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. Asset GetAsset(AssetHandle handle) @@ -128,10 +134,6 @@ namespace GlitchyEngine.Content /// The content manager will no longer manage the 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. Stream GetStream(StringView assetIdentifier); @@ -149,7 +151,7 @@ namespace GlitchyEngine.Content Runtime.NotImplemented(); } - public AssetHandle LoadAsset(StringView assetIdentifier) + public AssetHandle LoadAsset(StringView assetIdentifier, bool blocking = false) { Runtime.NotImplemented(); } @@ -169,11 +171,6 @@ namespace GlitchyEngine.Content Runtime.NotImplemented(); } - public void UpdateAssetIdentifier(Asset asset, StringView oldIdentifier, StringView newIdentifier) - { - Runtime.NotImplemented(); - } - public Stream GetStream(StringView assetIdentifier) { Runtime.NotImplemented(); diff --git a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Texture.bf b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Texture.bf index 851716b..cfd3b61 100644 --- a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Texture.bf +++ b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Texture.bf @@ -101,7 +101,7 @@ namespace GlitchyEngine.Renderer LoadDdsResourcePlatform(stream, ref nativeTexture); 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); } @@ -261,13 +261,6 @@ namespace GlitchyEngine.Renderer { return .(_nativeResourceView, _samplerState?.nativeSamplerState); } - - protected override void PlatformSneakySwappyTexture(Texture2D otherTexture) - { - Swap!(nativeDesc, otherTexture.nativeDesc); - Swap!(nativeTexture, otherTexture.nativeTexture); - Swap!(_nativeResourceView, otherTexture._nativeResourceView); - } } extension TextureCube diff --git a/GlitchyEngine/src/Renderer/Texture.bf b/GlitchyEngine/src/Renderer/Texture.bf index e25b365..490f735 100644 --- a/GlitchyEngine/src/Renderer/Texture.bf +++ b/GlitchyEngine/src/Renderer/Texture.bf @@ -63,19 +63,11 @@ namespace GlitchyEngine.Renderer public class Texture2D : Texture { - protected String _path ~ delete _; - //public override extern uint32 Width {get;} //public override extern uint32 Height {get;} public override uint32 Depth => 1; //public override extern uint32 ArraySize {get;} //public override extern uint32 MipLevels {get;} - - private this(StringView path, bool pngSrgb = false) - { - _path = new String(path); - LoadTexture(pngSrgb); - } // TODO: remove private this(Stream data) @@ -83,72 +75,6 @@ namespace GlitchyEngine.Renderer 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(); - - 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((.)rawData); - - LodePng.LodePng.Free(rawData); - } - protected void LoadDds(Stream stream) { LoadDdsPlatform(stream);