From ee30920b56ca44e1e4da1a984867546c91fc6d6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20L=C3=BCbe=C3=9F?= Date: Tue, 3 Jan 2023 22:21:26 +0100 Subject: [PATCH] Crappy hot reloading of textures and Properties editor for textures --- GlitchyEditor/content/Scenes/physics2D.scene | 8 +- .../TestMat/rustediron2_albedo.png.ass | 20 +- GlitchyEditor/src/AssetFile.bf | 8 +- .../src/Assets/TextureAssetLoader.bf | 272 ++++++++++++++++-- .../src/EditWindows/ContentBrowserWindow.bf | 8 +- .../src/EditWindows/PropertiesWindow.bf | 86 ++++++ GlitchyEditor/src/Editor.bf | 38 ++- GlitchyEditor/src/EditorContentManager.bf | 134 ++++++++- GlitchyEditor/src/EditorLayer.bf | 20 +- GlitchyEngine/src/Content/ContentManager.bf | 2 +- GlitchyEngine/src/Extension/System/IO/Path.bf | 21 ++ GlitchyEngine/src/Extension/System/String.bf | 6 + GlitchyEngine/src/ImGui/ImGuiExtension.bf | 104 +++++++ .../DX11/Renderer/Dx11RenderTarget.bf | 11 + .../src/Platform/DX11/Renderer/Dx11Texture.bf | 7 + GlitchyEngine/src/Renderer/RenderTarget.bf | 11 + GlitchyEngine/src/Renderer/Texture.bf | 21 ++ 17 files changed, 707 insertions(+), 70 deletions(-) create mode 100644 GlitchyEditor/src/EditWindows/PropertiesWindow.bf diff --git a/GlitchyEditor/content/Scenes/physics2D.scene b/GlitchyEditor/content/Scenes/physics2D.scene index 8fcf040..3decb20 100644 --- a/GlitchyEditor/content/Scenes/physics2D.scene +++ b/GlitchyEditor/content/Scenes/physics2D.scene @@ -164,7 +164,7 @@ OrthographicHeight = 10, OrthographicNearPlane = 0, OrthographicFarPlane = 10, - AspectRatio = 2.01173, + AspectRatio = 2.156692, FixedAspectRatio = false } }, @@ -176,12 +176,12 @@ SpriterRendererComponent = { Color = { R = 1, - G = 0, - B = 0, + G = 1, + B = 1, A = 1 }, IsCircle = true, - Sprite = "Textures//rocket.dds", + Sprite = "Textures/rocket.png", UvTransform = { X = 0, Y = 0, diff --git a/GlitchyEditor/content/Textures/TestMat/rustediron2_albedo.png.ass b/GlitchyEditor/content/Textures/TestMat/rustediron2_albedo.png.ass index 1f70168..1cabcd8 100644 --- a/GlitchyEditor/content/Textures/TestMat/rustediron2_albedo.png.ass +++ b/GlitchyEditor/content/Textures/TestMat/rustediron2_albedo.png.ass @@ -1,22 +1,20 @@ { AssetLoader = "EditorTextureAssetLoader", Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){ + _generateMipMaps = true, + _isSrgb = true, _samplerStateDescription = { MinFilter = .Linear, - MagFilter = .Linear, - MipFilter = .Linear, ComparisonFunction = .Never, - AddressModeU = .Clamp, - AddressModeV = .Clamp, + AddressModeU = .Wrap, + AddressModeV = .Border, AddressModeW = .Clamp, - MipMinLOD = -340282346638528859811704183484516925440, - MipMaxLOD = 340282346638528859811704183484516925440, - MaxAnisotropy = 1, + MipMaxLOD = 160, + MaxAnisotropy = 5, BorderColor = { - R = 1, - G = 1, - B = 1, - A = 1 + R = 0.756863, + G = 0.2, + A = 0.956863 } } } diff --git a/GlitchyEditor/src/AssetFile.bf b/GlitchyEditor/src/AssetFile.bf index b931777..8fae083 100644 --- a/GlitchyEditor/src/AssetFile.bf +++ b/GlitchyEditor/src/AssetFile.bf @@ -30,6 +30,8 @@ class AssetFile private bool _isDirectory; + private Object _loadedAsset; + public bool IsDirectory => _isDirectory; public StringView FilePath => _path; @@ -38,6 +40,8 @@ class AssetFile public AssetConfig AssetConfig => _assetConfig; + public Object LoadedAsset => _loadedAsset; + [AllowAppend] public this(EditorContentManager contentManager, StringView path, bool isDirectory) { @@ -102,10 +106,12 @@ class AssetFile } } - private void SaveAssetConfig() + public void SaveAssetConfig() { gBonEnv.serializeFlags |= .Verbose; Bon.SerializeIntoFile(_assetConfig, _assetConfigPath); + + _assetConfig.Config.[Friend]_changed = false; } } \ No newline at end of file diff --git a/GlitchyEditor/src/Assets/TextureAssetLoader.bf b/GlitchyEditor/src/Assets/TextureAssetLoader.bf index 2be2848..41bd6c6 100644 --- a/GlitchyEditor/src/Assets/TextureAssetLoader.bf +++ b/GlitchyEditor/src/Assets/TextureAssetLoader.bf @@ -7,14 +7,155 @@ using GlitchyEngine.Content; using GlitchyEngine.Renderer; using GlitchyEngine.Math; using DirectXTK; +using ImGui; namespace GlitchyEditor.Assets; +abstract class AssetPropertiesEditor +{ + private AssetFile _asset; + + public AssetFile Asset => _asset; + + public this(AssetFile asset) + { + _asset = asset; + } + + public abstract void ShowEditor(); +} + + +class TextureAssetPropertiesEditor : AssetPropertiesEditor +{ + EditorTextureAssetLoaderConfig _textureConfig; + + public this(AssetFile asset) : base(asset) + { + _textureConfig = asset.AssetConfig.Config as EditorTextureAssetLoaderConfig; + } + + static char8*[3] _filterFuncNames = char8*[]("Point", "Linear", "Anisotropic"); + + public override void ShowEditor() + { + if (_textureConfig == null) + return; + + bool generateMips = _textureConfig.GenerateMipMaps; + if (ImGui.Checkbox("Generate Mip Maps", &generateMips)) + _textureConfig.GenerateMipMaps = generateMips; + + bool isSrgb = _textureConfig.IsSRGB; + if (ImGui.Checkbox("Is sRGB", &isSrgb)) + _textureConfig.IsSRGB = isSrgb; + + SamplerStateDescription samplerStateDescription = _textureConfig.SamplerStateDescription; + + void ShowFilterCombo(String label, ref FilterFunction filterFunction) + { + int32 selectedFilter = filterFunction.Underlying; + if (ImGui.Combo(label, &selectedFilter, &_filterFuncNames, 3)) + filterFunction = (.)selectedFilter; + } + + ImGui.Separator(); + ImGui.TextUnformatted("Texture Filtering:"); + ImGui.Separator(); + + ImGui.EnumCombo("Min Filter", ref samplerStateDescription.MinFilter); + ImGui.AttachTooltip(""" + Sampling method used for minification. + If set to "Anisotropic" all Filters are set to "Anisotropic" internally. + """); + ImGui.EnumCombo("Mag Filter", ref samplerStateDescription.MagFilter); + ImGui.AttachTooltip(""" + Sampling method used for magnification. + If set to "Anisotropic" all Filters are set to "Anisotropic" internally. + """); + ImGui.EnumCombo("Mip Map Filter", ref samplerStateDescription.MipFilter); + ImGui.AttachTooltip(""" + Method used for mip-level sampling. + If set to "Anisotropic" all Filters are set to "Anisotropic" internally. + """); + + if (samplerStateDescription.MagFilter == .Anisotropic || + samplerStateDescription.MinFilter == .Anisotropic || + samplerStateDescription.MipFilter == .Anisotropic) + { + ImGui.SliderScalar("Anisotropy Level", ref samplerStateDescription.MaxAnisotropy, 1, 16); + } + + ImGui.NewLine(); + + ImGui.EnumCombo("Filter Mode", ref samplerStateDescription.FilterMode); + ImGui.AttachTooltip("Filtering method to use when sampling a texture."); + + if (samplerStateDescription.FilterMode == .Comparison) + { + ImGui.EnumCombo("Comparison Function", ref samplerStateDescription.ComparisonFunction); + ImGui.AttachTooltip(""" + The function that is used to compare the sampled data against the existing sampled data. + Only applies if Filter Mode is set to FilterMode.Comparison. + """); + } + + ImGui.Separator(); + ImGui.TextUnformatted("Wrapping"); + ImGui.Separator(); + + ImGui.EnumCombo("Wrap Mode U", ref samplerStateDescription.AddressModeU); + ImGui.AttachTooltip("Method to use for resolving a u texture coordinate that is outside the 0 to 1 range."); + + ImGui.EnumCombo("Wrap Mode V", ref samplerStateDescription.AddressModeV); + ImGui.AttachTooltip("Method to use for resolving a v texture coordinate that is outside the 0 to 1 range."); + + ImGui.EnumCombo("Wrap Mode W", ref samplerStateDescription.AddressModeW); + ImGui.AttachTooltip("Method to use for resolving a w texture coordinate that is outside the 0 to 1 range."); + + if (samplerStateDescription.AddressModeU == .Border || + samplerStateDescription.AddressModeV == .Border || + samplerStateDescription.AddressModeW == .Border) + { + ImGui.ColorEdit4("Border Color", ref samplerStateDescription.BorderColor); + } + + ImGui.Separator(); + ImGui.TextUnformatted("Mip Maps"); + ImGui.Separator(); + + ImGui.DragFloat("Mip LOD Bias", &samplerStateDescription.MipLODBias, 0.1f); + ImGui.AttachTooltip(""" + Offset from the calculated mipmap level. + For example, if the GPU calculates that a texture should be sampled at mipmap level 3 and "Mip LOD Bias" is 2, then the texture will be sampled at mipmap level 5. + """); + + ImGui.DragFloat("Min Mip LOD", &samplerStateDescription.MipMinLOD); + ImGui.AttachTooltip("Lower end of the mipmap range to clamp access to, where 0 is the largest and most detailed mipmap level and any level higher than that is less detailed."); + + ImGui.DragFloat("Max LOD Bias", &samplerStateDescription.MipMaxLOD); + ImGui.AttachTooltip(""" + Upper end of the mipmap range to clamp access to, where 0 is the largest and most detailed mipmap level and any level higher than that is less detailed. + This value must be greater than or equal to "Min Mip LOD". To have no upper limit on LOD set this to a large value. + """); + + _textureConfig.SamplerStateDescription = samplerStateDescription; + } + + public static AssetPropertiesEditor Factory(AssetFile assetFile) + { + return new TextureAssetPropertiesEditor(assetFile); + } +} + [BonTarget, BonPolyRegister] class EditorTextureAssetLoaderConfig : AssetLoaderConfig { [BonInclude] private bool _generateMipMaps; + + [BonInclude] + private bool _isSrgb; [BonInclude] private SamplerStateDescription _samplerStateDescription = .(); @@ -24,6 +165,12 @@ class EditorTextureAssetLoaderConfig : AssetLoaderConfig get => _generateMipMaps; set => SetIfChanged(ref _generateMipMaps, value); } + + public bool IsSRGB + { + get => _isSrgb; + set => SetIfChanged(ref _isSrgb, value); + } public SamplerStateDescription SamplerStateDescription { @@ -32,7 +179,12 @@ class EditorTextureAssetLoaderConfig : AssetLoaderConfig } } -class EditorTextureAssetLoader : IAssetLoader +interface IReloadingAssetLoader +{ + public void ReloadAsset(AssetFile assetFile, Stream data); +} + +class EditorTextureAssetLoader : IAssetLoader, IReloadingAssetLoader { private static readonly List _fileExtensions = new .(){".png", ".dds"} ~ delete _; // ".jpg", ".bmp" @@ -61,29 +213,33 @@ class EditorTextureAssetLoader : IAssetLoader const String PngMagicWord = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"; const String DdsMagicWord = "DDS "; - private static Texture LoadTexture(Stream data, EditorTextureAssetLoaderConfig config) + enum TextureType { - Debug.Profiler.ProfileResourceFunction!(); + Unknown, + DDS, + PNG + } + private static TextureType GetTextureType(Stream data) + { + int64 position = data.Position; + var readResult = data.Read(); - data.Position = 0; - + data.Position = position; + char8[8] magicWord; - - Texture texture = null; - if (readResult case .Ok(out magicWord)) { StringView strView = .(&magicWord, magicWord.Count); if (strView.StartsWith(PngMagicWord)) { - texture = LoadPng(data, config); + return .PNG; } else if (strView.StartsWith(DdsMagicWord)) { - texture = LoadDds(data, config); + return .DDS; } else { @@ -91,28 +247,82 @@ class EditorTextureAssetLoader : IAssetLoader } } + return .Unknown; + } + + private static Texture LoadTexture(Stream data, EditorTextureAssetLoaderConfig config) + { + Debug.Profiler.ProfileResourceFunction!(); + + Texture texture = null; + + switch(GetTextureType(data)) + { + case .DDS: + texture = LoadDds(data, config); + case .PNG: + texture = LoadPng(data, config); + case .Unknown: + Runtime.FatalError("Unknown image format."); + } + Log.EngineLogger.AssertDebug(texture != null); - /*SamplerStateDescription samplerDesc = .() - { - MinFilter = config.MinFilter, - MagFilter = config.MagFilter, - MipFilter = config.MipFilter, - AddressModeU = config.WrapModeU, - AddressModeV = config.WrapModeV, - AddressModeW = config.WrapModeW - };*/ - - SamplerState sam = SamplerStateManager.GetSampler(config.SamplerStateDescription); - - texture.SamplerState = sam; - - sam.ReleaseRef(); + SetSampler(texture, config); return texture; } - private static Texture LoadPng(Stream data, EditorTextureAssetLoaderConfig config) + 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!(); @@ -134,7 +344,7 @@ class EditorTextureAssetLoader : IAssetLoader // 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, .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); @@ -145,6 +355,14 @@ class EditorTextureAssetLoader : IAssetLoader 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) { Texture2D texture = new [Friend]Texture2D(data); diff --git a/GlitchyEditor/src/EditWindows/ContentBrowserWindow.bf b/GlitchyEditor/src/EditWindows/ContentBrowserWindow.bf index 320e541..8a22d26 100644 --- a/GlitchyEditor/src/EditWindows/ContentBrowserWindow.bf +++ b/GlitchyEditor/src/EditWindows/ContentBrowserWindow.bf @@ -14,7 +14,7 @@ namespace GlitchyEditor.EditWindows class ContentBrowserWindow : EditorWindow { // TODO: Get from project - const String ContentDirectory = "./content"; + //const String ContentDirectory = "./content"; private append String _currentDirectory = .(); @@ -25,6 +25,8 @@ namespace GlitchyEditor.EditWindows public EditorContentManager _manager; + public StringView SelectedFile => _selectedFile; + public this(EditorContentManager contentManager) { _manager = contentManager; @@ -191,8 +193,8 @@ namespace GlitchyEditor.EditWindows String fullpath = scope String(entry->Path); // TODO: this is dirty - if (fullpath.StartsWith(ContentDirectory, .OrdinalIgnoreCase)) - fullpath.Remove(0, ContentDirectory.Length); + if (fullpath.StartsWith(_manager.ContentDirectory, .OrdinalIgnoreCase)) + fullpath.Remove(0, _manager.ContentDirectory.Length); ImGui.SetDragDropPayload("CONTENT_BROWSER_ITEM", fullpath.CStr(), (.)fullpath.Length, .Once); diff --git a/GlitchyEditor/src/EditWindows/PropertiesWindow.bf b/GlitchyEditor/src/EditWindows/PropertiesWindow.bf new file mode 100644 index 0000000..d10674c --- /dev/null +++ b/GlitchyEditor/src/EditWindows/PropertiesWindow.bf @@ -0,0 +1,86 @@ +using ImGui; +using System; +using GlitchyEngine.Collections; +using GlitchyEngine.Content; +using System.Reflection; +using GlitchyEngine; +using GlitchyEditor.Assets; + +namespace GlitchyEditor.EditWindows; + +class PropertiesWindow : EditorWindow +{ + private AssetPropertiesEditor _currentPropertiesEditor ~ delete _; + + public this(Editor editor) + { + _editor = editor; + } + + protected override void InternalShow() + { + defer { ImGui.End(); } + if(!ImGui.Begin("Properties", &_open, .None)) + return; + + ShowAssetProperties(); + } + + /// Gets the AssetFile for the asset currently selected in the ContentBrowserWindow + /// @returns the AssetFile for the currently selected asset of null, if no file is selected. + private AssetFile GetCurrentAssetFile() + { + StringView selectedFileName = _editor.ContentBrowserWindow.SelectedFile; + + Result> treeNode = _editor.ContentManager.AssetHierarchy.GetNodeFromPath(selectedFileName); + + if (treeNode case .Ok(let assetNode)) + return assetNode->AssetFile; + + return null; + } + + private void ShowAssetProperties() + { + AssetFile assetFile = GetCurrentAssetFile(); + + if (_currentPropertiesEditor?.Asset != assetFile) + { + delete _currentPropertiesEditor; + _currentPropertiesEditor = _editor.ContentManager.GetNewPropertiesEditor(assetFile); + } + + if (assetFile == null) + return; + + // TODO: allow changing AssetLoader + // assetFile.AssetConfig.AssetLoade + + // TODO: ignore file + /*ImGui.Checkbox("Ignore", &assetFile.AssetConfig.IgnoreFile); + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("If checked this file will be ignored and not treated as an asset.");*/ + + ShowPropertiesEditor(assetFile); + } + + private void ShowPropertiesEditor(AssetFile assetFile) + { + if (_currentPropertiesEditor == null) + return; + + _currentPropertiesEditor.ShowEditor(); + + if (!assetFile.AssetConfig.Config.Changed) + { + ImGui.BeginDisabled(); + defer:: { ImGui.EndDisabled(); } + } + + ImGui.Separator(); + + if (ImGui.Button("Apply")) + assetFile.SaveAssetConfig(); + } +} diff --git a/GlitchyEditor/src/Editor.bf b/GlitchyEditor/src/Editor.bf index 87df92a..af374f1 100644 --- a/GlitchyEditor/src/Editor.bf +++ b/GlitchyEditor/src/Editor.bf @@ -12,16 +12,14 @@ namespace GlitchyEditor class Editor { private Scene _scene; - + + private EditorContentManager _contentManager; + private EntityHierarchyWindow _entityHierarchyWindow ~ delete _; private ComponentEditWindow _componentEditWindow ~ delete _; - private SceneViewportWindow _sceneViewportWindow = new .(this) ~ delete _; + private SceneViewportWindow _sceneViewportWindow~ delete _; private ContentBrowserWindow _contentBrowserWindow ~ delete _; - - public EntityHierarchyWindow EntityHierarchyWindow => _entityHierarchyWindow; - public ComponentEditWindow ComponentEditWindow => _componentEditWindow; - public SceneViewportWindow SceneViewportWindow => _sceneViewportWindow; - public ContentBrowserWindow ContentBrowserWindow => _contentBrowserWindow; + private PropertiesWindow _propertiesWindow ~ delete _; public Scene CurrentScene { @@ -33,27 +31,43 @@ namespace GlitchyEditor } } + public EditorContentManager ContentManager => _contentManager; + + public EntityHierarchyWindow EntityHierarchyWindow => _entityHierarchyWindow; + public ComponentEditWindow ComponentEditWindow => _componentEditWindow; + public SceneViewportWindow SceneViewportWindow => _sceneViewportWindow; + public ContentBrowserWindow ContentBrowserWindow => _contentBrowserWindow; + public PropertiesWindow PropertiesWindow => _propertiesWindow; + public EditorCamera* CurrentCamera { get; set; } public Event> RequestOpenScene ~ _.Dispose(); /// Creates a new editor for the given world - public this(Scene scene) + public this(Scene scene, EditorContentManager contentManager) { + _scene = scene; + _contentManager = contentManager; + + InitWindows(); + } + + private void InitWindows() + { + _sceneViewportWindow = new SceneViewportWindow(this); _entityHierarchyWindow = new EntityHierarchyWindow(this, _scene); - CurrentScene = scene; - _componentEditWindow = new ComponentEditWindow(_entityHierarchyWindow); - _contentBrowserWindow = new ContentBrowserWindow((.)Application.Get().ContentManager); + _propertiesWindow = new PropertiesWindow(this); } public void Update() { + _sceneViewportWindow.Show(); _entityHierarchyWindow.Show(); _componentEditWindow.Show(); - _sceneViewportWindow.Show(); _contentBrowserWindow.Show(); + _propertiesWindow.Show(); } } } diff --git a/GlitchyEditor/src/EditorContentManager.bf b/GlitchyEditor/src/EditorContentManager.bf index eff9497..e027fe1 100644 --- a/GlitchyEditor/src/EditorContentManager.bf +++ b/GlitchyEditor/src/EditorContentManager.bf @@ -122,6 +122,7 @@ class AssetHierarchy { _contentDirectory.Clear(); _contentDirectory.Append(value); + Path.Fixup(_contentDirectory); } } @@ -137,6 +138,8 @@ class AssetHierarchy _fileSystemDirty = true; SetupFileSystemWatcher(); + + Update(); } /// Initializes the FSW for the current ContentDirectory and registers the events. @@ -151,7 +154,8 @@ class AssetHierarchy Log.EngineLogger.Trace($"File content changed (\"{filename}\")"); //_fileSystemDirty = true; - // TODO: Handle file changes (reload asset, etc...) + + FileContentChanged(filename); }); fsw.OnCreated.Add(new (filename) => { @@ -262,6 +266,8 @@ class AssetHierarchy assetNode.Name = new String(); Path.GetFileName(filepathBuffer, assetNode.Name); + filepathBuffer.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + assetNode.Path = new String(filepathBuffer); assetNode.IsDirectory = false; @@ -308,6 +314,8 @@ class AssetHierarchy /// Recursively adds all Files and Subdirectories. void AddDirectoryToTree(String path, TreeNode parentNode) { + path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + // Try to find the node for the specified path in the given parent TreeNode treeNode = parentNode.Children.Where(scope (node) => node.Value.Path == path).FirstOrDefault(); @@ -358,6 +366,40 @@ class AssetHierarchy _fileSystemDirty = false; } + + private void FileContentChanged(StringView fileName) + { + var fileName; + + // Config files aren't really tracked but changing them effectively changes the corresponding file + // so we fire the event for them. + if (fileName.EndsWith(AssetFile.ConfigFileExtension)) + fileName.RemoveFromEnd(AssetFile.ConfigFileExtension.Length); + + String fileNameWithContentRoot = scope .(); + Path.InternalCombine(fileNameWithContentRoot, _contentDirectory, fileName); + + var nodeResult = GetNodeFromPath(fileNameWithContentRoot); + + TreeNode node = null; + + if (!(nodeResult case .Ok(out node))) + { + Log.EngineLogger.Error($"Could not find node for file \"{fileNameWithContentRoot}\""); + } + + // Don't fire event for directories. + if (node->IsDirectory) + return; + + OnFileContentChanged(node.Value); + + // TODO: Handle file changes (reload asset, etc...) + } + + public delegate void FileContentChangedFunc(AssetNode node); + + public Event OnFileContentChanged ~ _.Dispose(); } class EditorContentManager : IContentManager @@ -379,12 +421,56 @@ class EditorContentManager : IContentManager public this() { + _assetHierarchy.OnFileContentChanged.Add(new => OnFileContentChanged); + } + + private void OnFileContentChanged(AssetNode assetNode) + { + // Asset isn't loaded so we don't need to reload it. + if (assetNode.AssetFile.LoadedAsset == null) + return; + + String neededAssetLoaderName = assetNode.AssetFile.AssetConfig?.AssetLoader; + + if (String.IsNullOrWhiteSpace(neededAssetLoaderName)) + return; + + IAssetLoader assetLoader = null; + + String loaderNameBuffer = scope String(64); + + for (IAssetLoader loader in _assetLoaders) + { + loader.GetType().GetName(loaderNameBuffer..Clear()); + + if (loaderNameBuffer == neededAssetLoaderName) + { + assetLoader = loader; + break; + } + } + + if (assetLoader == null) + { + Log.EngineLogger.Error($"Could not find asset loader \"{neededAssetLoaderName}\""); + return; + } + + if (var assetReloader = assetLoader as IReloadingAssetLoader) + { + Stream stream = GetStream(assetNode.Path); + + assetReloader.ReloadAsset(assetNode.AssetFile, stream); + + delete stream; + } } public void SetContentDirectory(StringView contentDirectory) { _contentDirectory.Clear(); _contentDirectory.Append(contentDirectory); + Path.Fixup(_contentDirectory); _assetHierarchy.SetContentDirectory(contentDirectory); } @@ -404,6 +490,12 @@ class EditorContentManager : IContentManager private append List _supportedExtensions = .() ~ ClearAndDeleteItems!(_); private append List _assetLoaders = .() ~ ClearAndDeleteItems!(_); private append Dictionary _defaultAssetLoaders = .(); + private append Dictionary _assetPropertiesEditors = .() ~ { + for (String key in _.Keys) + { + delete key; + } + }; public void RegisterAssetLoader() where T : new, class, IAssetLoader { @@ -443,6 +535,30 @@ class EditorContentManager : IContentManager } } } + + public void SetAssetPropertiesEditor(Type assetLoaderType, function AssetPropertiesEditor(AssetFile) editorFactory) + { + String loaderTypeName = new String(); + assetLoaderType.GetName(loaderTypeName); + + _assetPropertiesEditors[loaderTypeName] = editorFactory; + } + + public void SetAssetPropertiesEditor(function AssetPropertiesEditor(AssetFile) editorFactory) where TAssetLoader : IAssetLoader + { + SetAssetPropertiesEditor(typeof(TAssetLoader), editorFactory); + } + + public AssetPropertiesEditor GetNewPropertiesEditor(AssetFile assetFile) + { + if (assetFile?.AssetConfig.AssetLoader == null) + return null; + + if (_assetPropertiesEditors.TryGetValue(assetFile.AssetConfig.AssetLoader, let propertiesEditorfactory)) + return propertiesEditorfactory(assetFile); + + return null; + } public bool IsLoaded(StringView identifier) { @@ -457,9 +573,19 @@ class EditorContentManager : IContentManager } String filePath = scope String(identifier.Length + _contentDirectory.Length + 2); - Path.InternalCombine(filePath, _contentDirectory, identifier); + Path.Combine(filePath, _contentDirectory, identifier); - AssetFile file = scope .(this, filePath, false); + Path.Fixup(filePath); + + //filePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + + Result> resultNode = AssetHierarchy.GetNodeFromPath(filePath); + + if (resultNode case .Err) + Runtime.FatalError(); + + //AssetFile file = scope .(this, filePath, false); + AssetFile file = resultNode->Value.AssetFile; IAssetLoader assetLoader = null; @@ -488,6 +614,8 @@ class EditorContentManager : IContentManager delete stream; + file.[Friend]_loadedAsset = loadedAsset; + return loadedAsset; } diff --git a/GlitchyEditor/src/EditorLayer.bf b/GlitchyEditor/src/EditorLayer.bf index 203b33c..5cdcd0d 100644 --- a/GlitchyEditor/src/EditorLayer.bf +++ b/GlitchyEditor/src/EditorLayer.bf @@ -57,6 +57,8 @@ namespace GlitchyEditor EditorIcons _editorIcons ~ _.ReleaseRef(); + EditorContentManager _contentManager; + enum SceneState { Edit, @@ -71,7 +73,7 @@ namespace GlitchyEditor { Application.Get().Window.IsVSync = false; - InitContentLoader(); + InitContentManager(); InitGraphics(); @@ -83,15 +85,17 @@ namespace GlitchyEditor NewScene(); } - private void InitContentLoader() + private void InitContentManager() { - EditorContentManager contentManager = new EditorContentManager(); - contentManager.SetContentDirectory("./content"); + _contentManager = new EditorContentManager(); + _contentManager.SetContentDirectory("./content"); - contentManager.RegisterAssetLoader(); - contentManager.SetAsDefaultAssetLoader(".png", ".dds"); + _contentManager.RegisterAssetLoader(); + _contentManager.SetAsDefaultAssetLoader(".png", ".dds"); + _contentManager.SetAssetPropertiesEditor(=> TextureAssetPropertiesEditor.Factory); - Application.Get().[Friend]_contentManager = contentManager; + // Todo: Sketchy... + Application.Get().[Friend]_contentManager = _contentManager; } private void InitGraphics() @@ -144,7 +148,7 @@ namespace GlitchyEditor private void InitEditor() { - _editor = new Editor(_scene); + _editor = new Editor(_scene, _contentManager); _editor.SceneViewportWindow.ViewportSizeChanged.Add(new (s, e) => ViewportSizeChanged(s, e)); _editor.CurrentCamera = &_camera; diff --git a/GlitchyEngine/src/Content/ContentManager.bf b/GlitchyEngine/src/Content/ContentManager.bf index d091e1e..08123f9 100644 --- a/GlitchyEngine/src/Content/ContentManager.bf +++ b/GlitchyEngine/src/Content/ContentManager.bf @@ -44,7 +44,7 @@ namespace GlitchyEngine.Content return true; } } - + interface IAssetLoader { static List FileExtensions { get; } diff --git a/GlitchyEngine/src/Extension/System/IO/Path.bf b/GlitchyEngine/src/Extension/System/IO/Path.bf index 01c7e4d..d609f96 100644 --- a/GlitchyEngine/src/Extension/System/IO/Path.bf +++ b/GlitchyEngine/src/Extension/System/IO/Path.bf @@ -36,4 +36,25 @@ extension Path Runtime.NotImplemented(); #endif } + + public static void Fixup(String path) + { + path.Replace(AltDirectorySeparatorChar, DirectorySeparatorChar); + path.Replace(scope $".{DirectorySeparatorChar}", ""); + path.Replace(scope $"{DirectorySeparatorChar}.", ""); + + if (path.StartsWith(DirectorySeparatorChar)) + path.Remove(0, 1); + } + + public static void Combine(String target, params StringView[] components) + { + for (var component in components) + { + if ((target.Length > 0) && (!target.EndsWith("\\")) && (!target.EndsWith("/")) && + (!component.StartsWith("\\")) && (!component.StartsWith("/"))) + target.Append(Path.DirectorySeparatorChar); + target.Append(component); + } + } } \ No newline at end of file diff --git a/GlitchyEngine/src/Extension/System/String.bf b/GlitchyEngine/src/Extension/System/String.bf index e21442d..307cc56 100644 --- a/GlitchyEngine/src/Extension/System/String.bf +++ b/GlitchyEngine/src/Extension/System/String.bf @@ -17,5 +17,11 @@ namespace System target[copiedChars] = '\0'; } + + /// Converts camel case and delimiter-separated words to normal words. + public void ToHumanReadable() + { + // TODO! + } } } \ No newline at end of file diff --git a/GlitchyEngine/src/ImGui/ImGuiExtension.bf b/GlitchyEngine/src/ImGui/ImGuiExtension.bf index 20b4d1f..16b85e4 100644 --- a/GlitchyEngine/src/ImGui/ImGuiExtension.bf +++ b/GlitchyEngine/src/ImGui/ImGuiExtension.bf @@ -2,6 +2,7 @@ using GlitchyEngine.Math; using GlitchyEngine.Renderer; using System; using GlitchyEngine; +using System.Collections; namespace GlitchyEngine.Math { @@ -215,5 +216,108 @@ namespace ImGui { ImGui.GetForegroundDrawList().AddRect(min, max, ImGui.GetColorU32(color.Value)); } + + /// Provides a combo Box to select an enum value. + public static bool EnumCombo(StringView label, ref T selectedValue) where T : enum + { + String selectedValueString = scope .(); + selectedValue.ToString(selectedValueString); + // TODO: make selectedValue human readable + + bool changed = false; + + if (ImGui.BeginCombo(label.ToScopeCStr!(), selectedValueString)) + { + for (let (name, value) in Enum.GetEnumerator()) + { + ImGui.PushID(name); + + if (ImGui.Selectable(name.ToScopeCStr!(), selectedValue == value)) + { + selectedValue = value; + changed = true; + } + + ImGui.PopID(); + } + + ImGui.EndCombo(); + } + + return changed; + } + + /// Provides a tooltip that will be show when the previously defined Widget is hovered. + public static void AttachTooltip(StringView tooltip) + { + if (!ImGui.IsItemHovered()) + return; + + ImGui.BeginTooltip(); + + ImGui.TextUnformatted(tooltip); + + ImGui.EndTooltip(); + } + + [Comptime] + private static DataType GetDataType() + { + DataType dataType = .COUNT; + + switch (typeof(T)) + { + case typeof(int8): + dataType = .S8; + case typeof(int16): + dataType = .S16; + case typeof(int32): + dataType = .S32; + case typeof(int64): + dataType = .S64; + case typeof(int): + if (sizeof(int) == 8) + dataType = .S64; + else if (sizeof(int) == 4) + dataType = .S32; + + case typeof(uint8): + dataType = .U8; + case typeof(uint16): + dataType = .U16; + case typeof(uint32): + dataType = .U32; + case typeof(uint64): + dataType = .U64; + case typeof(uint): + if (sizeof(uint) == 8) + dataType = .U64; + else if (sizeof(uint) == 4) + dataType = .U32; + //default: + // Runtime.Assert(dataType != .COUNT); + //Log.EngineLogger.Assert(dataType != .COUNT, "Unknown data type."); + } + + return dataType; + } + + // TODO: Add support for floats + public static bool DragScalar(char8* label, ref T value, float dragSpeed = (float) 1.0f, T minValue = typeof(T).MinValue, T maxValue = typeof(T).MaxValue, char8* format = null, SliderFlags sliderFlags = .None) where T : IInteger + { + DataType dataType = GetDataType(); + +#unwarn + return DragScalar(label, dataType, &value, dragSpeed, &minValue, &maxValue, format, sliderFlags); + } + + // TODO: Add support for floats + public static bool SliderScalar(char8* label, ref T value, T minValue = typeof(T).MinValue, T maxValue = typeof(T).MaxValue, char8* format = null, SliderFlags sliderFlags = .None) where T : IInteger + { + DataType dataType = GetDataType(); + +#unwarn + return SliderScalar(label, dataType, &value, &minValue, &maxValue, format, sliderFlags); + } } } \ No newline at end of file diff --git a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11RenderTarget.bf b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11RenderTarget.bf index a9d9044..ad44bba 100644 --- a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11RenderTarget.bf +++ b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11RenderTarget.bf @@ -125,6 +125,17 @@ namespace GlitchyEngine.Renderer { return .(_nativeResourceView, _samplerState.nativeSamplerState); } + + protected override void PlatformSneakySwappyTexture(RenderTarget2D otherTexture) + { + Swap!(_description, otherTexture._description); + + // Consider sneaky swapping _depthStencilTarget too... + Swap!(_depthStenilTarget, otherTexture._depthStenilTarget); + + Swap!(_nativeTexture, otherTexture._nativeTexture); + Swap!(_nativeRenderTargetView, otherTexture._nativeRenderTargetView); + } } extension RenderTargetFormat diff --git a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Texture.bf b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Texture.bf index c8d7c94..851716b 100644 --- a/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Texture.bf +++ b/GlitchyEngine/src/Platform/DX11/Renderer/Dx11Texture.bf @@ -261,6 +261,13 @@ 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/RenderTarget.bf b/GlitchyEngine/src/Renderer/RenderTarget.bf index 34addaf..12bdf0a 100644 --- a/GlitchyEngine/src/Renderer/RenderTarget.bf +++ b/GlitchyEngine/src/Renderer/RenderTarget.bf @@ -78,6 +78,17 @@ namespace GlitchyEngine.Renderer } protected extern TextureViewBinding PlatformGetViewBinding(); + + protected internal override void SneakySwappyTexture(Texture otherTexture) + { + Log.EngineLogger.AssertDebug(otherTexture is RenderTarget2D, "Swapping texture must be a RenderTarget2D!"); + + SamplerState = otherTexture.SamplerState; + + PlatformSneakySwappyTexture(otherTexture as RenderTarget2D); + } + + protected extern void PlatformSneakySwappyTexture(RenderTarget2D otherTexture); } [AllowDuplicates] diff --git a/GlitchyEngine/src/Renderer/Texture.bf b/GlitchyEngine/src/Renderer/Texture.bf index 548b068..7658358 100644 --- a/GlitchyEngine/src/Renderer/Texture.bf +++ b/GlitchyEngine/src/Renderer/Texture.bf @@ -29,6 +29,11 @@ namespace GlitchyEngine.Renderer public abstract uint32 MipLevels {get;} public abstract TextureViewBinding GetViewBinding(); + + /// Very dirtily swaps the internals with the given texture. + /// TODO: Please do this differently!!!!!!!!!!!!!!!!!!!!!! + /// This is for texture hot reloading POC, I know... it's bad... + protected internal abstract void SneakySwappyTexture(Texture otherTexture); } public struct Texture2DDesc @@ -198,6 +203,17 @@ namespace GlitchyEngine.Renderer } protected extern TextureViewBinding PlatformGetViewBinding(); + + protected internal override void SneakySwappyTexture(Texture otherTexture) + { + Log.EngineLogger.AssertDebug(otherTexture is Texture2D, "Swapping texture must be a Texture2D!"); + + SamplerState = otherTexture.SamplerState; + + PlatformSneakySwappyTexture(otherTexture as Texture2D); + } + + protected extern void PlatformSneakySwappyTexture(Texture2D otherTexture); } public class TextureCube : Texture @@ -234,5 +250,10 @@ namespace GlitchyEngine.Renderer } protected extern TextureViewBinding PlatformGetViewBinding(); + + protected internal override void SneakySwappyTexture(Texture otherTexture) + { + Runtime.NotImplemented(); + } } }