From da802e8e89938b77430e283f64890dd1f952b5eb Mon Sep 17 00:00:00 2001 From: Mengzhu Wang Date: Sun, 11 Jan 2026 17:25:52 +0100 Subject: [PATCH 01/10] add referenceManager --- SeeSharp.Blazor/LongSetting.razor | 25 + SeeSharp.ReferenceManager/App.razor | 12 + SeeSharp.ReferenceManager/Imports.cs | 32 + SeeSharp.ReferenceManager/MainLayout.razor | 3 + .../Pages/DocumentationHelper.cs | 71 ++ SeeSharp.ReferenceManager/Pages/Index.razor | 39 + .../Pages/IntegratorSelector.razor | 243 +++++ .../Pages/IntegratorSelector.razor.css | 104 +++ .../Pages/ReferenceRendering.razor | 838 ++++++++++++++++++ .../Pages/ReferenceRendering.razor.css | 159 ++++ SeeSharp.ReferenceManager/Pages/_Host.cshtml | 34 + SeeSharp.ReferenceManager/Program.cs | 27 + .../SeeSharp.ReferenceManager.csproj | 17 + SeeSharp.ReferenceManager/_Imports.razor | 4 + .../appsettings.Development.json | 9 + SeeSharp.ReferenceManager/appsettings.json | 9 + .../wwwroot/css/site.css | 86 ++ 17 files changed, 1712 insertions(+) create mode 100644 SeeSharp.Blazor/LongSetting.razor create mode 100644 SeeSharp.ReferenceManager/App.razor create mode 100644 SeeSharp.ReferenceManager/Imports.cs create mode 100644 SeeSharp.ReferenceManager/MainLayout.razor create mode 100644 SeeSharp.ReferenceManager/Pages/DocumentationHelper.cs create mode 100644 SeeSharp.ReferenceManager/Pages/Index.razor create mode 100644 SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor create mode 100644 SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor.css create mode 100644 SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor create mode 100644 SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.css create mode 100644 SeeSharp.ReferenceManager/Pages/_Host.cshtml create mode 100644 SeeSharp.ReferenceManager/Program.cs create mode 100644 SeeSharp.ReferenceManager/SeeSharp.ReferenceManager.csproj create mode 100644 SeeSharp.ReferenceManager/_Imports.razor create mode 100644 SeeSharp.ReferenceManager/appsettings.Development.json create mode 100644 SeeSharp.ReferenceManager/appsettings.json create mode 100644 SeeSharp.ReferenceManager/wwwroot/css/site.css diff --git a/SeeSharp.Blazor/LongSetting.razor b/SeeSharp.Blazor/LongSetting.razor new file mode 100644 index 00000000..fab53adb --- /dev/null +++ b/SeeSharp.Blazor/LongSetting.razor @@ -0,0 +1,25 @@ +@using Microsoft.AspNetCore.Mvc + +@namespace SeeSharp.Blazor + +@inherits SettingBase + +@{ + base.BuildRenderTree(__builder); +} + +@code { + protected override string Type => "number"; + + protected override long ParseValue(ChangeEventArgs e) + { + if (long.TryParse((string)e.Value, out long result)) + return result; + return Value; + } + + protected override Dictionary CustomAttributes => new() { + { "step", 1 } + }; +} + diff --git a/SeeSharp.ReferenceManager/App.razor b/SeeSharp.ReferenceManager/App.razor new file mode 100644 index 00000000..6fd3ed1b --- /dev/null +++ b/SeeSharp.ReferenceManager/App.razor @@ -0,0 +1,12 @@ + + + + + + + Not found + +

Sorry, there's nothing at this address.

+
+
+
diff --git a/SeeSharp.ReferenceManager/Imports.cs b/SeeSharp.ReferenceManager/Imports.cs new file mode 100644 index 00000000..b6c33e5a --- /dev/null +++ b/SeeSharp.ReferenceManager/Imports.cs @@ -0,0 +1,32 @@ +global using System; +global using System.Collections.Concurrent; +global using System.Collections.Generic; +global using System.Diagnostics; +global using System.IO; +global using System.Linq; +global using System.Numerics; +global using System.Text.Json; +global using System.Text.Json.Serialization; +global using System.Threading; +global using System.Threading.Tasks; + +global using TinyEmbree; +global using SimpleImageIO; + +global using SeeSharp; +global using SeeSharp.Cameras; +global using SeeSharp.Common; +global using SeeSharp.Experiments; +global using SeeSharp.Geometry; +global using SeeSharp.Images; +global using SeeSharp.Integrators; +global using SeeSharp.Integrators.Bidir; +global using SeeSharp.Integrators.Common; +global using SeeSharp.Integrators.Util; +global using SeeSharp.Sampling; +global using SeeSharp.Shading; +global using SeeSharp.Shading.Background; +global using SeeSharp.Shading.Emitters; +global using SeeSharp.Shading.Materials; + +global using SeeSharp.Blazor; diff --git a/SeeSharp.ReferenceManager/MainLayout.razor b/SeeSharp.ReferenceManager/MainLayout.razor new file mode 100644 index 00000000..a5af3489 --- /dev/null +++ b/SeeSharp.ReferenceManager/MainLayout.razor @@ -0,0 +1,3 @@ +@inherits LayoutComponentBase + +
@Body
diff --git a/SeeSharp.ReferenceManager/Pages/DocumentationHelper.cs b/SeeSharp.ReferenceManager/Pages/DocumentationHelper.cs new file mode 100644 index 00000000..ed2449bd --- /dev/null +++ b/SeeSharp.ReferenceManager/Pages/DocumentationHelper.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Xml.Linq; + +namespace SeeSharp.Blazor; + +public static class DocumentationHelper +{ + private static Dictionary _loadedXmlDocumentation = new(); + + public static void LoadXmlDocumentation(Assembly assembly) + { + var assemblyPath = assembly.Location; + var xmlPath = Path.ChangeExtension(assemblyPath, ".xml"); + + if (File.Exists(xmlPath)) + { + try + { + var doc = XDocument.Load(xmlPath); + foreach (var member in doc.Descendants("member")) + { + var name = member.Attribute("name")?.Value; + var summary = member.Element("summary")?.Value.Trim(); + + if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(summary)) + { + string cleanSummary = Regex.Replace(summary, @"\s+", " "); + _loadedXmlDocumentation[name] = cleanSummary; + } + } + } + catch (Exception ex) + { + Console.WriteLine($"[DocHelper] Error loading XML: {ex.Message}"); + } + } + } + + public static string GetSummary(MemberInfo member) + { + if (member == null || member.DeclaringType == null) return ""; + + string prefix = member is PropertyInfo ? "P:" : "F:"; + + Type declaringType = member.DeclaringType; + string typeName = declaringType.FullName; + + if (declaringType.IsGenericType) + { + int bracketIndex = typeName.IndexOf('['); + if (bracketIndex > 0) + { + typeName = typeName.Substring(0, bracketIndex); + } + } + + string key = $"{prefix}{typeName}.{member.Name}"; + + if (_loadedXmlDocumentation.TryGetValue(key, out var summary)) + { + return summary; + } + + return ""; + } +} \ No newline at end of file diff --git a/SeeSharp.ReferenceManager/Pages/Index.razor b/SeeSharp.ReferenceManager/Pages/Index.razor new file mode 100644 index 00000000..60c1eed3 --- /dev/null +++ b/SeeSharp.ReferenceManager/Pages/Index.razor @@ -0,0 +1,39 @@ +@page "/" + +@using System.Reflection +@using System.Text.RegularExpressions + + +
+ +
+ + +@code { + /// Enumerates all .razor components in this folder + public IEnumerable<(string Name, string Url)> GetExperimentPages() + { + var routableComponents = Assembly + .GetExecutingAssembly() + .ExportedTypes + .Where(t => t.IsSubclassOf(typeof(ComponentBase))) + .Where(c => c + .GetCustomAttributes(inherit: true) + .OfType() + .Count() > 0); + + foreach (var routableComponent in routableComponents) + { + string name = routableComponent.ToString().Replace("SeeSharp.ReferenceManager.Pages.", string.Empty); + if (name != "Index") + yield return (name, name); + } + } +} diff --git a/SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor b/SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor new file mode 100644 index 00000000..52b77f29 --- /dev/null +++ b/SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor @@ -0,0 +1,243 @@ +@using SeeSharp.Experiments +@using SeeSharp +@using SeeSharp.Blazor +@using SeeSharp.Integrators +@using System.Reflection +@using System.Collections.Generic +@using System.ComponentModel + +
+
+ + +
+
+ +
+ @if (CurrentIntegrator != null) + { + var groups = GetParameterGroups(CurrentIntegrator); + + @foreach (var group in groups) + { +
+ + @group.Title + + +
+ @foreach (var prop in group.Properties) + { + @RenderSetting(prop, () => prop.GetValue(CurrentIntegrator), v => prop.SetValue(CurrentIntegrator, v)) + } + @foreach (var field in group.Fields) + { + @RenderSetting(field, () => field.GetValue(CurrentIntegrator), v => field.SetValue(CurrentIntegrator, v)) + } +
+
+ } + } +
+ +@code { + [Parameter] public Scene scene { get; set; } = default!; + + public List addedIntegrators { get; private set; } = new(); + + public Integrator? CurrentIntegrator => addedIntegrators.FirstOrDefault(); + + Type[] integratorTypes = Array.Empty(); + string? selectedIntegrator; + + class ParameterGroup + { + public string Title { get; set; } = ""; + public List Properties { get; set; } = new(); + public List Fields { get; set; } = new(); + public bool HasParameters => Properties.Any() || Fields.Any(); + } + + protected override void OnInitialized() + { + integratorTypes = new Type[] {typeof(PathTracer), typeof(VertexConnectionAndMerging)}; + if (integratorTypes.Length > 0) { + selectedIntegrator = integratorTypes[0].FullName; + ReplaceIntegrator(); + } + DocumentationHelper.LoadXmlDocumentation(typeof(Integrator).Assembly); + } + + void OnSelectionChanged(ChangeEventArgs e) + { + selectedIntegrator = e.Value?.ToString(); + if (!string.IsNullOrEmpty(selectedIntegrator)) + { + ReplaceIntegrator(); + } + } + + void ReplaceIntegrator() + { + if (string.IsNullOrEmpty(selectedIntegrator)) return; + var type = integratorTypes.FirstOrDefault(t => t.FullName == selectedIntegrator); + if (type == null) return; + + addedIntegrators.Clear(); + + var integrator = (Integrator)Activator.CreateInstance(type)!; + addedIntegrators.Add(integrator); + + StateHasChanged(); + } + + List GetParameterGroups(Integrator integrator) + { + var groups = new List(); + var currentType = integrator.GetType(); + + var allProps = GetFilteredProps(currentType); + var allFields = GetFilteredFields(currentType); + + while (currentType != null && currentType != typeof(object)) { + bool IsCurrentDeclared(MemberInfo m) + { + var d = m.DeclaringType; + var cur = currentType; + if (d != null && d.IsGenericType && !d.IsGenericTypeDefinition) d = d.GetGenericTypeDefinition(); + if (cur != null && cur.IsGenericType && !cur.IsGenericTypeDefinition) cur = cur.GetGenericTypeDefinition(); + return d == cur; + } + + string title = FormatClassName(currentType); + + if (currentType == typeof(Integrator)) title = "Global Settings"; + + var group = new ParameterGroup { + Title = title, + Properties = allProps.Where(p => IsCurrentDeclared(p)).ToList(), + Fields = allFields.Where(f => IsCurrentDeclared(f)).ToList() + }; + + if (group.HasParameters) + groups.Add(group); + + currentType = currentType.BaseType; + } + + return groups; + } + + string GetDescription(MemberInfo member) + { + return DocumentationHelper.GetSummary(member) ?? ""; + } + + string FormatClassName(Type t) { + string name = t.Name; + if (name.Contains('`')) name = name.Substring(0, name.IndexOf('`')); + return System.Text.RegularExpressions.Regex.Replace(name, "(\\B[A-Z])", " $1"); + } + + bool IsVisible(MemberInfo m) + { + if (m.Name == "Name" || m.Name == "Disabled") return false; + if (m is PropertyInfo p && (!p.CanRead || !p.CanWrite)) return false; + if (m is FieldInfo f && (f.IsLiteral || f.IsInitOnly)) return false; + + Type t = (m is PropertyInfo pi) ? pi.PropertyType : ((FieldInfo)m).FieldType; + Type underlyingType = Nullable.GetUnderlyingType(t) ?? t; + + return underlyingType == typeof(int) || underlyingType == typeof(long) || + underlyingType == typeof(uint) || underlyingType == typeof(ulong) || + underlyingType == typeof(float) || underlyingType == typeof(double) || + underlyingType == typeof(bool) || underlyingType.IsEnum || IsStruct(underlyingType); + } + + IEnumerable GetFilteredProps(Type type) => + type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(IsVisible); + + IEnumerable GetFilteredFields(Type type) => + type.GetFields(BindingFlags.Public | BindingFlags.Instance).Where(IsVisible); + + bool IsStruct(Type t) => + t.IsValueType && !t.IsPrimitive && !t.IsEnum && t != typeof(string) && t != typeof(decimal) && t != typeof(DateTime) && Nullable.GetUnderlyingType(t) == null; + + RenderFragment RenderSetting(MemberInfo member, Func getter, Action setter) => __builder => + { + string name = member.Name; + string desc = GetDescription(member); + + Type type = (member is PropertyInfo p) ? p.PropertyType : ((FieldInfo)member).FieldType; + Type underlyingType = Nullable.GetUnderlyingType(type) ?? type; + + if (underlyingType == typeof(int) || underlyingType == typeof(uint)) + { + int val = Convert.ToInt32(getter() ?? 0); + + + } + else if (underlyingType == typeof(long) || underlyingType == typeof(ulong)) + { + long val = Convert.ToInt64(getter() ?? 0); + + + } + else if (underlyingType == typeof(float) || underlyingType == typeof(double)) + { + float val = Convert.ToSingle(getter() ?? 0.0f); + + + } + else if (underlyingType == typeof(bool)) + { + bool val = Convert.ToBoolean(getter() ?? false); + + + } + }; + + public void TriggerReset() + { + if (CurrentIntegrator != null) + { + var type = CurrentIntegrator.GetType(); + var defaultInstance = Activator.CreateInstance(type); + + if (defaultInstance != null) + { + foreach (var prop in GetFilteredProps(type)) { + prop.SetValue(CurrentIntegrator, prop.GetValue(defaultInstance)); + } + foreach (var field in GetFilteredFields(type)) { + field.SetValue(CurrentIntegrator, field.GetValue(defaultInstance)); + } + StateHasChanged(); + } + } + } + + public async Task TriggerApply() + { + await Task.Yield(); + StateHasChanged(); + } +} \ No newline at end of file diff --git a/SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor.css b/SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor.css new file mode 100644 index 00000000..575c6010 --- /dev/null +++ b/SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor.css @@ -0,0 +1,104 @@ +.integrator-selector { + display: flex; + flex-direction: column; + gap: 0.4em; + margin-bottom: 10px; +} + +.integrator-container { + display: flex; + flex-direction: column; + gap: 0.5em; +} + +.integrator-header { + display: flex; + align-items: center; + padding: 4px 0; + cursor: pointer; + user-select: none; + background: transparent; + border: none; +} + +.integrator-content { + border: 1px solid #e0e0e0; + border-radius: 4px; + padding: 10px; + margin-top: 4px; +} + +summary { + cursor: pointer; + font-weight: 600; + font-size: 0.85em; + color: #000; + outline: none; + display: flex; + align-items: center; + padding: 4px 0; +} +summary::before { + content: '▶'; + font-size: 0.7em; + margin-right: 6px; + display: inline-block; + transition: transform 0.2s; + color: #000; +} +details[open] > summary::before { transform: rotate(90deg); } +summary::-webkit-details-marker { display: none; } + + +details > div { + margin-left: 6px; + padding-left: 12px; + border-left: 2px solid #e0e0e0; + margin-top: 2px; + margin-bottom: 2px; +} +details:hover > div { + border-left-color: #ccc; +} + + +.struct-details { + width: 100%; + margin-bottom: 2px; +} + +.struct-summary { + cursor: pointer; + list-style: none; + font-size: samller; + font-weight: normal; + color: #000; + background-color: #f5f5f5; + display: flex; + align-items: center; + transition: background-color 0.2s; +} +.struct-summary:hover { + background-color: #e9e9e9; +} +.struct-summary::before { + content: '▶'; + display: inline-block; + font-size: 0.7em; + margin-right: 8px; + color: #000; + transition: transform 0.2s ease; +} +.struct-details[open] > .struct-summary::before { + transform: rotate(90deg); +} + +.struct-content { + margin-left: 10px; + padding-left: 10px; + padding-top: 5px; + padding-bottom: 5px; + border-left: 2px solid #e0e0e0; +} + + \ No newline at end of file diff --git a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor new file mode 100644 index 00000000..ec2cfcc3 --- /dev/null +++ b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor @@ -0,0 +1,838 @@ +@page "/ReferenceRendering" +@using SeeSharp.Experiments +@using SeeSharp.Integrators +@using SeeSharp.Blazor +@using System.IO +@using SimpleImageIO +@using System.Reflection +@using System.Threading.Tasks +@using System.Text.Json +@using System.Text.Json.Nodes +@using System.Diagnostics +@using System.Linq +@using System.Text.RegularExpressions; + +
+ +
+ +
+ +
+
Parameters
+
+ +
+ +
+ + +
+
+ +
+
Available references
+
+ @if (referenceFiles.Count == 0) { +
No references found
+ } + @foreach (var file in referenceFiles) { +
+
+ @file.Resolution, max depth: @file.MaxDepth + @if (file.FilePath.EndsWith("-partial.exr")) { + (Partial) + } +
+
+ } +
+ +
+
Render new
+ +
+ +
+ x +
+
+ +
+ + +
+ + + +
+ SPP: + + +
+ +
+ +
Render more
+ +
+ + +
+ + + + +
+
+ +
+
+
Viewer
+
+ @if (flip != null) { + + } + else { + Select a reference or render + } +
+
+ +
+
Info
+
+ @if (selectedFile != null) { +
+ RenderTime: @selectedFile.RenderTimeDisplay + StartTime: @selectedFile.StartTimeDisplay + WriteTime: @selectedFile.WriteTimeDisplay + Iterations: @selectedFile.Spp + Version: @selectedFile.Version +
+ +
+
+ Integrator Settings +
+
+ Integrator: + @selectedFile.IntegratorName +
+ @{ + if (!string.IsNullOrEmpty(selectedFile.RawJsonConfig)) + { + var node = System.Text.Json.Nodes.JsonNode.Parse(selectedFile.RawJsonConfig); + if (node != null) + { + var ignoredKeys = new HashSet { + "RenderTime", "NumIterations", "RenderStartTime", "RenderWriteTime", + "SeeSharpVersion" + }; + foreach (var kvp in node.AsObject()) + { + if (ignoredKeys.Contains(kvp.Key)) continue; + string valStr = kvp.Value?.ToString() ?? "null"; + string typeClass = "val"; + if (kvp.Value is System.Text.Json.Nodes.JsonValue jval) { + if (jval.TryGetValue(out _)) typeClass = "bool"; + else if (jval.TryGetValue(out _)) typeClass = "str"; + } +
+ @kvp.Key: + @valStr +
+ } + } + } + } +
+
+
+ } + else { +
No image selected
+ } +
+
+ +
+
+ +@code { + class ReferenceInfo { + public string FilePath { get; set; } = ""; + public string Resolution { get; set; } = ""; + public int MaxDepth { get; set; } + public string IntegratorName { get; set; } = ""; + + public int Spp { get; set; } + public string RenderTimeDisplay { get; set; } = ""; + public string Version { get; set; } = ""; + public string StartTimeDisplay { get; set; } = ""; + public string WriteTimeDisplay { get; set; } = ""; + public string Timestamp { get; set; } = ""; + + public string RawJsonConfig { get; set; } = ""; + } + + SceneSelector sceneSelector; + IntegratorSelector integratorSelector; + Scene scene; + SceneFromFile currentSceneFile; + string currentSceneDirectory = ""; + FlipBook flip; + + List referenceFiles = new(); + ReferenceInfo selectedFile; + + int renderWidth = 512; + int renderHeight = 512; + int renderMaxDepth = 5; + int quickPreviewSpp = 1; + bool isRendering = false; + int additionalSpp = 32; + + void OnSceneLoaded(SceneFromFile sceneFromFile) + { + currentSceneFile = sceneFromFile; + scene = sceneFromFile.MakeScene(); + + if (!string.IsNullOrEmpty(sceneFromFile.Name)) + { + string sceneName = Path.GetFileNameWithoutExtension(sceneFromFile.Name); + var dir = new DirectoryInfo(AppContext.BaseDirectory); + while (dir != null && dir.Name != "SeeSharp") + dir = dir.Parent; + if (dir != null) + currentSceneDirectory = Path.Combine(dir.FullName, "Data", "Scenes", sceneName); + + ScanReferences(currentSceneDirectory); + } + } + + void ScanReferences(string sceneDir) + { + referenceFiles.Clear(); + if (string.IsNullOrEmpty(sceneDir)) return; + + string refDir = Path.Combine(sceneDir, "References"); + if (!Directory.Exists(refDir)) return; + + var exrFiles = Directory.GetFiles(refDir, "*.exr") + .OrderByDescending(f => File.GetLastWriteTime(f)); + + foreach (var f in exrFiles) { + var info = new ReferenceInfo { + FilePath = f, + Resolution = GetResolution(f), + Timestamp = File.GetLastWriteTime(f).ToString("yyyy-MM-dd HH:mm:ss") + }; + + ReadMetadataFromJson(info, f); + + referenceFiles.Add(info); + } + } + + void ReadMetadataFromJson(ReferenceInfo info, string exrPath) + { + string folder = Path.GetDirectoryName(exrPath); + string fileNameNoExt = Path.GetFileNameWithoutExtension(exrPath); + string jsonPath = Path.Combine(folder, $"{fileNameNoExt}.json"); + + if (!File.Exists(jsonPath)) return; + + string jsonContent = File.ReadAllText(jsonPath); + var root = JsonNode.Parse(jsonContent); + if (root == null) return; + + info.Version = root["SeeSharpVersion"]?.ToString() ?? ""; + info.StartTimeDisplay = root["RenderStartTime"]?.ToString() ?? ""; + info.WriteTimeDisplay = root["RenderWriteTime"]?.ToString() ?? ""; + + if (root["RenderTime"] != null) { + double ms = root["RenderTime"].GetValue(); + TimeSpan t = TimeSpan.FromMilliseconds(ms); + info.RenderTimeDisplay = t.TotalMinutes >= 1 ? $"{t.Minutes:D2}m {t.Seconds:D2}s" : $"{ms:F0}ms"; + } + + if (root["NumIterations"] != null) info.Spp = root["NumIterations"].GetValue(); + + var settingsNode = root["Settings"]; + if (settingsNode != null) { + var options = new JsonSerializerOptions { WriteIndented = true }; + info.RawJsonConfig = settingsNode.ToJsonString(options); + info.MaxDepth = settingsNode["MaxDepth"]?.GetValue() ?? 0; + } + + string integratorName = root["Name"]?.GetValue(); + info.IntegratorName = integratorName.Split('.').Last(); + } + + string GetResolution(string filePath) + { + string filename = Path.GetFileNameWithoutExtension(filePath); + var match = Regex.Match(filename, @"Width(\d+)-Height(\d+)", RegexOptions.IgnoreCase); + if (match.Success) return $"{match.Groups[1].Value}x{match.Groups[2].Value}"; + + return "Unknown"; + } + + void ResetConfig() + { + integratorSelector?.TriggerReset(); + } + + async Task ApplyConfig() + { + if (integratorSelector == null) return; + + string folder = Path.GetDirectoryName(selectedFile?.FilePath) ?? currentSceneDirectory ?? ""; + if (!folder.EndsWith("References")) { + string refDir = Path.Combine(folder, "References"); + if (Directory.Exists(refDir)) + folder = refDir; + } + string configPath = Path.Combine(folder, "Config.json"); + if (File.Exists(configPath)) { + if (LoadIntegratorFromJson(configPath)) + return; + } + } + + bool LoadIntegratorFromJson(string path) { + if (!File.Exists(path)) return false; + + string json = File.ReadAllText(path); + var root = JsonNode.Parse(json); + + JsonNode settingsNode = root["Settings"]; + + if (settingsNode == null) return false; + + var currentIntegrator = integratorSelector.addedIntegrators.FirstOrDefault(); + if (currentIntegrator == null) return false; + + var options = new JsonSerializerOptions { IncludeFields = true, PropertyNameCaseInsensitive = true }; + var loaded = JsonSerializer.Deserialize(settingsNode, currentIntegrator.GetType(), options); + + CopyProperties(currentIntegrator, loaded); + StateHasChanged(); + return true; + } + + void CopyProperties(object target, object source) { + if (target == null || source == null || target.GetType() != source.GetType()) return; + var type = target.GetType(); + + bool IsConfigParam(Type t) { + return t == typeof(string) || t.IsValueType; + } + + foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanRead && p.CanWrite)) { + if (IsConfigParam(prop.PropertyType)) + prop.SetValue(target, prop.GetValue(source)); + } + foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) { + if (IsConfigParam(field.FieldType)) + field.SetValue(target, field.GetValue(source)); + } + } + + void SelectReference(ReferenceInfo file) + { + selectedFile = file; + + string folder = Path.GetDirectoryName(file.FilePath); + string configPath = Path.Combine(folder, "Config.json"); + int configTargetSpp = 0; + + if (File.Exists(configPath)) { + var root = JsonNode.Parse(File.ReadAllText(configPath)); + var settings = root["Settings"]; + configTargetSpp = settings?["TotalSpp"]?.GetValue() + ?? settings?["NumIterations"]?.GetValue() + ?? 0; + } + + bool isPartial = file.FilePath.EndsWith("-partial.exr"); + if (isPartial && configTargetSpp > file.Spp) { + additionalSpp = configTargetSpp - file.Spp; + } + + if (File.Exists(file.FilePath)) { + UpdateViewerFromFile(file.FilePath); + } + } + + void UpdateViewerFromFile(string path) + { + flip = null; + StateHasChanged(); + + if (!File.Exists(path)) return; + var layers = SimpleImageIO.Layers.LoadFromFile(path); + var img = layers.Values.OfType().FirstOrDefault(); + + if (img != null) + UpdatePreviewFromMemory(img); + } + + async void UpdatePreviewFromMemory(RgbImage image) { + var imgClone = new RgbImage(image.Width, image.Height); + CopyImage(imgClone, image); + flip = null; + StateHasChanged(); + await Task.Delay(1); + var newFlip = new FlipBook(660, 580); + newFlip.Add("", imgClone); + flip = newFlip; + StateHasChanged(); + } + + void CopyImage(RgbImage target, RgbImage source) { + Parallel.For(0, target.Height, y => { + for (int x = 0; x < target.Width; ++x) + target.SetPixel(x, y, source.GetPixel(x, y)); + }); + } + + async Task RenderReference() + { + if (currentSceneFile == null || integratorSelector.addedIntegrators.Count == 0) return; + + isRendering = true; + flip = null; + await InvokeAsync(StateHasChanged); + + var curIntegrator = integratorSelector.addedIntegrators.First(); + + await Task.Run(async () => { + var startTime = DateTime.Now; + var stopwatch = Stopwatch.StartNew(); + + var renderScene = scene; + var renderIntegrator = CloneIntegrator(curIntegrator); + + SetMaxDepth(renderIntegrator, renderMaxDepth); + int targetSpp = GetTargetSpp(renderIntegrator); + + string referencesRoot = Path.Combine(currentSceneDirectory, "References"); + Directory.CreateDirectory(referencesRoot); + + SaveConfig(referencesRoot, renderIntegrator); + + string baseName = $"MaxDepth{renderMaxDepth}-Width{renderWidth}-Height{renderHeight}"; + string finalPath = Path.Combine(referencesRoot, baseName + ".exr"); + string partialPath = Path.Combine(referencesRoot, baseName + "-partial.exr"); + + var accumulator = new RgbImage(renderWidth, renderHeight); + renderScene.FrameBuffer = new FrameBuffer(renderWidth, renderHeight, ""); + renderScene.Prepare(); + + int currentSpp = 0; + int nextTargetSpp = 1; + + while (currentSpp < targetSpp) + { + int delta = nextTargetSpp - currentSpp; + if (currentSpp + delta > targetSpp) delta = targetSpp - currentSpp; + if (delta <= 0) break; + + SetBatchSpp(renderIntegrator, delta); + + renderScene.FrameBuffer = new FrameBuffer(renderWidth, renderHeight, ""); + renderIntegrator.Render(renderScene); + + var batchImg = new RgbImage(renderWidth, renderHeight); + CopyImage(batchImg, renderScene.FrameBuffer.Image); + batchImg.Scale((float)delta); + AddImage(accumulator, batchImg); + + currentSpp += delta; + + if (currentSpp == nextTargetSpp || currentSpp == targetSpp) { + SetBatchSpp(renderIntegrator, currentSpp); + WriteImageWithJson(accumulator, partialPath, renderIntegrator, currentSpp, renderMaxDepth, stopwatch.ElapsedMilliseconds, startTime); + + if (currentSpp < targetSpp) nextTargetSpp *= 2; + if (nextTargetSpp > targetSpp) nextTargetSpp = targetSpp; + } + } + stopwatch.Stop(); + + SetBatchSpp(renderIntegrator, targetSpp); + WriteImageWithJson(accumulator, finalPath, renderIntegrator, targetSpp, renderMaxDepth, stopwatch.ElapsedMilliseconds, startTime); + + if (File.Exists(partialPath)) File.Delete(partialPath); + string partialJson = Path.ChangeExtension(partialPath, ".json"); + if (File.Exists(partialJson)) File.Delete(partialJson); + + var newRef = new ReferenceInfo { + FilePath = finalPath, + Resolution = $"{renderWidth}x{renderHeight}", + MaxDepth = renderMaxDepth, + Spp = targetSpp, + Version = typeof(SeeSharp.Scene).Assembly.GetName().Version?.ToString() ?? "Unknown", + StartTimeDisplay = startTime.ToString("yyyy-MM-dd HH:mm:ss"), + WriteTimeDisplay = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), + Timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), + IntegratorName = renderIntegrator.GetType().Name + }; + + double ms = stopwatch.ElapsedMilliseconds; + TimeSpan t = TimeSpan.FromMilliseconds(ms); + newRef.RenderTimeDisplay = t.TotalMinutes >= 1 ? $"{t.Minutes:D2}m {t.Seconds:D2}s" : $"{ms:F0}ms"; + + newRef.RawJsonConfig = JsonSerializer.Serialize(renderIntegrator, renderIntegrator.GetType(), new JsonSerializerOptions { WriteIndented = true, IncludeFields = true }); + + await InvokeAsync(() => { + var existing = referenceFiles.FirstOrDefault(r => r.FilePath == finalPath); + if (existing != null) referenceFiles.Remove(existing); + + referenceFiles.Insert(0, newRef); + selectedFile = newRef; + UpdateViewerFromFile(finalPath); + StateHasChanged(); + }); + }); + + isRendering = false; + await InvokeAsync(StateHasChanged); + } + + async Task RenderMoreSamples() + { + if (currentSceneFile == null || selectedFile == null || !File.Exists(selectedFile.FilePath)) return; + if (integratorSelector.addedIntegrators.Count == 0) return; + + isRendering = true; + await InvokeAsync(StateHasChanged); + var curIntegrator = integratorSelector.addedIntegrators.First(); + + await Task.Run(async () => { + long previousRenderTimeMs = 0; + DateTime originalStartTime = DateTime.Now; + uint originalBaseSeed = 0; + Integrator renderIntegrator = null; + + string folder = Path.GetDirectoryName(selectedFile.FilePath); + string fileNameNoExt = Path.GetFileNameWithoutExtension(selectedFile.FilePath); + string jsonPath = Path.Combine(folder, $"{fileNameNoExt}.json"); + + if (File.Exists(jsonPath)) { + try { + var root = JsonNode.Parse(File.ReadAllText(jsonPath)); + if (root != null) { + previousRenderTimeMs = (long)(root["RenderTime"]?.GetValue() ?? 0); + string startStr = root["RenderStartTime"]?.ToString(); + if (!DateTime.TryParse(startStr, out originalStartTime)) + DateTime.TryParseExact(startStr, "dd/M/yyyy HH:mm:ss", null, System.Globalization.DateTimeStyles.None, out originalStartTime); + + var settingsNode = root["Settings"]; + var uiType = integratorSelector.addedIntegrators.FirstOrDefault()?.GetType(); + if (settingsNode != null && uiType != null) { + var options = new JsonSerializerOptions { IncludeFields = true, PropertyNameCaseInsensitive = true }; + renderIntegrator = JsonSerializer.Deserialize(settingsNode, uiType, options) as Integrator; + } + } + } catch {} + } + + if (renderIntegrator == null) { + string configPath = Path.Combine(folder, "Config.json"); + if (File.Exists(configPath)) { + var root = JsonNode.Parse(File.ReadAllText(configPath)); + var settingsNode = root["Settings"]; + var uiType = integratorSelector.addedIntegrators.FirstOrDefault()?.GetType(); + if (settingsNode != null && uiType != null) { + var options = new JsonSerializerOptions { IncludeFields = true, PropertyNameCaseInsensitive = true }; + renderIntegrator = JsonSerializer.Deserialize(settingsNode, uiType, options) as Integrator; + } + } + } + + var stopwatch = Stopwatch.StartNew(); + + var oldImg = new RgbImage(selectedFile.FilePath); + int oldSpp = selectedFile.Spp; + int targetTotalSpp = oldSpp + additionalSpp; + + var accumulator = new RgbImage(oldImg.Width, oldImg.Height); + CopyImage(accumulator, oldImg); + accumulator.Scale((float)oldSpp); + + var renderScene = scene; + + originalBaseSeed = GetBaseSeed(renderIntegrator); + SetBaseSeed(renderIntegrator, originalBaseSeed + (uint)oldSpp); + + SetMaxDepth(renderIntegrator, selectedFile.MaxDepth); + + int currentSpp = oldSpp; + int nextTargetSpp = currentSpp + 1; + + renderScene.FrameBuffer = new FrameBuffer(oldImg.Width, oldImg.Height, ""); + renderScene.Prepare(); + + string currentPath = selectedFile.FilePath; + string finalPath, partialPath; + + if (currentPath.EndsWith("-partial.exr")) { + partialPath = currentPath; + finalPath = currentPath.Replace("-partial.exr", ".exr"); + } else { + finalPath = currentPath; + partialPath = Path.ChangeExtension(currentPath, "-partial.exr"); + } + + while (currentSpp < targetTotalSpp) + { + int delta = nextTargetSpp - currentSpp; + if (currentSpp + delta > targetTotalSpp) delta = targetTotalSpp - currentSpp; + if (delta <= 0) break; + + SetBatchSpp(renderIntegrator, delta); + renderScene.FrameBuffer = new FrameBuffer(oldImg.Width, oldImg.Height, ""); + renderIntegrator.Render(renderScene); + + var batchImg = new RgbImage(oldImg.Width, oldImg.Height); + CopyImage(batchImg, renderScene.FrameBuffer.Image); + batchImg.Scale((float)delta); + AddImage(accumulator, batchImg); + + currentSpp += delta; + long totalTimeNow = previousRenderTimeMs + stopwatch.ElapsedMilliseconds; + + SetBaseSeed(renderIntegrator, originalBaseSeed); + SetBatchSpp(renderIntegrator, currentSpp); + WriteImageWithJson(accumulator, partialPath, renderIntegrator, currentSpp, renderMaxDepth, totalTimeNow, originalStartTime); + + SetBaseSeed(renderIntegrator, originalBaseSeed + (uint)currentSpp); + + if (currentSpp < targetTotalSpp) nextTargetSpp += Math.Max(1, (nextTargetSpp - oldSpp) * 2); + if (nextTargetSpp > targetTotalSpp) nextTargetSpp = targetTotalSpp; + } + stopwatch.Stop(); + + long finalTotalTime = previousRenderTimeMs + stopwatch.ElapsedMilliseconds; + + SetBaseSeed(renderIntegrator, originalBaseSeed); + SetBatchSpp(renderIntegrator, targetTotalSpp); + WriteImageWithJson(accumulator, finalPath, renderIntegrator, targetTotalSpp, renderMaxDepth, finalTotalTime, originalStartTime); + + if (finalPath != partialPath) { + if (File.Exists(partialPath)) File.Delete(partialPath); + string pJson = Path.ChangeExtension(partialPath, ".json"); + if (File.Exists(pJson)) File.Delete(pJson); + } + + var newRef = new ReferenceInfo { FilePath = finalPath }; + ReadMetadataFromJson(newRef, finalPath); + + newRef.Timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + newRef.StartTimeDisplay = originalStartTime.ToString("yyyy-MM-dd HH:mm:ss"); + newRef.WriteTimeDisplay = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + newRef.Spp = targetTotalSpp; + newRef.Resolution = $"{oldImg.Width}x{oldImg.Height}"; + + TimeSpan t = TimeSpan.FromMilliseconds(finalTotalTime); + newRef.RenderTimeDisplay = t.TotalMinutes >= 1 ? $"{t.Minutes:D2}m {t.Seconds:D2}s" : $"{finalTotalTime}ms"; + newRef.RawJsonConfig = JsonSerializer.Serialize(renderIntegrator, renderIntegrator.GetType(), new JsonSerializerOptions { WriteIndented = true, IncludeFields = true }); + + await InvokeAsync(() => { + var existing = referenceFiles.FirstOrDefault(r => r.FilePath == currentPath); + if (existing != null) referenceFiles.Remove(existing); + + var existingFinal = referenceFiles.FirstOrDefault(r => r.FilePath == finalPath); + if (existingFinal != null) referenceFiles.Remove(existingFinal); + + referenceFiles.Insert(0, newRef); + selectedFile = newRef; + + UpdateViewerFromFile(finalPath); + StateHasChanged(); + }); + }); + + isRendering = false; + await InvokeAsync(StateHasChanged); + } + + async Task RenderQuickPreview() + { + if (currentSceneFile == null || integratorSelector.addedIntegrators.Count == 0) return; + + isRendering = true; + selectedFile = null; + flip = null; + + await InvokeAsync(StateHasChanged); + + var curIntegrator = integratorSelector.addedIntegrators.First(); + + await Task.Run(async () => { + var stopwatch = Stopwatch.StartNew(); + var renderScene = currentSceneFile.MakeScene(); + var renderIntegrator = CloneIntegrator(curIntegrator); + + int targetSpp = quickPreviewSpp < 1 ? 1 : quickPreviewSpp; + SetBatchSpp(renderIntegrator, targetSpp); + SetMaxDepth(renderIntegrator, renderMaxDepth); + + renderScene.FrameBuffer = new FrameBuffer(renderWidth, renderHeight, ""); + renderScene.Prepare(); + renderIntegrator.Render(renderScene); + + stopwatch.Stop(); + + var resultImg = new RgbImage(renderWidth, renderHeight); + CopyImage(resultImg, renderScene.FrameBuffer.Image); + + await InvokeAsync(() => { + UpdatePreviewFromMemory(resultImg); + }); + }); + + isRendering = false; + await InvokeAsync(StateHasChanged); + } + + Integrator CloneIntegrator(Integrator source) { + var type = source.GetType(); + var clone = (Integrator)Activator.CreateInstance(type); + bool IsConfigParam(Type t) { + return t == typeof(string) || t.IsValueType; + } + foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanRead && p.CanWrite)) + if (IsConfigParam(prop.PropertyType)) + prop.SetValue(clone, prop.GetValue(source)); + foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) + if (IsConfigParam(field.FieldType)) + field.SetValue(clone, field.GetValue(source)); + return clone; + } + + void SetMaxDepth(Integrator integrator, int depth) { + var type = integrator.GetType(); + var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; + var prop = type.GetProperty("MaxDepth", flags); + if (prop != null && prop.CanWrite) { + prop.SetValue(integrator, depth); + return; + } + var field = type.GetField("MaxDepth", flags); + if (field != null) { + field.SetValue(integrator, depth); + } + } + + int GetTargetSpp(Integrator integrator) { + var type = integrator.GetType(); + var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; + var propSpp = type.GetProperty("TotalSpp", flags); + if (propSpp != null) + return (int)propSpp.GetValue(integrator); + + var fieldSpp = type.GetField("TotalSpp", flags); + if (fieldSpp != null) + return (int)fieldSpp.GetValue(integrator); + + var propIter = type.GetProperty("NumIterations", flags); + if (propIter != null) + return (int)propIter.GetValue(integrator); + + var fieldIter = type.GetField("NumIterations", flags); + if (fieldIter != null) + return (int)fieldIter.GetValue(integrator); + return 16; + } + + void SaveConfig(string folder, Integrator integrator) { + var options = new JsonSerializerOptions { WriteIndented = true, IncludeFields = true }; + var rootNode = new JsonObject(); + rootNode.Add("Name", integrator.GetType().Name); + var settingsNode = JsonSerializer.SerializeToNode(integrator, integrator.GetType(), options); + rootNode.Add("Settings", settingsNode); + File.WriteAllText(Path.Combine(folder, "Config.json"), rootNode.ToJsonString(options)); + } + + void SetBatchSpp(Integrator integrator, int batchCount) { + var type = integrator.GetType(); + var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; + var targetNames = new[] { "TotalSpp", "NumIterations"}; + foreach (var name in targetNames) { + var prop = type.GetProperty(name, flags); + if (prop != null && prop.CanWrite) { + prop.SetValue(integrator, batchCount); + return; + } + var field = type.GetField(name, flags); + if (field != null) { + field.SetValue(integrator, batchCount); + return; + } + } + } + + void AddImage(RgbImage target, RgbImage source) { + Parallel.For(0, target.Height, y => { + for (int x = 0; x < target.Width; ++x) + target.SetPixel(x, y, target.GetPixel(x, y) + source.GetPixel(x, y)); + }); + } + + void WriteImageWithJson(RgbImage image, string filename, Integrator integrator, int spp, int depth, long timeMs, DateTime startTime) + { + var output = new RgbImage(image.Width, image.Height); + CopyImage(output, image); + if (spp > 0) output.Scale(1.0f / spp); + output.WriteToFile(filename); + + var rootNode = new JsonObject(); + rootNode.Add("RenderTime", timeMs); + rootNode.Add("NumIterations", spp); + rootNode.Add("RenderStartTime", startTime.ToString("yyyy-MM-dd HH:mm:ss")); + rootNode.Add("RenderWriteTime", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + rootNode.Add("SeeSharpVersion", typeof(Scene).Assembly.GetName().Version?.ToString() ?? "Unknown"); + rootNode.Add("Name", integrator.GetType().Name); + + var options = new JsonSerializerOptions { WriteIndented = true, IncludeFields = true }; + var settingsNode = JsonSerializer.SerializeToNode(integrator, integrator.GetType(), options); + rootNode.Add("Settings", settingsNode); + + string jsonPath = Path.ChangeExtension(filename, ".json"); + File.WriteAllText(jsonPath, rootNode.ToJsonString(options)); + } + + uint GetBaseSeed(Integrator integrator) { + var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; + var seedProp = integrator.GetType().GetProperty("BaseSeed", flags); + var seedField = integrator.GetType().GetField("BaseSeed", flags); + if (seedProp != null) return (uint)(seedProp.GetValue(integrator) ?? 0u); + if (seedField != null) return (uint)(seedField.GetValue(integrator) ?? 0u); + return 0; + } + + void SetBaseSeed(Integrator integrator, uint seed) { + var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; + var seedProp = integrator.GetType().GetProperty("BaseSeed", flags); + var seedField = integrator.GetType().GetField("BaseSeed", flags); + if (seedProp != null) seedProp.SetValue(integrator, seed); + else if (seedField != null) seedField.SetValue(integrator, seed); + } +} \ No newline at end of file diff --git a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.css b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.css new file mode 100644 index 00000000..3585df77 --- /dev/null +++ b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.css @@ -0,0 +1,159 @@ +.experiment-layout { + display: grid; + grid-template-columns: 350px 350px 1fr; + grid-template-rows: auto 1fr; + gap: 15px; + height: calc(100vh - 80px); + padding: 10px; + box-sizing: border-box; +} + +.layout-header { + grid-column: 1 / -1; + background: transparent; +} + +.col-box { + display: flex; + flex-direction: column; + border: 1px solid #ccc; + background: white; + overflow: hidden; + height: 600px; +} + +.col-title { + padding: 8px 8px; + background: #eee; + border-bottom: 1px solid #ddd; + font-weight: bold; + font-size: 1em; +} + +.col-content { + flex-grow: 1; + overflow-y: auto; + padding: 10px; +} + +.btn-success-anim { + background-color: #28a745 !important; + border-color: #28a745 !important; + color: white !important; + transition: all 0.1s ease; +} + +.ref-item { + padding: 1px; + border: 1px solid #eee; + margin-bottom: 2px; + cursor: pointer; + transition: background 0.2s; +} +.ref-item:hover { background-color: #f5f5f5; } +.ref-item.active { background-color: #e3f2fd; border-color: #2196F3; } + +.render-form { + border-top: 1px solid #ddd; + padding: 8px; +} + +.form-group { + margin-bottom: 5px; + display: flex; + justify-content: space-between; + align-items: center; + font-size: smaller; +} +.form-group input { width: 70px; padding: 2px; } + +.right-col-wrapper { + display: flex; + flex-direction: column; + gap: 15px; + height: auto; + overflow: visible; +} + +.viewer-box { + height: 600px; + display: flex; + flex-direction: column; + border: 1px solid #ccc; + background: white; + overflow: hidden; +} + +.viewer-area { + flex: 1; + width: 100%; + background-color: #2e2e2e; + position: relative; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; +} + +.slider-area { + flex: 0 0 auto; + padding: 5px 10px; + background: #f1f1f1; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + z-index: 5; +} + +.meta-box { + height: auto; + min-height: 100px; + border: 1px solid #ccc; + background: white; + display: flex; + flex-direction: column; + margin-bottom: 10px; +} + +.meta-content { + padding: 5px 5px; + overflow-y: auto; + flex: 1; +} + +.meta-grid { + display: grid; + grid-template-columns: auto 1fr; + column-gap: 10px; + row-gap: 5px; + font-size: smaller; + align-items: center; +} +.meta-grid .label { + font-weight: bold; + text-align: right; +} + +.empty-tip { + color: #ccc; + text-align: center; + margin-top: 40px; + font-style: italic; +} + +.config-tree { + margin-left: 4px; + padding-left: 10px; + border-left: 2px solid #e0e0e0; + display: flex; + flex-direction: column; + gap: 2px; +} + +.config-item { + font-size: smaller; + display: flex; +} + +.config-key { + margin-right: 6px; +} diff --git a/SeeSharp.ReferenceManager/Pages/_Host.cshtml b/SeeSharp.ReferenceManager/Pages/_Host.cshtml new file mode 100644 index 00000000..356b5c71 --- /dev/null +++ b/SeeSharp.ReferenceManager/Pages/_Host.cshtml @@ -0,0 +1,34 @@ +@page "/" +@using Microsoft.AspNetCore.Components.Web +@namespace SeeSharp.ReferenceManager.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers + + + + + + + + + + + @Html.Raw(SeeSharp.Blazor.Scripts.AllScripts) + + + + + +
+ + An error has occurred. This application may no longer respond until reloaded. + + + An unhandled exception has occurred. See browser dev tools for details. + + Reload + 🗙 +
+ + + + diff --git a/SeeSharp.ReferenceManager/Program.cs b/SeeSharp.ReferenceManager/Program.cs new file mode 100644 index 00000000..ffae204c --- /dev/null +++ b/SeeSharp.ReferenceManager/Program.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; + +SceneRegistry.AddSourceRelativeToScript("../Data/Scenes"); + +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddRazorPages(); +builder.Services.AddServerSideBlazor(); + +var app = builder.Build(); + +if (!app.Environment.IsDevelopment()) +{ + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); +} + +app.UseHttpsRedirection(); + +app.UseStaticFiles(); + +app.UseRouting(); + +app.MapBlazorHub(); +app.MapFallbackToPage("/_Host"); + +app.Run(); diff --git a/SeeSharp.ReferenceManager/SeeSharp.ReferenceManager.csproj b/SeeSharp.ReferenceManager/SeeSharp.ReferenceManager.csproj new file mode 100644 index 00000000..ce65c30d --- /dev/null +++ b/SeeSharp.ReferenceManager/SeeSharp.ReferenceManager.csproj @@ -0,0 +1,17 @@ + + + + Exe + net9.0 + enable + + + + + + + + + + + diff --git a/SeeSharp.ReferenceManager/_Imports.razor b/SeeSharp.ReferenceManager/_Imports.razor new file mode 100644 index 00000000..178485ed --- /dev/null +++ b/SeeSharp.ReferenceManager/_Imports.razor @@ -0,0 +1,4 @@ +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.JSInterop +@using SeeSharp.ReferenceManager \ No newline at end of file diff --git a/SeeSharp.ReferenceManager/appsettings.Development.json b/SeeSharp.ReferenceManager/appsettings.Development.json new file mode 100644 index 00000000..770d3e93 --- /dev/null +++ b/SeeSharp.ReferenceManager/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "DetailedErrors": true, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/SeeSharp.ReferenceManager/appsettings.json b/SeeSharp.ReferenceManager/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/SeeSharp.ReferenceManager/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/SeeSharp.ReferenceManager/wwwroot/css/site.css b/SeeSharp.ReferenceManager/wwwroot/css/site.css new file mode 100644 index 00000000..ddc98cca --- /dev/null +++ b/SeeSharp.ReferenceManager/wwwroot/css/site.css @@ -0,0 +1,86 @@ +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 3.5rem; + top: 0.5rem; + } + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + + .blazor-error-boundary::after { + content: "An error has occurred." + } + +html { + font-family: system-ui; +} + +button { + background-color: #a4e1f2; + border-style: none; + /* border-width: 2px; + border-color: #245e6f; */ + color: black; + font-size: medium; + padding-left: 8px; + padding-right: 8px; + padding-bottom: 4px; + padding-top: 4px; +} + button:hover { + background-color: #c9eff4; + cursor: pointer; + } + button:disabled { + background-color: #e5f1f5; + color: #96b4bd; + border-color: #96b4bd; + } + +.experiment-settings { + display: flex; + gap: 0.25em; + flex-direction: column; + float: left; + margin-right: 1em; +} + +.experiment-results { + display: flex; + gap: 10px; + flex-wrap: wrap; + align-items: flex-start; +} + +table { + border-collapse: collapse; +} +td, th { + border: none; + padding: 4px; +} +tr:hover { background-color: #e7f2f1; } +th { + padding-top: 6px; + padding-bottom: 6px; + text-align: left; + background-color: #4a96af; + color: white; + font-size: smaller; +} \ No newline at end of file From 4bad8cb63216dd2968f4c26e4d60356f003a2d6c Mon Sep 17 00:00:00 2001 From: Pascal Grittmann Date: Thu, 15 Jan 2026 14:51:56 +0100 Subject: [PATCH 02/10] add project to sln --- SeeSharp.sln | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/SeeSharp.sln b/SeeSharp.sln index c0161e52..aa5cbbc6 100644 --- a/SeeSharp.sln +++ b/SeeSharp.sln @@ -25,6 +25,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeeSharp.Blazor", "SeeSharp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MaterialTest", "MaterialTest\MaterialTest.csproj", "{5440A486-D6C5-47C8-8B53-C09F1EFE0A7F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeeSharp.ReferenceManager", "SeeSharp.ReferenceManager\SeeSharp.ReferenceManager.csproj", "{EBF9A5F3-AE1D-4795-98CA-6D8B6CDF08A4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -34,9 +36,6 @@ Global Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {3B8C0540-28DA-4071-A3B1-03391D07630F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3B8C0540-28DA-4071-A3B1-03391D07630F}.Debug|Any CPU.Build.0 = Debug|Any CPU @@ -170,5 +169,20 @@ Global {5440A486-D6C5-47C8-8B53-C09F1EFE0A7F}.Release|x64.Build.0 = Release|Any CPU {5440A486-D6C5-47C8-8B53-C09F1EFE0A7F}.Release|x86.ActiveCfg = Release|Any CPU {5440A486-D6C5-47C8-8B53-C09F1EFE0A7F}.Release|x86.Build.0 = Release|Any CPU + {EBF9A5F3-AE1D-4795-98CA-6D8B6CDF08A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EBF9A5F3-AE1D-4795-98CA-6D8B6CDF08A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EBF9A5F3-AE1D-4795-98CA-6D8B6CDF08A4}.Debug|x64.ActiveCfg = Debug|Any CPU + {EBF9A5F3-AE1D-4795-98CA-6D8B6CDF08A4}.Debug|x64.Build.0 = Debug|Any CPU + {EBF9A5F3-AE1D-4795-98CA-6D8B6CDF08A4}.Debug|x86.ActiveCfg = Debug|Any CPU + {EBF9A5F3-AE1D-4795-98CA-6D8B6CDF08A4}.Debug|x86.Build.0 = Debug|Any CPU + {EBF9A5F3-AE1D-4795-98CA-6D8B6CDF08A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EBF9A5F3-AE1D-4795-98CA-6D8B6CDF08A4}.Release|Any CPU.Build.0 = Release|Any CPU + {EBF9A5F3-AE1D-4795-98CA-6D8B6CDF08A4}.Release|x64.ActiveCfg = Release|Any CPU + {EBF9A5F3-AE1D-4795-98CA-6D8B6CDF08A4}.Release|x64.Build.0 = Release|Any CPU + {EBF9A5F3-AE1D-4795-98CA-6D8B6CDF08A4}.Release|x86.ActiveCfg = Release|Any CPU + {EBF9A5F3-AE1D-4795-98CA-6D8B6CDF08A4}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection EndGlobal From 96c891cca1cdc31a9fc1a7e9ed0ad81e113c254d Mon Sep 17 00:00:00 2001 From: Mengzhu Wang Date: Wed, 21 Jan 2026 15:13:40 +0100 Subject: [PATCH 03/10] separate logic from UI --- .../Pages/IntegratorSelector.razor | 213 +---- .../Pages/IntegratorSelector.razor.cs | 88 ++ .../Pages/IntegratorSelector.razor.css | 58 -- .../Pages/IntegratorUtils.cs | 80 ++ .../Pages/ReferenceRendering.razor | 766 ++---------------- .../Pages/ReferenceRendering.razor.cs | 406 ++++++++++ .../Pages/ReferenceRendering.razor.css | 16 - .../Pages/ReferenceUtils.cs | 249 ++++++ .../Pages/RenderSetting.razor | 41 + .../Common/DocumentationReader.cs | 17 +- 10 files changed, 971 insertions(+), 963 deletions(-) create mode 100644 SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor.cs create mode 100644 SeeSharp.ReferenceManager/Pages/IntegratorUtils.cs create mode 100644 SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs create mode 100644 SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs create mode 100644 SeeSharp.ReferenceManager/Pages/RenderSetting.razor rename SeeSharp.ReferenceManager/Pages/DocumentationHelper.cs => SeeSharp/Common/DocumentationReader.cs (78%) diff --git a/SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor b/SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor index 52b77f29..de298dcd 100644 --- a/SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor +++ b/SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor @@ -9,10 +9,10 @@
- @foreach (var type in integratorTypes) { - + }
@@ -33,211 +33,20 @@
@foreach (var prop in group.Properties) { - @RenderSetting(prop, () => prop.GetValue(CurrentIntegrator), v => prop.SetValue(CurrentIntegrator, v)) + } @foreach (var field in group.Fields) { - @RenderSetting(field, () => field.GetValue(CurrentIntegrator), v => field.SetValue(CurrentIntegrator, v)) + }
} } -
- -@code { - [Parameter] public Scene scene { get; set; } = default!; - - public List addedIntegrators { get; private set; } = new(); - - public Integrator? CurrentIntegrator => addedIntegrators.FirstOrDefault(); - - Type[] integratorTypes = Array.Empty(); - string? selectedIntegrator; - - class ParameterGroup - { - public string Title { get; set; } = ""; - public List Properties { get; set; } = new(); - public List Fields { get; set; } = new(); - public bool HasParameters => Properties.Any() || Fields.Any(); - } - - protected override void OnInitialized() - { - integratorTypes = new Type[] {typeof(PathTracer), typeof(VertexConnectionAndMerging)}; - if (integratorTypes.Length > 0) { - selectedIntegrator = integratorTypes[0].FullName; - ReplaceIntegrator(); - } - DocumentationHelper.LoadXmlDocumentation(typeof(Integrator).Assembly); - } - - void OnSelectionChanged(ChangeEventArgs e) - { - selectedIntegrator = e.Value?.ToString(); - if (!string.IsNullOrEmpty(selectedIntegrator)) - { - ReplaceIntegrator(); - } - } - - void ReplaceIntegrator() - { - if (string.IsNullOrEmpty(selectedIntegrator)) return; - var type = integratorTypes.FirstOrDefault(t => t.FullName == selectedIntegrator); - if (type == null) return; - - addedIntegrators.Clear(); - - var integrator = (Integrator)Activator.CreateInstance(type)!; - addedIntegrators.Add(integrator); - - StateHasChanged(); - } - - List GetParameterGroups(Integrator integrator) - { - var groups = new List(); - var currentType = integrator.GetType(); - - var allProps = GetFilteredProps(currentType); - var allFields = GetFilteredFields(currentType); - - while (currentType != null && currentType != typeof(object)) { - bool IsCurrentDeclared(MemberInfo m) - { - var d = m.DeclaringType; - var cur = currentType; - if (d != null && d.IsGenericType && !d.IsGenericTypeDefinition) d = d.GetGenericTypeDefinition(); - if (cur != null && cur.IsGenericType && !cur.IsGenericTypeDefinition) cur = cur.GetGenericTypeDefinition(); - return d == cur; - } - - string title = FormatClassName(currentType); - - if (currentType == typeof(Integrator)) title = "Global Settings"; - - var group = new ParameterGroup { - Title = title, - Properties = allProps.Where(p => IsCurrentDeclared(p)).ToList(), - Fields = allFields.Where(f => IsCurrentDeclared(f)).ToList() - }; - - if (group.HasParameters) - groups.Add(group); - - currentType = currentType.BaseType; - } - - return groups; - } - - string GetDescription(MemberInfo member) - { - return DocumentationHelper.GetSummary(member) ?? ""; - } - - string FormatClassName(Type t) { - string name = t.Name; - if (name.Contains('`')) name = name.Substring(0, name.IndexOf('`')); - return System.Text.RegularExpressions.Regex.Replace(name, "(\\B[A-Z])", " $1"); - } - - bool IsVisible(MemberInfo m) - { - if (m.Name == "Name" || m.Name == "Disabled") return false; - if (m is PropertyInfo p && (!p.CanRead || !p.CanWrite)) return false; - if (m is FieldInfo f && (f.IsLiteral || f.IsInitOnly)) return false; - - Type t = (m is PropertyInfo pi) ? pi.PropertyType : ((FieldInfo)m).FieldType; - Type underlyingType = Nullable.GetUnderlyingType(t) ?? t; - - return underlyingType == typeof(int) || underlyingType == typeof(long) || - underlyingType == typeof(uint) || underlyingType == typeof(ulong) || - underlyingType == typeof(float) || underlyingType == typeof(double) || - underlyingType == typeof(bool) || underlyingType.IsEnum || IsStruct(underlyingType); - } - - IEnumerable GetFilteredProps(Type type) => - type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(IsVisible); - - IEnumerable GetFilteredFields(Type type) => - type.GetFields(BindingFlags.Public | BindingFlags.Instance).Where(IsVisible); - - bool IsStruct(Type t) => - t.IsValueType && !t.IsPrimitive && !t.IsEnum && t != typeof(string) && t != typeof(decimal) && t != typeof(DateTime) && Nullable.GetUnderlyingType(t) == null; - - RenderFragment RenderSetting(MemberInfo member, Func getter, Action setter) => __builder => - { - string name = member.Name; - string desc = GetDescription(member); - - Type type = (member is PropertyInfo p) ? p.PropertyType : ((FieldInfo)member).FieldType; - Type underlyingType = Nullable.GetUnderlyingType(type) ?? type; - - if (underlyingType == typeof(int) || underlyingType == typeof(uint)) - { - int val = Convert.ToInt32(getter() ?? 0); - - - } - else if (underlyingType == typeof(long) || underlyingType == typeof(ulong)) - { - long val = Convert.ToInt64(getter() ?? 0); - - - } - else if (underlyingType == typeof(float) || underlyingType == typeof(double)) - { - float val = Convert.ToSingle(getter() ?? 0.0f); - - - } - else if (underlyingType == typeof(bool)) - { - bool val = Convert.ToBoolean(getter() ?? false); - - - } - }; - - public void TriggerReset() - { - if (CurrentIntegrator != null) - { - var type = CurrentIntegrator.GetType(); - var defaultInstance = Activator.CreateInstance(type); - - if (defaultInstance != null) - { - foreach (var prop in GetFilteredProps(type)) { - prop.SetValue(CurrentIntegrator, prop.GetValue(defaultInstance)); - } - foreach (var field in GetFilteredFields(type)) { - field.SetValue(CurrentIntegrator, field.GetValue(defaultInstance)); - } - StateHasChanged(); - } - } - } - - public async Task TriggerApply() - { - await Task.Yield(); - StateHasChanged(); - } -} \ No newline at end of file + \ No newline at end of file diff --git a/SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor.cs b/SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor.cs new file mode 100644 index 00000000..a44bbae1 --- /dev/null +++ b/SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor.cs @@ -0,0 +1,88 @@ +using Microsoft.AspNetCore.Components; + +namespace SeeSharp.ReferenceManager.Pages; + +public partial class IntegratorSelector : ComponentBase +{ + [Parameter] public Scene scene { get; set; } = default!; + + public List addedIntegrators { get; private set; } = new(); + + public Integrator? CurrentIntegrator => addedIntegrators.FirstOrDefault(); + + Type[] integratorTypes = Array.Empty(); + string? selectedIntegrator; + private string lastIntegrator; + + protected override void OnInitialized() + { + var types = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => type.IsClass && !type.IsAbstract && typeof(Integrator).IsAssignableFrom(type) && + !type.ContainsGenericParameters && !typeof(DebugVisualizer).IsAssignableFrom(type)); + integratorTypes = types.Where(t => !types.Any(other => other.IsSubclassOf(t))).ToArray(); + + if (integratorTypes.Length > 0) { + selectedIntegrator = integratorTypes[0].FullName; + lastIntegrator = selectedIntegrator; + ReplaceIntegrator(); + } + DocumentationReader.LoadXmlDocumentation(typeof(Integrator).Assembly); + } + + void OnSelectionChanged(ChangeEventArgs e) + { + selectedIntegrator = e.Value?.ToString(); + if (!string.IsNullOrEmpty(selectedIntegrator)) + { + lastIntegrator = selectedIntegrator; + ReplaceIntegrator(); + } + } + + void ReplaceIntegrator() + { + if (string.IsNullOrEmpty(selectedIntegrator)) return; + var type = integratorTypes.FirstOrDefault(t => t.FullName == selectedIntegrator); + if (type == null) return; + + addedIntegrators.Clear(); + + var integrator = (Integrator)Activator.CreateInstance(type)!; + addedIntegrators.Add(integrator); + + StateHasChanged(); + } + + protected List GetParameterGroups(Integrator integrator) + => IntegratorUtils.GetParameterGroups(integrator); + + protected string FormatClassName(Type t) => IntegratorUtils.FormatClassName(t); + + public void TriggerReset() + { + selectedIntegrator = lastIntegrator; + ReplaceIntegrator(); + } + + public bool TrySelectIntegrator(string simpleName) + { + var targetType = integratorTypes.FirstOrDefault(t => t.Name == simpleName || t.Name == simpleName + "`1"); + + if (targetType != null && targetType.FullName != selectedIntegrator) + { + selectedIntegrator = targetType.FullName; + ReplaceIntegrator(); + return true; + } + return targetType != null; + } + + public Type? GetIntegratorType(string simpleName) + { + if (string.IsNullOrEmpty(simpleName)) return null; + return integratorTypes.FirstOrDefault(t => + t.Name.Equals(simpleName, StringComparison.OrdinalIgnoreCase) || + t.Name.Equals(simpleName + "`1", StringComparison.OrdinalIgnoreCase)); + } +} \ No newline at end of file diff --git a/SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor.css b/SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor.css index 575c6010..f33a08c6 100644 --- a/SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor.css +++ b/SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor.css @@ -11,23 +11,6 @@ gap: 0.5em; } -.integrator-header { - display: flex; - align-items: center; - padding: 4px 0; - cursor: pointer; - user-select: none; - background: transparent; - border: none; -} - -.integrator-content { - border: 1px solid #e0e0e0; - border-radius: 4px; - padding: 10px; - margin-top: 4px; -} - summary { cursor: pointer; font-weight: 600; @@ -60,45 +43,4 @@ details > div { details:hover > div { border-left-color: #ccc; } - - -.struct-details { - width: 100%; - margin-bottom: 2px; -} - -.struct-summary { - cursor: pointer; - list-style: none; - font-size: samller; - font-weight: normal; - color: #000; - background-color: #f5f5f5; - display: flex; - align-items: center; - transition: background-color 0.2s; -} -.struct-summary:hover { - background-color: #e9e9e9; -} -.struct-summary::before { - content: '▶'; - display: inline-block; - font-size: 0.7em; - margin-right: 8px; - color: #000; - transition: transform 0.2s ease; -} -.struct-details[open] > .struct-summary::before { - transform: rotate(90deg); -} - -.struct-content { - margin-left: 10px; - padding-left: 10px; - padding-top: 5px; - padding-bottom: 5px; - border-left: 2px solid #e0e0e0; -} - \ No newline at end of file diff --git a/SeeSharp.ReferenceManager/Pages/IntegratorUtils.cs b/SeeSharp.ReferenceManager/Pages/IntegratorUtils.cs new file mode 100644 index 00000000..f40c2fb8 --- /dev/null +++ b/SeeSharp.ReferenceManager/Pages/IntegratorUtils.cs @@ -0,0 +1,80 @@ +using System.Reflection; + +namespace SeeSharp.ReferenceManager.Pages; + +public class ParameterGroup { + public string Title { get; set; } = ""; + public List Properties { get; set; } = new(); + public List Fields { get; set; } = new(); + public bool HasParameters => Properties.Any() || Fields.Any(); +} + +public static class IntegratorUtils { + public static List GetParameterGroups(Integrator integrator) + { + var groups = new List(); + var currentType = integrator.GetType(); + + var allProps = GetFilteredProps(currentType); + var allFields = GetFilteredFields(currentType); + + while (currentType != null && currentType != typeof(object)) { + bool IsCurrentDeclared(MemberInfo m) + { + var d = m.DeclaringType; + var cur = currentType; + if (d != null && d.IsGenericType && !d.IsGenericTypeDefinition) d = d.GetGenericTypeDefinition(); + if (cur != null && cur.IsGenericType && !cur.IsGenericTypeDefinition) cur = cur.GetGenericTypeDefinition(); + return d == cur; + } + + string title = FormatClassName(currentType); + + bool isGlobalSettings = (currentType == typeof(Integrator)); + if (isGlobalSettings) title = "Global Settings"; + + var group = new ParameterGroup { + Title = title, + Properties = allProps.Where(p => IsCurrentDeclared(p)) + .Where(p => !isGlobalSettings || (p.Name != "MaxDepth" && p.Name != "MinDepth")).ToList(), + Fields = allFields.Where(f => IsCurrentDeclared(f)).ToList() + }; + + if (group.HasParameters) + groups.Add(group); + + currentType = currentType.BaseType; + } + + return groups; + } + + public static string FormatClassName(Type t) + { + string name = t.Name; + if (name.Contains('`')) name = name.Substring(0, name.IndexOf('`')); + return System.Text.RegularExpressions.Regex.Replace(name, "(\\B[A-Z])", " $1"); + } + + public static bool IsVisible(MemberInfo m) + { + if (m is PropertyInfo p && (!p.CanRead || !p.CanWrite)) return false; + if (m is FieldInfo f && (f.IsLiteral || f.IsInitOnly)) return false; + + Type t = (m is PropertyInfo pi) ? pi.PropertyType : ((FieldInfo)m).FieldType; + Type underlyingType = Nullable.GetUnderlyingType(t) ?? t; + + return underlyingType.IsPrimitive; + } + + public static IEnumerable GetFilteredProps(Type type) => + type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(IsVisible); + + public static IEnumerable GetFilteredFields(Type type) => + type.GetFields(BindingFlags.Public | BindingFlags.Instance).Where(IsVisible); + + public static string GetDescription(MemberInfo member) + { + return DocumentationReader.GetSummary(member) ?? ""; + } +} \ No newline at end of file diff --git a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor index ec2cfcc3..c8bd96e3 100644 --- a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor +++ b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor @@ -26,7 +26,7 @@
- +
@@ -62,6 +62,11 @@ + +
+ + +
@@ -123,10 +128,61 @@ Iterations: @selectedFile.Spp Version: @selectedFile.Version + + @if (isVersionMismatch) + { +
+ Version Differs @ReferenceUtils.CurrentSeeSharpVersion +
+ } + else if (isVersionWarning) + { +
+ Version Differs @ReferenceUtils.CurrentSeeSharpVersion +
+ } + + @if (selectedFile.RenderSteps != null && selectedFile.RenderSteps.Count > 0) + { +
+
+ Render History +
+ @for (int i = 0; i < selectedFile.RenderSteps.Count; i++) + { + var step = selectedFile.RenderSteps[i]; +
+
+ @(i + 1) @step.Type +
+
+ RenderTime: @(step.DurationMs)ms
+ StartTime: @step.StartTime
+ WriteTime: @step.WriteTime +
+
+ } +
+
+
+ }
+ @if (isStructureMismatch) + { +
Parameters Mismatch
+ }
Integrator Settings + +
+ +
+
Integrator: @@ -139,23 +195,42 @@ if (node != null) { var ignoredKeys = new HashSet { - "RenderTime", "NumIterations", "RenderStartTime", "RenderWriteTime", - "SeeSharpVersion" + "RenderTime", "NumIterations", "RenderStartTime", "RenderWriteTime", + "SeeSharpVersion", "RenderSteps", "NaNWarnings" }; foreach (var kvp in node.AsObject()) { - if (ignoredKeys.Contains(kvp.Key)) continue; + bool isExtra = extraKeys.Contains(kvp.Key); + if (!isExtra && ignoredKeys.Contains(kvp.Key)) continue; + string valStr = kvp.Value?.ToString() ?? "null"; - string typeClass = "val"; + string typeClass = "val"; if (kvp.Value is System.Text.Json.Nodes.JsonValue jval) { if (jval.TryGetValue(out _)) typeClass = "bool"; else if (jval.TryGetValue(out _)) typeClass = "str"; } -
- @kvp.Key: + + string styleKey = isExtra ? "color: red; " : ""; + string titleAttr = isExtra ? "Parameter not found in current code" : ""; + +
+ @kvp.Key: @valStr
} + + if (missingKeys.Count > 0) + { +
+
Missing Parameters:
+ @foreach (var key in missingKeys) + { +
+ @key +
+ } +
+ } } } } @@ -170,669 +245,4 @@
-
- -@code { - class ReferenceInfo { - public string FilePath { get; set; } = ""; - public string Resolution { get; set; } = ""; - public int MaxDepth { get; set; } - public string IntegratorName { get; set; } = ""; - - public int Spp { get; set; } - public string RenderTimeDisplay { get; set; } = ""; - public string Version { get; set; } = ""; - public string StartTimeDisplay { get; set; } = ""; - public string WriteTimeDisplay { get; set; } = ""; - public string Timestamp { get; set; } = ""; - - public string RawJsonConfig { get; set; } = ""; - } - - SceneSelector sceneSelector; - IntegratorSelector integratorSelector; - Scene scene; - SceneFromFile currentSceneFile; - string currentSceneDirectory = ""; - FlipBook flip; - - List referenceFiles = new(); - ReferenceInfo selectedFile; - - int renderWidth = 512; - int renderHeight = 512; - int renderMaxDepth = 5; - int quickPreviewSpp = 1; - bool isRendering = false; - int additionalSpp = 32; - - void OnSceneLoaded(SceneFromFile sceneFromFile) - { - currentSceneFile = sceneFromFile; - scene = sceneFromFile.MakeScene(); - - if (!string.IsNullOrEmpty(sceneFromFile.Name)) - { - string sceneName = Path.GetFileNameWithoutExtension(sceneFromFile.Name); - var dir = new DirectoryInfo(AppContext.BaseDirectory); - while (dir != null && dir.Name != "SeeSharp") - dir = dir.Parent; - if (dir != null) - currentSceneDirectory = Path.Combine(dir.FullName, "Data", "Scenes", sceneName); - - ScanReferences(currentSceneDirectory); - } - } - - void ScanReferences(string sceneDir) - { - referenceFiles.Clear(); - if (string.IsNullOrEmpty(sceneDir)) return; - - string refDir = Path.Combine(sceneDir, "References"); - if (!Directory.Exists(refDir)) return; - - var exrFiles = Directory.GetFiles(refDir, "*.exr") - .OrderByDescending(f => File.GetLastWriteTime(f)); - - foreach (var f in exrFiles) { - var info = new ReferenceInfo { - FilePath = f, - Resolution = GetResolution(f), - Timestamp = File.GetLastWriteTime(f).ToString("yyyy-MM-dd HH:mm:ss") - }; - - ReadMetadataFromJson(info, f); - - referenceFiles.Add(info); - } - } - - void ReadMetadataFromJson(ReferenceInfo info, string exrPath) - { - string folder = Path.GetDirectoryName(exrPath); - string fileNameNoExt = Path.GetFileNameWithoutExtension(exrPath); - string jsonPath = Path.Combine(folder, $"{fileNameNoExt}.json"); - - if (!File.Exists(jsonPath)) return; - - string jsonContent = File.ReadAllText(jsonPath); - var root = JsonNode.Parse(jsonContent); - if (root == null) return; - - info.Version = root["SeeSharpVersion"]?.ToString() ?? ""; - info.StartTimeDisplay = root["RenderStartTime"]?.ToString() ?? ""; - info.WriteTimeDisplay = root["RenderWriteTime"]?.ToString() ?? ""; - - if (root["RenderTime"] != null) { - double ms = root["RenderTime"].GetValue(); - TimeSpan t = TimeSpan.FromMilliseconds(ms); - info.RenderTimeDisplay = t.TotalMinutes >= 1 ? $"{t.Minutes:D2}m {t.Seconds:D2}s" : $"{ms:F0}ms"; - } - - if (root["NumIterations"] != null) info.Spp = root["NumIterations"].GetValue(); - - var settingsNode = root["Settings"]; - if (settingsNode != null) { - var options = new JsonSerializerOptions { WriteIndented = true }; - info.RawJsonConfig = settingsNode.ToJsonString(options); - info.MaxDepth = settingsNode["MaxDepth"]?.GetValue() ?? 0; - } - - string integratorName = root["Name"]?.GetValue(); - info.IntegratorName = integratorName.Split('.').Last(); - } - - string GetResolution(string filePath) - { - string filename = Path.GetFileNameWithoutExtension(filePath); - var match = Regex.Match(filename, @"Width(\d+)-Height(\d+)", RegexOptions.IgnoreCase); - if (match.Success) return $"{match.Groups[1].Value}x{match.Groups[2].Value}"; - - return "Unknown"; - } - - void ResetConfig() - { - integratorSelector?.TriggerReset(); - } - - async Task ApplyConfig() - { - if (integratorSelector == null) return; - - string folder = Path.GetDirectoryName(selectedFile?.FilePath) ?? currentSceneDirectory ?? ""; - if (!folder.EndsWith("References")) { - string refDir = Path.Combine(folder, "References"); - if (Directory.Exists(refDir)) - folder = refDir; - } - string configPath = Path.Combine(folder, "Config.json"); - if (File.Exists(configPath)) { - if (LoadIntegratorFromJson(configPath)) - return; - } - } - - bool LoadIntegratorFromJson(string path) { - if (!File.Exists(path)) return false; - - string json = File.ReadAllText(path); - var root = JsonNode.Parse(json); - - JsonNode settingsNode = root["Settings"]; - - if (settingsNode == null) return false; - - var currentIntegrator = integratorSelector.addedIntegrators.FirstOrDefault(); - if (currentIntegrator == null) return false; - - var options = new JsonSerializerOptions { IncludeFields = true, PropertyNameCaseInsensitive = true }; - var loaded = JsonSerializer.Deserialize(settingsNode, currentIntegrator.GetType(), options); - - CopyProperties(currentIntegrator, loaded); - StateHasChanged(); - return true; - } - - void CopyProperties(object target, object source) { - if (target == null || source == null || target.GetType() != source.GetType()) return; - var type = target.GetType(); - - bool IsConfigParam(Type t) { - return t == typeof(string) || t.IsValueType; - } - - foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanRead && p.CanWrite)) { - if (IsConfigParam(prop.PropertyType)) - prop.SetValue(target, prop.GetValue(source)); - } - foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) { - if (IsConfigParam(field.FieldType)) - field.SetValue(target, field.GetValue(source)); - } - } - - void SelectReference(ReferenceInfo file) - { - selectedFile = file; - - string folder = Path.GetDirectoryName(file.FilePath); - string configPath = Path.Combine(folder, "Config.json"); - int configTargetSpp = 0; - - if (File.Exists(configPath)) { - var root = JsonNode.Parse(File.ReadAllText(configPath)); - var settings = root["Settings"]; - configTargetSpp = settings?["TotalSpp"]?.GetValue() - ?? settings?["NumIterations"]?.GetValue() - ?? 0; - } - - bool isPartial = file.FilePath.EndsWith("-partial.exr"); - if (isPartial && configTargetSpp > file.Spp) { - additionalSpp = configTargetSpp - file.Spp; - } - - if (File.Exists(file.FilePath)) { - UpdateViewerFromFile(file.FilePath); - } - } - - void UpdateViewerFromFile(string path) - { - flip = null; - StateHasChanged(); - - if (!File.Exists(path)) return; - var layers = SimpleImageIO.Layers.LoadFromFile(path); - var img = layers.Values.OfType().FirstOrDefault(); - - if (img != null) - UpdatePreviewFromMemory(img); - } - - async void UpdatePreviewFromMemory(RgbImage image) { - var imgClone = new RgbImage(image.Width, image.Height); - CopyImage(imgClone, image); - flip = null; - StateHasChanged(); - await Task.Delay(1); - var newFlip = new FlipBook(660, 580); - newFlip.Add("", imgClone); - flip = newFlip; - StateHasChanged(); - } - - void CopyImage(RgbImage target, RgbImage source) { - Parallel.For(0, target.Height, y => { - for (int x = 0; x < target.Width; ++x) - target.SetPixel(x, y, source.GetPixel(x, y)); - }); - } - - async Task RenderReference() - { - if (currentSceneFile == null || integratorSelector.addedIntegrators.Count == 0) return; - - isRendering = true; - flip = null; - await InvokeAsync(StateHasChanged); - - var curIntegrator = integratorSelector.addedIntegrators.First(); - - await Task.Run(async () => { - var startTime = DateTime.Now; - var stopwatch = Stopwatch.StartNew(); - - var renderScene = scene; - var renderIntegrator = CloneIntegrator(curIntegrator); - - SetMaxDepth(renderIntegrator, renderMaxDepth); - int targetSpp = GetTargetSpp(renderIntegrator); - - string referencesRoot = Path.Combine(currentSceneDirectory, "References"); - Directory.CreateDirectory(referencesRoot); - - SaveConfig(referencesRoot, renderIntegrator); - - string baseName = $"MaxDepth{renderMaxDepth}-Width{renderWidth}-Height{renderHeight}"; - string finalPath = Path.Combine(referencesRoot, baseName + ".exr"); - string partialPath = Path.Combine(referencesRoot, baseName + "-partial.exr"); - - var accumulator = new RgbImage(renderWidth, renderHeight); - renderScene.FrameBuffer = new FrameBuffer(renderWidth, renderHeight, ""); - renderScene.Prepare(); - - int currentSpp = 0; - int nextTargetSpp = 1; - - while (currentSpp < targetSpp) - { - int delta = nextTargetSpp - currentSpp; - if (currentSpp + delta > targetSpp) delta = targetSpp - currentSpp; - if (delta <= 0) break; - - SetBatchSpp(renderIntegrator, delta); - - renderScene.FrameBuffer = new FrameBuffer(renderWidth, renderHeight, ""); - renderIntegrator.Render(renderScene); - - var batchImg = new RgbImage(renderWidth, renderHeight); - CopyImage(batchImg, renderScene.FrameBuffer.Image); - batchImg.Scale((float)delta); - AddImage(accumulator, batchImg); - - currentSpp += delta; - - if (currentSpp == nextTargetSpp || currentSpp == targetSpp) { - SetBatchSpp(renderIntegrator, currentSpp); - WriteImageWithJson(accumulator, partialPath, renderIntegrator, currentSpp, renderMaxDepth, stopwatch.ElapsedMilliseconds, startTime); - - if (currentSpp < targetSpp) nextTargetSpp *= 2; - if (nextTargetSpp > targetSpp) nextTargetSpp = targetSpp; - } - } - stopwatch.Stop(); - - SetBatchSpp(renderIntegrator, targetSpp); - WriteImageWithJson(accumulator, finalPath, renderIntegrator, targetSpp, renderMaxDepth, stopwatch.ElapsedMilliseconds, startTime); - - if (File.Exists(partialPath)) File.Delete(partialPath); - string partialJson = Path.ChangeExtension(partialPath, ".json"); - if (File.Exists(partialJson)) File.Delete(partialJson); - - var newRef = new ReferenceInfo { - FilePath = finalPath, - Resolution = $"{renderWidth}x{renderHeight}", - MaxDepth = renderMaxDepth, - Spp = targetSpp, - Version = typeof(SeeSharp.Scene).Assembly.GetName().Version?.ToString() ?? "Unknown", - StartTimeDisplay = startTime.ToString("yyyy-MM-dd HH:mm:ss"), - WriteTimeDisplay = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), - Timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), - IntegratorName = renderIntegrator.GetType().Name - }; - - double ms = stopwatch.ElapsedMilliseconds; - TimeSpan t = TimeSpan.FromMilliseconds(ms); - newRef.RenderTimeDisplay = t.TotalMinutes >= 1 ? $"{t.Minutes:D2}m {t.Seconds:D2}s" : $"{ms:F0}ms"; - - newRef.RawJsonConfig = JsonSerializer.Serialize(renderIntegrator, renderIntegrator.GetType(), new JsonSerializerOptions { WriteIndented = true, IncludeFields = true }); - - await InvokeAsync(() => { - var existing = referenceFiles.FirstOrDefault(r => r.FilePath == finalPath); - if (existing != null) referenceFiles.Remove(existing); - - referenceFiles.Insert(0, newRef); - selectedFile = newRef; - UpdateViewerFromFile(finalPath); - StateHasChanged(); - }); - }); - - isRendering = false; - await InvokeAsync(StateHasChanged); - } - - async Task RenderMoreSamples() - { - if (currentSceneFile == null || selectedFile == null || !File.Exists(selectedFile.FilePath)) return; - if (integratorSelector.addedIntegrators.Count == 0) return; - - isRendering = true; - await InvokeAsync(StateHasChanged); - var curIntegrator = integratorSelector.addedIntegrators.First(); - - await Task.Run(async () => { - long previousRenderTimeMs = 0; - DateTime originalStartTime = DateTime.Now; - uint originalBaseSeed = 0; - Integrator renderIntegrator = null; - - string folder = Path.GetDirectoryName(selectedFile.FilePath); - string fileNameNoExt = Path.GetFileNameWithoutExtension(selectedFile.FilePath); - string jsonPath = Path.Combine(folder, $"{fileNameNoExt}.json"); - - if (File.Exists(jsonPath)) { - try { - var root = JsonNode.Parse(File.ReadAllText(jsonPath)); - if (root != null) { - previousRenderTimeMs = (long)(root["RenderTime"]?.GetValue() ?? 0); - string startStr = root["RenderStartTime"]?.ToString(); - if (!DateTime.TryParse(startStr, out originalStartTime)) - DateTime.TryParseExact(startStr, "dd/M/yyyy HH:mm:ss", null, System.Globalization.DateTimeStyles.None, out originalStartTime); - - var settingsNode = root["Settings"]; - var uiType = integratorSelector.addedIntegrators.FirstOrDefault()?.GetType(); - if (settingsNode != null && uiType != null) { - var options = new JsonSerializerOptions { IncludeFields = true, PropertyNameCaseInsensitive = true }; - renderIntegrator = JsonSerializer.Deserialize(settingsNode, uiType, options) as Integrator; - } - } - } catch {} - } - - if (renderIntegrator == null) { - string configPath = Path.Combine(folder, "Config.json"); - if (File.Exists(configPath)) { - var root = JsonNode.Parse(File.ReadAllText(configPath)); - var settingsNode = root["Settings"]; - var uiType = integratorSelector.addedIntegrators.FirstOrDefault()?.GetType(); - if (settingsNode != null && uiType != null) { - var options = new JsonSerializerOptions { IncludeFields = true, PropertyNameCaseInsensitive = true }; - renderIntegrator = JsonSerializer.Deserialize(settingsNode, uiType, options) as Integrator; - } - } - } - - var stopwatch = Stopwatch.StartNew(); - - var oldImg = new RgbImage(selectedFile.FilePath); - int oldSpp = selectedFile.Spp; - int targetTotalSpp = oldSpp + additionalSpp; - - var accumulator = new RgbImage(oldImg.Width, oldImg.Height); - CopyImage(accumulator, oldImg); - accumulator.Scale((float)oldSpp); - - var renderScene = scene; - - originalBaseSeed = GetBaseSeed(renderIntegrator); - SetBaseSeed(renderIntegrator, originalBaseSeed + (uint)oldSpp); - - SetMaxDepth(renderIntegrator, selectedFile.MaxDepth); - - int currentSpp = oldSpp; - int nextTargetSpp = currentSpp + 1; - - renderScene.FrameBuffer = new FrameBuffer(oldImg.Width, oldImg.Height, ""); - renderScene.Prepare(); - - string currentPath = selectedFile.FilePath; - string finalPath, partialPath; - - if (currentPath.EndsWith("-partial.exr")) { - partialPath = currentPath; - finalPath = currentPath.Replace("-partial.exr", ".exr"); - } else { - finalPath = currentPath; - partialPath = Path.ChangeExtension(currentPath, "-partial.exr"); - } - - while (currentSpp < targetTotalSpp) - { - int delta = nextTargetSpp - currentSpp; - if (currentSpp + delta > targetTotalSpp) delta = targetTotalSpp - currentSpp; - if (delta <= 0) break; - - SetBatchSpp(renderIntegrator, delta); - renderScene.FrameBuffer = new FrameBuffer(oldImg.Width, oldImg.Height, ""); - renderIntegrator.Render(renderScene); - - var batchImg = new RgbImage(oldImg.Width, oldImg.Height); - CopyImage(batchImg, renderScene.FrameBuffer.Image); - batchImg.Scale((float)delta); - AddImage(accumulator, batchImg); - - currentSpp += delta; - long totalTimeNow = previousRenderTimeMs + stopwatch.ElapsedMilliseconds; - - SetBaseSeed(renderIntegrator, originalBaseSeed); - SetBatchSpp(renderIntegrator, currentSpp); - WriteImageWithJson(accumulator, partialPath, renderIntegrator, currentSpp, renderMaxDepth, totalTimeNow, originalStartTime); - - SetBaseSeed(renderIntegrator, originalBaseSeed + (uint)currentSpp); - - if (currentSpp < targetTotalSpp) nextTargetSpp += Math.Max(1, (nextTargetSpp - oldSpp) * 2); - if (nextTargetSpp > targetTotalSpp) nextTargetSpp = targetTotalSpp; - } - stopwatch.Stop(); - - long finalTotalTime = previousRenderTimeMs + stopwatch.ElapsedMilliseconds; - - SetBaseSeed(renderIntegrator, originalBaseSeed); - SetBatchSpp(renderIntegrator, targetTotalSpp); - WriteImageWithJson(accumulator, finalPath, renderIntegrator, targetTotalSpp, renderMaxDepth, finalTotalTime, originalStartTime); - - if (finalPath != partialPath) { - if (File.Exists(partialPath)) File.Delete(partialPath); - string pJson = Path.ChangeExtension(partialPath, ".json"); - if (File.Exists(pJson)) File.Delete(pJson); - } - - var newRef = new ReferenceInfo { FilePath = finalPath }; - ReadMetadataFromJson(newRef, finalPath); - - newRef.Timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); - newRef.StartTimeDisplay = originalStartTime.ToString("yyyy-MM-dd HH:mm:ss"); - newRef.WriteTimeDisplay = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); - newRef.Spp = targetTotalSpp; - newRef.Resolution = $"{oldImg.Width}x{oldImg.Height}"; - - TimeSpan t = TimeSpan.FromMilliseconds(finalTotalTime); - newRef.RenderTimeDisplay = t.TotalMinutes >= 1 ? $"{t.Minutes:D2}m {t.Seconds:D2}s" : $"{finalTotalTime}ms"; - newRef.RawJsonConfig = JsonSerializer.Serialize(renderIntegrator, renderIntegrator.GetType(), new JsonSerializerOptions { WriteIndented = true, IncludeFields = true }); - - await InvokeAsync(() => { - var existing = referenceFiles.FirstOrDefault(r => r.FilePath == currentPath); - if (existing != null) referenceFiles.Remove(existing); - - var existingFinal = referenceFiles.FirstOrDefault(r => r.FilePath == finalPath); - if (existingFinal != null) referenceFiles.Remove(existingFinal); - - referenceFiles.Insert(0, newRef); - selectedFile = newRef; - - UpdateViewerFromFile(finalPath); - StateHasChanged(); - }); - }); - - isRendering = false; - await InvokeAsync(StateHasChanged); - } - - async Task RenderQuickPreview() - { - if (currentSceneFile == null || integratorSelector.addedIntegrators.Count == 0) return; - - isRendering = true; - selectedFile = null; - flip = null; - - await InvokeAsync(StateHasChanged); - - var curIntegrator = integratorSelector.addedIntegrators.First(); - - await Task.Run(async () => { - var stopwatch = Stopwatch.StartNew(); - var renderScene = currentSceneFile.MakeScene(); - var renderIntegrator = CloneIntegrator(curIntegrator); - - int targetSpp = quickPreviewSpp < 1 ? 1 : quickPreviewSpp; - SetBatchSpp(renderIntegrator, targetSpp); - SetMaxDepth(renderIntegrator, renderMaxDepth); - - renderScene.FrameBuffer = new FrameBuffer(renderWidth, renderHeight, ""); - renderScene.Prepare(); - renderIntegrator.Render(renderScene); - - stopwatch.Stop(); - - var resultImg = new RgbImage(renderWidth, renderHeight); - CopyImage(resultImg, renderScene.FrameBuffer.Image); - - await InvokeAsync(() => { - UpdatePreviewFromMemory(resultImg); - }); - }); - - isRendering = false; - await InvokeAsync(StateHasChanged); - } - - Integrator CloneIntegrator(Integrator source) { - var type = source.GetType(); - var clone = (Integrator)Activator.CreateInstance(type); - bool IsConfigParam(Type t) { - return t == typeof(string) || t.IsValueType; - } - foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanRead && p.CanWrite)) - if (IsConfigParam(prop.PropertyType)) - prop.SetValue(clone, prop.GetValue(source)); - foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) - if (IsConfigParam(field.FieldType)) - field.SetValue(clone, field.GetValue(source)); - return clone; - } - - void SetMaxDepth(Integrator integrator, int depth) { - var type = integrator.GetType(); - var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; - var prop = type.GetProperty("MaxDepth", flags); - if (prop != null && prop.CanWrite) { - prop.SetValue(integrator, depth); - return; - } - var field = type.GetField("MaxDepth", flags); - if (field != null) { - field.SetValue(integrator, depth); - } - } - - int GetTargetSpp(Integrator integrator) { - var type = integrator.GetType(); - var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; - var propSpp = type.GetProperty("TotalSpp", flags); - if (propSpp != null) - return (int)propSpp.GetValue(integrator); - - var fieldSpp = type.GetField("TotalSpp", flags); - if (fieldSpp != null) - return (int)fieldSpp.GetValue(integrator); - - var propIter = type.GetProperty("NumIterations", flags); - if (propIter != null) - return (int)propIter.GetValue(integrator); - - var fieldIter = type.GetField("NumIterations", flags); - if (fieldIter != null) - return (int)fieldIter.GetValue(integrator); - return 16; - } - - void SaveConfig(string folder, Integrator integrator) { - var options = new JsonSerializerOptions { WriteIndented = true, IncludeFields = true }; - var rootNode = new JsonObject(); - rootNode.Add("Name", integrator.GetType().Name); - var settingsNode = JsonSerializer.SerializeToNode(integrator, integrator.GetType(), options); - rootNode.Add("Settings", settingsNode); - File.WriteAllText(Path.Combine(folder, "Config.json"), rootNode.ToJsonString(options)); - } - - void SetBatchSpp(Integrator integrator, int batchCount) { - var type = integrator.GetType(); - var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; - var targetNames = new[] { "TotalSpp", "NumIterations"}; - foreach (var name in targetNames) { - var prop = type.GetProperty(name, flags); - if (prop != null && prop.CanWrite) { - prop.SetValue(integrator, batchCount); - return; - } - var field = type.GetField(name, flags); - if (field != null) { - field.SetValue(integrator, batchCount); - return; - } - } - } - - void AddImage(RgbImage target, RgbImage source) { - Parallel.For(0, target.Height, y => { - for (int x = 0; x < target.Width; ++x) - target.SetPixel(x, y, target.GetPixel(x, y) + source.GetPixel(x, y)); - }); - } - - void WriteImageWithJson(RgbImage image, string filename, Integrator integrator, int spp, int depth, long timeMs, DateTime startTime) - { - var output = new RgbImage(image.Width, image.Height); - CopyImage(output, image); - if (spp > 0) output.Scale(1.0f / spp); - output.WriteToFile(filename); - - var rootNode = new JsonObject(); - rootNode.Add("RenderTime", timeMs); - rootNode.Add("NumIterations", spp); - rootNode.Add("RenderStartTime", startTime.ToString("yyyy-MM-dd HH:mm:ss")); - rootNode.Add("RenderWriteTime", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); - rootNode.Add("SeeSharpVersion", typeof(Scene).Assembly.GetName().Version?.ToString() ?? "Unknown"); - rootNode.Add("Name", integrator.GetType().Name); - - var options = new JsonSerializerOptions { WriteIndented = true, IncludeFields = true }; - var settingsNode = JsonSerializer.SerializeToNode(integrator, integrator.GetType(), options); - rootNode.Add("Settings", settingsNode); - - string jsonPath = Path.ChangeExtension(filename, ".json"); - File.WriteAllText(jsonPath, rootNode.ToJsonString(options)); - } - - uint GetBaseSeed(Integrator integrator) { - var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; - var seedProp = integrator.GetType().GetProperty("BaseSeed", flags); - var seedField = integrator.GetType().GetField("BaseSeed", flags); - if (seedProp != null) return (uint)(seedProp.GetValue(integrator) ?? 0u); - if (seedField != null) return (uint)(seedField.GetValue(integrator) ?? 0u); - return 0; - } - - void SetBaseSeed(Integrator integrator, uint seed) { - var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; - var seedProp = integrator.GetType().GetProperty("BaseSeed", flags); - var seedField = integrator.GetType().GetField("BaseSeed", flags); - if (seedProp != null) seedProp.SetValue(integrator, seed); - else if (seedField != null) seedField.SetValue(integrator, seed); - } -} \ No newline at end of file +
\ No newline at end of file diff --git a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs new file mode 100644 index 00000000..bc10160d --- /dev/null +++ b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs @@ -0,0 +1,406 @@ +using System.Text.Json.Nodes; + +namespace SeeSharp.ReferenceManager.Pages; + +public partial class ReferenceRendering +{ + private SceneSelector sceneSelector; + private IntegratorSelector integratorSelector; + private Scene scene; + private SceneFromFile currentSceneFile; + private string currentSceneDirectory = ""; + private FlipBook flip; + + private List referenceFiles = new(); + private ReferenceInfo selectedFile; + + private int renderWidth = 512; + private int renderHeight = 512; + private int renderMaxDepth = 5; + private int renderMinDepth = 1; + private int quickPreviewSpp = 1; + private bool isRendering = false; + private int additionalSpp = 32; + private bool isStructureMismatch = false; + private bool isVersionMismatch = false; + private bool isVersionWarning = false; + private HashSet extraKeys = new(); + private HashSet missingKeys = new(); + + private void OnSceneLoaded(SceneFromFile sceneFromFile) + { + currentSceneFile = sceneFromFile; + scene = sceneFromFile.MakeScene(); + + if (!string.IsNullOrEmpty(sceneFromFile.Name)) + { + string sceneName = Path.GetFileNameWithoutExtension(sceneFromFile.Name); + var dir = new DirectoryInfo(AppContext.BaseDirectory); + while (dir != null && dir.Name != "SeeSharp") + dir = dir.Parent; + if (dir != null) + currentSceneDirectory = Path.Combine(dir.FullName, "Data", "Scenes", sceneName); + + ReferenceUtils.ScanReferences(currentSceneDirectory, referenceFiles); + } + } + + private void ResetConfig() => integratorSelector?.TriggerReset(); + + private void SaveSceneConfig() + { + if (integratorSelector?.CurrentIntegrator == null) return; + string folder = Path.Combine(currentSceneDirectory, "References"); + if (!Directory.Exists(folder)) Directory.CreateDirectory(folder); + ReferenceUtils.SaveConfig(folder, integratorSelector.CurrentIntegrator); + StateHasChanged(); + } + + private void ApplyReferenceSettings() + { + if (selectedFile == null || string.IsNullOrEmpty(selectedFile.RawJsonConfig)) return; + if (integratorSelector == null) return; + + if (!string.IsNullOrEmpty(selectedFile.IntegratorName)) + { + bool success = integratorSelector.TrySelectIntegrator(selectedFile.IntegratorName); + } + + var currentIntegrator = integratorSelector.CurrentIntegrator; + if (currentIntegrator == null) return; + + var node = JsonNode.Parse(selectedFile.RawJsonConfig); + if (node != null) { + var options = new JsonSerializerOptions { IncludeFields = true, PropertyNameCaseInsensitive = true }; + var loadedIntegrator = JsonSerializer.Deserialize(node, currentIntegrator.GetType(), options); + + if (loadedIntegrator != null) { + ReferenceUtils.CopyValues(currentIntegrator, loadedIntegrator); + if (selectedFile.MaxDepth > 0) renderMaxDepth = selectedFile.MaxDepth; + if (selectedFile.MinDepth > 0) renderMinDepth = selectedFile.MinDepth; + StateHasChanged(); + } + } + } + + private void SelectReference(ReferenceInfo file) + { + selectedFile = file; + if (File.Exists(file.FilePath)) + UpdateViewerFromFile(file.FilePath); + + CheckParamsMatch(); + } + + private void UpdateViewerFromFile(string path) + { + flip = null; + StateHasChanged(); + + if (!File.Exists(path)) return; + var layers = SimpleImageIO.Layers.LoadFromFile(path); + var img = layers.Values.OfType().FirstOrDefault(); + + if (img != null) + UpdatePreviewFromMemory(img); + } + + private async void UpdatePreviewFromMemory(RgbImage image) { + var imgClone = new RgbImage(image.Width, image.Height); + ReferenceUtils.CopyImage(imgClone, image); + flip = null; + StateHasChanged(); + await Task.Delay(1); + var newFlip = new FlipBook(660, 580); + newFlip.Add("", imgClone); + flip = newFlip; + StateHasChanged(); + } + + async Task RenderQuickPreview() + { + if (currentSceneFile == null || integratorSelector.addedIntegrators.Count == 0) return; + + isRendering = true; + selectedFile = null; + flip = null; + + await InvokeAsync(StateHasChanged); + + var curIntegrator = integratorSelector.addedIntegrators.First(); + + await Task.Run(async () => { + var stopwatch = Stopwatch.StartNew(); + var renderScene = currentSceneFile.MakeScene(); + var renderIntegrator = ReferenceUtils.CloneIntegrator(curIntegrator); + + int targetSpp = quickPreviewSpp < 1 ? 1 : quickPreviewSpp; + ReferenceUtils.SetBatchSpp(renderIntegrator, targetSpp); + ReferenceUtils.SetMaxDepth(renderIntegrator, renderMaxDepth); + ReferenceUtils.SetMinDepth(renderIntegrator, renderMinDepth); + + renderScene.FrameBuffer = new FrameBuffer(renderWidth, renderHeight, ""); + renderScene.Prepare(); + renderIntegrator.Render(renderScene); + + stopwatch.Stop(); + + var resultImg = new RgbImage(renderWidth, renderHeight); + ReferenceUtils.CopyImage(resultImg, renderScene.FrameBuffer.Image); + + await InvokeAsync(() => { + UpdatePreviewFromMemory(resultImg); + }); + }); + + isRendering = false; + await InvokeAsync(StateHasChanged); + } + + async Task RenderReference() + { + if (currentSceneFile == null || integratorSelector.addedIntegrators.Count == 0) return; + + isRendering = true; + flip = null; + await InvokeAsync(StateHasChanged); + + var curIntegrator = integratorSelector.addedIntegrators.First(); + + await Task.Run(async () => { + var renderIntegrator = ReferenceUtils.CloneIntegrator(curIntegrator); + + string referencesRoot = Path.Combine(currentSceneDirectory, "References"); + Directory.CreateDirectory(referencesRoot); + + ReferenceUtils.SetMaxDepth(renderIntegrator, renderMaxDepth); + ReferenceUtils.SetMinDepth(renderIntegrator, renderMinDepth); + ReferenceUtils.SaveConfig(referencesRoot, renderIntegrator); + + string baseName = $"MaxDepth{renderMaxDepth}-Width{renderWidth}-Height{renderHeight}"; + string finalPath = Path.Combine(referencesRoot, baseName + ".exr"); + string partialPath = Path.Combine(referencesRoot, baseName + "-partial.exr"); + + var fbFlags = FrameBuffer.Flags.WriteContinously | FrameBuffer.Flags.WriteExponentially | FrameBuffer.Flags.IgnoreNanAndInf; + + ReferenceUtils.PrepareFrameBuffer(scene, renderWidth, renderHeight, partialPath, renderIntegrator, fbFlags); + scene.Prepare(); + + int targetSpp = ReferenceUtils.GetTargetSpp(renderIntegrator); + ReferenceUtils.SetBatchSpp(renderIntegrator, targetSpp); + + renderIntegrator.Render(scene); + scene.FrameBuffer.WriteToFile(); + + int actualSpp = scene.FrameBuffer.CurIteration; + bool isCompleted = actualSpp >= targetSpp; + string finalPathToLoad = partialPath; + + if (isCompleted) + { + if (File.Exists(finalPath)) File.Delete(finalPath); + if (File.Exists(partialPath)) File.Move(partialPath, finalPath); + + string pJson = Path.ChangeExtension(partialPath, ".json"); + string fJson = Path.ChangeExtension(finalPath, ".json"); + + if (File.Exists(fJson)) File.Delete(fJson); + if (File.Exists(pJson)) File.Move(pJson, fJson); + + finalPathToLoad = finalPath; + } + + await InvokeAsync(() => { + ReferenceUtils.ScanReferences(currentSceneDirectory, referenceFiles); + selectedFile = referenceFiles.FirstOrDefault(r => r.FilePath == finalPathToLoad); + if (selectedFile != null) UpdateViewerFromFile(finalPathToLoad); + StateHasChanged(); + }); + }); + + isRendering = false; + await InvokeAsync(StateHasChanged); + } + + async Task RenderMoreSamples(bool isResume) + { + if (currentSceneFile == null || selectedFile == null || !File.Exists(selectedFile.FilePath)) return; + if (integratorSelector.addedIntegrators.Count == 0) return; + + isRendering = true; + await InvokeAsync(StateHasChanged); + + await Task.Run(async () => { + try { + string filePath = selectedFile.FilePath; + var oldImg = new RgbImage(filePath); + int currentSpp = selectedFile.Spp; + + string folder = Path.GetDirectoryName(filePath); + string baseNameNoSuffix = Path.GetFileNameWithoutExtension(filePath).Replace("-partial", ""); + string jsonPath = Path.Combine(folder, baseNameNoSuffix + (filePath.Contains("-partial") ? "-partial.json" : ".json")); + + string thisStepStart = DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss"); + JsonNode rootNode = null; + Integrator renderIntegrator = null; + long totalPreviousMs = 0; + int settingsTargetSpp = 0; + + if (File.Exists(jsonPath)) { + rootNode = JsonNode.Parse(File.ReadAllText(jsonPath)); + totalPreviousMs = (long)(rootNode?["RenderTime"]?.GetValue() ?? 0); + var settingsNode = rootNode?["Settings"]; + if (settingsNode != null) { + settingsTargetSpp = settingsNode["TotalSpp"]?.GetValue() ?? settingsNode["NumIterations"]?.GetValue() ?? 0; + Type targetType = integratorSelector.GetIntegratorType(rootNode["Name"]?.GetValue()); + if (targetType != null) { + var options = new JsonSerializerOptions { IncludeFields = true, PropertyNameCaseInsensitive = true }; + renderIntegrator = JsonSerializer.Deserialize(settingsNode, targetType, options) as Integrator; + } + } + } + + int batchSpp = isResume ? (settingsTargetSpp - currentSpp) : additionalSpp; + int finalTotalSpp = currentSpp + batchSpp; + if (batchSpp <= 0) return; + + ReferenceUtils.SetBatchSpp(renderIntegrator, batchSpp); + if (!isResume) + { + uint originalBaseSeed = ReferenceUtils.GetBaseSeed(renderIntegrator); + ReferenceUtils.SetBaseSeed(renderIntegrator, originalBaseSeed + (uint)currentSpp); + } + + string currentPartialPath = Path.Combine(folder, baseNameNoSuffix + "-partial.exr"); + var fbFlags = FrameBuffer.Flags.WriteContinously | FrameBuffer.Flags.WriteExponentially | FrameBuffer.Flags.IgnoreNanAndInf; + + scene.FrameBuffer = new FrameBuffer(oldImg.Width, oldImg.Height, currentPartialPath, fbFlags); + scene.Prepare(); + + var stopwatch = Stopwatch.StartNew(); + renderIntegrator.Render(scene); + stopwatch.Stop(); + + long thisStepMs = stopwatch.ElapsedMilliseconds; + string thisStepWrite = DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss"); + + var newImg = scene.FrameBuffer.Image; + var finalImg = new RgbImage(oldImg.Width, oldImg.Height); + float wOld = (float)currentSpp / finalTotalSpp; + float wNew = (float)batchSpp / finalTotalSpp; + + Parallel.For(0, finalImg.Height, row => { + for (int col = 0; col < finalImg.Width; ++col) { + finalImg.SetPixel(col, row, oldImg.GetPixel(col, row) * wOld + newImg.GetPixel(col, row) * wNew); + } + }); + + if (rootNode == null) rootNode = new JsonObject(); + rootNode["RenderTime"] = totalPreviousMs + thisStepMs; + rootNode["RenderWriteTime"] = thisStepWrite; + rootNode["NumIterations"] = finalTotalSpp; + + var steps = rootNode["RenderSteps"]?.AsArray() ?? new JsonArray(); + steps.Add(new JsonObject { + ["Type"] = isResume ? "Resume" : "More", + ["DurationMs"] = thisStepMs, + ["StartTime"] = thisStepStart, + ["WriteTime"] = thisStepWrite + }); + rootNode["RenderSteps"] = steps; + + if (!isResume && rootNode["Settings"] != null) { + rootNode["Settings"]["TotalSpp"] = finalTotalSpp; + } + + string finalSavePath = isResume ? Path.Combine(folder, baseNameNoSuffix + ".exr") : filePath; + finalImg.WriteToFile(finalSavePath); + File.WriteAllText(Path.ChangeExtension(finalSavePath, ".json"), rootNode.ToJsonString(new JsonSerializerOptions { WriteIndented = true })); + + if (File.Exists(currentPartialPath)) File.Delete(currentPartialPath); + string partialJsonPath = Path.ChangeExtension(currentPartialPath, ".json"); + if (File.Exists(partialJsonPath)) File.Delete(partialJsonPath); + + if (isResume && filePath != finalSavePath) { + if (File.Exists(filePath)) File.Delete(filePath); + string oldJ = Path.ChangeExtension(filePath, ".json"); + if (File.Exists(oldJ)) File.Delete(oldJ); + } + + await InvokeAsync(() => { + ReferenceUtils.ScanReferences(currentSceneDirectory, referenceFiles); + selectedFile = referenceFiles.FirstOrDefault(r => r.FilePath == finalSavePath); + if (selectedFile != null) UpdateViewerFromFile(finalSavePath); + StateHasChanged(); + }); + } catch (Exception ex) { Console.WriteLine(ex.ToString()); } + }); + + isRendering = false; + await InvokeAsync(StateHasChanged); + } + + private void CheckParamsMatch() + { + isStructureMismatch = false; + isVersionMismatch = false; + isVersionWarning = false; + extraKeys.Clear(); + missingKeys.Clear(); + + if (selectedFile == null) return; + if (string.IsNullOrEmpty(selectedFile.RawJsonConfig)) return; + + (int Major, int Minor, int Patch) ParseSemVer(string ver) + { + if (string.IsNullOrEmpty(ver)) return (0, 0, 0); + int idx = ver.IndexOfAny(new[] { '+', '-' }); + if (idx >= 0) ver = ver.Substring(0, idx); + + var parts = ver.Split('.'); + int maj = parts.Length > 0 && int.TryParse(parts[0], out int m) ? m : 0; + int min = parts.Length > 1 && int.TryParse(parts[1], out int n) ? n : 0; + int pat = parts.Length > 2 && int.TryParse(parts[2], out int p) ? p : 0; + + return (maj, min, pat); + } + + string currentVerStr = ReferenceUtils.CurrentSeeSharpVersion; + string fileVerStr = selectedFile.Version; + + var cur = ParseSemVer(currentVerStr); + var file = ParseSemVer(fileVerStr); + + if (cur.Major != file.Major || cur.Minor != file.Minor) isVersionMismatch = true; + else if (cur.Patch != file.Patch) isVersionWarning = true; + + Type targetType = integratorSelector?.GetIntegratorType(selectedFile.IntegratorName); + if (targetType == null) { + isStructureMismatch = true; + StateHasChanged(); + return; + } + + object dummyIntegrator = Activator.CreateInstance(targetType); + var options = new JsonSerializerOptions { IncludeFields = true }; + var codeJson = JsonSerializer.Serialize(dummyIntegrator, targetType, options); + var codeNode = JsonNode.Parse(codeJson)?.AsObject(); + var fileNode = JsonNode.Parse(selectedFile.RawJsonConfig)?.AsObject(); + + if (codeNode != null && fileNode != null) + { + var codeKeys = codeNode.Select(k => k.Key).ToHashSet(); + var fileKeys = fileNode.Select(k => k.Key).ToHashSet(); + + foreach (var key in fileKeys) { + if (!codeKeys.Contains(key)) extraKeys.Add(key); + } + foreach (var key in codeKeys) { + if (!fileKeys.Contains(key)) missingKeys.Add(key); + } + if (extraKeys.Count > 0 || missingKeys.Count > 0) isStructureMismatch = true; + } + + StateHasChanged(); + } +} \ No newline at end of file diff --git a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.css b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.css index 3585df77..eb04fdbb 100644 --- a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.css +++ b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.css @@ -36,13 +36,6 @@ padding: 10px; } -.btn-success-anim { - background-color: #28a745 !important; - border-color: #28a745 !important; - color: white !important; - transition: all 0.1s ease; -} - .ref-item { padding: 1px; border: 1px solid #eee; @@ -95,15 +88,6 @@ overflow: hidden; } -.slider-area { - flex: 0 0 auto; - padding: 5px 10px; - background: #f1f1f1; - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - z-index: 5; -} - .meta-box { height: auto; min-height: 100px; diff --git a/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs b/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs new file mode 100644 index 00000000..621fa5a2 --- /dev/null +++ b/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs @@ -0,0 +1,249 @@ +using System.Reflection; +using System.Text.Json.Nodes; +using System.Text.RegularExpressions; + +namespace SeeSharp.ReferenceManager.Pages; + +public class ReferenceInfo { + public string FilePath { get; set; } = ""; + public string Resolution { get; set; } = ""; + public int MaxDepth { get; set; } + public int MinDepth { get; set; } + public string IntegratorName { get; set; } = ""; + public int Spp { get; set; } + public string RenderTimeDisplay { get; set; } = ""; + public string Version { get; set; } = ""; + public string StartTimeDisplay { get; set; } = ""; + public string WriteTimeDisplay { get; set; } = ""; + public string Timestamp { get; set; } = ""; + public string RawJsonConfig { get; set; } = ""; + public List RenderSteps { get; set; } = new(); +} + +public class RenderStep { + public string Type { get; set; } = ""; + public double DurationMs { get; set; } + public string StartTime { get; set; } = ""; + public string WriteTime { get; set; } = ""; +} + +public static class ReferenceUtils { + public static void ScanReferences(string sceneDir, List referenceFiles) { + referenceFiles.Clear(); + if (string.IsNullOrEmpty(sceneDir)) return; + + string refDir = Path.Combine(sceneDir, "References"); + if (!Directory.Exists(refDir)) return; + + var exrFiles = Directory.GetFiles(refDir, "*.exr") + .OrderByDescending(f => File.GetLastWriteTime(f)); + + foreach (var f in exrFiles) { + var info = new ReferenceInfo { + FilePath = f, + Resolution = GetResolution(f), + Timestamp = File.GetLastWriteTime(f).ToString("yyyy-MM-dd HH:mm:ss") + }; + ReadMetadataFromJson(info, f); + referenceFiles.Add(info); + } + } + + public static void ReadMetadataFromJson(ReferenceInfo info, string exrPath) { + string folder = Path.GetDirectoryName(exrPath); + string fileNameNoExt = Path.GetFileNameWithoutExtension(exrPath); + string jsonPath = Path.Combine(folder, $"{fileNameNoExt}.json"); + + if (!File.Exists(jsonPath)) return; + + string jsonContent = File.ReadAllText(jsonPath); + var root = JsonNode.Parse(jsonContent); + if (root == null) return; + + info.Version = root["SeeSharpVersion"]?.ToString() ?? ""; + info.StartTimeDisplay = root["RenderStartTime"]?.ToString() ?? ""; + info.WriteTimeDisplay = root["RenderWriteTime"]?.ToString() ?? ""; + + if (root["RenderTime"] != null) { + double ms = root["RenderTime"].GetValue(); + TimeSpan t = TimeSpan.FromMilliseconds(ms); + if (t.TotalMinutes >= 1) info.RenderTimeDisplay = $"{(int)t.TotalMinutes:D2} m {t.Seconds:D2} s"; + else if (t.TotalSeconds >= 1) info.RenderTimeDisplay = $"{t.TotalSeconds:F1} s"; + else info.RenderTimeDisplay = $"{ms:F0} ms"; + } + + if (root["NumIterations"] != null) info.Spp = root["NumIterations"].GetValue(); + + var stepsNode = root["RenderSteps"]; + if (stepsNode is JsonArray arr) { + foreach (var step in arr) { + if (step == null) continue; + info.RenderSteps.Add(new RenderStep { + Type = step["Type"]?.ToString() ?? "Unknown", + DurationMs = step["DurationMs"]?.GetValue() ?? 0, + StartTime = step["StartTime"]?.ToString() ?? "", + WriteTime = step["WriteTime"]?.ToString() ?? "" + }); + } + } + + var settingsNode = root["Settings"]; + if (settingsNode != null) { + var options = new JsonSerializerOptions { WriteIndented = true }; + info.RawJsonConfig = settingsNode.ToJsonString(options); + info.MaxDepth = settingsNode["MaxDepth"]?.GetValue() ?? 0; + info.MinDepth = settingsNode["MinDepth"]?.GetValue() ?? 0; + } + + string integratorName = root["Name"]?.GetValue(); + info.IntegratorName = integratorName.Split('.').Last(); + } + + public static string GetResolution(string filePath) { + string filename = Path.GetFileNameWithoutExtension(filePath); + var match = Regex.Match(filename, @"Width(\d+)-Height(\d+)", RegexOptions.IgnoreCase); + if (match.Success) return $"{match.Groups[1].Value}x{match.Groups[2].Value}"; + return "Unknown"; + } + + public static void CopyValues(object target, object source) { + if (target == null || source == null || target.GetType() != source.GetType()) return; + var type = target.GetType(); + + bool IsConfigParam(Type t) { + return t == typeof(string) || t.IsValueType; + } + + foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanRead && p.CanWrite)) { + if (IsConfigParam(prop.PropertyType)) + prop.SetValue(target, prop.GetValue(source)); + } + foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) { + if (IsConfigParam(field.FieldType)) + field.SetValue(target, field.GetValue(source)); + } + } + + public static void CopyImage(RgbImage target, RgbImage source) { + Parallel.For(0, target.Height, y => { + for (int x = 0; x < target.Width; ++x) + target.SetPixel(x, y, source.GetPixel(x, y)); + }); + } + + public static Integrator CloneIntegrator(Integrator source) { + var type = source.GetType(); + var clone = (Integrator)Activator.CreateInstance(type); + CopyValues(clone, source); + return clone; + } + + public static void SetMaxDepth(Integrator integrator, int depth) { + var type = integrator.GetType(); + var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; + var prop = type.GetProperty("MaxDepth", flags); + if (prop != null && prop.CanWrite) { + prop.SetValue(integrator, depth); + return; + } + var field = type.GetField("MaxDepth", flags); + if (field != null) { + field.SetValue(integrator, depth); + } + } + + public static void SetMinDepth(Integrator integrator, int depth) { + var type = integrator.GetType(); + var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; + var prop = type.GetProperty("MinDepth", flags); + if (prop != null && prop.CanWrite) { + prop.SetValue(integrator, depth); + return; + } + var field = type.GetField("MinDepth", flags); + if (field != null) { + field.SetValue(integrator, depth); + } + } + + public static int GetTargetSpp(Integrator integrator) { + var type = integrator.GetType(); + var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; + var propSpp = type.GetProperty("TotalSpp", flags); + if (propSpp != null) return (int)propSpp.GetValue(integrator); + + var fieldSpp = type.GetField("TotalSpp", flags); + if (fieldSpp != null) return (int)fieldSpp.GetValue(integrator); + + var propIter = type.GetProperty("NumIterations", flags); + if (propIter != null) return (int)propIter.GetValue(integrator); + + var fieldIter = type.GetField("NumIterations", flags); + if (fieldIter != null) return (int)fieldIter.GetValue(integrator); + return 16; + } + + public static void SaveConfig(string folder, Integrator integrator) { + var options = new JsonSerializerOptions { WriteIndented = true, IncludeFields = true }; + var rootNode = new JsonObject(); + rootNode.Add("Name", integrator.GetType().Name); + var settingsNode = JsonSerializer.SerializeToNode(integrator, integrator.GetType(), options); + rootNode.Add("Settings", settingsNode); + File.WriteAllText(Path.Combine(folder, "Config.json"), rootNode.ToJsonString(options)); + } + + public static void SetBatchSpp(Integrator integrator, int batchCount) { + var type = integrator.GetType(); + var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; + var targetNames = new[] { "TotalSpp", "NumIterations"}; + foreach (var name in targetNames) { + var prop = type.GetProperty(name, flags); + if (prop != null && prop.CanWrite) { + prop.SetValue(integrator, batchCount); + return; + } + var field = type.GetField(name, flags); + if (field != null) { + field.SetValue(integrator, batchCount); + return; + } + } + } + + public static uint GetBaseSeed(Integrator integrator) { + var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; + var seedProp = integrator.GetType().GetProperty("BaseSeed", flags); + var seedField = integrator.GetType().GetField("BaseSeed", flags); + if (seedProp != null) return (uint)(seedProp.GetValue(integrator) ?? 0u); + if (seedField != null) return (uint)(seedField.GetValue(integrator) ?? 0u); + return 0; + } + + public static void SetBaseSeed(Integrator integrator, uint seed) { + var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; + var seedProp = integrator.GetType().GetProperty("BaseSeed", flags); + var seedField = integrator.GetType().GetField("BaseSeed", flags); + if (seedProp != null) seedProp.SetValue(integrator, seed); + else if (seedField != null) seedField.SetValue(integrator, seed); + } + + public static void PrepareFrameBuffer(Scene scene, int width, int height, string finalPath, Integrator integrator, FrameBuffer.Flags flags) { + if (scene == null) return; + + scene.FrameBuffer = new FrameBuffer(width, height, finalPath, flags); + + var options = new JsonSerializerOptions { + IncludeFields = true, + WriteIndented = true + }; + var fullSettingsNode = JsonSerializer.SerializeToNode(integrator, integrator.GetType(), options); + + scene.FrameBuffer.MetaData["Name"] = integrator.GetType().Name; + scene.FrameBuffer.MetaData["Settings"] = fullSettingsNode; + } + + public static string CurrentSeeSharpVersion { get; } = + typeof(Scene).Assembly + .GetCustomAttribute()? + .InformationalVersion ?? "Unknown"; +} \ No newline at end of file diff --git a/SeeSharp.ReferenceManager/Pages/RenderSetting.razor b/SeeSharp.ReferenceManager/Pages/RenderSetting.razor new file mode 100644 index 00000000..30d84471 --- /dev/null +++ b/SeeSharp.ReferenceManager/Pages/RenderSetting.razor @@ -0,0 +1,41 @@ +@using System.Reflection +@using SeeSharp.Blazor +@using SeeSharp.ReferenceManager.Pages + +@{ + string name = Member.Name; + string desc = IntegratorUtils.GetDescription(Member); + + Type type = (Member is PropertyInfo p) ? p.PropertyType : ((FieldInfo)Member).FieldType; + Type underlyingType = Nullable.GetUnderlyingType(type) ?? type; + TypeCode typeCode = Type.GetTypeCode(underlyingType); + + if (typeCode == TypeCode.Boolean) + { + bool val = Convert.ToBoolean(Getter() ?? false); + + } + else if (typeCode == TypeCode.Single || typeCode == TypeCode.Double) + { + float val = (float)Convert.ToDouble(Getter() ?? 0.0); + + } + else if (typeCode >= TypeCode.SByte && typeCode <= TypeCode.UInt64) + { + long val = Convert.ToInt64(Getter() ?? 0); + + } +} + +@code { + [Parameter] public MemberInfo Member { get; set; } = default!; + [Parameter] public Func Getter { get; set; } = default!; + [Parameter] public Action Setter { get; set; } = default!; +} \ No newline at end of file diff --git a/SeeSharp.ReferenceManager/Pages/DocumentationHelper.cs b/SeeSharp/Common/DocumentationReader.cs similarity index 78% rename from SeeSharp.ReferenceManager/Pages/DocumentationHelper.cs rename to SeeSharp/Common/DocumentationReader.cs index ed2449bd..39cf6abd 100644 --- a/SeeSharp.ReferenceManager/Pages/DocumentationHelper.cs +++ b/SeeSharp/Common/DocumentationReader.cs @@ -1,16 +1,15 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using System.Xml.Linq; namespace SeeSharp.Blazor; -public static class DocumentationHelper +/// +/// Load xml documentation files and retrieve summary to show description +/// +public static class DocumentationReader { - private static Dictionary _loadedXmlDocumentation = new(); + private static Dictionary loadedXmlDocumentation = new(); public static void LoadXmlDocumentation(Assembly assembly) { @@ -30,13 +29,13 @@ public static void LoadXmlDocumentation(Assembly assembly) if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(summary)) { string cleanSummary = Regex.Replace(summary, @"\s+", " "); - _loadedXmlDocumentation[name] = cleanSummary; + loadedXmlDocumentation[name] = cleanSummary; } } } catch (Exception ex) { - Console.WriteLine($"[DocHelper] Error loading XML: {ex.Message}"); + Logger.Log($"Error loading XML: {ex.Message}", Verbosity.Error); } } } @@ -61,7 +60,7 @@ public static string GetSummary(MemberInfo member) string key = $"{prefix}{typeName}.{member.Name}"; - if (_loadedXmlDocumentation.TryGetValue(key, out var summary)) + if (loadedXmlDocumentation.TryGetValue(key, out var summary)) { return summary; } From 918898ba17be4d0f5ffef22530cde04e4ce18b2d Mon Sep 17 00:00:00 2001 From: Pascal Grittmann Date: Wed, 28 Jan 2026 11:37:53 +0100 Subject: [PATCH 04/10] fix css loading issue, set default page, and prevent crash on missing integrator name --- .gitignore | 2 - SeeSharp.ReferenceManager/Pages/Index.razor | 39 ------------- .../Pages/ReferenceRendering.razor | 38 ++++++------ .../Pages/ReferenceUtils.cs | 58 +++++++++---------- .../Properties/launchSettings.json | 35 +++++++++++ 5 files changed, 83 insertions(+), 89 deletions(-) delete mode 100644 SeeSharp.ReferenceManager/Pages/Index.razor create mode 100644 SeeSharp.ReferenceManager/Properties/launchSettings.json diff --git a/.gitignore b/.gitignore index e657b0a2..0315f7dc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,5 @@ # User specific editor and IDE configurations .vs -launchSettings.json -!SeeSharp.Templates/content/SeeSharp.Blazor.Template/Properties/launchSettings.json .idea SeeSharp.sln.DotSettings.user diff --git a/SeeSharp.ReferenceManager/Pages/Index.razor b/SeeSharp.ReferenceManager/Pages/Index.razor deleted file mode 100644 index 60c1eed3..00000000 --- a/SeeSharp.ReferenceManager/Pages/Index.razor +++ /dev/null @@ -1,39 +0,0 @@ -@page "/" - -@using System.Reflection -@using System.Text.RegularExpressions - - -
- -
- - -@code { - /// Enumerates all .razor components in this folder - public IEnumerable<(string Name, string Url)> GetExperimentPages() - { - var routableComponents = Assembly - .GetExecutingAssembly() - .ExportedTypes - .Where(t => t.IsSubclassOf(typeof(ComponentBase))) - .Where(c => c - .GetCustomAttributes(inherit: true) - .OfType() - .Count() > 0); - - foreach (var routableComponent in routableComponents) - { - string name = routableComponent.ToString().Replace("SeeSharp.ReferenceManager.Pages.", string.Empty); - if (name != "Index") - yield return (name, name); - } - } -} diff --git a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor index c8bd96e3..495a9b1f 100644 --- a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor +++ b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor @@ -1,4 +1,4 @@ -@page "/ReferenceRendering" +@page "/" @using SeeSharp.Experiments @using SeeSharp.Integrators @using SeeSharp.Blazor @@ -10,10 +10,10 @@ @using System.Text.Json.Nodes @using System.Diagnostics @using System.Linq -@using System.Text.RegularExpressions; +@using System.Text.RegularExpressions;
- +
@@ -23,7 +23,7 @@
- +
@@ -39,18 +39,18 @@ @foreach (var file in referenceFiles) {
- @file.Resolution, max depth: @file.MaxDepth + @file.Resolution, max depth: @file.MaxDepth @if (file.FilePath.EndsWith("-partial.exr")) { (Partial) } -
+
}
Render new
- +
@@ -67,7 +67,7 @@
- + @@ -81,23 +81,23 @@

- +
Render more
- +
- - @@ -166,7 +166,7 @@
} - +
@if (isStructureMismatch) { @@ -176,7 +176,7 @@ Integrator Settings
-
- \ No newline at end of file + diff --git a/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs b/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs index 621fa5a2..261958ea 100644 --- a/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs +++ b/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs @@ -12,11 +12,11 @@ public class ReferenceInfo { public string IntegratorName { get; set; } = ""; public int Spp { get; set; } public string RenderTimeDisplay { get; set; } = ""; - public string Version { get; set; } = ""; + public string Version { get; set; } = ""; public string StartTimeDisplay { get; set; } = ""; public string WriteTimeDisplay { get; set; } = ""; - public string Timestamp { get; set; } = ""; - public string RawJsonConfig { get; set; } = ""; + public string Timestamp { get; set; } = ""; + public string RawJsonConfig { get; set; } = ""; public List RenderSteps { get; set; } = new(); } @@ -80,7 +80,7 @@ public static void ReadMetadataFromJson(ReferenceInfo info, string exrPath) { if (step == null) continue; info.RenderSteps.Add(new RenderStep { Type = step["Type"]?.ToString() ?? "Unknown", - DurationMs = step["DurationMs"]?.GetValue() ?? 0, + DurationMs = step["DurationMs"]?.GetValue() ?? 0, StartTime = step["StartTime"]?.ToString() ?? "", WriteTime = step["WriteTime"]?.ToString() ?? "" }); @@ -94,9 +94,9 @@ public static void ReadMetadataFromJson(ReferenceInfo info, string exrPath) { info.MaxDepth = settingsNode["MaxDepth"]?.GetValue() ?? 0; info.MinDepth = settingsNode["MinDepth"]?.GetValue() ?? 0; } - + string integratorName = root["Name"]?.GetValue(); - info.IntegratorName = integratorName.Split('.').Last(); + info.IntegratorName = integratorName?.Split('.')?.Last() ?? "Unknown"; } public static string GetResolution(string filePath) { @@ -110,23 +110,23 @@ public static void CopyValues(object target, object source) { if (target == null || source == null || target.GetType() != source.GetType()) return; var type = target.GetType(); - bool IsConfigParam(Type t) { + bool IsConfigParam(Type t) { return t == typeof(string) || t.IsValueType; } foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanRead && p.CanWrite)) { - if (IsConfigParam(prop.PropertyType)) - prop.SetValue(target, prop.GetValue(source)); + if (IsConfigParam(prop.PropertyType)) + prop.SetValue(target, prop.GetValue(source)); } foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) { - if (IsConfigParam(field.FieldType)) + if (IsConfigParam(field.FieldType)) field.SetValue(target, field.GetValue(source)); } } public static void CopyImage(RgbImage target, RgbImage source) { Parallel.For(0, target.Height, y => { - for (int x = 0; x < target.Width; ++x) + for (int x = 0; x < target.Width; ++x) target.SetPixel(x, y, source.GetPixel(x, y)); }); } @@ -142,12 +142,12 @@ public static void SetMaxDepth(Integrator integrator, int depth) { var type = integrator.GetType(); var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; var prop = type.GetProperty("MaxDepth", flags); - if (prop != null && prop.CanWrite) { - prop.SetValue(integrator, depth); - return; + if (prop != null && prop.CanWrite) { + prop.SetValue(integrator, depth); + return; } var field = type.GetField("MaxDepth", flags); - if (field != null) { + if (field != null) { field.SetValue(integrator, depth); } } @@ -156,12 +156,12 @@ public static void SetMinDepth(Integrator integrator, int depth) { var type = integrator.GetType(); var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; var prop = type.GetProperty("MinDepth", flags); - if (prop != null && prop.CanWrite) { - prop.SetValue(integrator, depth); - return; + if (prop != null && prop.CanWrite) { + prop.SetValue(integrator, depth); + return; } var field = type.GetField("MinDepth", flags); - if (field != null) { + if (field != null) { field.SetValue(integrator, depth); } } @@ -198,14 +198,14 @@ public static void SetBatchSpp(Integrator integrator, int batchCount) { var targetNames = new[] { "TotalSpp", "NumIterations"}; foreach (var name in targetNames) { var prop = type.GetProperty(name, flags); - if (prop != null && prop.CanWrite) { - prop.SetValue(integrator, batchCount); + if (prop != null && prop.CanWrite) { + prop.SetValue(integrator, batchCount); return; } var field = type.GetField(name, flags); - if (field != null) { - field.SetValue(integrator, batchCount); - return; + if (field != null) { + field.SetValue(integrator, batchCount); + return; } } } @@ -231,18 +231,18 @@ public static void PrepareFrameBuffer(Scene scene, int width, int height, string if (scene == null) return; scene.FrameBuffer = new FrameBuffer(width, height, finalPath, flags); - - var options = new JsonSerializerOptions { + + var options = new JsonSerializerOptions { IncludeFields = true, - WriteIndented = true + WriteIndented = true }; var fullSettingsNode = JsonSerializer.SerializeToNode(integrator, integrator.GetType(), options); scene.FrameBuffer.MetaData["Name"] = integrator.GetType().Name; - scene.FrameBuffer.MetaData["Settings"] = fullSettingsNode; + scene.FrameBuffer.MetaData["Settings"] = fullSettingsNode; } - public static string CurrentSeeSharpVersion { get; } = + public static string CurrentSeeSharpVersion { get; } = typeof(Scene).Assembly .GetCustomAttribute()? .InformationalVersion ?? "Unknown"; diff --git a/SeeSharp.ReferenceManager/Properties/launchSettings.json b/SeeSharp.ReferenceManager/Properties/launchSettings.json new file mode 100644 index 00000000..4b0b1aac --- /dev/null +++ b/SeeSharp.ReferenceManager/Properties/launchSettings.json @@ -0,0 +1,35 @@ +{ + "iisSettings": { + "iisExpress": { + "applicationUrl": "http://localhost:18831", + "sslPort": 44326 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5229", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7055;http://localhost:5229", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": false, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} From bd23a5cd90a7f0d641d1862391c12f6e3c63273a Mon Sep 17 00:00:00 2001 From: Mengzhu Wang Date: Fri, 30 Jan 2026 16:28:35 +0100 Subject: [PATCH 05/10] fix reference list depth, fix display image --- .../Pages/ReferenceRendering.razor | 4 ++ .../Pages/ReferenceRendering.razor.cs | 23 +++++--- .../Pages/ReferenceUtils.cs | 57 +++++++++++++++---- 3 files changed, 65 insertions(+), 19 deletions(-) diff --git a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor index 495a9b1f..12bcbca4 100644 --- a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor +++ b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor @@ -40,6 +40,10 @@
@file.Resolution, max depth: @file.MaxDepth + @if (file.MinDepth > 1) + { + , min depth: @file.MinDepth + } @if (file.FilePath.EndsWith("-partial.exr")) { (Partial) } diff --git a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs index bc10160d..5d843264 100644 --- a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs +++ b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs @@ -97,12 +97,16 @@ private void UpdateViewerFromFile(string path) flip = null; StateHasChanged(); - if (!File.Exists(path)) return; - var layers = SimpleImageIO.Layers.LoadFromFile(path); - var img = layers.Values.OfType().FirstOrDefault(); - - if (img != null) + RgbImage img = null; + if (File.Exists(path)) { + // support legacy .exr files + var layers = Layers.LoadFromFile(path); + if (layers.TryGetValue("", out Image image)) + img = ReferenceUtils.InpaintNaNs(image) as RgbImage; + else if (layers.TryGetValue("default", out var defaultImg)) + img = ReferenceUtils.InpaintNaNs(defaultImg) as RgbImage; UpdatePreviewFromMemory(img); + } } private async void UpdatePreviewFromMemory(RgbImage image) { @@ -170,16 +174,17 @@ async Task RenderReference() await Task.Run(async () => { var renderIntegrator = ReferenceUtils.CloneIntegrator(curIntegrator); - string referencesRoot = Path.Combine(currentSceneDirectory, "References"); + string referencesRoot = Path.Join(currentSceneDirectory, "References"); Directory.CreateDirectory(referencesRoot); ReferenceUtils.SetMaxDepth(renderIntegrator, renderMaxDepth); ReferenceUtils.SetMinDepth(renderIntegrator, renderMinDepth); ReferenceUtils.SaveConfig(referencesRoot, renderIntegrator); - string baseName = $"MaxDepth{renderMaxDepth}-Width{renderWidth}-Height{renderHeight}"; - string finalPath = Path.Combine(referencesRoot, baseName + ".exr"); - string partialPath = Path.Combine(referencesRoot, baseName + "-partial.exr"); + string minDepthString = renderMinDepth > 1 ? $"MinDepth{renderMinDepth}-" : ""; + string baseName = $"{minDepthString}MaxDepth{renderMaxDepth}-Width{renderWidth}-Height{renderHeight}"; + string finalPath = Path.Join(referencesRoot, baseName + ".exr"); + string partialPath = Path.Join(referencesRoot, baseName + "-partial.exr"); var fbFlags = FrameBuffer.Flags.WriteContinously | FrameBuffer.Flags.WriteExponentially | FrameBuffer.Flags.IgnoreNanAndInf; diff --git a/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs b/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs index 261958ea..abe35f40 100644 --- a/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs +++ b/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs @@ -41,14 +41,60 @@ public static void ScanReferences(string sceneDir, List reference foreach (var f in exrFiles) { var info = new ReferenceInfo { FilePath = f, - Resolution = GetResolution(f), Timestamp = File.GetLastWriteTime(f).ToString("yyyy-MM-dd HH:mm:ss") }; ReadMetadataFromJson(info, f); + ParseExrName(info, f); referenceFiles.Add(info); } } + public static void ParseExrName(ReferenceInfo info, string filePath) { + string filename = Path.GetFileNameWithoutExtension(filePath); + var match = Regex.Match(filename, @"(?:MinDepth(?\d+)-)?MaxDepth(?\d+)-Width(?\d+)-Height(?\d+)", RegexOptions.IgnoreCase); + + if (match.Success) { + info.MinDepth = match.Groups["min"].Success ? int.Parse(match.Groups["min"].Value) : 1; + info.MaxDepth = int.Parse(match.Groups["max"].Value); + info.Resolution = $"{match.Groups["w"].Value}x{match.Groups["h"].Value}"; + } else { + if (string.IsNullOrEmpty(info.Resolution)) + Logger.Warning($"Can't find resolution."); + else if (info.MaxDepth <= 0) + Logger.Warning($"The maximum depth is wrong."); + } + } + + public static T InpaintNaNs(T image) where T : Image { + bool hadAny = false; + for (int row = 0; row < image.Height; ++row) { + for (int col = 0; col < image.Width; ++col) { + for (int chan = 0; chan < image.NumChannels; ++chan) { + if (!float.IsFinite(image[col, row, chan])) { + float total = 0; + int num = 0; + void TryAdd(int c, int r) { + if (c >= 0 && r >= 0 && c < image.Width && r < image.Height && float.IsFinite(image[c, r, chan])) { + total += image[c, r, chan]; + num++; + } + } + TryAdd(col - 1, row); + TryAdd(col + 1, row); + TryAdd(col, row - 1); + TryAdd(col, row + 1); + image[col, row, chan] = total / num; + + hadAny = true; + } + } + } + } + if (hadAny) + Logger.Warning("Removed NaN / Inf from reference image (check the reference's .json in the scene directory for details)"); + return image; + } + public static void ReadMetadataFromJson(ReferenceInfo info, string exrPath) { string folder = Path.GetDirectoryName(exrPath); string fileNameNoExt = Path.GetFileNameWithoutExtension(exrPath); @@ -91,21 +137,12 @@ public static void ReadMetadataFromJson(ReferenceInfo info, string exrPath) { if (settingsNode != null) { var options = new JsonSerializerOptions { WriteIndented = true }; info.RawJsonConfig = settingsNode.ToJsonString(options); - info.MaxDepth = settingsNode["MaxDepth"]?.GetValue() ?? 0; - info.MinDepth = settingsNode["MinDepth"]?.GetValue() ?? 0; } string integratorName = root["Name"]?.GetValue(); info.IntegratorName = integratorName?.Split('.')?.Last() ?? "Unknown"; } - public static string GetResolution(string filePath) { - string filename = Path.GetFileNameWithoutExtension(filePath); - var match = Regex.Match(filename, @"Width(\d+)-Height(\d+)", RegexOptions.IgnoreCase); - if (match.Success) return $"{match.Groups[1].Value}x{match.Groups[2].Value}"; - return "Unknown"; - } - public static void CopyValues(object target, object source) { if (target == null || source == null || target.GetType() != source.GetType()) return; var type = target.GetType(); From 9b91ea73a01469e46055896dd5927c750f4ec8af Mon Sep 17 00:00:00 2001 From: Mengzhu Wang Date: Fri, 30 Jan 2026 20:19:46 +0100 Subject: [PATCH 06/10] improve reference rendering --- .../Pages/ReferenceRendering.razor.cs | 58 ++++++------------- .../Pages/ReferenceUtils.cs | 45 -------------- SeeSharp/Experiments/SceneConfig.cs | 3 +- SeeSharp/Experiments/SceneFromFile.cs | 30 ++++++++-- 4 files changed, 45 insertions(+), 91 deletions(-) diff --git a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs index 5d843264..14a10b78 100644 --- a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs +++ b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs @@ -102,9 +102,9 @@ private void UpdateViewerFromFile(string path) // support legacy .exr files var layers = Layers.LoadFromFile(path); if (layers.TryGetValue("", out Image image)) - img = ReferenceUtils.InpaintNaNs(image) as RgbImage; + img = SceneFromFile.InpaintNaNs(image) as RgbImage; else if (layers.TryGetValue("default", out var defaultImg)) - img = ReferenceUtils.InpaintNaNs(defaultImg) as RgbImage; + img = SceneFromFile.InpaintNaNs(defaultImg) as RgbImage; UpdatePreviewFromMemory(img); } } @@ -170,55 +170,31 @@ async Task RenderReference() await InvokeAsync(StateHasChanged); var curIntegrator = integratorSelector.addedIntegrators.First(); - await Task.Run(async () => { var renderIntegrator = ReferenceUtils.CloneIntegrator(curIntegrator); - - string referencesRoot = Path.Join(currentSceneDirectory, "References"); - Directory.CreateDirectory(referencesRoot); - - ReferenceUtils.SetMaxDepth(renderIntegrator, renderMaxDepth); - ReferenceUtils.SetMinDepth(renderIntegrator, renderMinDepth); - ReferenceUtils.SaveConfig(referencesRoot, renderIntegrator); - - string minDepthString = renderMinDepth > 1 ? $"MinDepth{renderMinDepth}-" : ""; - string baseName = $"{minDepthString}MaxDepth{renderMaxDepth}-Width{renderWidth}-Height{renderHeight}"; - string finalPath = Path.Join(referencesRoot, baseName + ".exr"); - string partialPath = Path.Join(referencesRoot, baseName + "-partial.exr"); - - var fbFlags = FrameBuffer.Flags.WriteContinously | FrameBuffer.Flags.WriteExponentially | FrameBuffer.Flags.IgnoreNanAndInf; - - ReferenceUtils.PrepareFrameBuffer(scene, renderWidth, renderHeight, partialPath, renderIntegrator, fbFlags); - scene.Prepare(); - int targetSpp = ReferenceUtils.GetTargetSpp(renderIntegrator); ReferenceUtils.SetBatchSpp(renderIntegrator, targetSpp); - renderIntegrator.Render(scene); - scene.FrameBuffer.WriteToFile(); - - int actualSpp = scene.FrameBuffer.CurIteration; - bool isCompleted = actualSpp >= targetSpp; - string finalPathToLoad = partialPath; + currentSceneFile.MaxDepth = renderMaxDepth; + currentSceneFile.MinDepth = renderMinDepth; - if (isCompleted) - { - if (File.Exists(finalPath)) File.Delete(finalPath); - if (File.Exists(partialPath)) File.Move(partialPath, finalPath); - - string pJson = Path.ChangeExtension(partialPath, ".json"); - string fJson = Path.ChangeExtension(finalPath, ".json"); - - if (File.Exists(fJson)) File.Delete(fJson); - if (File.Exists(pJson)) File.Move(pJson, fJson); + string referencesRoot = Path.Join(currentSceneDirectory, "References"); + Directory.CreateDirectory(referencesRoot); + string minDepthString = renderMinDepth > 1 ? $"MinDepth{renderMinDepth}-" : ""; + string finalPath = Path.Combine(referencesRoot, $"{minDepthString}MaxDepth{renderMaxDepth}-Width{renderWidth}-Height{renderHeight}.exr"); + if (File.Exists(finalPath)) File.Delete(finalPath); - finalPathToLoad = finalPath; - } + var resultImg = currentSceneFile.GetReferenceImage( + renderWidth, + renderHeight, + allowRender: true, + config: renderIntegrator + ); await InvokeAsync(() => { ReferenceUtils.ScanReferences(currentSceneDirectory, referenceFiles); - selectedFile = referenceFiles.FirstOrDefault(r => r.FilePath == finalPathToLoad); - if (selectedFile != null) UpdateViewerFromFile(finalPathToLoad); + selectedFile = referenceFiles.FirstOrDefault(r => r.FilePath == finalPath); + if (selectedFile != null) UpdateViewerFromFile(finalPath); StateHasChanged(); }); }); diff --git a/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs b/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs index abe35f40..2b151096 100644 --- a/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs +++ b/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs @@ -65,36 +65,6 @@ public static void ParseExrName(ReferenceInfo info, string filePath) { } } - public static T InpaintNaNs(T image) where T : Image { - bool hadAny = false; - for (int row = 0; row < image.Height; ++row) { - for (int col = 0; col < image.Width; ++col) { - for (int chan = 0; chan < image.NumChannels; ++chan) { - if (!float.IsFinite(image[col, row, chan])) { - float total = 0; - int num = 0; - void TryAdd(int c, int r) { - if (c >= 0 && r >= 0 && c < image.Width && r < image.Height && float.IsFinite(image[c, r, chan])) { - total += image[c, r, chan]; - num++; - } - } - TryAdd(col - 1, row); - TryAdd(col + 1, row); - TryAdd(col, row - 1); - TryAdd(col, row + 1); - image[col, row, chan] = total / num; - - hadAny = true; - } - } - } - } - if (hadAny) - Logger.Warning("Removed NaN / Inf from reference image (check the reference's .json in the scene directory for details)"); - return image; - } - public static void ReadMetadataFromJson(ReferenceInfo info, string exrPath) { string folder = Path.GetDirectoryName(exrPath); string fileNameNoExt = Path.GetFileNameWithoutExtension(exrPath); @@ -264,21 +234,6 @@ public static void SetBaseSeed(Integrator integrator, uint seed) { else if (seedField != null) seedField.SetValue(integrator, seed); } - public static void PrepareFrameBuffer(Scene scene, int width, int height, string finalPath, Integrator integrator, FrameBuffer.Flags flags) { - if (scene == null) return; - - scene.FrameBuffer = new FrameBuffer(width, height, finalPath, flags); - - var options = new JsonSerializerOptions { - IncludeFields = true, - WriteIndented = true - }; - var fullSettingsNode = JsonSerializer.SerializeToNode(integrator, integrator.GetType(), options); - - scene.FrameBuffer.MetaData["Name"] = integrator.GetType().Name; - scene.FrameBuffer.MetaData["Settings"] = fullSettingsNode; - } - public static string CurrentSeeSharpVersion { get; } = typeof(Scene).Assembly .GetCustomAttribute()? diff --git a/SeeSharp/Experiments/SceneConfig.cs b/SeeSharp/Experiments/SceneConfig.cs index 87fd8792..9afd5085 100644 --- a/SeeSharp/Experiments/SceneConfig.cs +++ b/SeeSharp/Experiments/SceneConfig.cs @@ -31,6 +31,7 @@ public abstract class SceneConfig { /// Width of the image /// Height of the image /// If false, missing references are not rendered and null is returned instead + /// Use integrator parameters if provided, otherwise read them from the JSON file /// The reference image - public abstract RgbImage GetReferenceImage(int width, int height, bool allowRender = true); + public abstract RgbImage GetReferenceImage(int width, int height, bool allowRender = true, Integrator config = null); } \ No newline at end of file diff --git a/SeeSharp/Experiments/SceneFromFile.cs b/SeeSharp/Experiments/SceneFromFile.cs index 21ad3af5..47602807 100644 --- a/SeeSharp/Experiments/SceneFromFile.cs +++ b/SeeSharp/Experiments/SceneFromFile.cs @@ -46,7 +46,7 @@ public SceneFromFile WithName(string newName) { WriteIndented = true }; - static T InpaintNaNs(T image) where T : Image { + public static T InpaintNaNs(T image) where T : Image { bool hadAny = false; for (int row = 0; row < image.Height; ++row) { for (int col = 0; col < image.Width; ++col) { @@ -86,8 +86,9 @@ void TryAdd(int c, int r) { /// Width in pixels /// Height in pixels /// If false, missing references are not rendered and null is returned instead + /// Use integrator parameters if provided, otherwise read them from the JSON file /// The reference image - public override RgbImage GetReferenceImage(int width, int height, bool allowRender = true) { + public override RgbImage GetReferenceImage(int width, int height, bool allowRender = true, Integrator config = null) { string refDir = Path.Join(file.DirectoryName, "References"); Directory.CreateDirectory(refDir); @@ -110,6 +111,11 @@ public override RgbImage GetReferenceImage(int width, int height, bool allowRend string referenceSpecs = Path.Join(refDir, "Config.json"); Integrator refIntegrator = DefaultReferenceIntegrator; + if (config != null) { + refIntegrator = config; + goto IntegratorLoaded; + } + if (File.Exists(referenceSpecs)) { var json = JsonNode.Parse(File.ReadAllText(referenceSpecs)); @@ -150,7 +156,7 @@ public override RgbImage GetReferenceImage(int width, int height, bool allowRend // Overwrite the reference specs in case they got updated (e.g., migrated to new format) File.WriteAllText(referenceSpecs, $$""" { - "Name": "{{refIntegrator.GetType().FullName}}", + "Name": "{{refIntegrator.GetType().Name}}", "Settings": {{JsonSerializer.Serialize(refIntegrator, refIntegrator.GetType(), refSerializerOptions)}} } """); @@ -158,15 +164,31 @@ public override RgbImage GetReferenceImage(int width, int height, bool allowRend refIntegrator.MaxDepth = MaxDepth; refIntegrator.MinDepth = MinDepth; + string partialFilename = Path.Join(refDir, $"{minDepthString}MaxDepth{MaxDepth}-Width{width}-Height{height}-partial.exr"); + if (File.Exists(partialFilename)) File.Delete(partialFilename); + using Scene scn = MakeScene(); - scn.FrameBuffer = new(width, height, filename, + scn.FrameBuffer = new(width, height, partialFilename, FrameBuffer.Flags.IgnoreNanAndInf | FrameBuffer.Flags.WriteContinously | FrameBuffer.Flags.WriteExponentially); // output intermediate results exponentially to avoid loosing everything on a crash + + // Write settings to metadata for preview parameters in the info table + scn.FrameBuffer.MetaData["Name"] = refIntegrator.GetType().Name; + scn.FrameBuffer.MetaData["Settings"] = JsonSerializer.SerializeToNode(refIntegrator, refIntegrator.GetType(), refSerializerOptions); + scn.Prepare(); refIntegrator.Render(scn); scn.FrameBuffer.WriteToFile(); + if (File.Exists(filename)) File.Delete(filename); + File.Move(partialFilename, filename); + + string pJson = Path.ChangeExtension(partialFilename, ".json"); + string fJson = Path.ChangeExtension(filename, ".json"); + if (File.Exists(fJson)) File.Delete(fJson); + if (File.Exists(pJson)) File.Move(pJson, fJson); + return InpaintNaNs(scn.FrameBuffer.Image); } From 6dedf71f0832fe80eb462e0a21202f2e186b6fb9 Mon Sep 17 00:00:00 2001 From: Pascal Grittmann Date: Mon, 2 Feb 2026 15:42:32 +0100 Subject: [PATCH 07/10] improve CSS for image viewer Size to fit and hide the "tools" panel by default --- .../Pages/ReferenceRendering.razor.cs | 55 ++++++++++--------- .../Pages/ReferenceRendering.razor.css | 30 +++++----- 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs index 14a10b78..2e6fd787 100644 --- a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs +++ b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs @@ -6,14 +6,14 @@ public partial class ReferenceRendering { private SceneSelector sceneSelector; private IntegratorSelector integratorSelector; - private Scene scene; - private SceneFromFile currentSceneFile; - private string currentSceneDirectory = ""; - private FlipBook flip; - + private Scene scene; + private SceneFromFile currentSceneFile; + private string currentSceneDirectory = ""; + private FlipBook flip; + private List referenceFiles = new(); private ReferenceInfo selectedFile; - + private int renderWidth = 512; private int renderHeight = 512; private int renderMaxDepth = 5; @@ -40,7 +40,7 @@ private void OnSceneLoaded(SceneFromFile sceneFromFile) dir = dir.Parent; if (dir != null) currentSceneDirectory = Path.Combine(dir.FullName, "Data", "Scenes", sceneName); - + ReferenceUtils.ScanReferences(currentSceneDirectory, referenceFiles); } } @@ -73,7 +73,7 @@ private void ApplyReferenceSettings() if (node != null) { var options = new JsonSerializerOptions { IncludeFields = true, PropertyNameCaseInsensitive = true }; var loadedIntegrator = JsonSerializer.Deserialize(node, currentIntegrator.GetType(), options); - + if (loadedIntegrator != null) { ReferenceUtils.CopyValues(currentIntegrator, loadedIntegrator); if (selectedFile.MaxDepth > 0) renderMaxDepth = selectedFile.MaxDepth; @@ -86,9 +86,9 @@ private void ApplyReferenceSettings() private void SelectReference(ReferenceInfo file) { selectedFile = file; - if (File.Exists(file.FilePath)) + if (File.Exists(file.FilePath)) UpdateViewerFromFile(file.FilePath); - + CheckParamsMatch(); } @@ -101,9 +101,9 @@ private void UpdateViewerFromFile(string path) if (File.Exists(path)) { // support legacy .exr files var layers = Layers.LoadFromFile(path); - if (layers.TryGetValue("", out Image image)) + if (layers.TryGetValue("", out Image image)) img = SceneFromFile.InpaintNaNs(image) as RgbImage; - else if (layers.TryGetValue("default", out var defaultImg)) + else if (layers.TryGetValue("default", out var defaultImg)) img = SceneFromFile.InpaintNaNs(defaultImg) as RgbImage; UpdatePreviewFromMemory(img); } @@ -112,11 +112,12 @@ private void UpdateViewerFromFile(string path) private async void UpdatePreviewFromMemory(RgbImage image) { var imgClone = new RgbImage(image.Width, image.Height); ReferenceUtils.CopyImage(imgClone, image); - flip = null; - StateHasChanged(); + flip = null; + StateHasChanged(); await Task.Delay(1); var newFlip = new FlipBook(660, 580); newFlip.Add("", imgClone); + newFlip.SetToolVisibility(false); flip = newFlip; StateHasChanged(); } @@ -145,7 +146,7 @@ await Task.Run(async () => { renderScene.FrameBuffer = new FrameBuffer(renderWidth, renderHeight, ""); renderScene.Prepare(); - renderIntegrator.Render(renderScene); + renderIntegrator.Render(renderScene); stopwatch.Stop(); @@ -181,13 +182,13 @@ await Task.Run(async () => { string referencesRoot = Path.Join(currentSceneDirectory, "References"); Directory.CreateDirectory(referencesRoot); string minDepthString = renderMinDepth > 1 ? $"MinDepth{renderMinDepth}-" : ""; - string finalPath = Path.Combine(referencesRoot, $"{minDepthString}MaxDepth{renderMaxDepth}-Width{renderWidth}-Height{renderHeight}.exr"); + string finalPath = Path.Combine(referencesRoot, $"{minDepthString}MaxDepth{renderMaxDepth}-Width{renderWidth}-Height{renderHeight}.exr"); if (File.Exists(finalPath)) File.Delete(finalPath); var resultImg = currentSceneFile.GetReferenceImage( - renderWidth, - renderHeight, - allowRender: true, + renderWidth, + renderHeight, + allowRender: true, config: renderIntegrator ); @@ -220,7 +221,7 @@ await Task.Run(async () => { string folder = Path.GetDirectoryName(filePath); string baseNameNoSuffix = Path.GetFileNameWithoutExtension(filePath).Replace("-partial", ""); string jsonPath = Path.Combine(folder, baseNameNoSuffix + (filePath.Contains("-partial") ? "-partial.json" : ".json")); - + string thisStepStart = DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss"); JsonNode rootNode = null; Integrator renderIntegrator = null; @@ -252,16 +253,16 @@ await Task.Run(async () => { ReferenceUtils.SetBaseSeed(renderIntegrator, originalBaseSeed + (uint)currentSpp); } - string currentPartialPath = Path.Combine(folder, baseNameNoSuffix + "-partial.exr"); + string currentPartialPath = Path.Combine(folder, baseNameNoSuffix + "-partial.exr"); var fbFlags = FrameBuffer.Flags.WriteContinously | FrameBuffer.Flags.WriteExponentially | FrameBuffer.Flags.IgnoreNanAndInf; - + scene.FrameBuffer = new FrameBuffer(oldImg.Width, oldImg.Height, currentPartialPath, fbFlags); scene.Prepare(); var stopwatch = Stopwatch.StartNew(); renderIntegrator.Render(scene); stopwatch.Stop(); - + long thisStepMs = stopwatch.ElapsedMilliseconds; string thisStepWrite = DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss"); @@ -337,12 +338,12 @@ private void CheckParamsMatch() if (string.IsNullOrEmpty(ver)) return (0, 0, 0); int idx = ver.IndexOfAny(new[] { '+', '-' }); if (idx >= 0) ver = ver.Substring(0, idx); - + var parts = ver.Split('.'); int maj = parts.Length > 0 && int.TryParse(parts[0], out int m) ? m : 0; int min = parts.Length > 1 && int.TryParse(parts[1], out int n) ? n : 0; int pat = parts.Length > 2 && int.TryParse(parts[2], out int p) ? p : 0; - + return (maj, min, pat); } @@ -367,12 +368,12 @@ private void CheckParamsMatch() var codeJson = JsonSerializer.Serialize(dummyIntegrator, targetType, options); var codeNode = JsonNode.Parse(codeJson)?.AsObject(); var fileNode = JsonNode.Parse(selectedFile.RawJsonConfig)?.AsObject(); - + if (codeNode != null && fileNode != null) { var codeKeys = codeNode.Select(k => k.Key).ToHashSet(); var fileKeys = fileNode.Select(k => k.Key).ToHashSet(); - + foreach (var key in fileKeys) { if (!codeKeys.Contains(key)) extraKeys.Add(key); } diff --git a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.css b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.css index eb04fdbb..d573ae1b 100644 --- a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.css +++ b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.css @@ -1,7 +1,7 @@ .experiment-layout { display: grid; - grid-template-columns: 350px 350px 1fr; - grid-template-rows: auto 1fr; + grid-template-columns: 350px 350px 1fr; + grid-template-rows: auto 1fr; gap: 15px; height: calc(100vh - 80px); padding: 10px; @@ -18,8 +18,8 @@ flex-direction: column; border: 1px solid #ccc; background: white; - overflow: hidden; - height: 600px; + overflow: hidden; + height: 600px; } .col-title { @@ -62,14 +62,14 @@ .right-col-wrapper { display: flex; - flex-direction: column; + flex-direction: column; gap: 15px; - height: auto; - overflow: visible; + height: auto; + overflow: visible; } .viewer-box { - height: 600px; + height: auto; display: flex; flex-direction: column; border: 1px solid #ccc; @@ -78,24 +78,24 @@ } .viewer-area { - flex: 1; + flex: 1; width: 100%; - background-color: #2e2e2e; + background-color: #2e2e2e; position: relative; display: flex; justify-content: center; align-items: center; - overflow: hidden; + overflow: hidden; } .meta-box { height: auto; - min-height: 100px; + min-height: 100px; border: 1px solid #ccc; background: white; display: flex; flex-direction: column; - margin-bottom: 10px; + margin-bottom: 10px; } .meta-content { @@ -106,7 +106,7 @@ .meta-grid { display: grid; - grid-template-columns: auto 1fr; + grid-template-columns: auto 1fr; column-gap: 10px; row-gap: 5px; font-size: smaller; @@ -114,7 +114,7 @@ } .meta-grid .label { font-weight: bold; - text-align: right; + text-align: right; } .empty-tip { From 3c00a37b2610b82f6fc97793b2c17040411941f2 Mon Sep 17 00:00:00 2001 From: Mengzhu Wang Date: Wed, 4 Feb 2026 19:25:57 +0100 Subject: [PATCH 08/10] fix scene load path, show full path --- SeeSharp.Blazor/SceneSelector.razor | 2 +- .../Pages/ReferenceRendering.razor.cs | 15 ++++++--------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/SeeSharp.Blazor/SceneSelector.razor b/SeeSharp.Blazor/SceneSelector.razor index 77a46387..6a7f8fcf 100644 --- a/SeeSharp.Blazor/SceneSelector.razor +++ b/SeeSharp.Blazor/SceneSelector.razor @@ -49,7 +49,7 @@ } else if (!string.IsNullOrEmpty(scene?.Name)) { -

Loaded "@(scene.Name)"

+

Loaded "@(scene.Name)" from "@scene.SourceDirectory"

}
diff --git a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs index 2e6fd787..6b565d46 100644 --- a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs +++ b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs @@ -32,16 +32,13 @@ private void OnSceneLoaded(SceneFromFile sceneFromFile) currentSceneFile = sceneFromFile; scene = sceneFromFile.MakeScene(); - if (!string.IsNullOrEmpty(sceneFromFile.Name)) + if (sceneFromFile != null) { - string sceneName = Path.GetFileNameWithoutExtension(sceneFromFile.Name); - var dir = new DirectoryInfo(AppContext.BaseDirectory); - while (dir != null && dir.Name != "SeeSharp") - dir = dir.Parent; - if (dir != null) - currentSceneDirectory = Path.Combine(dir.FullName, "Data", "Scenes", sceneName); - - ReferenceUtils.ScanReferences(currentSceneDirectory, referenceFiles); + currentSceneDirectory = sceneFromFile.SourceDirectory; + if (!string.IsNullOrEmpty(currentSceneDirectory)) + { + ReferenceUtils.ScanReferences(currentSceneDirectory, referenceFiles); + } } } From 1cb46d547f7b36c024cfd50106c80426597f5b4a Mon Sep 17 00:00:00 2001 From: Pascal Grittmann Date: Wed, 11 Feb 2026 11:54:32 +0100 Subject: [PATCH 09/10] polish --- .../Pages/ReferenceRendering.razor.cs | 297 ++++++++++++------ .../Pages/ReferenceUtils.cs | 9 +- 2 files changed, 202 insertions(+), 104 deletions(-) diff --git a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs index 6b565d46..e05e2b65 100644 --- a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs +++ b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs @@ -46,17 +46,21 @@ private void OnSceneLoaded(SceneFromFile sceneFromFile) private void SaveSceneConfig() { - if (integratorSelector?.CurrentIntegrator == null) return; + if (integratorSelector?.CurrentIntegrator == null) + return; string folder = Path.Combine(currentSceneDirectory, "References"); - if (!Directory.Exists(folder)) Directory.CreateDirectory(folder); + if (!Directory.Exists(folder)) + Directory.CreateDirectory(folder); ReferenceUtils.SaveConfig(folder, integratorSelector.CurrentIntegrator); StateHasChanged(); } private void ApplyReferenceSettings() { - if (selectedFile == null || string.IsNullOrEmpty(selectedFile.RawJsonConfig)) return; - if (integratorSelector == null) return; + if (selectedFile == null || string.IsNullOrEmpty(selectedFile.RawJsonConfig)) + return; + if (integratorSelector == null) + return; if (!string.IsNullOrEmpty(selectedFile.IntegratorName)) { @@ -64,17 +68,30 @@ private void ApplyReferenceSettings() } var currentIntegrator = integratorSelector.CurrentIntegrator; - if (currentIntegrator == null) return; + if (currentIntegrator == null) + return; var node = JsonNode.Parse(selectedFile.RawJsonConfig); - if (node != null) { - var options = new JsonSerializerOptions { IncludeFields = true, PropertyNameCaseInsensitive = true }; - var loadedIntegrator = JsonSerializer.Deserialize(node, currentIntegrator.GetType(), options); + if (node != null) + { + var options = new JsonSerializerOptions + { + IncludeFields = true, + PropertyNameCaseInsensitive = true, + }; + var loadedIntegrator = JsonSerializer.Deserialize( + node, + currentIntegrator.GetType(), + options + ); - if (loadedIntegrator != null) { + if (loadedIntegrator != null) + { ReferenceUtils.CopyValues(currentIntegrator, loadedIntegrator); - if (selectedFile.MaxDepth > 0) renderMaxDepth = selectedFile.MaxDepth; - if (selectedFile.MinDepth > 0) renderMinDepth = selectedFile.MinDepth; + if (selectedFile.MaxDepth > 0) + renderMaxDepth = selectedFile.MaxDepth; + if (selectedFile.MinDepth > 0) + renderMinDepth = selectedFile.MinDepth; StateHasChanged(); } } @@ -95,7 +112,8 @@ private void UpdateViewerFromFile(string path) StateHasChanged(); RgbImage img = null; - if (File.Exists(path)) { + if (File.Exists(path)) + { // support legacy .exr files var layers = Layers.LoadFromFile(path); if (layers.TryGetValue("", out Image image)) @@ -106,22 +124,23 @@ private void UpdateViewerFromFile(string path) } } - private async void UpdatePreviewFromMemory(RgbImage image) { - var imgClone = new RgbImage(image.Width, image.Height); - ReferenceUtils.CopyImage(imgClone, image); + private async void UpdatePreviewFromMemory(RgbImage image) + { + var imgClone = image.Copy(); flip = null; StateHasChanged(); await Task.Delay(1); - var newFlip = new FlipBook(660, 580); - newFlip.Add("", imgClone); - newFlip.SetToolVisibility(false); - flip = newFlip; + flip = new FlipBook() + .Add("", imgClone) + .SetToolVisibility(false) + .SetCustomSizeCSS("width: 100%; height: 580px; resize: vertical; overflow: auto;"); StateHasChanged(); } async Task RenderQuickPreview() { - if (currentSceneFile == null || integratorSelector.addedIntegrators.Count == 0) return; + if (currentSceneFile == null || integratorSelector.addedIntegrators.Count == 0) + return; isRendering = true; selectedFile = null; @@ -131,28 +150,29 @@ async Task RenderQuickPreview() var curIntegrator = integratorSelector.addedIntegrators.First(); - await Task.Run(async () => { - var stopwatch = Stopwatch.StartNew(); - var renderScene = currentSceneFile.MakeScene(); - var renderIntegrator = ReferenceUtils.CloneIntegrator(curIntegrator); + await Task.Run(async () => + { + var stopwatch = Stopwatch.StartNew(); + var renderScene = currentSceneFile.MakeScene(); + var renderIntegrator = ReferenceUtils.CloneIntegrator(curIntegrator); - int targetSpp = quickPreviewSpp < 1 ? 1 : quickPreviewSpp; - ReferenceUtils.SetBatchSpp(renderIntegrator, targetSpp); - ReferenceUtils.SetMaxDepth(renderIntegrator, renderMaxDepth); - ReferenceUtils.SetMinDepth(renderIntegrator, renderMinDepth); + int targetSpp = quickPreviewSpp < 1 ? 1 : quickPreviewSpp; + ReferenceUtils.SetBatchSpp(renderIntegrator, targetSpp); + ReferenceUtils.SetMaxDepth(renderIntegrator, renderMaxDepth); + ReferenceUtils.SetMinDepth(renderIntegrator, renderMinDepth); - renderScene.FrameBuffer = new FrameBuffer(renderWidth, renderHeight, ""); - renderScene.Prepare(); - renderIntegrator.Render(renderScene); + renderScene.FrameBuffer = new FrameBuffer(renderWidth, renderHeight, ""); + renderScene.Prepare(); + renderIntegrator.Render(renderScene); - stopwatch.Stop(); + stopwatch.Stop(); - var resultImg = new RgbImage(renderWidth, renderHeight); - ReferenceUtils.CopyImage(resultImg, renderScene.FrameBuffer.Image); + var resultImg = renderScene.FrameBuffer.Image.Copy() as RgbImage; - await InvokeAsync(() => { - UpdatePreviewFromMemory(resultImg); - }); + await InvokeAsync(() => + { + UpdatePreviewFromMemory(resultImg); + }); }); isRendering = false; @@ -161,14 +181,16 @@ await InvokeAsync(() => { async Task RenderReference() { - if (currentSceneFile == null || integratorSelector.addedIntegrators.Count == 0) return; + if (currentSceneFile == null || integratorSelector.addedIntegrators.Count == 0) + return; isRendering = true; flip = null; await InvokeAsync(StateHasChanged); var curIntegrator = integratorSelector.addedIntegrators.First(); - await Task.Run(async () => { + await Task.Run(async () => + { var renderIntegrator = ReferenceUtils.CloneIntegrator(curIntegrator); int targetSpp = ReferenceUtils.GetTargetSpp(renderIntegrator); ReferenceUtils.SetBatchSpp(renderIntegrator, targetSpp); @@ -179,8 +201,12 @@ await Task.Run(async () => { string referencesRoot = Path.Join(currentSceneDirectory, "References"); Directory.CreateDirectory(referencesRoot); string minDepthString = renderMinDepth > 1 ? $"MinDepth{renderMinDepth}-" : ""; - string finalPath = Path.Combine(referencesRoot, $"{minDepthString}MaxDepth{renderMaxDepth}-Width{renderWidth}-Height{renderHeight}.exr"); - if (File.Exists(finalPath)) File.Delete(finalPath); + string finalPath = Path.Combine( + referencesRoot, + $"{minDepthString}MaxDepth{renderMaxDepth}-Width{renderWidth}-Height{renderHeight}.exr" + ); + if (File.Exists(finalPath)) + File.Delete(finalPath); var resultImg = currentSceneFile.GetReferenceImage( renderWidth, @@ -189,10 +215,12 @@ await Task.Run(async () => { config: renderIntegrator ); - await InvokeAsync(() => { + await InvokeAsync(() => + { ReferenceUtils.ScanReferences(currentSceneDirectory, referenceFiles); selectedFile = referenceFiles.FirstOrDefault(r => r.FilePath == finalPath); - if (selectedFile != null) UpdateViewerFromFile(finalPath); + if (selectedFile != null) + UpdateViewerFromFile(finalPath); StateHasChanged(); }); }); @@ -203,21 +231,29 @@ await InvokeAsync(() => { async Task RenderMoreSamples(bool isResume) { - if (currentSceneFile == null || selectedFile == null || !File.Exists(selectedFile.FilePath)) return; - if (integratorSelector.addedIntegrators.Count == 0) return; + if (currentSceneFile == null || selectedFile == null || !File.Exists(selectedFile.FilePath)) + return; + if (integratorSelector.addedIntegrators.Count == 0) + return; isRendering = true; await InvokeAsync(StateHasChanged); - await Task.Run(async () => { - try { + await Task.Run(async () => + { + try + { string filePath = selectedFile.FilePath; var oldImg = new RgbImage(filePath); int currentSpp = selectedFile.Spp; string folder = Path.GetDirectoryName(filePath); - string baseNameNoSuffix = Path.GetFileNameWithoutExtension(filePath).Replace("-partial", ""); - string jsonPath = Path.Combine(folder, baseNameNoSuffix + (filePath.Contains("-partial") ? "-partial.json" : ".json")); + string baseNameNoSuffix = Path.GetFileNameWithoutExtension(filePath) + .Replace("-partial", ""); + string jsonPath = Path.Combine( + folder, + baseNameNoSuffix + (filePath.Contains("-partial") ? "-partial.json" : ".json") + ); string thisStepStart = DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss"); JsonNode rootNode = null; @@ -225,35 +261,61 @@ await Task.Run(async () => { long totalPreviousMs = 0; int settingsTargetSpp = 0; - if (File.Exists(jsonPath)) { + if (File.Exists(jsonPath)) + { rootNode = JsonNode.Parse(File.ReadAllText(jsonPath)); totalPreviousMs = (long)(rootNode?["RenderTime"]?.GetValue() ?? 0); var settingsNode = rootNode?["Settings"]; - if (settingsNode != null) { - settingsTargetSpp = settingsNode["TotalSpp"]?.GetValue() ?? settingsNode["NumIterations"]?.GetValue() ?? 0; - Type targetType = integratorSelector.GetIntegratorType(rootNode["Name"]?.GetValue()); - if (targetType != null) { - var options = new JsonSerializerOptions { IncludeFields = true, PropertyNameCaseInsensitive = true }; - renderIntegrator = JsonSerializer.Deserialize(settingsNode, targetType, options) as Integrator; + if (settingsNode != null) + { + settingsTargetSpp = + settingsNode["TotalSpp"]?.GetValue() + ?? settingsNode["NumIterations"]?.GetValue() + ?? 0; + Type targetType = integratorSelector.GetIntegratorType( + rootNode["Name"]?.GetValue() + ); + if (targetType != null) + { + var options = new JsonSerializerOptions + { + IncludeFields = true, + PropertyNameCaseInsensitive = true, + }; + renderIntegrator = + JsonSerializer.Deserialize(settingsNode, targetType, options) + as Integrator; } } } int batchSpp = isResume ? (settingsTargetSpp - currentSpp) : additionalSpp; int finalTotalSpp = currentSpp + batchSpp; - if (batchSpp <= 0) return; + if (batchSpp <= 0) + return; ReferenceUtils.SetBatchSpp(renderIntegrator, batchSpp); if (!isResume) { uint originalBaseSeed = ReferenceUtils.GetBaseSeed(renderIntegrator); - ReferenceUtils.SetBaseSeed(renderIntegrator, originalBaseSeed + (uint)currentSpp); + ReferenceUtils.SetBaseSeed( + renderIntegrator, + originalBaseSeed + (uint)currentSpp + ); } string currentPartialPath = Path.Combine(folder, baseNameNoSuffix + "-partial.exr"); - var fbFlags = FrameBuffer.Flags.WriteContinously | FrameBuffer.Flags.WriteExponentially | FrameBuffer.Flags.IgnoreNanAndInf; - - scene.FrameBuffer = new FrameBuffer(oldImg.Width, oldImg.Height, currentPartialPath, fbFlags); + var fbFlags = + FrameBuffer.Flags.WriteContinously + | FrameBuffer.Flags.WriteExponentially + | FrameBuffer.Flags.IgnoreNanAndInf; + + scene.FrameBuffer = new FrameBuffer( + oldImg.Width, + oldImg.Height, + currentPartialPath, + fbFlags + ); scene.Prepare(); var stopwatch = Stopwatch.StartNew(); @@ -268,51 +330,82 @@ await Task.Run(async () => { float wOld = (float)currentSpp / finalTotalSpp; float wNew = (float)batchSpp / finalTotalSpp; - Parallel.For(0, finalImg.Height, row => { - for (int col = 0; col < finalImg.Width; ++col) { - finalImg.SetPixel(col, row, oldImg.GetPixel(col, row) * wOld + newImg.GetPixel(col, row) * wNew); + Parallel.For( + 0, + finalImg.Height, + row => + { + for (int col = 0; col < finalImg.Width; ++col) + { + finalImg.SetPixel( + col, + row, + oldImg.GetPixel(col, row) * wOld + newImg.GetPixel(col, row) * wNew + ); + } } - }); + ); - if (rootNode == null) rootNode = new JsonObject(); + if (rootNode == null) + rootNode = new JsonObject(); rootNode["RenderTime"] = totalPreviousMs + thisStepMs; rootNode["RenderWriteTime"] = thisStepWrite; rootNode["NumIterations"] = finalTotalSpp; var steps = rootNode["RenderSteps"]?.AsArray() ?? new JsonArray(); - steps.Add(new JsonObject { - ["Type"] = isResume ? "Resume" : "More", - ["DurationMs"] = thisStepMs, - ["StartTime"] = thisStepStart, - ["WriteTime"] = thisStepWrite - }); + steps.Add( + new JsonObject + { + ["Type"] = isResume ? "Resume" : "More", + ["DurationMs"] = thisStepMs, + ["StartTime"] = thisStepStart, + ["WriteTime"] = thisStepWrite, + } + ); rootNode["RenderSteps"] = steps; - if (!isResume && rootNode["Settings"] != null) { + if (!isResume && rootNode["Settings"] != null) + { rootNode["Settings"]["TotalSpp"] = finalTotalSpp; } - string finalSavePath = isResume ? Path.Combine(folder, baseNameNoSuffix + ".exr") : filePath; + string finalSavePath = isResume + ? Path.Combine(folder, baseNameNoSuffix + ".exr") + : filePath; finalImg.WriteToFile(finalSavePath); - File.WriteAllText(Path.ChangeExtension(finalSavePath, ".json"), rootNode.ToJsonString(new JsonSerializerOptions { WriteIndented = true })); + File.WriteAllText( + Path.ChangeExtension(finalSavePath, ".json"), + rootNode.ToJsonString(new JsonSerializerOptions { WriteIndented = true }) + ); - if (File.Exists(currentPartialPath)) File.Delete(currentPartialPath); + if (File.Exists(currentPartialPath)) + File.Delete(currentPartialPath); string partialJsonPath = Path.ChangeExtension(currentPartialPath, ".json"); - if (File.Exists(partialJsonPath)) File.Delete(partialJsonPath); + if (File.Exists(partialJsonPath)) + File.Delete(partialJsonPath); - if (isResume && filePath != finalSavePath) { - if (File.Exists(filePath)) File.Delete(filePath); + if (isResume && filePath != finalSavePath) + { + if (File.Exists(filePath)) + File.Delete(filePath); string oldJ = Path.ChangeExtension(filePath, ".json"); - if (File.Exists(oldJ)) File.Delete(oldJ); + if (File.Exists(oldJ)) + File.Delete(oldJ); } - await InvokeAsync(() => { + await InvokeAsync(() => + { ReferenceUtils.ScanReferences(currentSceneDirectory, referenceFiles); selectedFile = referenceFiles.FirstOrDefault(r => r.FilePath == finalSavePath); - if (selectedFile != null) UpdateViewerFromFile(finalSavePath); + if (selectedFile != null) + UpdateViewerFromFile(finalSavePath); StateHasChanged(); }); - } catch (Exception ex) { Console.WriteLine(ex.ToString()); } + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + } }); isRendering = false; @@ -327,14 +420,18 @@ private void CheckParamsMatch() extraKeys.Clear(); missingKeys.Clear(); - if (selectedFile == null) return; - if (string.IsNullOrEmpty(selectedFile.RawJsonConfig)) return; + if (selectedFile == null) + return; + if (string.IsNullOrEmpty(selectedFile.RawJsonConfig)) + return; (int Major, int Minor, int Patch) ParseSemVer(string ver) { - if (string.IsNullOrEmpty(ver)) return (0, 0, 0); + if (string.IsNullOrEmpty(ver)) + return (0, 0, 0); int idx = ver.IndexOfAny(new[] { '+', '-' }); - if (idx >= 0) ver = ver.Substring(0, idx); + if (idx >= 0) + ver = ver.Substring(0, idx); var parts = ver.Split('.'); int maj = parts.Length > 0 && int.TryParse(parts[0], out int m) ? m : 0; @@ -350,11 +447,14 @@ private void CheckParamsMatch() var cur = ParseSemVer(currentVerStr); var file = ParseSemVer(fileVerStr); - if (cur.Major != file.Major || cur.Minor != file.Minor) isVersionMismatch = true; - else if (cur.Patch != file.Patch) isVersionWarning = true; + if (cur.Major != file.Major || cur.Minor != file.Minor) + isVersionMismatch = true; + else if (cur.Patch != file.Patch) + isVersionWarning = true; Type targetType = integratorSelector?.GetIntegratorType(selectedFile.IntegratorName); - if (targetType == null) { + if (targetType == null) + { isStructureMismatch = true; StateHasChanged(); return; @@ -371,15 +471,20 @@ private void CheckParamsMatch() var codeKeys = codeNode.Select(k => k.Key).ToHashSet(); var fileKeys = fileNode.Select(k => k.Key).ToHashSet(); - foreach (var key in fileKeys) { - if (!codeKeys.Contains(key)) extraKeys.Add(key); + foreach (var key in fileKeys) + { + if (!codeKeys.Contains(key)) + extraKeys.Add(key); } - foreach (var key in codeKeys) { - if (!fileKeys.Contains(key)) missingKeys.Add(key); + foreach (var key in codeKeys) + { + if (!fileKeys.Contains(key)) + missingKeys.Add(key); } - if (extraKeys.Count > 0 || missingKeys.Count > 0) isStructureMismatch = true; + if (extraKeys.Count > 0 || missingKeys.Count > 0) + isStructureMismatch = true; } StateHasChanged(); } -} \ No newline at end of file +} diff --git a/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs b/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs index 2b151096..1c01a3eb 100644 --- a/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs +++ b/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs @@ -52,7 +52,7 @@ public static void ScanReferences(string sceneDir, List reference public static void ParseExrName(ReferenceInfo info, string filePath) { string filename = Path.GetFileNameWithoutExtension(filePath); var match = Regex.Match(filename, @"(?:MinDepth(?\d+)-)?MaxDepth(?\d+)-Width(?\d+)-Height(?\d+)", RegexOptions.IgnoreCase); - + if (match.Success) { info.MinDepth = match.Groups["min"].Success ? int.Parse(match.Groups["min"].Value) : 1; info.MaxDepth = int.Parse(match.Groups["max"].Value); @@ -131,13 +131,6 @@ bool IsConfigParam(Type t) { } } - public static void CopyImage(RgbImage target, RgbImage source) { - Parallel.For(0, target.Height, y => { - for (int x = 0; x < target.Width; ++x) - target.SetPixel(x, y, source.GetPixel(x, y)); - }); - } - public static Integrator CloneIntegrator(Integrator source) { var type = source.GetType(); var clone = (Integrator)Activator.CreateInstance(type); From 81333e48ffc5b43d4c9bcdef6e213e262b3179de Mon Sep 17 00:00:00 2001 From: Pascal Grittmann Date: Wed, 11 Feb 2026 15:18:21 +0100 Subject: [PATCH 10/10] refactor common Integrator fields --- MaterialTest/Pages/IntegratorTest.razor.cs | 3 +- README.md | 2 +- .../GenericMaterial_Sampling.cs | 2 +- SeeSharp.Benchmark/Program.cs | 4 +- SeeSharp.Examples/MisCompensation.dib | 4 +- SeeSharp.Examples/PathVsVcm.cs | 7 +- SeeSharp.Examples/SphericalSampling.dib | 6 +- SeeSharp.IntegrationTests/LightProbeTest.cs | 2 +- SeeSharp.IntegrationTests/OutlierCacheTest.cs | 2 +- SeeSharp.IntegrationTests/Program.cs | 4 +- SeeSharp.PreviewRender/Program.cs | 6 +- .../Pages/IntegratorSelector.razor.cs | 20 ++-- .../Pages/ReferenceRendering.razor.cs | 20 ++-- .../Pages/ReferenceUtils.cs | 102 ++++++------------ .../Pages/RenderSetting.razor | 14 +-- .../Pages/Experiment.razor.cs | 4 +- .../content/SeeSharp.Template/MyExperiment.cs | 2 +- SeeSharp.Validation/Validator.cs | 6 +- SeeSharp/Common/ProgressBar.cs | 8 +- SeeSharp/Experiments/SceneFromFile.cs | 8 +- SeeSharp/Integrators/Bidir/BidirBase.cs | 7 +- .../Integrators/Bidir/CameraStoringVCM.cs | 2 - SeeSharp/Integrators/Bidir/PhotonMapper.cs | 5 - SeeSharp/Integrators/Bidir/TechPyramid.cs | 5 +- .../Integrators/Bidir/VertexCacheBidir.cs | 2 +- SeeSharp/Integrators/DebugVisualizer.cs | 7 +- SeeSharp/Integrators/Integrator.cs | 10 ++ SeeSharp/Integrators/PathTracer.cs | 13 +-- 28 files changed, 107 insertions(+), 170 deletions(-) diff --git a/MaterialTest/Pages/IntegratorTest.razor.cs b/MaterialTest/Pages/IntegratorTest.razor.cs index 521cf499..ebc203df 100644 --- a/MaterialTest/Pages/IntegratorTest.razor.cs +++ b/MaterialTest/Pages/IntegratorTest.razor.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore.Components; -using SeeSharp.Blazor; namespace MaterialTest.Pages; @@ -22,7 +21,7 @@ void RunExperiment() scene.Prepare(); VertexConnectionAndMerging vcm = new() { - NumIterations = NumSamples, + NumIterations = (uint)NumSamples, MaxDepth = MaxDepth, RenderTechniquePyramid = true }; diff --git a/README.md b/README.md index 376cafac..476ec0a8 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ using SeeSharp.Integrators.Bidir; // Configure an experiment that compares VCM and path tracing. class PathVsVcm : Experiment { public override List MakeMethods() => new() { - new("PathTracer", new PathTracer() { MaxDepth = 5, TotalSpp = 4 }), + new("PathTracer", new PathTracer() { MaxDepth = 5, NumIterations = 4 }), new("Vcm", new VertexConnectionAndMerging() { MaxDepth = 5, NumIterations = 2 }) }; } diff --git a/SeeSharp.Benchmark/GenericMaterial_Sampling.cs b/SeeSharp.Benchmark/GenericMaterial_Sampling.cs index 7d468e56..bb8ab86f 100644 --- a/SeeSharp.Benchmark/GenericMaterial_Sampling.cs +++ b/SeeSharp.Benchmark/GenericMaterial_Sampling.cs @@ -69,7 +69,7 @@ static float TestRender(GenericMaterial.Parameters parameters, string name) { scene.Prepare(); PathTracer integrator = new() { - TotalSpp = 1, + NumIterations = 1, NumShadowRays = 0, MaxDepth = 2, }; diff --git a/SeeSharp.Benchmark/Program.cs b/SeeSharp.Benchmark/Program.cs index ff46c0ad..9eb54f27 100644 --- a/SeeSharp.Benchmark/Program.cs +++ b/SeeSharp.Benchmark/Program.cs @@ -7,7 +7,7 @@ SceneRegistry.AddSourceRelativeToScript("../data/scenes"); BenchRender("PathTracer - 16spp", new PathTracer() { - TotalSpp = 16, + NumIterations = 16, }); BenchRender("BDPT - 8spp", new VertexCacheBidir() { @@ -51,4 +51,4 @@ void BenchRender(string name, Integrator integrator) { GenericMaterial_Sampling.Benchmark(); -VectorBench.BenchComputeBasisVectors(10000000); +VectorBench.BenchComputeBasisVectors(10000000); \ No newline at end of file diff --git a/SeeSharp.Examples/MisCompensation.dib b/SeeSharp.Examples/MisCompensation.dib index cd01526b..f0a840a0 100644 --- a/SeeSharp.Examples/MisCompensation.dib +++ b/SeeSharp.Examples/MisCompensation.dib @@ -25,7 +25,7 @@ scene.FrameBuffer = new(512, 512, ""); scene.Prepare(); new PathTracer() { - TotalSpp = 10 + NumIterations = 10 }.Render(scene); var plain = scene.FrameBuffer.Image; @@ -36,7 +36,7 @@ scene.FrameBuffer = new(512, 512, ""); scene.Prepare(); new PathTracer() { - TotalSpp = 10 + NumIterations = 10 }.Render(scene); HTML(FlipBook.Make(("plain", plain), ("comp", scene.FrameBuffer.Image))) diff --git a/SeeSharp.Examples/PathVsVcm.cs b/SeeSharp.Examples/PathVsVcm.cs index d33ec990..d28faa13 100644 --- a/SeeSharp.Examples/PathVsVcm.cs +++ b/SeeSharp.Examples/PathVsVcm.cs @@ -1,8 +1,3 @@ -using SeeSharp.Experiments; -using SeeSharp.Integrators; -using SeeSharp.Integrators.Bidir; -using System.Collections.Generic; - namespace SeeSharp.Examples; /// @@ -10,7 +5,7 @@ namespace SeeSharp.Examples; /// class PathVsVcm : Experiment { public override List MakeMethods() => [ - new("PathTracer", new PathTracer() { TotalSpp = 4 }), + new("PathTracer", new PathTracer() { NumIterations = 4 }), new("Vcm", new VertexConnectionAndMerging() { NumIterations = 2 }) ]; } \ No newline at end of file diff --git a/SeeSharp.Examples/SphericalSampling.dib b/SeeSharp.Examples/SphericalSampling.dib index 0844d17e..e7aa1cff 100644 --- a/SeeSharp.Examples/SphericalSampling.dib +++ b/SeeSharp.Examples/SphericalSampling.dib @@ -37,18 +37,16 @@ scene.Prepare(); #!csharp abstract class DirectIllum : Integrator { - public int TotalSpp = 20; - protected Scene scene; public DirectIllum(int spp) { - TotalSpp = spp; + SampleCount = spp; } public override void Render(Scene scene) { this.scene = scene; - for (uint sampleIndex = 0; sampleIndex < TotalSpp; ++sampleIndex) { + for (uint sampleIndex = 0; sampleIndex < SampleCount; ++sampleIndex) { scene.FrameBuffer.StartIteration(); Parallel.For(0, scene.FrameBuffer.Height, row => { for (uint col = 0; col < scene.FrameBuffer.Width; ++col) { diff --git a/SeeSharp.IntegrationTests/LightProbeTest.cs b/SeeSharp.IntegrationTests/LightProbeTest.cs index a66136b5..7171b85c 100644 --- a/SeeSharp.IntegrationTests/LightProbeTest.cs +++ b/SeeSharp.IntegrationTests/LightProbeTest.cs @@ -88,4 +88,4 @@ public static void CornellProbe() { scene.FrameBuffer.WriteToFile(); } } -} +} \ No newline at end of file diff --git a/SeeSharp.IntegrationTests/OutlierCacheTest.cs b/SeeSharp.IntegrationTests/OutlierCacheTest.cs index 2198fe85..89e2efa4 100644 --- a/SeeSharp.IntegrationTests/OutlierCacheTest.cs +++ b/SeeSharp.IntegrationTests/OutlierCacheTest.cs @@ -15,7 +15,7 @@ public static void RenderPT() { scene.Prepare(); var integrator = new PathTracer() { - TotalSpp = 4, + NumIterations = 4, MaxDepth = 10, BaseSeed = 1234, }; diff --git a/SeeSharp.IntegrationTests/Program.cs b/SeeSharp.IntegrationTests/Program.cs index 038ddf71..41bfb769 100644 --- a/SeeSharp.IntegrationTests/Program.cs +++ b/SeeSharp.IntegrationTests/Program.cs @@ -12,7 +12,7 @@ public static void PathTracerTimeBudget() { scene.Prepare(); var integrator = new PathTracer() { - TotalSpp = 498989, + NumIterations = 498989, MaximumRenderTimeMs = 4500, MaxDepth = 5 }; @@ -43,4 +43,4 @@ static void Main(string[] args) { // OutlierCacheTest.RenderPT(); OutlierCacheTest.RenderVCM(); } -} +} \ No newline at end of file diff --git a/SeeSharp.PreviewRender/Program.cs b/SeeSharp.PreviewRender/Program.cs index d4e5209b..56e73839 100644 --- a/SeeSharp.PreviewRender/Program.cs +++ b/SeeSharp.PreviewRender/Program.cs @@ -44,12 +44,12 @@ static int Main( if (algo == "PT") { new PathTracer() { MaxDepth = maxdepth, - TotalSpp = samples, + NumIterations = (uint)samples, }.Render(sc); } else if (algo == "VCM") { new VertexConnectionAndMerging() { MaxDepth = maxdepth, - NumIterations = samples, + NumIterations = (uint)samples, }.Render(sc); } else { Logger.Error($"Unknown rendering algorithm: {algo}. Use PT or VCM"); @@ -67,4 +67,4 @@ static int Main( return 0; } -} +} \ No newline at end of file diff --git a/SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor.cs b/SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor.cs index a44bbae1..956decf0 100644 --- a/SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor.cs +++ b/SeeSharp.ReferenceManager/Pages/IntegratorSelector.razor.cs @@ -5,20 +5,20 @@ namespace SeeSharp.ReferenceManager.Pages; public partial class IntegratorSelector : ComponentBase { [Parameter] public Scene scene { get; set; } = default!; - + public List addedIntegrators { get; private set; } = new(); - - public Integrator? CurrentIntegrator => addedIntegrators.FirstOrDefault(); + + public Integrator CurrentIntegrator => addedIntegrators.FirstOrDefault(); Type[] integratorTypes = Array.Empty(); - string? selectedIntegrator; + string selectedIntegrator; private string lastIntegrator; protected override void OnInitialized() { var types = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(assembly => assembly.GetTypes()) - .Where(type => type.IsClass && !type.IsAbstract && typeof(Integrator).IsAssignableFrom(type) && + .Where(type => type.IsClass && !type.IsAbstract && typeof(Integrator).IsAssignableFrom(type) && !type.ContainsGenericParameters && !typeof(DebugVisualizer).IsAssignableFrom(type)); integratorTypes = types.Where(t => !types.Any(other => other.IsSubclassOf(t))).ToArray(); @@ -54,7 +54,7 @@ void ReplaceIntegrator() StateHasChanged(); } - protected List GetParameterGroups(Integrator integrator) + protected List GetParameterGroups(Integrator integrator) => IntegratorUtils.GetParameterGroups(integrator); protected string FormatClassName(Type t) => IntegratorUtils.FormatClassName(t); @@ -68,7 +68,7 @@ public void TriggerReset() public bool TrySelectIntegrator(string simpleName) { var targetType = integratorTypes.FirstOrDefault(t => t.Name == simpleName || t.Name == simpleName + "`1"); - + if (targetType != null && targetType.FullName != selectedIntegrator) { selectedIntegrator = targetType.FullName; @@ -78,11 +78,11 @@ public bool TrySelectIntegrator(string simpleName) return targetType != null; } - public Type? GetIntegratorType(string simpleName) + public Type GetIntegratorType(string simpleName) { if (string.IsNullOrEmpty(simpleName)) return null; - return integratorTypes.FirstOrDefault(t => - t.Name.Equals(simpleName, StringComparison.OrdinalIgnoreCase) || + return integratorTypes.FirstOrDefault(t => + t.Name.Equals(simpleName, StringComparison.OrdinalIgnoreCase) || t.Name.Equals(simpleName + "`1", StringComparison.OrdinalIgnoreCase)); } } \ No newline at end of file diff --git a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs index e05e2b65..c10464a1 100644 --- a/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs +++ b/SeeSharp.ReferenceManager/Pages/ReferenceRendering.razor.cs @@ -157,9 +157,9 @@ await Task.Run(async () => var renderIntegrator = ReferenceUtils.CloneIntegrator(curIntegrator); int targetSpp = quickPreviewSpp < 1 ? 1 : quickPreviewSpp; - ReferenceUtils.SetBatchSpp(renderIntegrator, targetSpp); - ReferenceUtils.SetMaxDepth(renderIntegrator, renderMaxDepth); - ReferenceUtils.SetMinDepth(renderIntegrator, renderMinDepth); + renderIntegrator.NumIterations = (uint)targetSpp; + renderIntegrator.MinDepth = renderMinDepth; + renderIntegrator.MaxDepth = renderMaxDepth; renderScene.FrameBuffer = new FrameBuffer(renderWidth, renderHeight, ""); renderScene.Prepare(); @@ -192,9 +192,6 @@ async Task RenderReference() await Task.Run(async () => { var renderIntegrator = ReferenceUtils.CloneIntegrator(curIntegrator); - int targetSpp = ReferenceUtils.GetTargetSpp(renderIntegrator); - ReferenceUtils.SetBatchSpp(renderIntegrator, targetSpp); - currentSceneFile.MaxDepth = renderMaxDepth; currentSceneFile.MinDepth = renderMinDepth; @@ -294,14 +291,11 @@ await Task.Run(async () => if (batchSpp <= 0) return; - ReferenceUtils.SetBatchSpp(renderIntegrator, batchSpp); + renderIntegrator.NumIterations = (uint)batchSpp; if (!isResume) { - uint originalBaseSeed = ReferenceUtils.GetBaseSeed(renderIntegrator); - ReferenceUtils.SetBaseSeed( - renderIntegrator, - originalBaseSeed + (uint)currentSpp - ); + uint originalBaseSeed = renderIntegrator.BaseSeed; + renderIntegrator.BaseSeed = originalBaseSeed + (uint)currentSpp; } string currentPartialPath = Path.Combine(folder, baseNameNoSuffix + "-partial.exr"); @@ -487,4 +481,4 @@ private void CheckParamsMatch() StateHasChanged(); } -} +} \ No newline at end of file diff --git a/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs b/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs index 1c01a3eb..c9e66631 100644 --- a/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs +++ b/SeeSharp.ReferenceManager/Pages/ReferenceUtils.cs @@ -138,49 +138,42 @@ public static Integrator CloneIntegrator(Integrator source) { return clone; } - public static void SetMaxDepth(Integrator integrator, int depth) { - var type = integrator.GetType(); - var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; - var prop = type.GetProperty("MaxDepth", flags); - if (prop != null && prop.CanWrite) { - prop.SetValue(integrator, depth); - return; - } - var field = type.GetField("MaxDepth", flags); - if (field != null) { - field.SetValue(integrator, depth); - } - } + static T GetFieldOrProperty(object instance, string name) + { + var type = instance.GetType(); + var flags = BindingFlags.Public | BindingFlags.Instance; - public static void SetMinDepth(Integrator integrator, int depth) { - var type = integrator.GetType(); - var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; - var prop = type.GetProperty("MinDepth", flags); - if (prop != null && prop.CanWrite) { - prop.SetValue(integrator, depth); - return; - } - var field = type.GetField("MinDepth", flags); - if (field != null) { - field.SetValue(integrator, depth); - } + var propSpp = type.GetProperty(name, flags); + if (propSpp != null) + return (T)propSpp.GetValue(instance); + + var fieldSpp = type.GetField(name, flags); + if (fieldSpp != null) + return (T)fieldSpp.GetValue(instance); + + throw new ArgumentException($"No field or property named '{name}' in '{instance.GetType()}'"); } - public static int GetTargetSpp(Integrator integrator) { - var type = integrator.GetType(); - var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; - var propSpp = type.GetProperty("TotalSpp", flags); - if (propSpp != null) return (int)propSpp.GetValue(integrator); + static void SetFieldOrProperty(object instance, string name, T value) + { + var type = instance.GetType(); + var flags = BindingFlags.Public | BindingFlags.Instance; - var fieldSpp = type.GetField("TotalSpp", flags); - if (fieldSpp != null) return (int)fieldSpp.GetValue(integrator); + var prop = type.GetProperty(name, flags); + if (prop != null && prop.CanWrite) + { + prop.SetValue(instance, value); + return; + } - var propIter = type.GetProperty("NumIterations", flags); - if (propIter != null) return (int)propIter.GetValue(integrator); + var field = type.GetField(name, flags); + if (field != null) + { + field.SetValue(instance, value); + return; + } - var fieldIter = type.GetField("NumIterations", flags); - if (fieldIter != null) return (int)fieldIter.GetValue(integrator); - return 16; + throw new ArgumentException($"No field or property named '{name}' in '{instance.GetType()}'"); } public static void SaveConfig(string folder, Integrator integrator) { @@ -192,41 +185,6 @@ public static void SaveConfig(string folder, Integrator integrator) { File.WriteAllText(Path.Combine(folder, "Config.json"), rootNode.ToJsonString(options)); } - public static void SetBatchSpp(Integrator integrator, int batchCount) { - var type = integrator.GetType(); - var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; - var targetNames = new[] { "TotalSpp", "NumIterations"}; - foreach (var name in targetNames) { - var prop = type.GetProperty(name, flags); - if (prop != null && prop.CanWrite) { - prop.SetValue(integrator, batchCount); - return; - } - var field = type.GetField(name, flags); - if (field != null) { - field.SetValue(integrator, batchCount); - return; - } - } - } - - public static uint GetBaseSeed(Integrator integrator) { - var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; - var seedProp = integrator.GetType().GetProperty("BaseSeed", flags); - var seedField = integrator.GetType().GetField("BaseSeed", flags); - if (seedProp != null) return (uint)(seedProp.GetValue(integrator) ?? 0u); - if (seedField != null) return (uint)(seedField.GetValue(integrator) ?? 0u); - return 0; - } - - public static void SetBaseSeed(Integrator integrator, uint seed) { - var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; - var seedProp = integrator.GetType().GetProperty("BaseSeed", flags); - var seedField = integrator.GetType().GetField("BaseSeed", flags); - if (seedProp != null) seedProp.SetValue(integrator, seed); - else if (seedField != null) seedField.SetValue(integrator, seed); - } - public static string CurrentSeeSharpVersion { get; } = typeof(Scene).Assembly .GetCustomAttribute()? diff --git a/SeeSharp.ReferenceManager/Pages/RenderSetting.razor b/SeeSharp.ReferenceManager/Pages/RenderSetting.razor index 30d84471..50d1404a 100644 --- a/SeeSharp.ReferenceManager/Pages/RenderSetting.razor +++ b/SeeSharp.ReferenceManager/Pages/RenderSetting.razor @@ -5,15 +5,15 @@ @{ string name = Member.Name; string desc = IntegratorUtils.GetDescription(Member); - + Type type = (Member is PropertyInfo p) ? p.PropertyType : ((FieldInfo)Member).FieldType; Type underlyingType = Nullable.GetUnderlyingType(type) ?? type; TypeCode typeCode = Type.GetTypeCode(underlyingType); - if (typeCode == TypeCode.Boolean) + if (typeCode == TypeCode.Boolean) { bool val = Convert.ToBoolean(Getter() ?? false); - } else if (typeCode == TypeCode.Single || typeCode == TypeCode.Double) @@ -28,14 +28,14 @@ { long val = Convert.ToInt64(Getter() ?? 0); { + Setter(Convert.ChangeType(v, underlyingType)); })" /> } } @code { [Parameter] public MemberInfo Member { get; set; } = default!; - [Parameter] public Func Getter { get; set; } = default!; + [Parameter] public Func Getter { get; set; } = default!; [Parameter] public Action Setter { get; set; } = default!; -} \ No newline at end of file +} diff --git a/SeeSharp.Templates/content/SeeSharp.Blazor.Template/Pages/Experiment.razor.cs b/SeeSharp.Templates/content/SeeSharp.Blazor.Template/Pages/Experiment.razor.cs index 109cc8db..1152a32e 100644 --- a/SeeSharp.Templates/content/SeeSharp.Blazor.Template/Pages/Experiment.razor.cs +++ b/SeeSharp.Templates/content/SeeSharp.Blazor.Template/Pages/Experiment.razor.cs @@ -25,7 +25,7 @@ void RunExperiment() PathTracer pathTracer = new() { - TotalSpp = NumSamples, + SampleCount = NumSamples, MaxDepth = MaxDepth, }; pathTracer.Render(scene); @@ -35,7 +35,7 @@ void RunExperiment() scene.FrameBuffer = new(Width, Height, ""); VertexConnectionAndMerging vcm = new() { - NumIterations = NumSamples, + SampleCount = NumSamples, MaxDepth = MaxDepth, }; vcm.Render(scene); diff --git a/SeeSharp.Templates/content/SeeSharp.Template/MyExperiment.cs b/SeeSharp.Templates/content/SeeSharp.Template/MyExperiment.cs index 46d968aa..7c3b2a93 100644 --- a/SeeSharp.Templates/content/SeeSharp.Template/MyExperiment.cs +++ b/SeeSharp.Templates/content/SeeSharp.Template/MyExperiment.cs @@ -5,7 +5,7 @@ namespace SeeSharp.Template; class MyExperiment : Experiment { public override List MakeMethods() => [ new("Path tracing", new PathTracer() { TotalSpp = 4 }), - new("VCM", new VertexConnectionAndMerging() { NumIterations = 2 }) + new("VCM", new VertexConnectionAndMerging() { SampleCount = 2 }) ]; public override void OnDone(string workingDirectory, IEnumerable sceneNames, IEnumerable sceneExposures) { diff --git a/SeeSharp.Validation/Validator.cs b/SeeSharp.Validation/Validator.cs index eb773aba..ebf93fcb 100644 --- a/SeeSharp.Validation/Validator.cs +++ b/SeeSharp.Validation/Validator.cs @@ -64,17 +64,17 @@ public static List Validate(ValidationSceneFactory sceneFactory, bool useT var algorithms = new Dictionary() { { "PathTracer", new PathTracer() { - TotalSpp = sceneFactory.SamplesPerPixel, + NumIterations = (uint)sceneFactory.SamplesPerPixel, MaxDepth = sceneFactory.MaxDepth, RenderTechniquePyramid = false, }}, { "ClassicBidir", new ClassicBidir() { - NumIterations = sceneFactory.SamplesPerPixel / 2, + NumIterations = (uint)sceneFactory.SamplesPerPixel / 2, MaxDepth = sceneFactory.MaxDepth, RenderTechniquePyramid = false, }}, { "Vcm", new VertexConnectionAndMerging() { - NumIterations = sceneFactory.SamplesPerPixel / 2, + NumIterations = (uint)sceneFactory.SamplesPerPixel / 2, MaxDepth = sceneFactory.MaxDepth, RenderTechniquePyramid = false, }} diff --git a/SeeSharp/Common/ProgressBar.cs b/SeeSharp/Common/ProgressBar.cs index e1fff70b..6cea8bd7 100644 --- a/SeeSharp/Common/ProgressBar.cs +++ b/SeeSharp/Common/ProgressBar.cs @@ -85,12 +85,18 @@ public ProgressBar(int numBlocks = 20, bool displayWork = true, bool displayTime watcher.WriteCharEvent += OnOtherOutput; } } + /// + /// Notifies that the process has started. + /// + /// Amount of steps that are performed in total (e.g., render iterations) + public void Start(uint totalWork) => Start((int) totalWork); /// /// Notifies that the process has started. /// /// Amount of steps that are performed in total (e.g., render iterations) - public void Start(int totalWork) { + public void Start(int totalWork) + { total = totalWork; done = 0; accumSeconds = 0.0; diff --git a/SeeSharp/Experiments/SceneFromFile.cs b/SeeSharp/Experiments/SceneFromFile.cs index 47602807..5defa754 100644 --- a/SeeSharp/Experiments/SceneFromFile.cs +++ b/SeeSharp/Experiments/SceneFromFile.cs @@ -172,11 +172,11 @@ public override RgbImage GetReferenceImage(int width, int height, bool allowRend FrameBuffer.Flags.IgnoreNanAndInf | FrameBuffer.Flags.WriteContinously | FrameBuffer.Flags.WriteExponentially); // output intermediate results exponentially to avoid loosing everything on a crash - + // Write settings to metadata for preview parameters in the info table scn.FrameBuffer.MetaData["Name"] = refIntegrator.GetType().Name; scn.FrameBuffer.MetaData["Settings"] = JsonSerializer.SerializeToNode(refIntegrator, refIntegrator.GetType(), refSerializerOptions); - + scn.Prepare(); refIntegrator.Render(scn); scn.FrameBuffer.WriteToFile(); @@ -188,7 +188,7 @@ public override RgbImage GetReferenceImage(int width, int height, bool allowRend string fJson = Path.ChangeExtension(filename, ".json"); if (File.Exists(fJson)) File.Delete(fJson); if (File.Exists(pJson)) File.Move(pJson, fJson); - + return InpaintNaNs(scn.FrameBuffer.Image); } @@ -214,7 +214,7 @@ public SceneFromFile(string filename, int minDepth, int maxDepth, string name = public virtual Integrator DefaultReferenceIntegrator => new PathTracer() { BaseSeed = 571298512u, - TotalSpp = 512 + NumIterations = 512 }; /// diff --git a/SeeSharp/Integrators/Bidir/BidirBase.cs b/SeeSharp/Integrators/Bidir/BidirBase.cs index ea92190b..e758c76e 100644 --- a/SeeSharp/Integrators/Bidir/BidirBase.cs +++ b/SeeSharp/Integrators/Bidir/BidirBase.cs @@ -9,11 +9,6 @@ public abstract partial class BidirBase : Integrator { #region Parameters - /// - /// Number of iterations (batches of one sample per pixel) to render - /// - public int NumIterations { get; set; } = 2; - /// /// The maximum time in milliseconds that should be spent rendering. /// Excludes framebuffer overhead and other operations that are not part of the core rendering logic. @@ -104,7 +99,7 @@ public override void Render(Scene scene) { OnBeforeRender(); ProgressBar progressBar = new(prefix: "Rendering..."); - progressBar.Start(NumIterations); + progressBar.Start((int)NumIterations); RenderTimer timer = new(); Stopwatch lightTracerTimer = new(); Stopwatch pathTracerTimer = new(); diff --git a/SeeSharp/Integrators/Bidir/CameraStoringVCM.cs b/SeeSharp/Integrators/Bidir/CameraStoringVCM.cs index 5e2b1a2f..365710a9 100644 --- a/SeeSharp/Integrators/Bidir/CameraStoringVCM.cs +++ b/SeeSharp/Integrators/Bidir/CameraStoringVCM.cs @@ -5,8 +5,6 @@ namespace SeeSharp.Integrators.Bidir; public class CameraStoringVCM : Integrator where TLightPathData : new() { #region Parameters - public int NumIterations { get; set; } = 1; - /// /// The maximum time in milliseconds that should be spent rendering. /// Excludes framebuffer overhead and other operations that are not part of the core rendering logic. diff --git a/SeeSharp/Integrators/Bidir/PhotonMapper.cs b/SeeSharp/Integrators/Bidir/PhotonMapper.cs index b0995fc7..7345abca 100644 --- a/SeeSharp/Integrators/Bidir/PhotonMapper.cs +++ b/SeeSharp/Integrators/Bidir/PhotonMapper.cs @@ -5,11 +5,6 @@ /// computed from a fraction of the scene size. /// public class PhotonMapper : Integrator { - /// - /// Number of iterations to render. - /// - public int NumIterations = 2; - /// /// Maximum number of nearest neighbor photons to search for. /// diff --git a/SeeSharp/Integrators/Bidir/TechPyramid.cs b/SeeSharp/Integrators/Bidir/TechPyramid.cs index a329e14e..c3ac1c31 100644 --- a/SeeSharp/Integrators/Bidir/TechPyramid.cs +++ b/SeeSharp/Integrators/Bidir/TechPyramid.cs @@ -1,5 +1,4 @@ -using System.Linq; -using TechniqueNames = System.Collections.Generic.Dictionary +using TechniqueNames = System.Collections.Generic.Dictionary <(int cameraPathEdges, int lightPathEdges, int totalEdges), string>; namespace SeeSharp.Integrators.Bidir; @@ -120,4 +119,4 @@ public void WriteToFiles(string basename) { } return images; } -} +} \ No newline at end of file diff --git a/SeeSharp/Integrators/Bidir/VertexCacheBidir.cs b/SeeSharp/Integrators/Bidir/VertexCacheBidir.cs index fb85aeaa..3f13e11a 100644 --- a/SeeSharp/Integrators/Bidir/VertexCacheBidir.cs +++ b/SeeSharp/Integrators/Bidir/VertexCacheBidir.cs @@ -235,4 +235,4 @@ protected virtual float LightPathReciprocals(int lastCameraVertexIdx, in BidirPa if (EnableHitting) sumReciprocals += nextReciprocal; // Hitting the emitter directly return sumReciprocals; } -} +} \ No newline at end of file diff --git a/SeeSharp/Integrators/DebugVisualizer.cs b/SeeSharp/Integrators/DebugVisualizer.cs index 562641b1..f19c6338 100644 --- a/SeeSharp/Integrators/DebugVisualizer.cs +++ b/SeeSharp/Integrators/DebugVisualizer.cs @@ -4,16 +4,11 @@ /// Renders a simple and fast grayscale visualization of a scene /// public class DebugVisualizer : Integrator { - /// - /// Number of anti-aliasing samples to take in each pixel - /// - public int TotalSpp = 1; - /// /// Renders the given scene. /// public override void Render(Scene scene) { - for (uint sampleIndex = 0; sampleIndex < TotalSpp; ++sampleIndex) { + for (uint sampleIndex = 0; sampleIndex < NumIterations; ++sampleIndex) { scene.FrameBuffer.StartIteration(); Parallel.For(0, scene.FrameBuffer.Height, row => { diff --git a/SeeSharp/Integrators/Integrator.cs b/SeeSharp/Integrators/Integrator.cs index 49e6f45b..b7abf342 100644 --- a/SeeSharp/Integrators/Integrator.cs +++ b/SeeSharp/Integrators/Integrator.cs @@ -15,8 +15,18 @@ public abstract class Integrator { /// public int MinDepth { get; set; } = 1; + /// + /// Seed used during rendering. Ideally, every call to with the + /// same seed should produce the exact same image. + /// Might not be true due to non-determinism in the algorithm. + /// public uint BaseSeed { get; set; } = 0x0C030114; + /// + /// Number of samples per pixel (or closest equivalent) to use when calling + /// + public uint NumIterations { get; set; } = 1; + /// /// Renders a scene to the frame buffer that is specified by the object. /// diff --git a/SeeSharp/Integrators/PathTracer.cs b/SeeSharp/Integrators/PathTracer.cs index 2346a5ca..4689fe23 100644 --- a/SeeSharp/Integrators/PathTracer.cs +++ b/SeeSharp/Integrators/PathTracer.cs @@ -10,11 +10,6 @@ public class PathTracer : PathTracerBase { } /// generic type provided. /// public class PathTracerBase : Integrator { - /// - /// Number of samples per pixel to render - /// - public int TotalSpp = 20; - /// /// The maximum time in milliseconds that should be spent rendering. /// Excludes framebuffer overhead and other operations that are not part of the core rendering logic. @@ -71,7 +66,7 @@ public virtual void RegisterSample(Pixel pixel, RgbColor weight, float misWeight bool isNextEvent) { if (!RenderTechniquePyramid) return; - weight /= TotalSpp; + weight /= NumIterations; int cameraEdges = (int)depth - (isNextEvent ? 1 : 0); techPyramidRaw.Add(cameraEdges, 0, (int)depth, pixel, weight); techPyramidWeighted.Add(cameraEdges, 0, (int)depth, pixel, weight * misWeight); @@ -208,11 +203,11 @@ public override void Render(Scene scene) { denoiseBuffers = new(scene.FrameBuffer); ProgressBar progressBar = new(prefix: "Rendering..."); - progressBar.Start(TotalSpp); + progressBar.Start((int)NumIterations); RenderTimer timer = new(); ShadingStatCounter.Reset(); scene.Raytracer.ResetStats(); - for (uint sampleIndex = 0; sampleIndex < TotalSpp; ++sampleIndex) { + for (uint sampleIndex = 0; sampleIndex < NumIterations; ++sampleIndex) { long nextIterTime = timer.RenderTime + timer.PerIterationCost; if (MaximumRenderTimeMs.HasValue && nextIterTime > MaximumRenderTimeMs.Value) { Logger.Log("Maximum render time exhausted."); @@ -236,7 +231,7 @@ public override void Render(Scene scene) { OnPostIteration(sampleIndex); timer.EndRender(); - if (sampleIndex == TotalSpp - 1 && EnableDenoiser) + if (sampleIndex == NumIterations - 1 && EnableDenoiser) denoiseBuffers.Denoise(); PostprocessIteration(sampleIndex); scene.FrameBuffer.EndIteration();