Crappy hot reloading of textures and Properties editor for textures

This commit is contained in:
Simon Lübeß
2023-01-03 22:21:26 +01:00
parent 427323744b
commit ee30920b56
17 changed files with 707 additions and 70 deletions
+4 -4
View File
@@ -164,7 +164,7 @@
OrthographicHeight = 10, OrthographicHeight = 10,
OrthographicNearPlane = 0, OrthographicNearPlane = 0,
OrthographicFarPlane = 10, OrthographicFarPlane = 10,
AspectRatio = 2.01173, AspectRatio = 2.156692,
FixedAspectRatio = false FixedAspectRatio = false
} }
}, },
@@ -176,12 +176,12 @@
SpriterRendererComponent = { SpriterRendererComponent = {
Color = { Color = {
R = 1, R = 1,
G = 0, G = 1,
B = 0, B = 1,
A = 1 A = 1
}, },
IsCircle = true, IsCircle = true,
Sprite = "Textures//rocket.dds", Sprite = "Textures/rocket.png",
UvTransform = { UvTransform = {
X = 0, X = 0,
Y = 0, Y = 0,
@@ -1,22 +1,20 @@
{ {
AssetLoader = "EditorTextureAssetLoader", AssetLoader = "EditorTextureAssetLoader",
Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){ Config = (GlitchyEditor.Assets.EditorTextureAssetLoaderConfig){
_generateMipMaps = true,
_isSrgb = true,
_samplerStateDescription = { _samplerStateDescription = {
MinFilter = .Linear, MinFilter = .Linear,
MagFilter = .Linear,
MipFilter = .Linear,
ComparisonFunction = .Never, ComparisonFunction = .Never,
AddressModeU = .Clamp, AddressModeU = .Wrap,
AddressModeV = .Clamp, AddressModeV = .Border,
AddressModeW = .Clamp, AddressModeW = .Clamp,
MipMinLOD = -340282346638528859811704183484516925440, MipMaxLOD = 160,
MipMaxLOD = 340282346638528859811704183484516925440, MaxAnisotropy = 5,
MaxAnisotropy = 1,
BorderColor = { BorderColor = {
R = 1, R = 0.756863,
G = 1, G = 0.2,
B = 1, A = 0.956863
A = 1
} }
} }
} }
+7 -1
View File
@@ -30,6 +30,8 @@ class AssetFile
private bool _isDirectory; private bool _isDirectory;
private Object _loadedAsset;
public bool IsDirectory => _isDirectory; public bool IsDirectory => _isDirectory;
public StringView FilePath => _path; public StringView FilePath => _path;
@@ -38,6 +40,8 @@ class AssetFile
public AssetConfig AssetConfig => _assetConfig; public AssetConfig AssetConfig => _assetConfig;
public Object LoadedAsset => _loadedAsset;
[AllowAppend] [AllowAppend]
public this(EditorContentManager contentManager, StringView path, bool isDirectory) public this(EditorContentManager contentManager, StringView path, bool isDirectory)
{ {
@@ -102,10 +106,12 @@ class AssetFile
} }
} }
private void SaveAssetConfig() public void SaveAssetConfig()
{ {
gBonEnv.serializeFlags |= .Verbose; gBonEnv.serializeFlags |= .Verbose;
Bon.SerializeIntoFile(_assetConfig, _assetConfigPath); Bon.SerializeIntoFile(_assetConfig, _assetConfigPath);
_assetConfig.Config.[Friend]_changed = false;
} }
} }
+245 -27
View File
@@ -7,14 +7,155 @@ using GlitchyEngine.Content;
using GlitchyEngine.Renderer; using GlitchyEngine.Renderer;
using GlitchyEngine.Math; using GlitchyEngine.Math;
using DirectXTK; using DirectXTK;
using ImGui;
namespace GlitchyEditor.Assets; 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] [BonTarget, BonPolyRegister]
class EditorTextureAssetLoaderConfig : AssetLoaderConfig class EditorTextureAssetLoaderConfig : AssetLoaderConfig
{ {
[BonInclude] [BonInclude]
private bool _generateMipMaps; private bool _generateMipMaps;
[BonInclude]
private bool _isSrgb;
[BonInclude] [BonInclude]
private SamplerStateDescription _samplerStateDescription = .(); private SamplerStateDescription _samplerStateDescription = .();
@@ -24,6 +165,12 @@ class EditorTextureAssetLoaderConfig : AssetLoaderConfig
get => _generateMipMaps; get => _generateMipMaps;
set => SetIfChanged(ref _generateMipMaps, value); set => SetIfChanged(ref _generateMipMaps, value);
} }
public bool IsSRGB
{
get => _isSrgb;
set => SetIfChanged(ref _isSrgb, value);
}
public SamplerStateDescription SamplerStateDescription 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<StringView> _fileExtensions = new .(){".png", ".dds"} ~ delete _; // ".jpg", ".bmp" private static readonly List<StringView> _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 PngMagicWord = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A";
const String DdsMagicWord = "DDS "; 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<char8[8]>(); var readResult = data.Read<char8[8]>();
data.Position = 0; data.Position = position;
char8[8] magicWord; char8[8] magicWord;
Texture texture = null;
if (readResult case .Ok(out magicWord)) if (readResult case .Ok(out magicWord))
{ {
StringView strView = .(&magicWord, magicWord.Count); StringView strView = .(&magicWord, magicWord.Count);
if (strView.StartsWith(PngMagicWord)) if (strView.StartsWith(PngMagicWord))
{ {
texture = LoadPng(data, config); return .PNG;
} }
else if (strView.StartsWith(DdsMagicWord)) else if (strView.StartsWith(DdsMagicWord))
{ {
texture = LoadDds(data, config); return .DDS;
} }
else 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); Log.EngineLogger.AssertDebug(texture != null);
/*SamplerStateDescription samplerDesc = .() SetSampler(texture, config);
{
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();
return texture; 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!(); Debug.Profiler.ProfileResourceFunction!();
@@ -134,7 +344,7 @@ class EditorTextureAssetLoader : IAssetLoader
// TODO: load as SRGB because PNGs are usually not stored as linear // 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, 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); Texture2D texture = new Texture2D(desc);
texture.SetData<Color>((.)rawData); texture.SetData<Color>((.)rawData);
@@ -145,6 +355,14 @@ class EditorTextureAssetLoader : IAssetLoader
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)
{ {
Texture2D texture = new [Friend]Texture2D(data); Texture2D texture = new [Friend]Texture2D(data);
@@ -14,7 +14,7 @@ namespace GlitchyEditor.EditWindows
class ContentBrowserWindow : EditorWindow class ContentBrowserWindow : EditorWindow
{ {
// TODO: Get from project // TODO: Get from project
const String ContentDirectory = "./content"; //const String ContentDirectory = "./content";
private append String _currentDirectory = .(); private append String _currentDirectory = .();
@@ -25,6 +25,8 @@ namespace GlitchyEditor.EditWindows
public EditorContentManager _manager; public EditorContentManager _manager;
public StringView SelectedFile => _selectedFile;
public this(EditorContentManager contentManager) public this(EditorContentManager contentManager)
{ {
_manager = contentManager; _manager = contentManager;
@@ -191,8 +193,8 @@ namespace GlitchyEditor.EditWindows
String fullpath = scope String(entry->Path); String fullpath = scope String(entry->Path);
// TODO: this is dirty // TODO: this is dirty
if (fullpath.StartsWith(ContentDirectory, .OrdinalIgnoreCase)) if (fullpath.StartsWith(_manager.ContentDirectory, .OrdinalIgnoreCase))
fullpath.Remove(0, ContentDirectory.Length); fullpath.Remove(0, _manager.ContentDirectory.Length);
ImGui.SetDragDropPayload("CONTENT_BROWSER_ITEM", fullpath.CStr(), (.)fullpath.Length, .Once); ImGui.SetDragDropPayload("CONTENT_BROWSER_ITEM", fullpath.CStr(), (.)fullpath.Length, .Once);
@@ -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<AssetNode>> 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();
}
}
+26 -12
View File
@@ -12,16 +12,14 @@ namespace GlitchyEditor
class Editor class Editor
{ {
private Scene _scene; private Scene _scene;
private EditorContentManager _contentManager;
private EntityHierarchyWindow _entityHierarchyWindow ~ delete _; private EntityHierarchyWindow _entityHierarchyWindow ~ delete _;
private ComponentEditWindow _componentEditWindow ~ delete _; private ComponentEditWindow _componentEditWindow ~ delete _;
private SceneViewportWindow _sceneViewportWindow = new .(this) ~ delete _; private SceneViewportWindow _sceneViewportWindow~ delete _;
private ContentBrowserWindow _contentBrowserWindow ~ delete _; private ContentBrowserWindow _contentBrowserWindow ~ delete _;
private PropertiesWindow _propertiesWindow ~ delete _;
public EntityHierarchyWindow EntityHierarchyWindow => _entityHierarchyWindow;
public ComponentEditWindow ComponentEditWindow => _componentEditWindow;
public SceneViewportWindow SceneViewportWindow => _sceneViewportWindow;
public ContentBrowserWindow ContentBrowserWindow => _contentBrowserWindow;
public Scene CurrentScene 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 EditorCamera* CurrentCamera { get; set; }
public Event<EventHandler<StringView>> RequestOpenScene ~ _.Dispose(); public Event<EventHandler<StringView>> RequestOpenScene ~ _.Dispose();
/// Creates a new editor for the given world /// 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); _entityHierarchyWindow = new EntityHierarchyWindow(this, _scene);
CurrentScene = scene;
_componentEditWindow = new ComponentEditWindow(_entityHierarchyWindow); _componentEditWindow = new ComponentEditWindow(_entityHierarchyWindow);
_contentBrowserWindow = new ContentBrowserWindow((.)Application.Get().ContentManager); _contentBrowserWindow = new ContentBrowserWindow((.)Application.Get().ContentManager);
_propertiesWindow = new PropertiesWindow(this);
} }
public void Update() public void Update()
{ {
_sceneViewportWindow.Show();
_entityHierarchyWindow.Show(); _entityHierarchyWindow.Show();
_componentEditWindow.Show(); _componentEditWindow.Show();
_sceneViewportWindow.Show();
_contentBrowserWindow.Show(); _contentBrowserWindow.Show();
_propertiesWindow.Show();
} }
} }
} }
+131 -3
View File
@@ -122,6 +122,7 @@ class AssetHierarchy
{ {
_contentDirectory.Clear(); _contentDirectory.Clear();
_contentDirectory.Append(value); _contentDirectory.Append(value);
Path.Fixup(_contentDirectory);
} }
} }
@@ -137,6 +138,8 @@ class AssetHierarchy
_fileSystemDirty = true; _fileSystemDirty = true;
SetupFileSystemWatcher(); SetupFileSystemWatcher();
Update();
} }
/// Initializes the FSW for the current ContentDirectory and registers the events. /// Initializes the FSW for the current ContentDirectory and registers the events.
@@ -151,7 +154,8 @@ class AssetHierarchy
Log.EngineLogger.Trace($"File content changed (\"{filename}\")"); Log.EngineLogger.Trace($"File content changed (\"{filename}\")");
//_fileSystemDirty = true; //_fileSystemDirty = true;
// TODO: Handle file changes (reload asset, etc...)
FileContentChanged(filename);
}); });
fsw.OnCreated.Add(new (filename) => { fsw.OnCreated.Add(new (filename) => {
@@ -262,6 +266,8 @@ class AssetHierarchy
assetNode.Name = new String(); assetNode.Name = new String();
Path.GetFileName(filepathBuffer, assetNode.Name); Path.GetFileName(filepathBuffer, assetNode.Name);
filepathBuffer.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
assetNode.Path = new String(filepathBuffer); assetNode.Path = new String(filepathBuffer);
assetNode.IsDirectory = false; assetNode.IsDirectory = false;
@@ -308,6 +314,8 @@ class AssetHierarchy
/// Recursively adds all Files and Subdirectories. /// Recursively adds all Files and Subdirectories.
void AddDirectoryToTree(String path, TreeNode<AssetNode> parentNode) void AddDirectoryToTree(String path, TreeNode<AssetNode> parentNode)
{ {
path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
// Try to find the node for the specified path in the given parent // Try to find the node for the specified path in the given parent
TreeNode<AssetNode> treeNode = parentNode.Children.Where(scope (node) => node.Value.Path == path).FirstOrDefault(); TreeNode<AssetNode> treeNode = parentNode.Children.Where(scope (node) => node.Value.Path == path).FirstOrDefault();
@@ -358,6 +366,40 @@ class AssetHierarchy
_fileSystemDirty = false; _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<AssetNode> 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<FileContentChangedFunc> OnFileContentChanged ~ _.Dispose();
} }
class EditorContentManager : IContentManager class EditorContentManager : IContentManager
@@ -379,12 +421,56 @@ class EditorContentManager : IContentManager
public this() 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) public void SetContentDirectory(StringView contentDirectory)
{ {
_contentDirectory.Clear(); _contentDirectory.Clear();
_contentDirectory.Append(contentDirectory); _contentDirectory.Append(contentDirectory);
Path.Fixup(_contentDirectory);
_assetHierarchy.SetContentDirectory(contentDirectory); _assetHierarchy.SetContentDirectory(contentDirectory);
} }
@@ -404,6 +490,12 @@ class EditorContentManager : IContentManager
private append List<String> _supportedExtensions = .() ~ ClearAndDeleteItems!(_); private append List<String> _supportedExtensions = .() ~ ClearAndDeleteItems!(_);
private append List<IAssetLoader> _assetLoaders = .() ~ ClearAndDeleteItems!(_); private append List<IAssetLoader> _assetLoaders = .() ~ ClearAndDeleteItems!(_);
private append Dictionary<StringView, IAssetLoader> _defaultAssetLoaders = .(); private append Dictionary<StringView, IAssetLoader> _defaultAssetLoaders = .();
private append Dictionary<String, function AssetPropertiesEditor(AssetFile)> _assetPropertiesEditors = .() ~ {
for (String key in _.Keys)
{
delete key;
}
};
public void RegisterAssetLoader<T>() where T : new, class, IAssetLoader public void RegisterAssetLoader<T>() 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<TAssetLoader>(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) public bool IsLoaded(StringView identifier)
{ {
@@ -457,9 +573,19 @@ class EditorContentManager : IContentManager
} }
String filePath = scope String(identifier.Length + _contentDirectory.Length + 2); 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<TreeNode<AssetNode>> resultNode = AssetHierarchy.GetNodeFromPath(filePath);
if (resultNode case .Err)
Runtime.FatalError();
//AssetFile file = scope .(this, filePath, false);
AssetFile file = resultNode->Value.AssetFile;
IAssetLoader assetLoader = null; IAssetLoader assetLoader = null;
@@ -488,6 +614,8 @@ class EditorContentManager : IContentManager
delete stream; delete stream;
file.[Friend]_loadedAsset = loadedAsset;
return loadedAsset; return loadedAsset;
} }
+12 -8
View File
@@ -57,6 +57,8 @@ namespace GlitchyEditor
EditorIcons _editorIcons ~ _.ReleaseRef(); EditorIcons _editorIcons ~ _.ReleaseRef();
EditorContentManager _contentManager;
enum SceneState enum SceneState
{ {
Edit, Edit,
@@ -71,7 +73,7 @@ namespace GlitchyEditor
{ {
Application.Get().Window.IsVSync = false; Application.Get().Window.IsVSync = false;
InitContentLoader(); InitContentManager();
InitGraphics(); InitGraphics();
@@ -83,15 +85,17 @@ namespace GlitchyEditor
NewScene(); NewScene();
} }
private void InitContentLoader() private void InitContentManager()
{ {
EditorContentManager contentManager = new EditorContentManager(); _contentManager = new EditorContentManager();
contentManager.SetContentDirectory("./content"); _contentManager.SetContentDirectory("./content");
contentManager.RegisterAssetLoader<EditorTextureAssetLoader>(); _contentManager.RegisterAssetLoader<EditorTextureAssetLoader>();
contentManager.SetAsDefaultAssetLoader<EditorTextureAssetLoader>(".png", ".dds"); _contentManager.SetAsDefaultAssetLoader<EditorTextureAssetLoader>(".png", ".dds");
_contentManager.SetAssetPropertiesEditor<EditorTextureAssetLoader>(=> TextureAssetPropertiesEditor.Factory);
Application.Get().[Friend]_contentManager = contentManager; // Todo: Sketchy...
Application.Get().[Friend]_contentManager = _contentManager;
} }
private void InitGraphics() private void InitGraphics()
@@ -144,7 +148,7 @@ namespace GlitchyEditor
private void InitEditor() private void InitEditor()
{ {
_editor = new Editor(_scene); _editor = new Editor(_scene, _contentManager);
_editor.SceneViewportWindow.ViewportSizeChanged.Add(new (s, e) => ViewportSizeChanged(s, e)); _editor.SceneViewportWindow.ViewportSizeChanged.Add(new (s, e) => ViewportSizeChanged(s, e));
_editor.CurrentCamera = &_camera; _editor.CurrentCamera = &_camera;
+1 -1
View File
@@ -44,7 +44,7 @@ namespace GlitchyEngine.Content
return true; return true;
} }
} }
interface IAssetLoader interface IAssetLoader
{ {
static List<StringView> FileExtensions { get; } static List<StringView> FileExtensions { get; }
@@ -36,4 +36,25 @@ extension Path
Runtime.NotImplemented(); Runtime.NotImplemented();
#endif #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);
}
}
} }
@@ -17,5 +17,11 @@ namespace System
target[copiedChars] = '\0'; target[copiedChars] = '\0';
} }
/// Converts camel case and delimiter-separated words to normal words.
public void ToHumanReadable()
{
// TODO!
}
} }
} }
+104
View File
@@ -2,6 +2,7 @@ using GlitchyEngine.Math;
using GlitchyEngine.Renderer; using GlitchyEngine.Renderer;
using System; using System;
using GlitchyEngine; using GlitchyEngine;
using System.Collections;
namespace GlitchyEngine.Math namespace GlitchyEngine.Math
{ {
@@ -215,5 +216,108 @@ namespace ImGui
{ {
ImGui.GetForegroundDrawList().AddRect(min, max, ImGui.GetColorU32(color.Value)); ImGui.GetForegroundDrawList().AddRect(min, max, ImGui.GetColorU32(color.Value));
} }
/// Provides a combo Box to select an enum value.
public static bool EnumCombo<T>(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<T>())
{
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<T>()
{
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<T>(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<T>();
#unwarn
return DragScalar(label, dataType, &value, dragSpeed, &minValue, &maxValue, format, sliderFlags);
}
// TODO: Add support for floats
public static bool SliderScalar<T>(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<T>();
#unwarn
return SliderScalar(label, dataType, &value, &minValue, &maxValue, format, sliderFlags);
}
} }
} }
@@ -125,6 +125,17 @@ namespace GlitchyEngine.Renderer
{ {
return .(_nativeResourceView, _samplerState.nativeSamplerState); 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 extension RenderTargetFormat
@@ -261,6 +261,13 @@ 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
@@ -78,6 +78,17 @@ namespace GlitchyEngine.Renderer
} }
protected extern TextureViewBinding PlatformGetViewBinding(); 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] [AllowDuplicates]
+21
View File
@@ -29,6 +29,11 @@ namespace GlitchyEngine.Renderer
public abstract uint32 MipLevels {get;} public abstract uint32 MipLevels {get;}
public abstract TextureViewBinding GetViewBinding(); 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 public struct Texture2DDesc
@@ -198,6 +203,17 @@ namespace GlitchyEngine.Renderer
} }
protected extern TextureViewBinding PlatformGetViewBinding(); 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 public class TextureCube : Texture
@@ -234,5 +250,10 @@ namespace GlitchyEngine.Renderer
} }
protected extern TextureViewBinding PlatformGetViewBinding(); protected extern TextureViewBinding PlatformGetViewBinding();
protected internal override void SneakySwappyTexture(Texture otherTexture)
{
Runtime.NotImplemented();
}
} }
} }