mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
Crappy hot reloading of textures and Properties editor for textures
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<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 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]>();
|
||||
|
||||
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<Color>((.)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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
@@ -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<EventHandler<StringView>> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<AssetNode> parentNode)
|
||||
{
|
||||
path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||
|
||||
// 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();
|
||||
|
||||
@@ -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<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
|
||||
@@ -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<String> _supportedExtensions = .() ~ ClearAndDeleteItems!(_);
|
||||
private append List<IAssetLoader> _assetLoaders = .() ~ ClearAndDeleteItems!(_);
|
||||
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
|
||||
{
|
||||
@@ -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)
|
||||
{
|
||||
@@ -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<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;
|
||||
|
||||
@@ -488,6 +614,8 @@ class EditorContentManager : IContentManager
|
||||
|
||||
delete stream;
|
||||
|
||||
file.[Friend]_loadedAsset = loadedAsset;
|
||||
|
||||
return loadedAsset;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<EditorTextureAssetLoader>();
|
||||
contentManager.SetAsDefaultAssetLoader<EditorTextureAssetLoader>(".png", ".dds");
|
||||
_contentManager.RegisterAssetLoader<EditorTextureAssetLoader>();
|
||||
_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()
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace GlitchyEngine.Content
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
interface IAssetLoader
|
||||
{
|
||||
static List<StringView> FileExtensions { get; }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,5 +17,11 @@ namespace System
|
||||
|
||||
target[copiedChars] = '\0';
|
||||
}
|
||||
|
||||
/// Converts camel case and delimiter-separated words to normal words.
|
||||
public void ToHumanReadable()
|
||||
{
|
||||
// TODO!
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<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);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user