mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
373 lines
10 KiB
Beef
373 lines
10 KiB
Beef
using System;
|
|
using System.Collections;
|
|
using Bon;
|
|
using System.IO;
|
|
using GlitchyEngine;
|
|
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 = .();
|
|
|
|
public bool GenerateMipMaps
|
|
{
|
|
get => _generateMipMaps;
|
|
set => SetIfChanged(ref _generateMipMaps, value);
|
|
}
|
|
|
|
public bool IsSRGB
|
|
{
|
|
get => _isSrgb;
|
|
set => SetIfChanged(ref _isSrgb, value);
|
|
}
|
|
|
|
public SamplerStateDescription SamplerStateDescription
|
|
{
|
|
get => _samplerStateDescription;
|
|
set => SetIfChanged(ref _samplerStateDescription, value);
|
|
}
|
|
}
|
|
|
|
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"
|
|
|
|
public static List<StringView> FileExtensions => _fileExtensions;
|
|
|
|
public AssetLoaderConfig GetDefaultConfig()
|
|
{
|
|
return new EditorTextureAssetLoaderConfig();
|
|
}
|
|
|
|
public IRefCounted LoadAsset(Stream data, AssetLoaderConfig config)
|
|
{
|
|
var config;
|
|
|
|
if (config == null)
|
|
{
|
|
config = GetDefaultConfig();
|
|
defer:: delete config;
|
|
}
|
|
|
|
Log.EngineLogger.AssertDebug(config is EditorTextureAssetLoaderConfig, "config has wrong type.");
|
|
|
|
return LoadTexture(data, (EditorTextureAssetLoaderConfig)config);
|
|
}
|
|
|
|
const String PngMagicWord = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A";
|
|
const String DdsMagicWord = "DDS ";
|
|
|
|
enum TextureType
|
|
{
|
|
Unknown,
|
|
DDS,
|
|
PNG
|
|
}
|
|
|
|
private static TextureType GetTextureType(Stream data)
|
|
{
|
|
int64 position = data.Position;
|
|
|
|
var readResult = data.Read<char8[8]>();
|
|
|
|
data.Position = position;
|
|
|
|
char8[8] magicWord;
|
|
if (readResult case .Ok(out magicWord))
|
|
{
|
|
StringView strView = .(&magicWord, magicWord.Count);
|
|
|
|
if (strView.StartsWith(PngMagicWord))
|
|
{
|
|
return .PNG;
|
|
}
|
|
else if (strView.StartsWith(DdsMagicWord))
|
|
{
|
|
return .DDS;
|
|
}
|
|
else
|
|
{
|
|
Runtime.FatalError("Unknown image format.");
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
SetSampler(texture, config);
|
|
|
|
return texture;
|
|
}
|
|
|
|
public void ReloadAsset(AssetFile assetFile, Stream data)
|
|
{
|
|
Texture reloadingTexture = assetFile.LoadedAsset as Texture;
|
|
|
|
if (reloadingTexture == null)
|
|
{
|
|
Log.EngineLogger.Error($"{nameof(Self)}: Requested reload of \"{assetFile.FilePath}\" but it's not a Texture!");
|
|
return;
|
|
}
|
|
|
|
EditorTextureAssetLoaderConfig config = assetFile.AssetConfig.Config as EditorTextureAssetLoaderConfig;
|
|
|
|
if (config == null)
|
|
{
|
|
Log.EngineLogger.Error($"{nameof(Self)}: Config of asset \"{assetFile.FilePath}\" doesn't have the correct type!");
|
|
return;
|
|
}
|
|
|
|
switch(GetTextureType(data))
|
|
{
|
|
case .DDS:
|
|
ReloadDds(reloadingTexture as Texture2D, data, config);
|
|
case .PNG:
|
|
ReloadPng(reloadingTexture as Texture2D, data, config);
|
|
case .Unknown:
|
|
Runtime.FatalError("Unknown image format.");
|
|
}
|
|
|
|
SetSampler(reloadingTexture, config);
|
|
}
|
|
|
|
private static void SetSampler(Texture texture, EditorTextureAssetLoaderConfig config)
|
|
{
|
|
using (SamplerState samplerState = SamplerStateManager.GetSampler(config.SamplerStateDescription))
|
|
{
|
|
texture.SamplerState = samplerState;
|
|
}
|
|
}
|
|
|
|
private static void ReloadPng(Texture2D reloadingTexture, Stream data, EditorTextureAssetLoaderConfig config)
|
|
{
|
|
Debug.Profiler.ProfileResourceFunction!();
|
|
|
|
using (Texture2D newTexture = LoadPng(data, config))
|
|
{
|
|
reloadingTexture.[Friend]SneakySwappyTexture(newTexture);
|
|
}
|
|
}
|
|
|
|
private static Texture2D LoadPng(Stream data, EditorTextureAssetLoaderConfig config)
|
|
{
|
|
Debug.Profiler.ProfileResourceFunction!();
|
|
|
|
uint8[] pngData = new:ScopedAlloc! uint8[data.Length];
|
|
|
|
var result = data.TryRead(pngData);
|
|
|
|
if (result case .Err(let err))
|
|
{
|
|
Log.EngineLogger.Error($"Failed to read data from stream. Texture: Error: {err}");
|
|
}
|
|
|
|
uint8* rawData = ?;
|
|
uint32 width = 0, height = 0;
|
|
|
|
uint32 errorCode = LodePng.LodePng.Decode32(&rawData, &width, &height, pngData.Ptr, (.)pngData.Count);
|
|
|
|
Log.EngineLogger.Assert(errorCode == 0, "Failed to load png File");
|
|
|
|
// TODO: load as SRGB because PNGs are usually not stored as linear
|
|
//Texture2DDesc desc = .(width, height, srgb? .R8G8B8A8_UNorm_SRGB : .R8G8B8A8_UNorm, 1, 1, .Immutable);
|
|
Texture2DDesc desc = .(width, height, config.IsSRGB ? .R8G8B8A8_UNorm_SRGB : .R8G8B8A8_UNorm, 1, 1, .Immutable);
|
|
Texture2D texture = new Texture2D(desc);
|
|
texture.SetData<Color>((.)rawData);
|
|
|
|
// TODO: Generate mip maps
|
|
|
|
LodePng.LodePng.Free(rawData);
|
|
|
|
return texture;
|
|
}
|
|
|
|
private static void ReloadDds(Texture2D reloadingTexture, Stream data, EditorTextureAssetLoaderConfig config)
|
|
{
|
|
using (Texture2D newTexture = new [Friend]Texture2D(data))
|
|
{
|
|
reloadingTexture.[Friend]SneakySwappyTexture(newTexture);
|
|
}
|
|
}
|
|
|
|
private static Texture LoadDds(Stream data, EditorTextureAssetLoaderConfig config)
|
|
{
|
|
Texture2D texture = new [Friend]Texture2D(data);
|
|
|
|
return texture;
|
|
}
|
|
}
|