diff --git a/CUE4Parse b/CUE4Parse index 340b7567..691b9b93 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 340b7567fdc2ca050d9042389d09540be1044159 +Subproject commit 691b9b93ced53f723119106bc75094da77a4eaf3 diff --git a/FModel/Creator/Bases/FN/BaseBundle.cs b/FModel/Creator/Bases/FN/BaseBundle.cs index ea85a775..90c9de2b 100644 --- a/FModel/Creator/Bases/FN/BaseBundle.cs +++ b/FModel/Creator/Bases/FN/BaseBundle.cs @@ -59,7 +59,7 @@ public class BaseBundle : UCreator foreach (var reward in rewards) { if (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition")) continue; - quest.AddCompletionRequest(itemDefinition); + quest.AddCompletionReward(itemDefinition); } _quests.Add(quest); } diff --git a/FModel/Creator/Bases/FN/BaseQuest.cs b/FModel/Creator/Bases/FN/BaseQuest.cs index 48984aff..ef3159aa 100644 --- a/FModel/Creator/Bases/FN/BaseQuest.cs +++ b/FModel/Creator/Bases/FN/BaseQuest.cs @@ -44,12 +44,12 @@ public class BaseQuest : BaseIcon DisplayName = ReformatString(description, completionCount.ToString(), completionCount < 0); } - public void AddCompletionRequest(FSoftObjectPath itemDefinition) + public void AddCompletionReward(FSoftObjectPath itemDefinition) { _rewards.Add(itemDefinition.TryLoad(out UObject uObject) ? new Reward(uObject) : new Reward()); } - public void AddCompletionRequest(int quantity, string reward) + public void AddCompletionReward(int quantity, string reward) { _rewards.Add(new Reward(quantity, reward)); } diff --git a/FModel/FModel.csproj b/FModel/FModel.csproj index dc171282..bdfee340 100644 --- a/FModel/FModel.csproj +++ b/FModel/FModel.csproj @@ -164,7 +164,7 @@ - + diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index 2f915233..d2f1ade5 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -63,7 +63,6 @@ public partial class MainWindow await ApplicationViewModel.InitOodle(); await ApplicationViewModel.InitZlib(); - await ApplicationViewModel.InitDetex(); await _applicationView.CUE4Parse.Initialize(); await _applicationView.AesManager.InitAes(); await _applicationView.UpdateProvider(true); @@ -74,6 +73,7 @@ public partial class MainWindow _applicationView.CUE4Parse.VerifyConsoleVariables(), _applicationView.CUE4Parse.VerifyOnDemandArchives(), _applicationView.CUE4Parse.InitMappings(), + ApplicationViewModel.InitDetex(), ApplicationViewModel.InitVgmStream(), ApplicationViewModel.InitImGuiSettings(newOrUpdated), Task.Run(() => @@ -86,7 +86,10 @@ public partial class MainWindow #if DEBUG // await _threadWorkerView.Begin(cancellationToken => // _applicationView.CUE4Parse.Extract(cancellationToken, - // _applicationView.CUE4Parse.Provider["Marvel/Content/Marvel/Wwise/Assets/Events/Music/music_new/event/Entry.uasset"])); + // _applicationView.CUE4Parse.Provider["FortniteGame/Plugins/GameFeatures/BRCosmetics/Content/Characters/Player/Male/Medium/Bodies/M_MED_Lunch_Box/Meshes/M_MED_Lunch_Box.uasset"])); + await _threadWorkerView.Begin(cancellationToken => + _applicationView.CUE4Parse.Extract(cancellationToken, + _applicationView.CUE4Parse.Provider["FortniteGame/Plugins/GameFeatures/BlastBerryMap/Content/Maps/BlastBerry_Terrain/_Generated_/D5V3O7ZS9ZLQKFKU6WFZ27OYW.umap"])); #endif } diff --git a/FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs b/FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs index aeaf42b9..47b7bd55 100644 --- a/FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs +++ b/FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs @@ -106,7 +106,11 @@ public class FModelApiEndpoint : AbstractApiProvider public void CheckForUpdates(bool launch = false) { - if (DateTime.Now < UserSettings.Default.NextUpdateCheck) return; + if (DateTime.Now < UserSettings.Default.NextUpdateCheck) + { + Log.Warning("Updates have been silenced until {DateTime}", UserSettings.Default.NextUpdateCheck); + return; + } if (launch) { @@ -140,7 +144,8 @@ public class FModelApiEndpoint : AbstractApiProvider { UserSettings.Default.LastUpdateCheck = DateTime.Now; - if (((CustomMandatory)args.Mandatory).CommitHash == Constants.APP_COMMIT_ID) + var targetHash = ((CustomMandatory) args.Mandatory).CommitHash; + if (targetHash == Constants.APP_COMMIT_ID) { if (UserSettings.Default.ShowChangelog) ShowChangelog(args); @@ -152,6 +157,7 @@ public class FModelApiEndpoint : AbstractApiProvider UserSettings.Default.ShowChangelog = currentVersion != args.InstalledVersion; const string message = "A new update is available!"; + Log.Warning("{message} Version {CurrentVersion} ({Hash})", message, currentVersion, targetHash); Helper.OpenWindow(message, () => new UpdateView { Title = message, ResizeMode = ResizeMode.NoResize }.ShowDialog()); } else diff --git a/FModel/ViewModels/ApplicationViewModel.cs b/FModel/ViewModels/ApplicationViewModel.cs index e3faa892..dd0806c2 100644 --- a/FModel/ViewModels/ApplicationViewModel.cs +++ b/FModel/ViewModels/ApplicationViewModel.cs @@ -260,7 +260,7 @@ public class ApplicationViewModel : ViewModel ZlibHelper.Initialize(zlibPath); } - public static async ValueTask InitDetex() + public static async Task InitDetex() { var detexPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", DetexHelper.DLL_NAME); if (File.Exists(DetexHelper.DLL_NAME)) diff --git a/FModel/Views/Snooper/Animations/TimeTracker.cs b/FModel/Views/Snooper/Animations/TimeTracker.cs index 28cc9538..0fa761d3 100644 --- a/FModel/Views/Snooper/Animations/TimeTracker.cs +++ b/FModel/Views/Snooper/Animations/TimeTracker.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Numerics; using FModel.Views.Snooper.Shading; +using FModel.Views.Snooper.Textures; using ImGuiNET; namespace FModel.Views.Snooper.Animations; @@ -61,7 +62,7 @@ public class TimeTracker : IDisposable } private readonly string[] _icons = { "tl_forward", "tl_pause", "tl_rewind" }; - public void ImGuiTimeline(Snooper s, Save saver, Dictionary icons, List animations, Vector2 outliner, ImFontPtr fontPtr) + public void ImGuiTimeline(Snooper s, Save saver, Dictionary icons, List animations, Vector2 outliner, ImFontPtr fontPtr) { var dpiScale = ImGui.GetWindowDpiScale(); var thickness = 2.0f * dpiScale; diff --git a/FModel/Views/Snooper/AssetPool.cs b/FModel/Views/Snooper/AssetPool.cs new file mode 100644 index 00000000..123c06be --- /dev/null +++ b/FModel/Views/Snooper/AssetPool.cs @@ -0,0 +1,266 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CUE4Parse_Conversion.Meshes; +using CUE4Parse_Conversion.Textures; +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Assets.Exports.Animation; +using CUE4Parse.UE4.Assets.Exports.Component.SplineMesh; +using CUE4Parse.UE4.Assets.Exports.Material; +using CUE4Parse.UE4.Assets.Exports.SkeletalMesh; +using CUE4Parse.UE4.Assets.Exports.StaticMesh; +using CUE4Parse.UE4.Assets.Exports.Texture; +using CUE4Parse.UE4.Objects.Core.Misc; +using CUE4Parse.UE4.Objects.UObject; +using FModel.Settings; +using FModel.Views.Snooper.Models; +using FModel.Views.Snooper.Shading; +using FModel.Views.Snooper.Textures; + +namespace FModel.Views.Snooper; + +public class AssetPool +{ + public static AssetPool Get() => _singleton ??= new AssetPool(); + private static AssetPool _singleton; + + public FGuid SelectedModel { get; private set; } + + public readonly ConcurrentBag Queue; + + public readonly Dictionary Models; + public readonly Dictionary Textures; + + private readonly string _project; + + private AssetPool() + { + Queue = []; + + Models = []; + Textures = []; + + _project = Services.ApplicationService.ApplicationView.CUE4Parse.Provider.ProjectName.ToUpper(); + } + + public void OnTick() + { + var current = 0; + while (Queue.TryTake(out var asset)) + { + asset.Setup(); + switch (asset) + { + case UModel uModel: + Models[asset.Guid] = uModel; + break; + case ITexture texture: + Textures[asset.Guid] = texture; + break; + } + + current++; + if (current >= 1) break; + } + + foreach (var model in Models.Values) + { + model.Update(); + } + } + + public void AddModel(UStaticMesh staticMesh) + { + ThreadPool.QueueUserWorkItem(_ => + { + var guid = staticMesh.LightingGuid; + if (TryGetModel(guid, out var model)) + { + model.AddInstance(Transform.Identity); + } + else if (staticMesh.TryConvert(out var mesh)) + { + Queue.Add(new StaticModel(staticMesh, mesh) { Guid = guid }); + } + }); + } + public void AddModel(USkeletalMesh skeletalMesh) + { + ThreadPool.QueueUserWorkItem(_ => + { + var guid = new FGuid((uint) skeletalMesh.GetFullName().GetHashCode()); + if (!Models.ContainsKey(guid) && skeletalMesh.TryConvert(out var mesh)) + { + Queue.Add(new SkeletalModel(skeletalMesh, mesh) { Guid = guid }); + } + }); + } + public void AddModel(USkeleton skeleton) + { + ThreadPool.QueueUserWorkItem(_ => + { + var guid = skeleton.Guid; + if (!Models.ContainsKey(guid) && skeleton.TryConvert(out var _, out var box)) + { + Queue.Add(new SkeletalModel(skeleton, box) { Guid = guid }); + } + }); + } + public void AddTexture(UTexture texture, bool fix) + { + ThreadPool.QueueUserWorkItem(_ => + { + var guid = texture.LightingGuid; + if (TryGet(guid, out var _)) + { + // do something here?? + } + else if (texture.Format != EPixelFormat.PF_BC6H) // BC6H is not supported by Decode thus randomly crashes the app + { + var bitmap = texture switch + { + UTexture2D texture2D => texture2D.Decode(UserSettings.Default.PreviewMaxTextureSize, UserSettings.Default.CurrentDir.TexturePlatform), + UTexture2DArray texture2DArray => texture2DArray.DecodeTextureArray(UserSettings.Default.CurrentDir.TexturePlatform)?.FirstOrDefault(), + _ => texture.Decode(UserSettings.Default.CurrentDir.TexturePlatform) + }; + + if (bitmap is not null) + { + var t = new BitmapTexture(bitmap.ToSkBitmap(), texture); + if (fix) t.FixChannels(_project); + + Queue.Add(t); + } + } + }); + } + + public void AddModel(IPropertyHolder actor, UObject staticMeshComponent, UStaticMesh staticMesh, Transform transform) + { + ThreadPool.QueueUserWorkItem(_ => + { + var bSpline = staticMeshComponent is USplineMeshComponent; + var guid = staticMesh.LightingGuid; + if (TryGetModel(guid, out var model)) + { + model.AddInstance(transform); + if (bSpline && model is SplineModel splineModel) + splineModel.AddComponent((USplineMeshComponent)staticMeshComponent); + } + else if (staticMesh.TryConvert(out var mesh)) + { + model = bSpline ? new SplineModel(staticMesh, mesh, (USplineMeshComponent)staticMeshComponent, transform) : new StaticModel(staticMesh, mesh, transform); + model.Guid = guid; + + if (actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData")) + { + var material = model.Materials.FirstOrDefault(); + if (material is { IsUsed: true }) + { + for (int j = 0; j < textureData.Length; j++) + { + if (textureData[j]?.Load() is not { } textureDataIdx) + continue; + + if (textureDataIdx.TryGetValue(out FPackageIndex overrideMaterial, "OverrideMaterial") && + overrideMaterial.TryLoad(out var oMaterial) && oMaterial is UMaterialInterface oUnrealMaterial) + material.SwapMaterial(oUnrealMaterial); + + WorldTextureData(material, textureDataIdx, "Diffuse", j switch + { + 0 => "Diffuse", + > 0 => $"Diffuse_Texture_{j + 1}", + _ => CMaterialParams2.FallbackDiffuse + }); + WorldTextureData(material, textureDataIdx, "Normal", j switch + { + 0 => "Normals", + > 0 => $"Normals_Texture_{j + 1}", + _ => CMaterialParams2.FallbackNormals + }); + WorldTextureData(material, textureDataIdx, "Specular", j switch + { + 0 => "SpecularMasks", + > 0 => $"SpecularMasks_{j + 1}", + _ => CMaterialParams2.FallbackNormals + }); + } + } + } + + if (staticMeshComponent.TryGetValue(out FPackageIndex[] overrideMaterials, "OverrideMaterials")) + { + for (var j = 0; j < overrideMaterials.Length && j < model.Sections.Length; j++) + { + var matIndex = model.Sections[j].MaterialIndex; + if (matIndex < 0 || matIndex >= model.Materials.Length || matIndex >= overrideMaterials.Length || + overrideMaterials[matIndex].Load() is not UMaterialInterface unrealMaterial) continue; + + model.Materials[matIndex].SwapMaterial(unrealMaterial); + } + } + + Queue.Add(model); + } + }); + + void WorldTextureData(Material material, UObject textureData, string name, string key) + { + if (textureData.TryGetValue(out FPackageIndex package, name) && package.Load() is UTexture2D texture) + material.Parameters.Textures[key] = texture; + } + } + + public bool TryGet(FGuid guid, [MaybeNullWhen(true)] out T asset) + { + if (Models.TryGetValue(guid, out var m) && m is T model) + { + asset = model; + return true; + } + + if (Textures.TryGetValue(guid, out var t) && t is T texture) + { + asset = texture; + return true; + } + + foreach (var a in Queue) + { + if (a.Guid == guid && a is T value) + { + asset = value; + return true; + } + } + + asset = default; + return false; + } + public bool TryGetModel([MaybeNullWhen(false)] out UModel model) => TryGet(SelectedModel, out model); + public bool TryGetModel(FGuid guid, [MaybeNullWhen(false)] out UModel model) => TryGet(guid, out model); + public bool TryGetTexture(FGuid guid, [MaybeNullWhen(false)] out ITexture texture) => TryGet(guid, out texture); + + public void SelectModel(FGuid guid) + { + // unselect old + if (TryGetModel(out var model)) + model.IsSelected = false; + + // select new + if (!TryGetModel(guid, out model)) + SelectedModel = Guid.Empty; + else + { + model.IsSelected = true; + SelectedModel = guid; + } + + // SelectedSection = 0; + // SelectedMorph = 0; + } +} diff --git a/FModel/Views/Snooper/Buffers/FramebufferObject.cs b/FModel/Views/Snooper/Buffers/FramebufferObject.cs index 2a0f2645..5c30d2a8 100644 --- a/FModel/Views/Snooper/Buffers/FramebufferObject.cs +++ b/FModel/Views/Snooper/Buffers/FramebufferObject.cs @@ -1,5 +1,6 @@ using System; using FModel.Views.Snooper.Shading; +using FModel.Views.Snooper.Textures; using OpenTK.Graphics.OpenGL4; namespace FModel.Views.Snooper.Buffers; @@ -11,15 +12,15 @@ public class FramebufferObject : IDisposable private int _width; private int _height; + private readonly MsaaTexture _framebufferTexture; private readonly RenderbufferObject _renderbuffer; + private readonly FramebufferTexture _postProcessingTexture; private BufferObject _ebo; private BufferObject _vbo; private VertexArrayObject _vao; private Shader _shader; - private Texture _framebufferTexture; - private Texture _postProcessingTexture; public readonly uint[] Indices = { 0, 1, 2, 3, 4, 5 }; public readonly float[] Vertices = { @@ -37,7 +38,9 @@ public class FramebufferObject : IDisposable { _width = size.X; _height = size.Y; + _framebufferTexture = new MsaaTexture((uint) _width, (uint) _height); _renderbuffer = new RenderbufferObject(_width, _height); + _postProcessingTexture = new FramebufferTexture(_width, _height); } public void Setup() @@ -45,8 +48,7 @@ public class FramebufferObject : IDisposable _framebufferHandle = GL.GenFramebuffer(); Bind(); - _framebufferTexture = new Texture((uint) _width, (uint) _height); - + _framebufferTexture.Setup(); _renderbuffer.Setup(); _shader = new Shader("framebuffer"); @@ -69,7 +71,7 @@ public class FramebufferObject : IDisposable _postProcessingHandle = GL.GenFramebuffer(); Bind(_postProcessingHandle); - _postProcessingTexture = new Texture(_width, _height); + _postProcessingTexture.Setup(); status = GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer); if (status != FramebufferErrorCode.FramebufferComplete) diff --git a/FModel/Views/Snooper/Buffers/PickingTexture.cs b/FModel/Views/Snooper/Buffers/PickingTexture.cs index fe45cb7c..f4d27bf3 100644 --- a/FModel/Views/Snooper/Buffers/PickingTexture.cs +++ b/FModel/Views/Snooper/Buffers/PickingTexture.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using CUE4Parse.UE4.Objects.Core.Misc; using OpenTK.Graphics.OpenGL4; using System.Numerics; @@ -54,20 +55,18 @@ public class PickingTexture : IDisposable Bind(0); } - public void Render(Matrix4x4 viewMatrix, Matrix4x4 projMatrix, IDictionary models) + public void Render(Matrix4x4 viewMatrix, Matrix4x4 projMatrix) { Bind(); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); _shader.Render(viewMatrix, projMatrix); - foreach ((var guid, var model) in models) + foreach (var model in AssetPool.Get().Models.Values.Where(model => model.IsVisible)) { - _shader.SetUniform("uA", guid.A); - _shader.SetUniform("uB", guid.B); - _shader.SetUniform("uC", guid.C); - _shader.SetUniform("uD", guid.D); - - if (!model.IsVisible) continue; + _shader.SetUniform("uA", model.Guid.A); + _shader.SetUniform("uB", model.Guid.B); + _shader.SetUniform("uC", model.Guid.C); + _shader.SetUniform("uD", model.Guid.D); model.PickingRender(_shader); } diff --git a/FModel/Views/Snooper/IDelayedSetup.cs b/FModel/Views/Snooper/IDelayedSetup.cs new file mode 100644 index 00000000..3a963f83 --- /dev/null +++ b/FModel/Views/Snooper/IDelayedSetup.cs @@ -0,0 +1,11 @@ +using System; +using CUE4Parse.UE4.Objects.Core.Misc; + +namespace FModel.Views.Snooper; + +public interface IDelayedSetup : IDisposable +{ + public FGuid Guid { get; } + + public void Setup(); +} diff --git a/FModel/Views/Snooper/Lights/Light.cs b/FModel/Views/Snooper/Lights/Light.cs index 8159f3b5..8d8ce776 100644 --- a/FModel/Views/Snooper/Lights/Light.cs +++ b/FModel/Views/Snooper/Lights/Light.cs @@ -5,6 +5,7 @@ using CUE4Parse.UE4.Objects.Core.Math; using CUE4Parse.UE4.Objects.Core.Misc; using FModel.Views.Snooper.Buffers; using FModel.Views.Snooper.Shading; +using FModel.Views.Snooper.Textures; using ImGuiNET; using OpenTK.Graphics.OpenGL4; @@ -29,14 +30,14 @@ public abstract class Light : IDisposable 1f, -1f, 0 }; public readonly FGuid Model; - public readonly Texture Icon; + public readonly ITexture Icon; public Transform Transform; public Vector4 Color; public float Intensity; public bool IsSetup; - public Light(Texture icon, UObject light) + public Light(ITexture icon, UObject light) { Transform = new Transform { @@ -52,7 +53,7 @@ public abstract class Light : IDisposable Intensity = light.GetOrDefault("Intensity", 1.0f); } - public Light(FGuid model, Texture icon, UObject parent, UObject light, Transform transform) + public Light(FGuid model, ITexture icon, UObject parent, UObject light, Transform transform) { Transform = new Transform { diff --git a/FModel/Views/Snooper/Lights/PointLight.cs b/FModel/Views/Snooper/Lights/PointLight.cs index 55fcda20..c6bd0a70 100644 --- a/FModel/Views/Snooper/Lights/PointLight.cs +++ b/FModel/Views/Snooper/Lights/PointLight.cs @@ -2,6 +2,7 @@ using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Objects.Core.Misc; using FModel.Views.Snooper.Shading; +using FModel.Views.Snooper.Textures; using ImGuiNET; namespace FModel.Views.Snooper.Lights; @@ -11,7 +12,7 @@ public class PointLight : Light public float Linear; public float Quadratic; - public PointLight(Texture icon, UObject point) : base(icon, point) + public PointLight(ITexture icon, UObject point) : base(icon, point) { if (!point.TryGetValue(out float radius, "SourceRadius", "AttenuationRadius")) radius = 1.0f; @@ -21,7 +22,7 @@ public class PointLight : Light Quadratic = 75.0f / MathF.Pow(radius, 2.0f); } - public PointLight(FGuid model, Texture icon, UObject parent, UObject point, Transform transform) : base(model, icon, parent, point, transform) + public PointLight(FGuid model, ITexture icon, UObject parent, UObject point, Transform transform) : base(model, icon, parent, point, transform) { if (!point.TryGetValue(out float radius, "AttenuationRadius", "SourceRadius")) radius = 1.0f; diff --git a/FModel/Views/Snooper/Lights/SpotLight.cs b/FModel/Views/Snooper/Lights/SpotLight.cs index 3800d888..d8245234 100644 --- a/FModel/Views/Snooper/Lights/SpotLight.cs +++ b/FModel/Views/Snooper/Lights/SpotLight.cs @@ -1,6 +1,7 @@ using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Objects.Core.Misc; using FModel.Views.Snooper.Shading; +using FModel.Views.Snooper.Textures; using ImGuiNET; namespace FModel.Views.Snooper.Lights; @@ -11,7 +12,7 @@ public class SpotLight : Light public float InnerConeAngle; public float OuterConeAngle; - public SpotLight(Texture icon, UObject spot) : base(icon, spot) + public SpotLight(ITexture icon, UObject spot) : base(icon, spot) { if (!spot.TryGetValue(out Attenuation, "SourceRadius", "AttenuationRadius")) Attenuation = 1.0f; @@ -23,7 +24,7 @@ public class SpotLight : Light InnerConeAngle = OuterConeAngle - 10; } - public SpotLight(FGuid model, Texture icon, UObject parent, UObject spot, Transform transform) : base(model, icon, parent, spot, transform) + public SpotLight(FGuid model, ITexture icon, UObject parent, UObject spot, Transform transform) : base(model, icon, parent, spot, transform) { if (!spot.TryGetValue(out Attenuation, "AttenuationRadius", "SourceRadius")) Attenuation = 1.0f; diff --git a/FModel/Views/Snooper/Models/IRenderableModel.cs b/FModel/Views/Snooper/Models/IRenderableModel.cs index a863ec98..8f8b7e8f 100644 --- a/FModel/Views/Snooper/Models/IRenderableModel.cs +++ b/FModel/Views/Snooper/Models/IRenderableModel.cs @@ -1,12 +1,12 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Numerics; using FModel.Views.Snooper.Buffers; using FModel.Views.Snooper.Shading; +using FModel.Views.Snooper.Textures; namespace FModel.Views.Snooper.Models; -public interface IRenderableModel : IDisposable +public interface IRenderableModel : IDelayedSetup { protected int Handle { get; set; } protected BufferObject Ebo { get; set; } @@ -29,10 +29,7 @@ public interface IRenderableModel : IDisposable public bool IsSelected { get; set; } public bool ShowWireframe { get; set; } - public void Setup(Options options); - public void SetupInstances(); - public void Render(Shader shader, Texture checker = null, bool outline = false); + public void Render(Shader shader, ITexture checker = null, bool outline = false); public void PickingRender(Shader shader); - public void Update(Options options); public void AddInstance(Transform transform); } diff --git a/FModel/Views/Snooper/Models/Section.cs b/FModel/Views/Snooper/Models/Section.cs index 6720e167..57e1f70d 100644 --- a/FModel/Views/Snooper/Models/Section.cs +++ b/FModel/Views/Snooper/Models/Section.cs @@ -23,9 +23,11 @@ public class Section Color = Constants.COLOR_PALETTE[MaterialIndex % Constants.PALETTE_LENGTH]; } - public void SetupMaterial(Material material) + public void ValidateMaterial(Material material, int uvCount) { material.IsUsed = true; Show = !material.Parameters.IsNull && !material.Parameters.IsTranslucent; + + material.Validate(uvCount); } } diff --git a/FModel/Views/Snooper/Models/SkeletalModel.cs b/FModel/Views/Snooper/Models/SkeletalModel.cs index fcc25ac8..752db222 100644 --- a/FModel/Views/Snooper/Models/SkeletalModel.cs +++ b/FModel/Views/Snooper/Models/SkeletalModel.cs @@ -131,9 +131,9 @@ public class SkeletalModel : UModel IsVisible = true; } - public override void Setup(Options options) + public override void Setup() { - base.Setup(options); + base.Setup(); Skeleton.Setup(); if (!HasMorphTargets) return; diff --git a/FModel/Views/Snooper/Models/Skybox.cs b/FModel/Views/Snooper/Models/Skybox.cs index af04e3b5..e2efd744 100644 --- a/FModel/Views/Snooper/Models/Skybox.cs +++ b/FModel/Views/Snooper/Models/Skybox.cs @@ -2,6 +2,7 @@ using System.Numerics; using FModel.Views.Snooper.Buffers; using FModel.Views.Snooper.Shading; +using FModel.Views.Snooper.Textures; using OpenTK.Graphics.OpenGL4; namespace FModel.Views.Snooper.Models; @@ -16,7 +17,7 @@ public class Skybox : IDisposable private string[] _textures = { "px", "nx", "py", "ny", "pz", "nz" }; - private Texture _cubeMap; + private ITexture _cubeMap; private Shader _shader; public readonly uint[] Indices = { 0, 1, 3, 1, 2, 3 }; @@ -75,7 +76,7 @@ public class Skybox : IDisposable _vbo = new BufferObject(Vertices, BufferTarget.ArrayBuffer); _vao = new VertexArrayObject(_vbo, _ebo); - _cubeMap = new Texture(_textures); + _cubeMap = new ApplicationTexture(_textures); _shader = new Shader("skybox"); _vao.VertexAttributePointer(0, 3, VertexAttribPointerType.Float, 3, 0); // position diff --git a/FModel/Views/Snooper/Models/SplineModel.cs b/FModel/Views/Snooper/Models/SplineModel.cs index 3850a4ce..1cf13b91 100644 --- a/FModel/Views/Snooper/Models/SplineModel.cs +++ b/FModel/Views/Snooper/Models/SplineModel.cs @@ -88,9 +88,9 @@ public class SplineModel : StaticModel _splineParams.Add(new GpuParams(splineMesh)); } - public override void Setup(Options options) + public override void Setup() { - base.Setup(options); + base.Setup(); _ssbo = new BufferObject(_splineParams.ToArray(), BufferTarget.ShaderStorageBuffer); } diff --git a/FModel/Views/Snooper/Models/UModel.cs b/FModel/Views/Snooper/Models/UModel.cs index da3be5b4..530f9917 100644 --- a/FModel/Views/Snooper/Models/UModel.cs +++ b/FModel/Views/Snooper/Models/UModel.cs @@ -9,10 +9,12 @@ using CUE4Parse.UE4.Assets; using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports.Material; using CUE4Parse.UE4.Objects.Core.Math; +using CUE4Parse.UE4.Objects.Core.Misc; using CUE4Parse.Utils; using FModel.Settings; using FModel.Views.Snooper.Buffers; using FModel.Views.Snooper.Shading; +using FModel.Views.Snooper.Textures; using OpenTK.Graphics.OpenGL4; namespace FModel.Views.Snooper.Models; @@ -31,15 +33,15 @@ public abstract class UModel : IRenderableModel private readonly UObject _export; private readonly List _vertexAttributes = [ - new VertexAttribute { Size = 1, Type = VertexAttribPointerType.Int, Enabled = false }, // VertexIndex - new VertexAttribute { Size = 3, Type = VertexAttribPointerType.Float, Enabled = true }, // Position - new VertexAttribute { Size = 3, Type = VertexAttribPointerType.Float, Enabled = false }, // Normal - new VertexAttribute { Size = 3, Type = VertexAttribPointerType.Float, Enabled = false }, // Tangent - new VertexAttribute { Size = 2, Type = VertexAttribPointerType.Float, Enabled = false }, // UV - new VertexAttribute { Size = 1, Type = VertexAttribPointerType.Float, Enabled = false }, // TextureLayer - new VertexAttribute { Size = 1, Type = VertexAttribPointerType.Float, Enabled = false }, // Colors - new VertexAttribute { Size = 4, Type = VertexAttribPointerType.Float, Enabled = false }, // BoneIds - new VertexAttribute { Size = 4, Type = VertexAttribPointerType.Float, Enabled = false } // BoneWeights + new() { Size = 1, Type = VertexAttribPointerType.Int, Enabled = false }, // VertexIndex + new() { Size = 3, Type = VertexAttribPointerType.Float, Enabled = true }, // Position + new() { Size = 3, Type = VertexAttribPointerType.Float, Enabled = false }, // Normal + new() { Size = 3, Type = VertexAttribPointerType.Float, Enabled = false }, // Tangent + new() { Size = 2, Type = VertexAttribPointerType.Float, Enabled = false }, // UV + new() { Size = 1, Type = VertexAttribPointerType.Float, Enabled = false }, // TextureLayer + new() { Size = 1, Type = VertexAttribPointerType.Float, Enabled = false }, // Colors + new() { Size = 4, Type = VertexAttribPointerType.Float, Enabled = false }, // BoneIds + new() { Size = 4, Type = VertexAttribPointerType.Float, Enabled = false } // BoneWeights ]; public int Handle { get; set; } @@ -48,6 +50,7 @@ public abstract class UModel : IRenderableModel public BufferObject MatrixVbo { get; set; } public VertexArrayObject Vao { get; set; } + public FGuid Guid { get; set; } public string Path { get; } public string Name { get; } public string Type { get; protected set; } @@ -177,13 +180,13 @@ public abstract class UModel : IRenderableModel { var section = lod.Sections.Value[s]; Sections[s] = new Section(section.MaterialIndex, section.NumFaces * 3, section.FirstIndex); - if (section.IsValid) Sections[s].SetupMaterial(Materials[section.MaterialIndex]); + if (section.IsValid) Sections[s].ValidateMaterial(Materials[section.MaterialIndex], UvCount); } AddInstance(transform ?? Transform.Identity); } - public virtual void Setup(Options options) + public virtual void Setup() { Handle = GL.CreateProgram(); Ebo = new BufferObject(Indices, BufferTarget.ElementArrayBuffer); @@ -211,7 +214,7 @@ public abstract class UModel : IRenderableModel for (var i = 0; i < Materials.Length; i++) { if (!Materials[i].IsUsed) continue; - Materials[i].Setup(options, broken ? 1 : UvCount); + Materials[i].Setup(broken); } foreach (var collision in Collisions) @@ -219,7 +222,7 @@ public abstract class UModel : IRenderableModel collision.Setup(); } - if (options.Models.Count == 1 && Sections.All(x => !x.Show)) // visible if alone and invisible + // if (options.Models.Count == 1 && Sections.All(x => !x.Show)) // visible if alone and invisible { IsVisible = true; foreach (var section in Sections) @@ -227,26 +230,26 @@ public abstract class UModel : IRenderableModel section.Show = true; } } - else if (!IsVisible) // default: visible if one section is visible - { - foreach (var section in Sections) - { - if (section.Show) - { - IsVisible = true; - break; - } - } - } - else foreach (var section in Sections) // force visibility - { - section.Show = true; - } + // else if (!IsVisible) // default: visible if one section is visible + // { + // foreach (var section in Sections) + // { + // if (section.Show) + // { + // IsVisible = true; + // break; + // } + // } + // } + // else foreach (var section in Sections) // force visibility + // { + // section.Show = true; + // } IsSetup = true; } - public virtual void Render(Shader shader, Texture checker = null, bool outline = false) + public virtual void Render(Shader shader, ITexture checker = null, bool outline = false) { if (outline) GL.Disable(EnableCap.DepthTest); if (IsTwoSided) GL.Disable(EnableCap.CullFace); @@ -332,7 +335,7 @@ public abstract class UModel : IRenderableModel shader.SetUniform("uScaleDown", Constants.SCALE_DOWN_RATIO); } - public void Update(Options options) + public void Update() { MatrixVbo.Bind(); for (int instance = 0; instance < TransformsCount; instance++) @@ -353,11 +356,11 @@ public abstract class UModel : IRenderableModel var socketRelation = boneMatrix * worldMatrix; foreach (var info in socket.AttachedModels) { - if (!options.TryGetModel(info.Guid, out var attachedModel)) + if (!AssetPool.Get().TryGetModel(info.Guid, out var attachedModel)) continue; attachedModel.Transforms[info.Instance].Relation = socket.Transform.LocalMatrix * socketRelation; - attachedModel.Update(options); + attachedModel.Update(); } } } diff --git a/FModel/Views/Snooper/Options.cs b/FModel/Views/Snooper/Options.cs index 0f55dd27..346f0e01 100644 --- a/FModel/Views/Snooper/Options.cs +++ b/FModel/Views/Snooper/Options.cs @@ -9,6 +9,7 @@ using FModel.Views.Snooper.Animations; using FModel.Views.Snooper.Lights; using FModel.Views.Snooper.Models; using FModel.Views.Snooper.Shading; +using FModel.Views.Snooper.Textures; using SkiaSharp; namespace FModel.Views.Snooper; @@ -22,49 +23,54 @@ public class Options public int SelectedAnimation{ get; private set; } public readonly Dictionary Models; - public readonly Dictionary Textures; + public readonly Dictionary Textures; public readonly List Lights; public readonly TimeTracker Tracker; public readonly List Animations; - public readonly Dictionary Icons; + public readonly Dictionary Icons; private readonly string _game; public Options() { Models = new Dictionary(); - Textures = new Dictionary(); + Textures = new Dictionary(); Lights = new List(); Tracker = new TimeTracker(); Animations = new List(); - Icons = new Dictionary + Icons = new Dictionary { - ["material"] = new ("materialicon"), - ["square"] = new ("square"), - ["square_off"] = new ("square_off"), - ["cube"] = new ("cube"), - ["cube_off"] = new ("cube_off"), - ["light"] = new ("light"), - ["light_off"] = new ("light_off"), - ["noimage"] = new ("T_Placeholder_Item_Image"), - ["checker"] = new ("checker"), - ["pointlight"] = new ("pointlight"), - ["spotlight"] = new ("spotlight"), - ["link_on"] = new ("link_on"), - ["link_off"] = new ("link_off"), - ["link_has"] = new ("link_has"), - ["tl_play"] = new ("tl_play"), - ["tl_pause"] = new ("tl_pause"), - ["tl_rewind"] = new ("tl_rewind"), - ["tl_forward"] = new ("tl_forward"), - ["tl_previous"] = new ("tl_previous"), - ["tl_next"] = new ("tl_next"), + ["material"] = new ApplicationTexture("materialicon"), + ["square"] = new ApplicationTexture("square"), + ["square_off"] = new ApplicationTexture("square_off"), + ["cube"] = new ApplicationTexture("cube"), + ["cube_off"] = new ApplicationTexture("cube_off"), + ["light"] = new ApplicationTexture("light"), + ["light_off"] = new ApplicationTexture("light_off"), + ["noimage"] = new ApplicationTexture("T_Placeholder_Item_Image"), + ["checker"] = new ApplicationTexture("checker"), + ["pointlight"] = new ApplicationTexture("pointlight"), + ["spotlight"] = new ApplicationTexture("spotlight"), + ["link_on"] = new ApplicationTexture("link_on"), + ["link_off"] = new ApplicationTexture("link_off"), + ["link_has"] = new ApplicationTexture("link_has"), + ["tl_play"] = new ApplicationTexture("tl_play"), + ["tl_pause"] = new ApplicationTexture("tl_pause"), + ["tl_rewind"] = new ApplicationTexture("tl_rewind"), + ["tl_forward"] = new ApplicationTexture("tl_forward"), + ["tl_previous"] = new ApplicationTexture("tl_previous"), + ["tl_next"] = new ApplicationTexture("tl_next"), }; + foreach (var icon in Icons.Values) + { + icon.Setup(); + } + _game = Services.ApplicationService.ApplicationView.CUE4Parse.Provider.ProjectName.ToUpper(); SelectModel(Guid.Empty); @@ -72,11 +78,11 @@ public class Options public void SetupModelsAndLights() { - foreach (var model in Models.Values) - { - if (model.IsSetup) continue; - model.Setup(this); - } + // foreach (var model in Models.Values) + // { + // if (model.IsSetup) continue; + // model.Setup(this); + // } foreach (var light in Lights) { @@ -187,29 +193,6 @@ public class Options model.UpdateMorph(SelectedMorph); } - public bool TryGetTexture(UTexture o, bool fix, out Texture texture) - { - var guid = o.LightingGuid; - if (Textures.TryGetValue(guid, out texture)) return texture != null; - if (o.Format == EPixelFormat.PF_BC6H) return false; // BC6H is not supported by Decode thus randomly crashes the app - - var bitmap = o switch - { - UTexture2D texture2D => texture2D.Decode(UserSettings.Default.PreviewMaxTextureSize, UserSettings.Default.CurrentDir.TexturePlatform), - UTexture2DArray texture2DArray => texture2DArray.DecodeTextureArray(UserSettings.Default.CurrentDir.TexturePlatform)?.FirstOrDefault(), - _ => o.Decode(UserSettings.Default.CurrentDir.TexturePlatform) - }; - - if (bitmap is not null) - { - texture = new Texture(bitmap.ToSkBitmap(), o); - if (fix) TextureHelper.FixChannels(_game, texture); - Textures[guid] = texture; - } - - return texture != null; - } - public bool TryGetModel(out UModel model) => Models.TryGetValue(SelectedModel, out model); public bool TryGetModel(FGuid guid, out UModel model) => Models.TryGetValue(guid, out model); diff --git a/FModel/Views/Snooper/Renderer.cs b/FModel/Views/Snooper/Renderer.cs index dfa91ad3..725e88dd 100644 --- a/FModel/Views/Snooper/Renderer.cs +++ b/FModel/Views/Snooper/Renderer.cs @@ -86,13 +86,13 @@ public class Renderer : IDisposable switch (dummy) { case UStaticMesh when export.Value is UStaticMesh st: - LoadStaticMesh(st); + AssetPool.Get().AddModel(st); break; case USkeletalMesh when export.Value is USkeletalMesh sk: - LoadSkeletalMesh(sk); + AssetPool.Get().AddModel(sk); break; case USkeleton when export.Value is USkeleton skel: - LoadSkeleton(skel); + AssetPool.Get().AddModel(skel); break; case UMaterialInstance when export.Value is UMaterialInstance mi: LoadMaterialInstance(mi); @@ -117,7 +117,7 @@ public class Renderer : IDisposable if (!Options.TryGetModel(out var model) || !Options.TryGetSection(model, out var section)) return; model.Materials[section.MaterialIndex].SwapMaterial(unrealMaterial); - Application.Current.Dispatcher.Invoke(() => model.Materials[section.MaterialIndex].Setup(Options, model.UvCount)); + // Application.Current.Dispatcher.Invoke(() => model.Materials[section.MaterialIndex].Setup(model.UvCount)); } public void Animate(UObject anim) @@ -242,7 +242,7 @@ public class Renderer : IDisposable _collision = new Shader("collision", "bone"); Picking.Setup(); - Options.SetupModelsAndLights(); + // Options.SetupModelsAndLights(); } public void Render() @@ -258,9 +258,8 @@ public class Renderer : IDisposable _shader.SetUniform($"bVertexColors[{i}]", i == (int) Color); // render model pass - foreach (var model in Options.Models.Values) + foreach (var model in AssetPool.Get().Models.Values.Where(model => model.IsVisible)) { - if (!model.IsVisible) continue; model.Render(_shader, Color == VertexColor.TextureCoordinates ? Options.Icons["checker"] : null); } @@ -278,7 +277,7 @@ public class Renderer : IDisposable } // debug + outline pass - if (Options.TryGetModel(out var selected) && selected.IsVisible) + if (AssetPool.Get().TryGetModel(out var selected) && selected.IsVisible) { if (IsSkeletonTreeOpen && selected is SkeletalModel skeletalModel) { @@ -296,7 +295,7 @@ public class Renderer : IDisposable } // picking pass (dedicated FBO, binding to 0 afterward) - Picking.Render(viewMatrix, projMatrix, Options.Models); + Picking.Render(viewMatrix, projMatrix); } public void Update(Snooper wnd, float deltaSeconds) @@ -313,14 +312,15 @@ public class Renderer : IDisposable } { - foreach (var model in Options.Models.Values) - { - model.Update(Options); - } - if (IsSkeletonTreeOpen && Options.TryGetModel(out var selected) && selected is SkeletalModel { IsVisible: true } skeletalModel) - { - skeletalModel.Skeleton.UpdateVertices(); - } + AssetPool.Get().OnTick(); + // foreach (var model in Options.Models.Values) + // { + // model.Update(Options); + // } + // if (IsSkeletonTreeOpen && Options.TryGetModel(out var selected) && selected is SkeletalModel { IsVisible: true } skeletalModel) + // { + // skeletalModel.Skeleton.UpdateVertices(); + // } } CameraOp.Modify(wnd.KeyboardState, deltaSeconds); @@ -343,33 +343,6 @@ public class Renderer : IDisposable wnd.WindowShouldClose(true, true); } - private void LoadStaticMesh(UStaticMesh original) - { - var guid = original.LightingGuid; - if (Options.TryGetModel(guid, out var model)) - { - model.AddInstance(Transform.Identity); - Application.Current.Dispatcher.Invoke(() => model.SetupInstances()); - return; - } - - if (!original.TryConvert(out var mesh)) - return; - - Options.Models[guid] = new StaticModel(original, mesh); - Options.SelectModel(guid); - } - - private void LoadSkeletalMesh(USkeletalMesh original) - { - var guid = new FGuid((uint) original.GetFullName().GetHashCode()); - if (Options.Models.ContainsKey(guid) || !original.TryConvert(out var mesh)) return; - - var skeletalModel = new SkeletalModel(original, mesh); - Options.Models[guid] = skeletalModel; - Options.SelectModel(guid); - } - private void LoadSkeleton(USkeleton original) { var guid = original.Guid; @@ -390,7 +363,7 @@ public class Renderer : IDisposable if (Options.TryGetModel(guid, out var model)) { model.Materials[0].SwapMaterial(original); - Application.Current.Dispatcher.Invoke(() => model.Materials[0].Setup(Options, model.UvCount)); + // Application.Current.Dispatcher.Invoke(() => model.Materials[0].Setup(Options, model.UvCount)); return; } @@ -410,7 +383,7 @@ public class Renderer : IDisposable if (Options.TryGetModel(guid, out var model)) { model.AddInstance(Transform.Identity); - Application.Current.Dispatcher.Invoke(() => model.SetupInstances()); + // Application.Current.Dispatcher.Invoke(() => model.SetupInstances()); return; } @@ -589,87 +562,18 @@ public class Renderer : IDisposable } private void ProcessMesh(IPropertyHolder actor, UObject staticMeshComp, UStaticMesh m, Transform transform, bool forceShow) { - var bSpline = staticMeshComp is USplineMeshComponent; - var guid = m.LightingGuid; - if (Options.TryGetModel(guid, out var model)) - { - model.AddInstance(transform); - if (bSpline && model is SplineModel splineModel) - splineModel.AddComponent((USplineMeshComponent)staticMeshComp); - } - else if (m.TryConvert(out var mesh)) - { - model = bSpline ? new SplineModel(m, mesh, (USplineMeshComponent)staticMeshComp, transform) : new StaticModel(m, mesh, transform); - model.IsTwoSided = actor.GetOrDefault("bMirrored", staticMeshComp.GetOrDefault("bDisallowMeshPaintPerInstance", model.IsTwoSided)); + AssetPool.Get().AddModel(actor, staticMeshComp, m, transform); - if (actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData")) - { - var material = model.Materials.FirstOrDefault(); - if (material is { IsUsed: true }) - { - for (int j = 0; j < textureData.Length; j++) - { - if (textureData[j]?.Load() is not { } textureDataIdx) - continue; - - if (textureDataIdx.TryGetValue(out FPackageIndex overrideMaterial, "OverrideMaterial") && - overrideMaterial.TryLoad(out var oMaterial) && oMaterial is UMaterialInterface oUnrealMaterial) - material.SwapMaterial(oUnrealMaterial); - - WorldTextureData(material, textureDataIdx, "Diffuse", j switch - { - 0 => "Diffuse", - > 0 => $"Diffuse_Texture_{j + 1}", - _ => CMaterialParams2.FallbackDiffuse - }); - WorldTextureData(material, textureDataIdx, "Normal", j switch - { - 0 => "Normals", - > 0 => $"Normals_Texture_{j + 1}", - _ => CMaterialParams2.FallbackNormals - }); - WorldTextureData(material, textureDataIdx, "Specular", j switch - { - 0 => "SpecularMasks", - > 0 => $"SpecularMasks_{j + 1}", - _ => CMaterialParams2.FallbackNormals - }); - } - } - } - - if (staticMeshComp.TryGetValue(out FPackageIndex[] overrideMaterials, "OverrideMaterials")) - { - for (var j = 0; j < overrideMaterials.Length && j < model.Sections.Length; j++) - { - var matIndex = model.Sections[j].MaterialIndex; - if (matIndex < 0 || matIndex >= model.Materials.Length || matIndex >= overrideMaterials.Length || - overrideMaterials[matIndex].Load() is not UMaterialInterface unrealMaterial) continue; - - model.Materials[matIndex].SwapMaterial(unrealMaterial); - } - } - - if (forceShow) - { - foreach (var section in model.Sections) - { - section.Show = true; - } - } - Options.Models[guid] = model; - } - - if (actor.TryGetValue(out FPackageIndex treasureLight, "PointLight", "TreasureLight") && - treasureLight.TryLoad(out var pl1) && pl1.Template.TryLoad(out var pl2)) - { - Options.Lights.Add(new PointLight(guid, Options.Icons["pointlight"], pl1, pl2, transform)); - } - if (actor.TryGetValue(out FPackageIndex spotLight, "SpotLight") && - spotLight.TryLoad(out var sl1) && sl1.Template.TryLoad(out var sl2)) - { - Options.Lights.Add(new SpotLight(guid, Options.Icons["spotlight"], sl1, sl2, transform)); - } + // if (actor.TryGetValue(out FPackageIndex treasureLight, "PointLight", "TreasureLight") && + // treasureLight.TryLoad(out var pl1) && pl1.Template.TryLoad(out var pl2)) + // { + // Options.Lights.Add(new PointLight(guid, Options.Icons["pointlight"], pl1, pl2, transform)); + // } + // if (actor.TryGetValue(out FPackageIndex spotLight, "SpotLight") && + // spotLight.TryLoad(out var sl1) && sl1.Template.TryLoad(out var sl2)) + // { + // Options.Lights.Add(new SpotLight(guid, Options.Icons["spotlight"], sl1, sl2, transform)); + // } } private Transform CalculateTransform(IPropertyHolder staticMeshComp, Transform relation) diff --git a/FModel/Views/Snooper/Shading/Material.cs b/FModel/Views/Snooper/Shading/Material.cs index 729aa766..83765af3 100644 --- a/FModel/Views/Snooper/Shading/Material.cs +++ b/FModel/Views/Snooper/Shading/Material.cs @@ -9,6 +9,7 @@ using CUE4Parse.UE4.Objects.Core.Misc; using FModel.Extensions; using FModel.Settings; using FModel.Views.Snooper.Models; +using FModel.Views.Snooper.Textures; using ImGuiNET; using OpenTK.Graphics.OpenGL4; @@ -25,10 +26,10 @@ public class Material : IDisposable public int SelectedTexture; public bool IsUsed; - public Texture[] Diffuse; - public Texture[] Normals; - public Texture[] SpecularMasks; - public Texture[] Emissive; + public FGuid[] Diffuse; + public FGuid[] Normals; + public FGuid[] SpecularMasks; + public FGuid[] Emissive; public Vector4[] DiffuseColor; public Vector4[] EmissiveColor; @@ -48,13 +49,13 @@ public class Material : IDisposable Path = "None"; IsUsed = false; - Diffuse = Array.Empty(); - Normals = Array.Empty(); - SpecularMasks = Array.Empty(); - Emissive = Array.Empty(); + Diffuse = []; + Normals = []; + SpecularMasks = []; + Emissive = []; - DiffuseColor = Array.Empty(); - EmissiveColor = Array.Empty(); + DiffuseColor = []; + EmissiveColor = []; EmissiveRegion = new Vector4(0, 0, 1, 1); } @@ -70,39 +71,36 @@ public class Material : IDisposable unrealMaterial.GetParams(Parameters, UserSettings.Default.MaterialExportFormat); } - public void Setup(Options options, int uvCount) + public void Validate(int uvCount) { - _handle = GL.CreateProgram(); - if (uvCount < 1 || Parameters.IsNull) { - Diffuse = [new Texture(FLinearColor.Gray)]; - Normals = [new Texture(new FLinearColor(0.5f, 0.5f, 1f, 1f))]; - SpecularMasks = [new Texture(new FLinearColor(1f, 0.5f, 0.5f, 1f))]; - Emissive = new Texture[1]; - DiffuseColor = FillColors(1, Diffuse, CMaterialParams2.DiffuseColors, Vector4.One); + Diffuse = [new LinearColorTexture(FLinearColor.Gray).Guid]; + Normals = [new LinearColorTexture(new FLinearColor(0.5f, 0.5f, 1f, 1f)).Guid]; + SpecularMasks = []; + Emissive = new FGuid[1]; + DiffuseColor = FillColors(1, CMaterialParams2.DiffuseColors, Vector4.One); EmissiveColor = [Vector4.One]; } else { - { // textures - Diffuse = FillTextures(options, uvCount, Parameters.HasTopDiffuse, CMaterialParams2.Diffuse, CMaterialParams2.FallbackDiffuse, true); - Normals = FillTextures(options, uvCount, Parameters.HasTopNormals, CMaterialParams2.Normals, CMaterialParams2.FallbackNormals); - SpecularMasks = FillTextures(options, uvCount, Parameters.HasTopSpecularMasks, CMaterialParams2.SpecularMasks, CMaterialParams2.FallbackSpecularMasks); - Emissive = FillTextures(options, uvCount, true, CMaterialParams2.Emissive, CMaterialParams2.FallbackEmissive); - } + // textures + Diffuse = FillTextures(uvCount, Parameters.HasTopDiffuse, CMaterialParams2.Diffuse, CMaterialParams2.FallbackDiffuse, true); + Normals = FillTextures(uvCount, Parameters.HasTopNormals, CMaterialParams2.Normals, CMaterialParams2.FallbackNormals); + SpecularMasks = FillTextures(uvCount, Parameters.HasTopSpecularMasks, CMaterialParams2.SpecularMasks, CMaterialParams2.FallbackSpecularMasks); + Emissive = FillTextures(uvCount, true, CMaterialParams2.Emissive, CMaterialParams2.FallbackEmissive); - { // colors - DiffuseColor = FillColors(uvCount, Diffuse, CMaterialParams2.DiffuseColors, Vector4.One); - EmissiveColor = FillColors(uvCount, Emissive, CMaterialParams2.EmissiveColors, Vector4.One); - } + // colors + DiffuseColor = FillColors(uvCount, CMaterialParams2.DiffuseColors, Vector4.One); + EmissiveColor = FillColors(uvCount, CMaterialParams2.EmissiveColors, Vector4.One); { // ambient occlusion + color boost - if (Parameters.TryGetTexture2d(out var original, "M", "AEM", "AO") && - !original.Name.Equals("T_BlackMask") && options.TryGetTexture(original, false, out var transformed)) + if (Parameters.TryGetTexture2d(out var original, "M", "AEM", "AO") && !original.Name.Equals("T_BlackMask")) { + AssetPool.Get().AddTexture(original, false); + HasAo = true; - Ao = new AoParams { Texture = transformed }; + Ao = new AoParams { Texture = original.LightingGuid }; if (Parameters.TryGetLinearColor(out var l, "Skin Boost Color And Exponent")) { Ao.HasColorBoost = true; @@ -133,72 +131,94 @@ public class Material : IDisposable "EmissiveUVPositioning (RG)UpperLeft (BA)LowerRight")) EmissiveRegion = new Vector4(EmissiveUVs.R, EmissiveUVs.G, EmissiveUVs.B, EmissiveUVs.A); - if ((Parameters.TryGetSwitch(out var swizzleRoughnessToGreen, "SwizzleRoughnessToGreen") && swizzleRoughnessToGreen) || - Parameters.Textures.ContainsKey("SRM")) + if ((Parameters.TryGetSwitch(out var swizzleRoughnessToGreen, "SwizzleRoughnessToGreen") && swizzleRoughnessToGreen) || Parameters.Textures.ContainsKey("SRM")) { foreach (var specMask in SpecularMasks) { - specMask.SwizzleMask = new [] + if (AssetPool.Get().TryGet(specMask, out var bitmap)) { - (int) PixelFormat.Red, - (int) PixelFormat.Blue, - (int) PixelFormat.Green, - (int) PixelFormat.Alpha - }; - specMask.Swizzle(); + bitmap.SwizzleMask = + [ + (int) PixelFormat.Red, + (int) PixelFormat.Blue, + (int) PixelFormat.Green, + (int) PixelFormat.Alpha + ]; + } } } } } } - /// just the cache object + public void Setup(bool broken) + { + _handle = GL.CreateProgram(); + + // if (broken) + // { + // for (var i = 0; i < 1; i++) + // { + // Diffuse[i].Setup(); + // Normals[i].Setup(); + // } + // } + // else + // { + // foreach (var diffuse in Diffuse) diffuse.Setup(); + // foreach (var normal in Normals) normal.Setup(); + // foreach (var specMask in SpecularMasks) specMask.Setup(); + // foreach (var emissive in Emissive) emissive.Setup(); + // } + } + /// number of item in the array /// has at least 1 clearly defined texture, else will go straight to fallback /// list of texture parameter names by uv channel /// fallback texture name to use if no top texture found /// if no top texture, no fallback texture, then use the first texture found - private Texture[] FillTextures(Options options, int uvCount, bool top, string[][] triggers, string fallback, bool first = false) + private FGuid[] FillTextures(int uvCount, bool top, string[][] triggers, string fallback, bool first = false) { UTexture original; - Texture transformed; var fix = fallback == CMaterialParams2.FallbackSpecularMasks; - var textures = new Texture[uvCount]; + var textures = new FGuid[uvCount]; if (top) { for (int i = 0; i < textures.Length; i++) { - if (Parameters.TryGetTexture2d(out original, triggers[i]) && options.TryGetTexture(original, fix, out transformed)) - textures[i] = transformed; - else if (i > 0 && textures[i - 1] != null) + if (Parameters.TryGetTexture2d(out original, triggers[i])) + { + AssetPool.Get().AddTexture(original, fix); + textures[i] = original.LightingGuid; + } + else if (i > 0) textures[i] = textures[i - 1]; } } - else if (Parameters.TryGetTexture2d(out original, fallback) && options.TryGetTexture(original, fix, out transformed)) + else if (Parameters.TryGetTexture2d(out original, fallback)) { + AssetPool.Get().AddTexture(original, fix); for (int i = 0; i < textures.Length; i++) - textures[i] = transformed; + textures[i] = original.LightingGuid; } - else if (first && Parameters.TryGetFirstTexture2d(out original) && options.TryGetTexture(original, fix, out transformed)) + else if (first && Parameters.TryGetFirstTexture2d(out original)) { + AssetPool.Get().AddTexture(original, fix); for (int i = 0; i < textures.Length; i++) - textures[i] = transformed; + textures[i] = original.LightingGuid; } return textures; } /// number of item in the array - /// reference array /// list of color parameter names by uv channel /// fallback color to use if no trigger was found - private Vector4[] FillColors(int uvCount, Texture[] textures, string[][] triggers, Vector4 fallback) + private Vector4[] FillColors(int uvCount, string[][] triggers, Vector4 fallback) { var colors = new Vector4[uvCount]; for (int i = 0; i < colors.Length; i++) { - if (textures[i] == null) continue; - if (Parameters.TryGetLinearColor(out var color, triggers[i]) && color is { A: > 0 }) { colors[i] = new Vector4(color.R, color.G, color.B, color.A); @@ -211,38 +231,55 @@ public class Material : IDisposable public void Render(Shader shader) { var unit = 0; + ITexture bitmap; + for (var i = 0; i < Diffuse.Length; i++) { - shader.SetUniform($"uParameters.Diffuse[{i}].Sampler", unit); shader.SetUniform($"uParameters.Diffuse[{i}].Color", DiffuseColor[i]); - Diffuse[i]?.Bind(TextureUnit.Texture0 + unit++); + if (AssetPool.Get().TryGetTexture(Diffuse[i], out bitmap)) + { + shader.SetUniform($"uParameters.Diffuse[{i}].Sampler", unit); + bitmap?.Bind(TextureUnit.Texture0 + unit++); + } } for (var i = 0; i < Normals.Length; i++) { - shader.SetUniform($"uParameters.Normals[{i}].Sampler", unit); - Normals[i]?.Bind(TextureUnit.Texture0 + unit++); + if (AssetPool.Get().TryGetTexture(Normals[i], out bitmap)) + { + shader.SetUniform($"uParameters.Normals[{i}].Sampler", unit); + bitmap?.Bind(TextureUnit.Texture0 + unit++); + } } for (var i = 0; i < SpecularMasks.Length; i++) { - shader.SetUniform($"uParameters.SpecularMasks[{i}].Sampler", unit); - SpecularMasks[i]?.Bind(TextureUnit.Texture0 + unit++); + if (AssetPool.Get().TryGetTexture(SpecularMasks[i], out bitmap)) + { + shader.SetUniform($"uParameters.SpecularMasks[{i}].Sampler", unit); + bitmap?.Bind(TextureUnit.Texture0 + unit++); + } } for (var i = 0; i < Emissive.Length; i++) { - shader.SetUniform($"uParameters.Emissive[{i}].Sampler", unit); - shader.SetUniform($"uParameters.Emissive[{i}].Color", EmissiveColor[i]); - Emissive[i]?.Bind(TextureUnit.Texture0 + unit++); + if (AssetPool.Get().TryGetTexture(Emissive[i], out bitmap)) + { + shader.SetUniform($"uParameters.Emissive[{i}].Color", EmissiveColor[i]); + shader.SetUniform($"uParameters.Emissive[{i}].Sampler", unit); + bitmap?.Bind(TextureUnit.Texture0 + unit++); + } } - Ao.Texture?.Bind(TextureUnit.Texture31); - shader.SetUniform("uParameters.Ao.Sampler", 31); - shader.SetUniform("uParameters.Ao.HasColorBoost", Ao.HasColorBoost); - shader.SetUniform("uParameters.Ao.ColorBoost.Color", Ao.ColorBoost.Color); - shader.SetUniform("uParameters.Ao.ColorBoost.Exponent", Ao.ColorBoost.Exponent); - shader.SetUniform("uParameters.HasAo", HasAo); + if (HasAo && AssetPool.Get().TryGetTexture(Ao.Texture, out bitmap)) + { + bitmap?.Bind(TextureUnit.Texture31); + shader.SetUniform("uParameters.Ao.Sampler", 31); + shader.SetUniform("uParameters.Ao.HasColorBoost", Ao.HasColorBoost); + shader.SetUniform("uParameters.Ao.ColorBoost.Color", Ao.ColorBoost.Color); + shader.SetUniform("uParameters.Ao.ColorBoost.Exponent", Ao.ColorBoost.Exponent); + shader.SetUniform("uParameters.HasAo", HasAo); + } shader.SetUniform("uParameters.EmissiveRegion", EmissiveRegion); shader.SetUniform("uParameters.RoughnessMin", RoughnessMin); @@ -311,7 +348,7 @@ public class Material : IDisposable } } - public bool ImGuiTextures(Dictionary icons, UModel model) + public bool ImGuiTextures(Dictionary icons, UModel model) { if (ImGui.BeginTable("material_textures", 2)) { @@ -340,21 +377,24 @@ public class Material : IDisposable ImGui.EndTable(); } - var texture = GetSelectedTexture() ?? icons["noimage"]; + if (GetSelectedTexture() is not { } g || !AssetPool.Get().TryGetTexture(g, out var texture)) + { + texture = icons["noimage"]; + } ImGui.Image(texture.GetPointer(), new Vector2(ImGui.GetContentRegionAvail().X - ImGui.GetScrollX()), Vector2.Zero, Vector2.One); return ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left); } - public Texture GetSelectedTexture() + public FGuid? GetSelectedTexture() { return SelectedTexture switch { 0 when Diffuse.Length > 0 => Diffuse[SelectedChannel], 1 when Normals.Length > 0 => Normals[SelectedChannel], 2 when SpecularMasks.Length > 0 => SpecularMasks[SelectedChannel], - 3 => Ao.Texture, + 3 when HasAo => Ao.Texture, 4 when Emissive.Length > 0 => Emissive[SelectedChannel], _ => null }; @@ -371,30 +411,13 @@ public class Material : IDisposable public void Dispose() { - for (int i = 0; i < Diffuse.Length; i++) - { - Diffuse[i]?.Dispose(); - } - for (int i = 0; i < Normals.Length; i++) - { - Normals[i]?.Dispose(); - } - for (int i = 0; i < SpecularMasks.Length; i++) - { - SpecularMasks[i]?.Dispose(); - } - for (int i = 0; i < Emissive.Length; i++) - { - Emissive[i]?.Dispose(); - } - Ao.Texture?.Dispose(); GL.DeleteProgram(_handle); } } public struct AoParams { - public Texture Texture; + public FGuid Texture; public Boost ColorBoost; public bool HasColorBoost; diff --git a/FModel/Views/Snooper/Shading/Texture.cs b/FModel/Views/Snooper/Shading/Texture.cs deleted file mode 100644 index c425c5ee..00000000 --- a/FModel/Views/Snooper/Shading/Texture.cs +++ /dev/null @@ -1,316 +0,0 @@ -using System; -using System.Numerics; -using System.Windows; -using CUE4Parse_Conversion.Textures; -using CUE4Parse.UE4.Assets.Exports.Texture; -using CUE4Parse.UE4.Objects.Core.Math; -using CUE4Parse.UE4.Objects.Core.Misc; -using ImGuiNET; -using OpenTK.Graphics.OpenGL4; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using SkiaSharp; - -namespace FModel.Views.Snooper.Shading; - -public class Texture : IDisposable -{ - private readonly int _handle; - private readonly TextureType _type; - private readonly TextureTarget _target; - - public readonly string Type; - public readonly FGuid Guid; - public readonly string Name; - public readonly string Path; - public readonly EPixelFormat Format; - public readonly uint ImportedWidth; - public readonly uint ImportedHeight; - public int Width; - public int Height; - - private const int DisabledChannel = (int)BlendingFactor.Zero; - private readonly bool[] _values = [true, true, true, true]; - private readonly string[] _labels = ["R", "G", "B", "A"]; - public int[] SwizzleMask = - [ - (int) PixelFormat.Red, - (int) PixelFormat.Green, - (int) PixelFormat.Blue, - (int) PixelFormat.Alpha - ]; - - private Texture(TextureType type) - { - _handle = GL.GenTexture(); - _type = type; - _target = _type switch - { - TextureType.Cubemap => TextureTarget.TextureCubeMap, - TextureType.MsaaFramebuffer => TextureTarget.Texture2DMultisample, - _ => TextureTarget.Texture2D - }; - - Guid = new FGuid(); - } - - public Texture(uint width, uint height) : this(TextureType.MsaaFramebuffer) - { - Width = (int) width; - Height = (int) height; - Bind(TextureUnit.Texture0); - - GL.TexImage2DMultisample(TextureTargetMultisample.Texture2DMultisample, Constants.SAMPLES_COUNT, PixelInternalFormat.Rgb, Width, Height, true); - GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, _target, _handle, 0); - } - - public Texture(int width, int height) : this(TextureType.Framebuffer) - { - Width = width; - Height = height; - Bind(TextureUnit.Texture0); - - GL.TexImage2D(_target, 0, PixelInternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgb, PixelType.UnsignedByte, IntPtr.Zero); - - GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Linear); - GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear); - GL.TexParameter(_target, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge); - GL.TexParameter(_target, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge); - - GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, _target, _handle, 0); - } - - public Texture(SKBitmap bitmap, UTexture texture2D) : this(TextureType.Normal) - { - Type = texture2D.ExportType; - Guid = texture2D.LightingGuid; - Name = texture2D.Name; - Path = texture2D.GetPathName(); - Format = texture2D.Format; - Width = bitmap.Width; - Height = bitmap.Height; - Bind(TextureUnit.Texture0); - - var internalFormat = bitmap.ColorType switch - { - SKColorType.Gray8 => PixelInternalFormat.R8, - _ => texture2D.SRGB ? PixelInternalFormat.Srgb : PixelInternalFormat.Rgb - }; - - var pixelFormat = bitmap.ColorType switch - { - SKColorType.Gray8 => PixelFormat.Red, - SKColorType.Bgra8888 => PixelFormat.Bgra, - _ => PixelFormat.Rgba - }; - - GL.TexImage2D(_target, 0, internalFormat, Width, Height, 0, pixelFormat, PixelType.UnsignedByte, bitmap.Bytes); - GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear); - GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear); - GL.TexParameter(_target, TextureParameterName.TextureBaseLevel, 0); - GL.TexParameter(_target, TextureParameterName.TextureMaxLevel, 8); - - GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); - bitmap.Dispose(); - } - - public Texture(FLinearColor color) : this(TextureType.Normal) - { - Type = "LinearColor"; - Name = color.Hex; - Width = 1; - Height = 1; - Bind(TextureUnit.Texture0); - - GL.TexImage2D(_target, 0, PixelInternalFormat.Rgba, Width, Height, 0, PixelFormat.Rgba, PixelType.Float, ref color); - GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear); - GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear); - GL.TexParameter(_target, TextureParameterName.TextureBaseLevel, 0); - GL.TexParameter(_target, TextureParameterName.TextureMaxLevel, 8); - - GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); - } - - public Texture(string[] textures) : this(TextureType.Cubemap) - { - Bind(TextureUnit.Texture0); - - for (int t = 0; t < textures.Length; t++) - { - ProcessPixels(textures[t], TextureTarget.TextureCubeMapPositiveX + t); - } - - GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear); - GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear); - GL.TexParameter(_target, TextureParameterName.TextureWrapR, (int) TextureWrapMode.ClampToEdge); - GL.TexParameter(_target, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge); - GL.TexParameter(_target, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge); - - GL.GenerateMipmap(GenerateMipmapTarget.TextureCubeMap); - } - - public Texture(string texture) : this(TextureType.Normal) - { - Bind(TextureUnit.Texture0); - - ProcessPixels(texture, _target); - - GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Linear); - GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear); - GL.TexParameter(_target, TextureParameterName.TextureWrapR, (int) TextureWrapMode.ClampToEdge); - GL.TexParameter(_target, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge); - GL.TexParameter(_target, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge); - } - - private void ProcessPixels(string texture, TextureTarget target) - { - var info = Application.GetResourceStream(new Uri($"/FModel;component/Resources/{texture}.png", UriKind.Relative)); - using var img = Image.Load(info.Stream); - Width = img.Width; - Height = img.Height; - GL.TexImage2D(target, 0, PixelInternalFormat.Rgba8, Width, Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero); - img.ProcessPixelRows(accessor => - { - for (int y = 0; y < accessor.Height; y++) - { - GL.TexSubImage2D(target, 0, 0, y, accessor.Width, 1, PixelFormat.Rgba, PixelType.UnsignedByte, accessor.GetRowSpan(y).ToArray()); - } - }); - } - - public void Bind(TextureUnit textureSlot) - { - GL.ActiveTexture(textureSlot); - Bind(_target); - } - - public void Bind(TextureTarget target) - { - GL.BindTexture(target, _handle); - } - - public void Bind() - { - GL.BindTexture(_target, _handle); - } - - public void Swizzle() - { - Bind(); - GL.TexParameter(_target, TextureParameterName.TextureSwizzleRgba, SwizzleMask); - } - - public IntPtr GetPointer() => (IntPtr) _handle; - - public void WindowResized(int width, int height) - { - Width = width; - Height = height; - - Bind(); - switch (_type) - { - case TextureType.MsaaFramebuffer: - GL.TexImage2DMultisample(TextureTargetMultisample.Texture2DMultisample, Constants.SAMPLES_COUNT, PixelInternalFormat.Rgb, Width, Height, true); - GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, _target, _handle, 0); - break; - case TextureType.Framebuffer: - GL.TexImage2D(_target, 0, PixelInternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgb, PixelType.UnsignedByte, IntPtr.Zero); - GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, _target, _handle, 0); - break; - default: - throw new NotSupportedException(); - } - } - - public void Dispose() - { - GL.DeleteTexture(_handle); - } - - private Vector3 _scrolling = new (0.0f, 0.0f, 1.0f); - public void ImGuiTextureInspector() - { - if (ImGui.BeginTable("texture_inspector", 2, ImGuiTableFlags.SizingStretchProp)) - { - SnimGui.NoFramePaddingOnY(() => - { - SnimGui.Layout("Type");ImGui.Text($" : ({Format}) {Name}"); - SnimGui.TooltipCopy("(?) Click to Copy Path", Path); - SnimGui.Layout("Guid");ImGui.Text($" : {Guid.ToString(EGuidFormats.UniqueObjectGuid)}"); - SnimGui.Layout("Size"); - ImGui.Text($" : {Width}x{Height}"); - - SnimGui.Layout("Swizzle"); - for (int c = 0; c < SwizzleMask.Length; c++) - { - if (ImGui.Checkbox(_labels[c], ref _values[c])) - { - Bind(); - GL.TexParameter(_target, TextureParameterName.TextureSwizzleR + c, _values[c] ? SwizzleMask[c] : DisabledChannel); - } - ImGui.SameLine(); - } - - ImGui.EndTable(); - }); - } - - var io = ImGui.GetIO(); - var canvasP0 = ImGui.GetCursorScreenPos(); - var canvasSize = ImGui.GetContentRegionAvail(); - if (canvasSize.X < 50.0f) canvasSize.X = 50.0f; - if (canvasSize.Y < 50.0f) canvasSize.Y = 50.0f; - var canvasP1 = canvasP0 + canvasSize; - var origin = new Vector2(canvasP0.X + _scrolling.X, canvasP0.Y + _scrolling.Y); - var absoluteMiddle = canvasSize / 2.0f; - - ImGui.InvisibleButton("texture_inspector_canvas", canvasSize, ImGuiButtonFlags.MouseButtonLeft); - if (ImGui.IsItemActive() && ImGui.IsMouseDragging(ImGuiMouseButton.Left)) - { - _scrolling.X += io.MouseDelta.X; - _scrolling.Y += io.MouseDelta.Y; - } - else if (ImGui.IsItemHovered() && io.MouseWheel != 0.0f) - { - var zoomFactor = 1.0f + io.MouseWheel * 0.1f; - var mousePosCanvas = io.MousePos - origin; - - _scrolling.X -= (mousePosCanvas.X - absoluteMiddle.X) * (zoomFactor - 1); - _scrolling.Y -= (mousePosCanvas.Y - absoluteMiddle.Y) * (zoomFactor - 1); - _scrolling.Z *= zoomFactor; - origin = new Vector2(canvasP0.X + _scrolling.X, canvasP0.Y + _scrolling.Y); - } - - var drawList = ImGui.GetWindowDrawList(); - drawList.AddRectFilled(canvasP0, canvasP1, 0xFF242424); - drawList.PushClipRect(canvasP0, canvasP1, true); - { - var sensitivity = _scrolling.Z * 25.0f; - for (float x = _scrolling.X % sensitivity; x < canvasSize.X; x += sensitivity) - drawList.AddLine(canvasP0 with { X = canvasP0.X + x }, canvasP1 with { X = canvasP0.X + x }, 0x28C8C8C8); - for (float y = _scrolling.Y % sensitivity; y < canvasSize.Y; y += sensitivity) - drawList.AddLine(canvasP0 with { Y = canvasP0.Y + y }, canvasP1 with { Y = canvasP0.Y + y }, 0x28C8C8C8); - } - drawList.PopClipRect(); - - drawList.PushClipRect(canvasP0, canvasP1, true); - { - var relativeMiddle = origin + absoluteMiddle; - var ratio = Math.Min(canvasSize.X / Width, canvasSize.Y / Height) * 0.95f * _scrolling.Z; - var size = new Vector2(Width, Height) * ratio / 2f; - - drawList.AddImage(GetPointer(), relativeMiddle - size, relativeMiddle + size); - drawList.AddRect(relativeMiddle - size, relativeMiddle + size, 0xFFFFFFFF); - } - drawList.PopClipRect(); - } -} - -public enum TextureType -{ - Normal, - Cubemap, - Framebuffer, - MsaaFramebuffer -} diff --git a/FModel/Views/Snooper/Shading/TextureHelper.cs b/FModel/Views/Snooper/Shading/TextureHelper.cs deleted file mode 100644 index 56849b58..00000000 --- a/FModel/Views/Snooper/Shading/TextureHelper.cs +++ /dev/null @@ -1,70 +0,0 @@ -using OpenTK.Graphics.OpenGL4; - -namespace FModel.Views.Snooper.Shading; - -public static class TextureHelper -{ - /// - /// Red : Specular (not used anymore) - /// Green : Metallic - /// Blue : Roughness - /// - public static void FixChannels(string game, Texture texture) - { - switch (game) - { - // R: Whatever (AO / S / E / ...) - // G: Roughness - // B: Metallic - case "GAMEFACE": - case "HK_PROJECT": - case "COSMICSHAKE": - case "PHOENIX": - case "ATOMICHEART": - case "MULTIVERSUS": - case "BODYCAM": - { - texture.SwizzleMask = - [ - (int) PixelFormat.Red, - (int) PixelFormat.Blue, - (int) PixelFormat.Green, - (int) PixelFormat.Alpha - ]; - break; - } - // R: Metallic - // G: Roughness - // B: Whatever (AO / S / E / ...) - case "SHOOTERGAME": - case "DIVINEKNOCKOUT": - case "MOONMAN": - { - texture.SwizzleMask = - [ - (int) PixelFormat.Blue, - (int) PixelFormat.Red, - (int) PixelFormat.Green, - (int) PixelFormat.Alpha - ]; - break; - } - // R: Roughness - // G: Metallic - // B: Whatever (AO / S / E / ...) - case "CCFF7R": - case "PJ033": - { - texture.SwizzleMask = - [ - (int) PixelFormat.Blue, - (int) PixelFormat.Green, - (int) PixelFormat.Red, - (int) PixelFormat.Alpha - ]; - break; - } - } - texture.Swizzle(); - } -} diff --git a/FModel/Views/Snooper/SnimGui.cs b/FModel/Views/Snooper/SnimGui.cs index c8cf6f40..779543db 100644 --- a/FModel/Views/Snooper/SnimGui.cs +++ b/FModel/Views/Snooper/SnimGui.cs @@ -11,6 +11,7 @@ using FModel.Settings; using FModel.Views.Snooper.Animations; using FModel.Views.Snooper.Models; using FModel.Views.Snooper.Shading; +using FModel.Views.Snooper.Textures; using ImGuizmoNET; using OpenTK.Graphics.OpenGL4; @@ -174,7 +175,7 @@ public class SnimGui if (ImGui.BeginTable("world_details", 2, ImGuiTableFlags.SizingStretchProp)) { var b = false; - var length = s.Renderer.Options.Models.Count; + var length = AssetPool.Get().Models.Count; NoFramePaddingOnY(() => { @@ -184,7 +185,7 @@ public class SnimGui if (ImGui.SmallButton("Save All")) { - foreach (var model in s.Renderer.Options.Models.Values) + foreach (var model in AssetPool.Get().Models.Values) { b |= model.Save(out _, out _); } @@ -239,12 +240,12 @@ public class SnimGui for (int i = 0; i < s.Renderer.Options.Lights.Count; i++) { var light = s.Renderer.Options.Lights[i]; - var id = s.Renderer.Options.TryGetModel(light.Model, out var lightModel) ? lightModel.Name : "None"; + var id = AssetPool.Get().TryGetModel(light.Model, out var lightModel) ? lightModel.Name : "None"; id += $"##{i}"; if (ImGui.TreeNode(id) && ImGui.BeginTable(id, 2)) { - s.Renderer.Options.SelectModel(light.Model); + AssetPool.Get().SelectModel(light.Model); light.ImGuiLight(); ImGui.EndTable(); ImGui.TreePop(); @@ -380,7 +381,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio ImGui.TableHeadersRow(); var i = 0; - foreach ((var guid, var model) in s.Renderer.Options.Models) + foreach ((var guid, var model) in AssetPool.Get().Models) { ImGui.PushID(i); ImGui.TableNextRow(); @@ -397,14 +398,14 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio ImGui.Text(model.UvCount.ToString("D")); ImGui.TableNextColumn(); var doubleClick = false; - if (ImGui.Selectable(model.Name, s.Renderer.Options.SelectedModel == guid, ImGuiSelectableFlags.SpanAllColumns | ImGuiSelectableFlags.AllowDoubleClick)) + if (ImGui.Selectable(model.Name, AssetPool.Get().SelectedModel == guid, ImGuiSelectableFlags.SpanAllColumns | ImGuiSelectableFlags.AllowDoubleClick)) { - s.Renderer.Options.SelectModel(guid); + AssetPool.Get().SelectModel(guid); doubleClick = ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left); } Popup(() => { - s.Renderer.Options.SelectModel(guid); + AssetPool.Get().SelectModel(guid); if (ImGui.MenuItem("Show", null, model.IsVisible)) model.IsVisible = !model.IsVisible; if (ImGui.MenuItem("Wireframe", null, model.ShowWireframe)) model.ShowWireframe = !model.ShowWireframe; if (ImGui.MenuItem("Collisions", null, model.ShowCollisions, model.HasCollisions)) model.ShowCollisions = !model.ShowCollisions; @@ -443,7 +444,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio doubleClick = ImGui.MenuItem("Teleport To"); if (ImGui.MenuItem("Delete")) s.Renderer.Options.RemoveModel(guid); - if (ImGui.MenuItem("Deselect")) s.Renderer.Options.SelectModel(Guid.Empty); + if (ImGui.MenuItem("Deselect")) AssetPool.Get().SelectModel(Guid.Empty); ImGui.Separator(); if (ImGui.MenuItem("Copy Path to Clipboard")) ImGui.SetClipboardText(model.Path); }); @@ -470,8 +471,8 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio { MeshWindow("Sockets", s.Renderer, (icons, selectedModel) => { - var info = new SocketAttachementInfo { Guid = s.Renderer.Options.SelectedModel, Instance = selectedModel.SelectedInstance }; - foreach (var model in s.Renderer.Options.Models.Values) + var info = new SocketAttachementInfo { Guid = AssetPool.Get().SelectedModel, Instance = selectedModel.SelectedInstance }; + foreach (var model in AssetPool.Get().Models.Values) { if (!model.HasSockets || model.IsSelected) continue; if (ImGui.TreeNode($"{model.Name} [{model.Sockets.Count}]")) @@ -511,7 +512,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio NoFramePaddingOnY(() => { Layout("Entity");ImGui.Text($" : ({model.Type}) {model.Name}"); - Layout("Guid");ImGui.Text($" : {s.Renderer.Options.SelectedModel.ToString(EGuidFormats.UniqueObjectGuid)}"); + Layout("Guid");ImGui.Text($" : {AssetPool.Get().SelectedModel.ToString(EGuidFormats.UniqueObjectGuid)}"); if (model is SkeletalModel skeletalModel) { Layout("Skeleton");ImGui.Text($" : {skeletalModel.Skeleton.Name}"); @@ -666,7 +667,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio ImGui.PopStyleVar(); } - private void DrawMaterialInspector(Dictionary icons, UModel model, Section section) + private void DrawMaterialInspector(Dictionary icons, UModel model, Section section) { var material = model.Materials[section.MaterialIndex]; @@ -726,9 +727,13 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio if (!_tiOpen) return; if (ImGui.Begin("Texture Inspector", ref _tiOpen, ImGuiWindowFlags.NoScrollbar)) { - if (s.Renderer.Options.TryGetModel(out var model) && s.Renderer.Options.TryGetSection(model, out var section)) + if (AssetPool.Get().TryGetModel(out var model) && s.Renderer.Options.TryGetSection(model, out var section)) { - (model.Materials[section.MaterialIndex].GetSelectedTexture() ?? s.Renderer.Options.Icons["noimage"]).ImGuiTextureInspector(); + if (model.Materials[section.MaterialIndex].GetSelectedTexture() is not { } g || !AssetPool.Get().TryGetTexture(g, out var texture)) + { + texture = s.Renderer.Options.Icons["noimage"]; + } + texture.ImGuiInspector(); } } ImGui.End(); @@ -741,7 +746,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); if (ImGui.Begin("Skeleton Tree", ref s.Renderer.IsSkeletonTreeOpen, ImGuiWindowFlags.NoScrollbar)) { - if (s.Renderer.Options.TryGetModel(out var model) && model is SkeletalModel skeletalModel) + if (AssetPool.Get().TryGetModel(out var model) && model is SkeletalModel skeletalModel) { skeletalModel.Skeleton.ImGuiBoneBreadcrumb(); if (ImGui.BeginTable("skeleton_tree", 2, ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.RowBg, ImGui.GetContentRegionAvail(), ImGui.GetWindowWidth())) @@ -793,7 +798,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio if (ImGui.IsMouseClicked(ImGuiMouseButton.Right)) { var guid = s.Renderer.Picking.ReadPixel(ImGui.GetMousePos(), ImGui.GetCursorScreenPos(), size); - s.Renderer.Options.SelectModel(guid); + AssetPool.Get().SelectModel(guid); ImGui.SetWindowFocus("Outliner"); ImGui.SetWindowFocus("Details"); } @@ -847,7 +852,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio private void DrawGuizmo(Snooper s) { - var enableGuizmo = s.Renderer.Options.TryGetModel(out var selected) && selected.IsVisible; + var enableGuizmo = AssetPool.Get().TryGetModel(out var selected) && selected.IsVisible; if (enableGuizmo) { var view = s.Renderer.CameraOp.GetViewMatrix(); @@ -903,16 +908,16 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio ImGui.End(); } - private void MeshWindow(string name, Renderer renderer, Action, UModel> content, bool styled = true) + private void MeshWindow(string name, Renderer renderer, Action, UModel> content, bool styled = true) { Window(name, () => { - if (renderer.Options.TryGetModel(out var model)) content(renderer.Options.Icons, model); + if (AssetPool.Get().TryGetModel(out var model)) content(renderer.Options.Icons, model); else NoMeshSelected(); }, styled); } - private void SectionWindow(string name, Renderer renderer, Action, UModel, Section> content, bool styled = true) + private void SectionWindow(string name, Renderer renderer, Action, UModel, Section> content, bool styled = true) { MeshWindow(name, renderer, (icons, model) => { @@ -921,7 +926,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio }, styled); } - private void AnimationWindow(string name, Renderer renderer, Action, TimeTracker, List> content, bool styled = true) + private void AnimationWindow(string name, Renderer renderer, Action, TimeTracker, List> content, bool styled = true) { ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); Window(name, () => content(renderer.Options.Icons, renderer.Options.Tracker, renderer.Options.Animations), styled); diff --git a/FModel/Views/Snooper/Snooper.cs b/FModel/Views/Snooper/Snooper.cs index 5784e51c..7ac7f4f6 100644 --- a/FModel/Views/Snooper/Snooper.cs +++ b/FModel/Views/Snooper/Snooper.cs @@ -37,7 +37,7 @@ public class Snooper : GameWindow public bool TryLoadExport(CancellationToken cancellationToken, UObject dummy, Lazy export) { Renderer.Load(cancellationToken, dummy, export); - return Renderer.Options.Models.Count > 0; + return ThreadPool.PendingWorkItemCount > 0 || !AssetPool.Get().Queue.IsEmpty; } public unsafe void WindowShouldClose(bool value, bool clear) diff --git a/FModel/Views/Snooper/Textures/AbstractTexture.cs b/FModel/Views/Snooper/Textures/AbstractTexture.cs new file mode 100644 index 00000000..8df50fe1 --- /dev/null +++ b/FModel/Views/Snooper/Textures/AbstractTexture.cs @@ -0,0 +1,141 @@ +using System; +using System.Numerics; +using CUE4Parse.UE4.Objects.Core.Misc; +using ImGuiNET; +using OpenTK.Graphics.OpenGL4; + +namespace FModel.Views.Snooper.Textures; + +public abstract class AbstractTexture : ITexture +{ + public int Handle { get; private set; } + public TextureType Type { get; } + public TextureTarget Target { get; } + public FGuid Guid { get; protected init; } + public string Name { get; protected init; } + public int Width { get; protected set; } + public int Height { get; protected set; } + + public AbstractTexture(TextureType type) + { + Type = type; + Target = Type switch + { + TextureType.Cubemap => TextureTarget.TextureCubeMap, + TextureType.MsaaFramebuffer => TextureTarget.Texture2DMultisample, + _ => TextureTarget.Texture2D + }; + + Guid = new FGuid(); + } + + public virtual void Setup() + { + Handle = GL.GenTexture(); + Bind(TextureUnit.Texture0); + } + + protected virtual void ImGuiInspectorHeader() + { + + } + + public void Bind(TextureUnit unit) + { + GL.ActiveTexture(unit); + Bind(Target); + } + + public void Bind(TextureTarget target) + { + GL.BindTexture(target, Handle); + } + + public void Bind() + { + GL.BindTexture(Target, Handle); + } + + public IntPtr GetPointer() => Handle; + + public void WindowResized(int width, int height) + { + Width = width; + Height = height; + + Bind(); + switch (Type) + { + case TextureType.MsaaFramebuffer: + GL.TexImage2DMultisample(TextureTargetMultisample.Texture2DMultisample, Constants.SAMPLES_COUNT, PixelInternalFormat.Rgb, Width, Height, true); + break; + case TextureType.Framebuffer: + GL.TexImage2D(Target, 0, PixelInternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgb, PixelType.UnsignedByte, IntPtr.Zero); + break; + default: + throw new NotSupportedException(); + } + + GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, Target, Handle, 0); + } + + public void Dispose() + { + GL.DeleteTexture(Handle); + } + + private Vector3 _scrolling = new (0.0f, 0.0f, 1.0f); + public void ImGuiInspector() + { + ImGuiInspectorHeader(); + + var io = ImGui.GetIO(); + var canvasP0 = ImGui.GetCursorScreenPos(); + var canvasSize = ImGui.GetContentRegionAvail(); + if (canvasSize.X < 50.0f) canvasSize.X = 50.0f; + if (canvasSize.Y < 50.0f) canvasSize.Y = 50.0f; + var canvasP1 = canvasP0 + canvasSize; + var origin = new Vector2(canvasP0.X + _scrolling.X, canvasP0.Y + _scrolling.Y); + var absoluteMiddle = canvasSize / 2.0f; + + ImGui.InvisibleButton("texture_inspector_canvas", canvasSize, ImGuiButtonFlags.MouseButtonLeft); + if (ImGui.IsItemActive() && ImGui.IsMouseDragging(ImGuiMouseButton.Left)) + { + _scrolling.X += io.MouseDelta.X; + _scrolling.Y += io.MouseDelta.Y; + } + else if (ImGui.IsItemHovered() && io.MouseWheel != 0.0f) + { + var zoomFactor = 1.0f + io.MouseWheel * 0.1f; + var mousePosCanvas = io.MousePos - origin; + + _scrolling.X -= (mousePosCanvas.X - absoluteMiddle.X) * (zoomFactor - 1); + _scrolling.Y -= (mousePosCanvas.Y - absoluteMiddle.Y) * (zoomFactor - 1); + _scrolling.Z *= zoomFactor; + origin = new Vector2(canvasP0.X + _scrolling.X, canvasP0.Y + _scrolling.Y); + } + + var drawList = ImGui.GetWindowDrawList(); + drawList.AddRectFilled(canvasP0, canvasP1, 0xFF242424); + drawList.PushClipRect(canvasP0, canvasP1, true); + { + var sensitivity = _scrolling.Z * 25.0f; + for (float x = _scrolling.X % sensitivity; x < canvasSize.X; x += sensitivity) + drawList.AddLine(canvasP0 with { X = canvasP0.X + x }, canvasP1 with { X = canvasP0.X + x }, 0x28C8C8C8); + for (float y = _scrolling.Y % sensitivity; y < canvasSize.Y; y += sensitivity) + drawList.AddLine(canvasP0 with { Y = canvasP0.Y + y }, canvasP1 with { Y = canvasP0.Y + y }, 0x28C8C8C8); + } + drawList.PopClipRect(); + + drawList.PushClipRect(canvasP0, canvasP1, true); + { + var relativeMiddle = origin + absoluteMiddle; + var ratio = Math.Min(canvasSize.X / Width, canvasSize.Y / Height) * 0.95f * _scrolling.Z; + var size = new Vector2(Width, Height) * ratio / 2f; + + drawList.AddImage(GetPointer(), relativeMiddle - size, relativeMiddle + size); + drawList.AddRect(relativeMiddle - size, relativeMiddle + size, 0xFFFFFFFF); + } + drawList.PopClipRect(); + } +} diff --git a/FModel/Views/Snooper/Textures/ApplicationTexture.cs b/FModel/Views/Snooper/Textures/ApplicationTexture.cs new file mode 100644 index 00000000..6bd9051b --- /dev/null +++ b/FModel/Views/Snooper/Textures/ApplicationTexture.cs @@ -0,0 +1,74 @@ +using System; +using System.Windows; +using OpenTK.Graphics.OpenGL4; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + +namespace FModel.Views.Snooper.Textures; + +public class ApplicationTexture : AbstractTexture +{ + private readonly Image[] _images; + + public ApplicationTexture(string texture) : base(TextureType.Normal) + { + _images = [LoadImage(texture)]; + Width = _images[0].Width; + Height = _images[0].Height; + } + + public ApplicationTexture(string[] textures) : base(TextureType.Cubemap) + { + _images = new Image[textures.Length]; + for (var i = 0; i < _images.Length; i++) + { + _images[i] = LoadImage(textures[i]); + } + + Width = _images[0].Width; + Height = _images[0].Height; + } + + public override void Setup() + { + base.Setup(); + + var target = Type == TextureType.Cubemap ? TextureTarget.TextureCubeMapPositiveX : Target; + GL.TexImage2D(target, 0, PixelInternalFormat.Rgba8, Width, Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero); + for (var i = 0; i < _images.Length; i++) + { + _images[i].ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + GL.TexSubImage2D(target + i, 0, 0, y, accessor.Width, 1, PixelFormat.Rgba, PixelType.UnsignedByte, accessor.GetRowSpan(y).ToArray()); + } + }); + } + + if (Type == TextureType.Cubemap) + { + GL.TexParameter(Target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear); + } + else + { + GL.TexParameter(Target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Linear); + } + + GL.TexParameter(Target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear); + GL.TexParameter(Target, TextureParameterName.TextureWrapR, (int) TextureWrapMode.ClampToEdge); + GL.TexParameter(Target, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge); + GL.TexParameter(Target, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge); + + if (Type == TextureType.Cubemap) + { + GL.GenerateMipmap(GenerateMipmapTarget.TextureCubeMap); + } + } + + private Image LoadImage(string texture) + { + var info = Application.GetResourceStream(new Uri($"/FModel;component/Resources/{texture}.png", UriKind.Relative)); + return Image.Load(info.Stream); + } +} diff --git a/FModel/Views/Snooper/Textures/BitmapTexture.cs b/FModel/Views/Snooper/Textures/BitmapTexture.cs new file mode 100644 index 00000000..fe9c2a1b --- /dev/null +++ b/FModel/Views/Snooper/Textures/BitmapTexture.cs @@ -0,0 +1,161 @@ +using CUE4Parse.UE4.Assets.Exports.Texture; +using CUE4Parse.UE4.Objects.Core.Misc; +using ImGuiNET; +using OpenTK.Graphics.OpenGL4; +using SkiaSharp; + +namespace FModel.Views.Snooper.Textures; + +public class BitmapTexture : AbstractTexture +{ + private readonly byte[] _bytes; + private readonly SKColorType _type; + private readonly bool _isSrgb; + + private const int DisabledChannel = (int)BlendingFactor.Zero; + private readonly bool[] _values = [true, true, true, true]; + private readonly string[] _labels = ["R", "G", "B", "A"]; + public int[] SwizzleMask = + [ + (int) PixelFormat.Red, + (int) PixelFormat.Green, + (int) PixelFormat.Blue, + (int) PixelFormat.Alpha + ]; + + public readonly string Path; + public readonly EPixelFormat Format; + + public BitmapTexture(SKBitmap bitmap, UTexture texture) : base(TextureType.Normal) + { + Guid = texture.LightingGuid; + Name = texture.Name; + Width = bitmap.Width; + Height = bitmap.Height; + + Path = texture.GetPathName(); + Format = texture.Format; + + _bytes = bitmap.Bytes; + _type = bitmap.ColorType; + _isSrgb = texture.SRGB; + + bitmap.Dispose(); + } + + public override void Setup() + { + base.Setup(); + + var internalFormat = _type switch + { + SKColorType.Gray8 => PixelInternalFormat.R8, + _ => _isSrgb ? PixelInternalFormat.Srgb : PixelInternalFormat.Rgb + }; + + var pixelFormat = _type switch + { + SKColorType.Gray8 => PixelFormat.Red, + SKColorType.Bgra8888 => PixelFormat.Bgra, + _ => PixelFormat.Rgba + }; + + GL.TexImage2D(Target, 0, internalFormat, Width, Height, 0, pixelFormat, PixelType.UnsignedByte, _bytes); + GL.TexParameter(Target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear); + GL.TexParameter(Target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear); + GL.TexParameter(Target, TextureParameterName.TextureBaseLevel, 0); + GL.TexParameter(Target, TextureParameterName.TextureMaxLevel, 8); + + GL.TexParameter(Target, TextureParameterName.TextureSwizzleRgba, SwizzleMask); + + GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); + } + + protected override void ImGuiInspectorHeader() + { + base.ImGuiInspectorHeader(); + + if (ImGui.BeginTable("texture_inspector", 2, ImGuiTableFlags.SizingStretchProp)) + { + SnimGui.NoFramePaddingOnY(() => + { + SnimGui.Layout("Type");ImGui.Text($" : ({Format}) {Name}"); + SnimGui.TooltipCopy("(?) Click to Copy Path", Path); + SnimGui.Layout("Guid");ImGui.Text($" : {Guid.ToString(EGuidFormats.UniqueObjectGuid)}"); + SnimGui.Layout("Size"); + ImGui.Text($" : {Width}x{Height}"); + + SnimGui.Layout("Swizzle"); + for (int c = 0; c < SwizzleMask.Length; c++) + { + if (ImGui.Checkbox(_labels[c], ref _values[c])) + { + Bind(); + GL.TexParameter(Target, TextureParameterName.TextureSwizzleR + c, _values[c] ? SwizzleMask[c] : DisabledChannel); + } + ImGui.SameLine(); + } + + ImGui.EndTable(); + }); + } + } + + public void FixChannels(string project) + { + switch (project) + { + // R: Whatever (AO / S / E / ...) + // G: Roughness + // B: Metallic + case "GAMEFACE": + case "HK_PROJECT": + case "COSMICSHAKE": + case "PHOENIX": + case "ATOMICHEART": + case "MULTIVERSUS": + case "BODYCAM": + { + SwizzleMask = + [ + (int) PixelFormat.Red, + (int) PixelFormat.Blue, + (int) PixelFormat.Green, + (int) PixelFormat.Alpha + ]; + break; + } + // R: Metallic + // G: Roughness + // B: Whatever (AO / S / E / ...) + case "SHOOTERGAME": + case "DIVINEKNOCKOUT": + case "MOONMAN": + { + SwizzleMask = + [ + (int) PixelFormat.Blue, + (int) PixelFormat.Red, + (int) PixelFormat.Green, + (int) PixelFormat.Alpha + ]; + break; + } + // R: Roughness + // G: Metallic + // B: Whatever (AO / S / E / ...) + case "CCFF7R": + case "PJ033": + { + SwizzleMask = + [ + (int) PixelFormat.Blue, + (int) PixelFormat.Green, + (int) PixelFormat.Red, + (int) PixelFormat.Alpha + ]; + break; + } + } + } +} diff --git a/FModel/Views/Snooper/Textures/FramebufferTexture.cs b/FModel/Views/Snooper/Textures/FramebufferTexture.cs new file mode 100644 index 00000000..a851272a --- /dev/null +++ b/FModel/Views/Snooper/Textures/FramebufferTexture.cs @@ -0,0 +1,27 @@ +using System; +using OpenTK.Graphics.OpenGL4; + +namespace FModel.Views.Snooper.Textures; + +public class FramebufferTexture : AbstractTexture +{ + public FramebufferTexture(int width, int height) : base(TextureType.Framebuffer) + { + Width = width; + Height = height; + } + + public override void Setup() + { + base.Setup(); + + GL.TexImage2D(Target, 0, PixelInternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgb, PixelType.UnsignedByte, IntPtr.Zero); + + GL.TexParameter(Target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Linear); + GL.TexParameter(Target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear); + GL.TexParameter(Target, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge); + GL.TexParameter(Target, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge); + + GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, Target, Handle, 0); + } +} diff --git a/FModel/Views/Snooper/Textures/ITexture.cs b/FModel/Views/Snooper/Textures/ITexture.cs new file mode 100644 index 00000000..c2bd4589 --- /dev/null +++ b/FModel/Views/Snooper/Textures/ITexture.cs @@ -0,0 +1,31 @@ +using System; +using CUE4Parse.UE4.Objects.Core.Misc; +using FModel.Views.Snooper.Models; +using OpenTK.Graphics.OpenGL4; + +namespace FModel.Views.Snooper.Textures; + +public interface ITexture : IDelayedSetup +{ + public int Handle { get; } + public TextureType Type { get; } + public TextureTarget Target { get; } + + public string Name { get; } + public int Width { get; } + public int Height { get; } + + public void Bind(TextureUnit unit); + public void Bind(TextureTarget target); + public IntPtr GetPointer(); + + public void ImGuiInspector(); +} + +public enum TextureType +{ + Normal, + Cubemap, + Framebuffer, + MsaaFramebuffer +} diff --git a/FModel/Views/Snooper/Textures/LinearColorTexture.cs b/FModel/Views/Snooper/Textures/LinearColorTexture.cs new file mode 100644 index 00000000..5213a6eb --- /dev/null +++ b/FModel/Views/Snooper/Textures/LinearColorTexture.cs @@ -0,0 +1,33 @@ +using CUE4Parse.UE4.Objects.Core.Math; +using CUE4Parse.UE4.Objects.Core.Misc; +using OpenTK.Graphics.OpenGL4; + +namespace FModel.Views.Snooper.Textures; + +public class LinearColorTexture : AbstractTexture +{ + private FLinearColor _color; + + public LinearColorTexture(FLinearColor color) : base(TextureType.Normal) + { + _color = color; + + // Guid = new FGuid(_color.Hex); + Name = _color.Hex; + Width = 1; + Height = 1; + } + + public override void Setup() + { + base.Setup(); + + GL.TexImage2D(Target, 0, PixelInternalFormat.Rgba, Width, Height, 0, PixelFormat.Rgba, PixelType.Float, ref _color); + GL.TexParameter(Target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear); + GL.TexParameter(Target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear); + GL.TexParameter(Target, TextureParameterName.TextureBaseLevel, 0); + GL.TexParameter(Target, TextureParameterName.TextureMaxLevel, 8); + + GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); + } +} diff --git a/FModel/Views/Snooper/Textures/MsaaTexture.cs b/FModel/Views/Snooper/Textures/MsaaTexture.cs new file mode 100644 index 00000000..c936c428 --- /dev/null +++ b/FModel/Views/Snooper/Textures/MsaaTexture.cs @@ -0,0 +1,20 @@ +using OpenTK.Graphics.OpenGL4; + +namespace FModel.Views.Snooper.Textures; + +public class MsaaTexture : AbstractTexture +{ + public MsaaTexture(uint width, uint height) : base(TextureType.MsaaFramebuffer) + { + Width = (int) width; + Height = (int) height; + } + + public override void Setup() + { + base.Setup(); + + GL.TexImage2DMultisample(TextureTargetMultisample.Texture2DMultisample, Constants.SAMPLES_COUNT, PixelInternalFormat.Rgb, Width, Height, true); + GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, Target, Handle, 0); + } +}