Browse Source

Merge branch 'master' into development

flabbet 8 months ago
parent
commit
63a230617e
41 changed files with 763 additions and 77 deletions
  1. 1 1
      src/PixiDocks
  2. 5 2
      src/PixiEditor.Beta/PixiEditor.Beta.csproj
  3. 1 1
      src/PixiEditor.Beta/extension.json
  4. 62 3
      src/PixiEditor.Builder/build/Program.cs
  5. 2 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs
  6. 70 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CustomOutputNode.cs
  7. 2 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs
  8. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Structure/CreateStructureMember_Change.cs
  9. 7 2
      src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs
  10. 2 1
      src/PixiEditor.ChangeableDocument/Rendering/RenderContext.cs
  11. 244 0
      src/PixiEditor.Extensions.Sdk/build/PixiEditor.Extensions.MSPackageBuilder.deps.json
  12. BIN
      src/PixiEditor.Extensions.Sdk/build/PixiEditor.Extensions.MSPackageBuilder.dll
  13. 4 0
      src/PixiEditor/Data/Localization/Languages/en.json
  14. BIN
      src/PixiEditor/Extensions/PixiEditor.Beta.pixiext
  15. 5 0
      src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs
  16. 4 0
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/LineExecutor.cs
  17. 1 0
      src/PixiEditor/Models/Handlers/INodeGraphHandler.cs
  18. 1 1
      src/PixiEditor/Models/Handlers/IToolHandler.cs
  19. 51 8
      src/PixiEditor/Models/Rendering/SceneRenderer.cs
  20. 0 3
      src/PixiEditor/PixiEditor.csproj
  21. 2 2
      src/PixiEditor/Properties/AssemblyInfo.cs
  22. 0 8
      src/PixiEditor/ViewModels/Dock/LayoutManager.cs
  23. 52 0
      src/PixiEditor/ViewModels/Document/NodeGraphViewModel.cs
  24. 8 0
      src/PixiEditor/ViewModels/Document/Nodes/CustomOutputNodeViewModel.cs
  25. 11 1
      src/PixiEditor/ViewModels/Nodes/Properties/GenericEnumPropertyViewModel.cs
  26. 25 0
      src/PixiEditor/ViewModels/Nodes/Properties/StringPropertyViewModel.cs
  27. 2 0
      src/PixiEditor/ViewModels/SubViewModels/LayoutViewModel.cs
  28. 5 0
      src/PixiEditor/ViewModels/SubViewModels/ToolsViewModel.cs
  29. 23 0
      src/PixiEditor/ViewModels/SubViewModels/ViewportWindowViewModel.cs
  30. 32 12
      src/PixiEditor/ViewModels/SubViewModels/WindowViewModel.cs
  31. 3 0
      src/PixiEditor/Views/Dock/DocumentTemplate.axaml
  32. 1 11
      src/PixiEditor/Views/Dock/DocumentTemplate.axaml.cs
  33. 2 1
      src/PixiEditor/Views/LoadingWindow.axaml
  34. 2 2
      src/PixiEditor/Views/LoadingWindow.axaml.cs
  35. 2 0
      src/PixiEditor/Views/Main/ViewportControls/FixedViewport.axaml.cs
  36. 30 13
      src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml
  37. 43 2
      src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml.cs
  38. 12 0
      src/PixiEditor/Views/Nodes/NodeGraphView.cs
  39. 23 0
      src/PixiEditor/Views/Nodes/Properties/StringPropertyView.axaml
  40. 14 0
      src/PixiEditor/Views/Nodes/Properties/StringPropertyView.axaml.cs
  41. 8 1
      src/PixiEditor/Views/Rendering/Scene.cs

+ 1 - 1
src/PixiDocks

@@ -1 +1 @@
-Subproject commit 68dafe4747a6cead83359ede8e0bbc65cdda02a8
+Subproject commit 1bbf5698bd734cb263a364b6d08f52a9f004dec8

+ 5 - 2
src/PixiEditor.Beta/PixiEditor.Beta.csproj

@@ -8,9 +8,12 @@
     <Nullable>disable</Nullable>
     <PublishTrimmed>true</PublishTrimmed>
     <WasmSingleFileBundle>true</WasmSingleFileBundle>
+    <EventSourceSupport>false</EventSourceSupport>
+    <UseSystemResourceKeys>true</UseSystemResourceKeys>
+    <EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding>
+    <HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
+    <DebuggerSupport>false</DebuggerSupport>
     <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
-    <!--TODO: Temp solution, make it properly build by build system and copy to target dir on publish-->
-    <PixiExtOutputPath>$(MSBuildProjectDirectory)\..\PixiEditor\Extensions</PixiExtOutputPath>
     <ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
     <RootNamespace>PixiEditor.Beta</RootNamespace>
   </PropertyGroup>

+ 1 - 1
src/PixiEditor.Beta/extension.json

@@ -2,7 +2,7 @@
   "displayName": "PixiEditor Beta",
   "uniqueName": "PixiEditor.Beta",
   "description": "Open Beta of PixiEditor 2.0",
-  "version": "1.0.0",
+  "version": "1.0.1",
   "author": {
     "name": "PixiEditor",
     "email": "[email protected]",

+ 62 - 3
src/PixiEditor.Builder/build/Program.cs

@@ -23,6 +23,8 @@ public class BuildContext : FrostingContext
 {
     public string PathToProject { get; set; } = "../PixiEditor/PixiEditor.csproj";
 
+    public string[] ExtensionProjectsToInclude { get; set; } = [];
+
     public string CrashReportWebhookUrl { get; set; }
 
     public string AnalyticsUrl { get; set; }
@@ -34,7 +36,7 @@ public class BuildContext : FrostingContext
     public string OutputDirectory { get; set; } = "Builds";
 
     public bool SelfContained { get; set; } = false;
-    
+
     public string Runtime { get; set; }
 
     public BuildContext(ICakeContext context)
@@ -49,6 +51,12 @@ public class BuildContext : FrostingContext
             PathToProject = context.Arguments.GetArgument("project-path");
         }
 
+        bool hasCustomExtensionProjects = context.Arguments.HasArgument("extension-projects");
+        if (hasCustomExtensionProjects)
+        {
+            ExtensionProjectsToInclude = context.Arguments.GetArgument("extension-projects").Split(';');
+        }
+
         bool hasCustomConfiguration = context.Arguments.HasArgument("build-configuration");
         if (hasCustomConfiguration)
         {
@@ -60,7 +68,7 @@ public class BuildContext : FrostingContext
         {
             OutputDirectory = context.Arguments.GetArgument("o");
         }
-        
+
         bool hasSelfContained = context.Arguments.HasArgument("self-contained");
         if (hasSelfContained)
         {
@@ -80,7 +88,7 @@ public class BuildContext : FrostingContext
 }
 
 [TaskName("Default")]
-[IsDependentOn(typeof(BuildProjectTask))]
+[IsDependentOn(typeof(CopyExtensionsTask))]
 public sealed class DefaultTask : FrostingTask<BuildContext>
 {
     public override void Run(BuildContext context)
@@ -144,3 +152,54 @@ public sealed class BuildProjectTask : FrostingTask<BuildContext>
         File.WriteAllText(constantsPath, context.BackedUpConstants);
     }
 }
+
+[TaskName("BuildExtensions")]
+[IsDependentOn(typeof(BuildProjectTask))]
+public sealed class BuildExtensionsTask : FrostingTask<BuildContext>
+{
+    public override void Run(BuildContext context)
+    {
+        context.Log.Information("Building extensions...");
+        foreach (var project in context.ExtensionProjectsToInclude)
+        {
+            var settings = new DotNetPublishSettings() { Configuration = context.BuildConfiguration, };
+
+            context.DotNetPublish(project, settings);
+        }
+    }
+}
+
+[TaskName("CopyExtensions")]
+[IsDependentOn(typeof(BuildExtensionsTask))]
+public sealed class CopyExtensionsTask : FrostingTask<BuildContext>
+{
+    public override void Run(BuildContext context)
+    {
+        context.Log.Information("Copying extensions...");
+        foreach (var project in context.ExtensionProjectsToInclude)
+        {
+            string outputDir = Path.Combine(context.OutputDirectory, "Extensions");
+            string sourceDir = Path.Combine(project, "bin",
+                context.BuildConfiguration, "wasi-wasm", "Extensions");
+
+            CopyDirectoryContents(sourceDir, outputDir, context);
+        }
+    }
+
+    private void CopyDirectoryContents(string sourceDir, string targetDir, BuildContext context)
+    {
+        if (!Directory.Exists(targetDir))
+        {
+            Directory.CreateDirectory(targetDir);
+        }
+
+        context.Log.Information($"Copying contents of {sourceDir} to {targetDir}");
+
+        foreach (var file in Directory.GetFiles(sourceDir))
+        {
+            string targetFile = Path.Combine(targetDir, Path.GetFileName(file));
+            context.Log.Information($"Copying {file} to {targetFile}");
+            File.Copy(file, targetFile, true);
+        }
+    }
+}

+ 2 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs

@@ -11,7 +11,8 @@ public class NodeGraph : IReadOnlyNodeGraph, IDisposable
 {
     private readonly List<Node> _nodes = new();
     public IReadOnlyCollection<Node> Nodes => _nodes;
-    public OutputNode? OutputNode => Nodes.OfType<OutputNode>().FirstOrDefault();
+    public Node? OutputNode => CustomOutputNode ?? Nodes.OfType<OutputNode>().FirstOrDefault();
+    public Node? CustomOutputNode { get; set; }
 
     IReadOnlyCollection<IReadOnlyNode> IReadOnlyNodeGraph.AllNodes => Nodes;
     IReadOnlyNode IReadOnlyNodeGraph.OutputNode => OutputNode;

+ 70 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CustomOutputNode.cs

@@ -0,0 +1,70 @@
+using PixiEditor.ChangeableDocument.Changeables.Animations;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Rendering;
+using Drawie.Backend.Core;
+using Drawie.Backend.Core.Surfaces;
+using Drawie.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+
+[NodeInfo("CustomOutput")]
+public class CustomOutputNode : Node, IRenderInput, IPreviewRenderable
+{
+    public const string OutputNamePropertyName = "OutputName";
+    public RenderInputProperty Input { get; } 
+    public InputProperty<string> OutputName { get; }
+    
+    private VecI? lastDocumentSize;
+    public CustomOutputNode()
+    {
+        Input = new RenderInputProperty(this, OutputNode.InputPropertyName, "BACKGROUND", null);
+        AddInputProperty(Input);
+        
+        OutputName = CreateInput(OutputNamePropertyName, "OUTPUT_NAME", "");
+    }
+
+    public override Node CreateCopy()
+    {
+        return new CustomOutputNode();
+    }
+
+    protected override void OnExecute(RenderContext context)
+    {
+        if (context.TargetOutput == OutputName.Value)
+        {
+            lastDocumentSize = context.DocumentSize;
+
+            int saved = context.RenderSurface.Canvas.Save();
+            context.RenderSurface.Canvas.ClipRect(new RectD(0, 0, context.DocumentSize.X, context.DocumentSize.Y));
+            Input.Value?.Paint(context, context.RenderSurface);
+
+            context.RenderSurface.Canvas.RestoreToCount(saved);
+        }
+    }
+
+    RenderInputProperty IRenderInput.Background => Input;
+    public RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
+    {
+        if (lastDocumentSize == null)
+        {
+            return null;
+        }
+        
+        return new RectD(0, 0, lastDocumentSize.Value.X, lastDocumentSize.Value.Y); 
+    }
+
+    public bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
+    {
+        if (Input.Value == null)
+        {
+            return false;
+        }
+        
+        int saved = renderOn.Canvas.Save();
+        Input.Value.Paint(context, renderOn);
+        
+        renderOn.Canvas.RestoreToCount(saved);
+        
+        return true;
+    }
+}

+ 2 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs

@@ -28,6 +28,8 @@ public class OutputNode : Node, IRenderInput, IPreviewRenderable
 
     protected override void OnExecute(RenderContext context)
     {
+        if(!string.IsNullOrEmpty(context.TargetOutput)) return;
+        
         lastDocumentSize = context.DocumentSize;
         
         int saved = context.RenderSurface.Canvas.Save();

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Structure/CreateStructureMember_Change.cs

@@ -27,7 +27,7 @@ internal class CreateStructureMember_Change : Change
 
     public override bool InitializeAndValidate(Document target)
     {
-        if(structureMemberOfType.IsAbstract || structureMemberOfType.IsInterface || !structureMemberOfType.IsAssignableTo(typeof(StructureNode)))
+        if(structureMemberOfType == null || structureMemberOfType.IsAbstract || structureMemberOfType.IsInterface || !structureMemberOfType.IsAssignableTo(typeof(StructureNode)))
             return false;
         
         return target.TryFindNode<Node>(parentGuid, out _);

+ 7 - 2
src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs

@@ -202,7 +202,12 @@ public class DocumentRenderer : IPreviewRenderable
         NodeGraph membersOnlyGraph,
         Dictionary<Guid, Guid> nodeMapping)
     {
-        if(input == null) return membersOnlyGraph.OutputNode.Input;
+        if (input == null)
+        {
+            if(membersOnlyGraph.OutputNode is IRenderInput inputNode) return inputNode.Background;
+
+            return null;
+        }
         
         if (nodeMapping.ContainsKey(input.Node?.Id ?? Guid.Empty))
         {
@@ -228,6 +233,6 @@ public class DocumentRenderer : IPreviewRenderable
             return true;
         });
         
-        return found ?? membersOnlyGraph.OutputNode.Input;
+        return found ?? (membersOnlyGraph.OutputNode as IRenderInput)?.Background;
     }
 }

+ 2 - 1
src/PixiEditor.ChangeableDocument/Rendering/RenderContext.cs

@@ -18,7 +18,8 @@ public class RenderContext
     public DrawingSurface RenderSurface { get; set; }
     public bool FullRerender { get; set; } = false;
     
-    public ColorSpace ProcessingColorSpace { get; set; } 
+    public ColorSpace ProcessingColorSpace { get; set; }
+    public string? TargetOutput { get; set; }   
 
 
     public RenderContext(DrawingSurface renderSurface, KeyFrameTime frameTime, ChunkResolution chunkResolution,

+ 244 - 0
src/PixiEditor.Extensions.Sdk/build/PixiEditor.Extensions.MSPackageBuilder.deps.json

@@ -0,0 +1,244 @@
+{
+  "runtimeTarget": {
+    "name": ".NETStandard,Version=v2.0/",
+    "signature": ""
+  },
+  "compilationOptions": {},
+  "targets": {
+    ".NETStandard,Version=v2.0": {},
+    ".NETStandard,Version=v2.0/": {
+      "PixiEditor.Extensions.MSPackageBuilder/1.0.0": {
+        "dependencies": {
+          "Microsoft.Build.Utilities.Core": "17.12.6",
+          "NETStandard.Library": "2.0.3",
+          "Newtonsoft.Json": "13.0.3",
+          "StyleCop.Analyzers": "1.1.118"
+        },
+        "runtime": {
+          "PixiEditor.Extensions.MSPackageBuilder.dll": {}
+        }
+      },
+      "Microsoft.Build.Framework/17.12.6": {
+        "dependencies": {
+          "Microsoft.Win32.Registry": "5.0.0",
+          "System.Memory": "4.5.5",
+          "System.Runtime.CompilerServices.Unsafe": "6.0.0",
+          "System.Security.Principal.Windows": "5.0.0"
+        }
+      },
+      "Microsoft.Build.Utilities.Core/17.12.6": {
+        "dependencies": {
+          "Microsoft.Build.Framework": "17.12.6",
+          "Microsoft.NET.StringTools": "17.12.6",
+          "Microsoft.Win32.Registry": "5.0.0",
+          "System.Collections.Immutable": "8.0.0",
+          "System.Configuration.ConfigurationManager": "8.0.0",
+          "System.Memory": "4.5.5",
+          "System.Runtime.CompilerServices.Unsafe": "6.0.0",
+          "System.Security.Principal.Windows": "5.0.0",
+          "System.Text.Encoding.CodePages": "7.0.0"
+        }
+      },
+      "Microsoft.NET.StringTools/17.12.6": {
+        "dependencies": {
+          "System.Memory": "4.5.5",
+          "System.Runtime.CompilerServices.Unsafe": "6.0.0"
+        }
+      },
+      "Microsoft.NETCore.Platforms/1.1.0": {},
+      "Microsoft.Win32.Registry/5.0.0": {
+        "dependencies": {
+          "System.Buffers": "4.5.1",
+          "System.Memory": "4.5.5",
+          "System.Security.AccessControl": "5.0.0",
+          "System.Security.Principal.Windows": "5.0.0"
+        }
+      },
+      "NETStandard.Library/2.0.3": {
+        "dependencies": {
+          "Microsoft.NETCore.Platforms": "1.1.0"
+        }
+      },
+      "Newtonsoft.Json/13.0.3": {
+        "runtime": {
+          "lib/netstandard2.0/Newtonsoft.Json.dll": {
+            "assemblyVersion": "13.0.0.0",
+            "fileVersion": "13.0.3.27908"
+          }
+        }
+      },
+      "StyleCop.Analyzers/1.1.118": {},
+      "System.Buffers/4.5.1": {},
+      "System.Collections.Immutable/8.0.0": {
+        "dependencies": {
+          "System.Memory": "4.5.5",
+          "System.Runtime.CompilerServices.Unsafe": "6.0.0"
+        }
+      },
+      "System.Configuration.ConfigurationManager/8.0.0": {
+        "dependencies": {
+          "System.Security.Cryptography.ProtectedData": "8.0.0"
+        }
+      },
+      "System.Memory/4.5.5": {
+        "dependencies": {
+          "System.Buffers": "4.5.1",
+          "System.Numerics.Vectors": "4.4.0",
+          "System.Runtime.CompilerServices.Unsafe": "6.0.0"
+        }
+      },
+      "System.Numerics.Vectors/4.4.0": {},
+      "System.Runtime.CompilerServices.Unsafe/6.0.0": {},
+      "System.Security.AccessControl/5.0.0": {
+        "dependencies": {
+          "System.Security.Principal.Windows": "5.0.0"
+        }
+      },
+      "System.Security.Cryptography.ProtectedData/8.0.0": {
+        "dependencies": {
+          "System.Memory": "4.5.5"
+        }
+      },
+      "System.Security.Principal.Windows/5.0.0": {},
+      "System.Text.Encoding.CodePages/7.0.0": {
+        "dependencies": {
+          "System.Memory": "4.5.5",
+          "System.Runtime.CompilerServices.Unsafe": "6.0.0"
+        }
+      }
+    }
+  },
+  "libraries": {
+    "PixiEditor.Extensions.MSPackageBuilder/1.0.0": {
+      "type": "project",
+      "serviceable": false,
+      "sha512": ""
+    },
+    "Microsoft.Build.Framework/17.12.6": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-jleteC0seumLGTmTVwob97lcwPj/dfgzL/V3g/VVcMZgo2Ic7jzdy8AYpByPDh8e3uRq0SjCl6HOFCjhy5GzRQ==",
+      "path": "microsoft.build.framework/17.12.6",
+      "hashPath": "microsoft.build.framework.17.12.6.nupkg.sha512"
+    },
+    "Microsoft.Build.Utilities.Core/17.12.6": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-pU3GnHcXp8VRMGKxdJCq+tixfhFn+QwEbpqmZmc/nqFHFyuhlGwjonWZMIWcwuCv/8EHgxoOttFvna1vrN+RrA==",
+      "path": "microsoft.build.utilities.core/17.12.6",
+      "hashPath": "microsoft.build.utilities.core.17.12.6.nupkg.sha512"
+    },
+    "Microsoft.NET.StringTools/17.12.6": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-w8Ehofqte5bJoR+Fa3f6JwkwFEkGtXxqvQHGOVOSHDzgNVySvL5FSNhavbQSZ864el9c3rjdLPLAtBW8dq6fmg==",
+      "path": "microsoft.net.stringtools/17.12.6",
+      "hashPath": "microsoft.net.stringtools.17.12.6.nupkg.sha512"
+    },
+    "Microsoft.NETCore.Platforms/1.1.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==",
+      "path": "microsoft.netcore.platforms/1.1.0",
+      "hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512"
+    },
+    "Microsoft.Win32.Registry/5.0.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==",
+      "path": "microsoft.win32.registry/5.0.0",
+      "hashPath": "microsoft.win32.registry.5.0.0.nupkg.sha512"
+    },
+    "NETStandard.Library/2.0.3": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
+      "path": "netstandard.library/2.0.3",
+      "hashPath": "netstandard.library.2.0.3.nupkg.sha512"
+    },
+    "Newtonsoft.Json/13.0.3": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==",
+      "path": "newtonsoft.json/13.0.3",
+      "hashPath": "newtonsoft.json.13.0.3.nupkg.sha512"
+    },
+    "StyleCop.Analyzers/1.1.118": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-Onx6ovGSqXSK07n/0eM3ZusiNdB6cIlJdabQhWGgJp3Vooy9AaLS/tigeybOJAobqbtggTamoWndz72JscZBvw==",
+      "path": "stylecop.analyzers/1.1.118",
+      "hashPath": "stylecop.analyzers.1.1.118.nupkg.sha512"
+    },
+    "System.Buffers/4.5.1": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==",
+      "path": "system.buffers/4.5.1",
+      "hashPath": "system.buffers.4.5.1.nupkg.sha512"
+    },
+    "System.Collections.Immutable/8.0.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==",
+      "path": "system.collections.immutable/8.0.0",
+      "hashPath": "system.collections.immutable.8.0.0.nupkg.sha512"
+    },
+    "System.Configuration.ConfigurationManager/8.0.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-JlYi9XVvIREURRUlGMr1F6vOFLk7YSY4p1vHo4kX3tQ0AGrjqlRWHDi66ImHhy6qwXBG3BJ6Y1QlYQ+Qz6Xgww==",
+      "path": "system.configuration.configurationmanager/8.0.0",
+      "hashPath": "system.configuration.configurationmanager.8.0.0.nupkg.sha512"
+    },
+    "System.Memory/4.5.5": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
+      "path": "system.memory/4.5.5",
+      "hashPath": "system.memory.4.5.5.nupkg.sha512"
+    },
+    "System.Numerics.Vectors/4.4.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ==",
+      "path": "system.numerics.vectors/4.4.0",
+      "hashPath": "system.numerics.vectors.4.4.0.nupkg.sha512"
+    },
+    "System.Runtime.CompilerServices.Unsafe/6.0.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==",
+      "path": "system.runtime.compilerservices.unsafe/6.0.0",
+      "hashPath": "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512"
+    },
+    "System.Security.AccessControl/5.0.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==",
+      "path": "system.security.accesscontrol/5.0.0",
+      "hashPath": "system.security.accesscontrol.5.0.0.nupkg.sha512"
+    },
+    "System.Security.Cryptography.ProtectedData/8.0.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-+TUFINV2q2ifyXauQXRwy4CiBhqvDEDZeVJU7qfxya4aRYOKzVBpN+4acx25VcPB9ywUN6C0n8drWl110PhZEg==",
+      "path": "system.security.cryptography.protecteddata/8.0.0",
+      "hashPath": "system.security.cryptography.protecteddata.8.0.0.nupkg.sha512"
+    },
+    "System.Security.Principal.Windows/5.0.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==",
+      "path": "system.security.principal.windows/5.0.0",
+      "hashPath": "system.security.principal.windows.5.0.0.nupkg.sha512"
+    },
+    "System.Text.Encoding.CodePages/7.0.0": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-LSyCblMpvOe0N3E+8e0skHcrIhgV2huaNcjUUEa8hRtgEAm36aGkRoC8Jxlb6Ra6GSfF29ftduPNywin8XolzQ==",
+      "path": "system.text.encoding.codepages/7.0.0",
+      "hashPath": "system.text.encoding.codepages.7.0.0.nupkg.sha512"
+    }
+  }
+}

BIN
src/PixiEditor.Extensions.Sdk/build/PixiEditor.Extensions.MSPackageBuilder.dll


+ 4 - 0
src/PixiEditor/Data/Localization/Languages/en.json

@@ -797,4 +797,8 @@
   "CREATE_CEL_DESCRIPTIVE": "Create a new cel",
   "DUPLICATE_CEL": "Duplicate cel",
   "DUPLICATE_CEL_DESCRIPTIVE": "Duplicate cel in the current frame",
+  "RENDER_PREVIEW": "Render preview",
+  "OUTPUT_NAME": "Preview name",
+  "CUSTOM_OUTPUT_NODE": "Preview Node",
+  "TOGGLE_HUD": "Toggle HUD"
 }

BIN
src/PixiEditor/Extensions/PixiEditor.Beta.pixiext


+ 5 - 0
src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs

@@ -666,6 +666,11 @@ internal class DocumentUpdater
         ProcessStructureMemberProperty(info, property);
         
         property.InternalSetValue(info.Value);
+        
+        if (info.Property == CustomOutputNode.OutputNamePropertyName)
+        {
+            doc.NodeGraphHandler.UpdateAvailableRenderOutputs();
+        }
     }
     
     private void ProcessStructureMemberProperty(PropertyValueUpdated_ChangeInfo info, INodePropertyHandler property)

+ 4 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/LineExecutor.cs

@@ -164,7 +164,11 @@ internal abstract class LineExecutor<T> : SimpleShapeToolExecutor where T : ILin
     {
         if (!startedDrawing)
         {
+            internals.ActionAccumulator.AddFinishedActions(EndDraw());
+            AddMemberToSnapping();
+            
             base.OnLeftMouseButtonUp(argsPositionOnCanvas);
+            ActiveMode = ShapeToolMode.Preview;
             onEnded!(this);
             return;
         }

+ 1 - 0
src/PixiEditor/Models/Handlers/INodeGraphHandler.cs

@@ -20,4 +20,5 @@ internal interface INodeGraphHandler
    public void SetConnection(NodeConnectionViewModel connection);
    public void RemoveConnection(Guid nodeId, string property);
    public void RemoveConnections(Guid nodeId);
+   public void UpdateAvailableRenderOutputs();
 }

+ 1 - 1
src/PixiEditor/Models/Handlers/IToolHandler.cs

@@ -52,7 +52,7 @@ internal interface IToolHandler : IHandler
     /// <summary>
     ///     Layer type that should be created if no layer is selected incompatible one.
     /// </summary>
-    public Type LayerTypeToCreateOnEmptyUse { get; }
+    public Type? LayerTypeToCreateOnEmptyUse { get; }
 
     public virtual string? DefaultNewLayerName => null;
 

+ 51 - 8
src/PixiEditor/Models/Rendering/SceneRenderer.cs

@@ -4,6 +4,7 @@ using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Rendering;
 using Drawie.Backend.Core.Surfaces;
 using Drawie.Backend.Core.Surfaces.ImageData;
+using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.Models.Handlers;
@@ -22,14 +23,14 @@ internal class SceneRenderer
         DocumentViewModel = documentViewModel;
     }
 
-    public void RenderScene(DrawingSurface target, ChunkResolution resolution)
+    public void RenderScene(DrawingSurface target, ChunkResolution resolution, string? targetOutput = null)
     {
         if(Document.Renderer.IsBusy || DocumentViewModel.Busy) return;
-        RenderOnionSkin(target, resolution);
-        RenderGraph(target, resolution);
+        RenderOnionSkin(target, resolution, targetOutput);
+        RenderGraph(target, resolution, targetOutput);
     }
 
-    private void RenderGraph(DrawingSurface target, ChunkResolution resolution)
+    private void RenderGraph(DrawingSurface target, ChunkResolution resolution, string? targetOutput)
     {
         DrawingSurface renderTarget = target;
         Texture? texture = null;
@@ -42,7 +43,8 @@ internal class SceneRenderer
 
         RenderContext context = new(renderTarget, DocumentViewModel.AnimationHandler.ActiveFrameTime,
             resolution, Document.Size, Document.ProcessingColorSpace);
-        Document.NodeGraph.Execute(context);
+        context.TargetOutput = targetOutput;
+        SolveFinalNodeGraph(context.TargetOutput).Execute(context);
         
         if(texture != null)
         {
@@ -51,6 +53,43 @@ internal class SceneRenderer
         }
     }
 
+    private IReadOnlyNodeGraph SolveFinalNodeGraph(string? targetOutput)
+    {
+        if (targetOutput == null)
+        {
+            return Document.NodeGraph;
+        }
+
+        CustomOutputNode[] outputNodes = Document.NodeGraph.AllNodes.OfType<CustomOutputNode>().ToArray();
+        
+        foreach (CustomOutputNode outputNode in outputNodes)
+        {
+            if (outputNode.OutputName.Value == targetOutput)
+            {
+                return GraphFromOutputNode(outputNode);
+            }
+        }
+
+        return Document.NodeGraph;
+    }
+
+    private IReadOnlyNodeGraph GraphFromOutputNode(CustomOutputNode outputNode)
+    {
+        NodeGraph graph = new();
+        outputNode.TraverseBackwards(n =>
+        {
+            if (n is Node node)
+            {
+                graph.AddNode(node);
+            }
+            
+            return true;
+        });
+        
+        graph.CustomOutputNode = outputNode;
+        return graph;
+    }
+
     private bool HighDpiRenderNodePresent(IReadOnlyNodeGraph documentNodeGraph)
     {
         bool highDpiRenderNodePresent = false;
@@ -65,7 +104,7 @@ internal class SceneRenderer
         return highDpiRenderNodePresent;
     }
 
-    private void RenderOnionSkin(DrawingSurface target, ChunkResolution resolution)
+    private void RenderOnionSkin(DrawingSurface target, ChunkResolution resolution, string? targetOutput)
     {
         var animationData = Document.AnimationData;
         if (!DocumentViewModel.AnimationHandler.OnionSkinningEnabledBindable)
@@ -76,6 +115,8 @@ internal class SceneRenderer
         double onionOpacity = animationData.OnionOpacity / 100.0;
         double alphaFalloffMultiplier = 1.0 / animationData.OnionFrames;
 
+        var finalGraph = SolveFinalNodeGraph(targetOutput);
+        
         // Render previous frames'
         for (int i = 1; i <= animationData.OnionFrames; i++)
         {
@@ -88,7 +129,8 @@ internal class SceneRenderer
             double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
 
             RenderContext onionContext = new(target, frame, resolution, Document.Size, Document.ProcessingColorSpace, finalOpacity);
-            Document.NodeGraph.Execute(onionContext);
+            onionContext.TargetOutput = targetOutput;
+            finalGraph.Execute(onionContext);
         }
 
         // Render next frames
@@ -102,7 +144,8 @@ internal class SceneRenderer
 
             double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
             RenderContext onionContext = new(target, frame, resolution, Document.Size, Document.ProcessingColorSpace, finalOpacity);
-            Document.NodeGraph.Execute(onionContext);
+            onionContext.TargetOutput = targetOutput;
+            finalGraph.Execute(onionContext);
         }
     }
 }

+ 0 - 3
src/PixiEditor/PixiEditor.csproj

@@ -132,9 +132,6 @@
     <None Include="../../Third Party Licenses/**" LinkBase="Third Party Licenses/">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
-    <None Update="Extensions\PixiEditor.Beta.pixiext">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </None>
   </ItemGroup>
 
   <ItemGroup>

+ 2 - 2
src/PixiEditor/Properties/AssemblyInfo.cs

@@ -41,5 +41,5 @@ using System.Runtime.InteropServices;
 // You can specify all the values or you can default the Build and Revision Numbers
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("2.0.0.40")]
-[assembly: AssemblyFileVersion("2.0.0.40")]
+[assembly: AssemblyVersion("2.0.0.41")]
+[assembly: AssemblyFileVersion("2.0.0.41")]

+ 0 - 8
src/PixiEditor/ViewModels/Dock/LayoutManager.cs

@@ -66,15 +66,7 @@ internal class LayoutManager
                     First = new DockableArea()
                     {
                         Id = "DocumentArea", FallbackContent = new CreateDocumentFallbackView(),
-                        Dockables = [ DockContext.CreateDockable(nodeGraphDockViewModel) ]
                     },
-                    SecondSize = 200,
-                    SplitDirection = DockingDirection.Bottom,
-                    Second = new DockableArea
-                    {
-                        Id = "TimelineArea", 
-                        Dockables = [ DockContext.CreateDockable(timelineDockViewModel) ]
-                    }
                 },
                 SecondSize = 360,
                 SplitDirection = DockingDirection.Right,

+ 52 - 0
src/PixiEditor/ViewModels/Document/NodeGraphViewModel.cs

@@ -22,6 +22,7 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
     public ObservableCollection<INodeHandler> AllNodes { get; } = new();
     public ObservableCollection<NodeConnectionViewModel> Connections { get; } = new();
     public ObservableCollection<NodeFrameViewModelBase> Frames { get; } = new();
+    public ObservableCollection<string> AvailableRenderOutputs { get; } = new();
     public StructureTree StructureTree { get; } = new();
     public INodeHandler? OutputNode { get; private set; }
 
@@ -43,6 +44,7 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
 
         AllNodes.Add(node);
         StructureTree.Update(this);
+        UpdateAvailableRenderOutputs();
     }
 
     public void RemoveNode(Guid nodeId)
@@ -55,6 +57,7 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
         }
 
         StructureTree.Update(this);
+        UpdateAvailableRenderOutputs();
     }
 
     public void AddFrame(Guid frameId, IEnumerable<Guid> nodes)
@@ -322,4 +325,53 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
 
         Internals.ActionAccumulator.AddFinishedActions(action);
     }
+    
+    public void UpdateAvailableRenderOutputs()
+    {
+        List<string> outputs = new();
+        foreach (var node in AllNodes)
+        {
+            if (node.InternalName == typeof(CustomOutputNode).GetCustomAttribute<NodeInfoAttribute>().UniqueName)
+            {
+                var nameInput =
+                    node.Inputs.FirstOrDefault(x => x.PropertyName == CustomOutputNode.OutputNamePropertyName);
+
+                if (nameInput is { Value: string name } && !string.IsNullOrEmpty(name))
+                {
+                    if(outputs.Contains(name)) continue;
+                    
+                    outputs.Add(name);
+                }
+            }
+            else if (node.InternalName == typeof(OutputNode).GetCustomAttribute<NodeInfoAttribute>().UniqueName)
+            {
+                outputs.Insert(0, "DEFAULT");
+            }
+        }
+        
+        RemoveExcessiveRenderOutputs(outputs);
+        AddMissingRenderOutputs(outputs);
+    }
+
+    private void RemoveExcessiveRenderOutputs(List<string> outputs)
+    {
+        for (int i = AvailableRenderOutputs.Count - 1; i >= 0; i--)
+        {
+            if (!outputs.Contains(AvailableRenderOutputs[i]))
+            {
+                AvailableRenderOutputs.RemoveAt(i);
+            }
+        }
+    }
+    
+    private void AddMissingRenderOutputs(List<string> outputs)
+    {
+        foreach (var output in outputs)
+        {
+            if (!AvailableRenderOutputs.Contains(output))
+            {
+                AvailableRenderOutputs.Add(output);
+            }
+        }
+    }
 }

+ 8 - 0
src/PixiEditor/ViewModels/Document/Nodes/CustomOutputNodeViewModel.cs

@@ -0,0 +1,8 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.Extensions.Common.Localization;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes;
+
+[NodeViewModel("CUSTOM_OUTPUT_NODE", null, null)]
+internal class CustomOutputNodeViewModel : NodeViewModel<CustomOutputNode>;

+ 11 - 1
src/PixiEditor/ViewModels/Nodes/Properties/GenericEnumPropertyViewModel.cs

@@ -1,4 +1,5 @@
-using PixiEditor.Models.Handlers;
+using System.ComponentModel;
+using PixiEditor.Models.Handlers;
 
 namespace PixiEditor.ViewModels.Nodes.Properties;
 
@@ -7,6 +8,15 @@ internal class GenericEnumPropertyViewModel : NodePropertyViewModel
     public GenericEnumPropertyViewModel(INodeHandler node, Type propertyType, Type enumType) : base(node, propertyType)
     {
         Values = Enum.GetValues(enumType);
+        PropertyChanged += OnPropertyChanged;
+    }
+
+    private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e)
+    {
+        if (e.PropertyName == nameof(Value))
+        {
+            OnPropertyChanged(nameof(SelectedIndex));
+        }
     }
 
     public Array Values { get; }

+ 25 - 0
src/PixiEditor/ViewModels/Nodes/Properties/StringPropertyViewModel.cs

@@ -0,0 +1,25 @@
+using System.ComponentModel;
+
+namespace PixiEditor.ViewModels.Nodes.Properties;
+
+internal class StringPropertyViewModel : NodePropertyViewModel<string>
+{
+    public string StringValue
+    {
+        get => Value;
+        set => Value = value;
+    }
+    
+    public StringPropertyViewModel(NodeViewModel node, Type valueType) : base(node, valueType)
+    {
+        PropertyChanged += StringPropertyViewModel_PropertyChanged;
+    }
+    
+    private void StringPropertyViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
+    {
+        if (e.PropertyName == nameof(Value))
+        {
+            OnPropertyChanged(nameof(StringValue));
+        }
+    }
+}

+ 2 - 0
src/PixiEditor/ViewModels/SubViewModels/LayoutViewModel.cs

@@ -1,5 +1,7 @@
 using System.Collections.ObjectModel;
+using Avalonia.Input;
 using PixiDocks.Core.Docking;
+using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.ViewModels.Dock;
 
 namespace PixiEditor.ViewModels.SubViewModels;

+ 5 - 0
src/PixiEditor/ViewModels/SubViewModels/ToolsViewModel.cs

@@ -131,9 +131,12 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
 
     public void SetActiveToolSet(IToolSetHandler toolSetHandler)
     {
+        ActiveTool?.OnToolDeselected(false);
         ActiveToolSet = toolSetHandler;
         ActiveToolSet.ApplyToolSetSettings();
         UpdateEnabledState();
+        
+        ActiveTool?.OnToolSelected(false);
     }
 
     public void SetupToolsTooltipShortcuts()
@@ -382,6 +385,8 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>, IToolsHandler
 
         if (ActiveTool is not { CanBeUsedOnActiveLayer: true })
         {
+            if(ActiveTool.LayerTypeToCreateOnEmptyUse == null) return;
+            
             Guid? createdLayer = Owner.LayersSubViewModel.NewLayer(
                 ActiveTool.LayerTypeToCreateOnEmptyUse,
                 ActionSource.Automated,

+ 23 - 0
src/PixiEditor/ViewModels/SubViewModels/ViewportWindowViewModel.cs

@@ -32,6 +32,7 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
     private string _index = "";
 
     private bool _flipX;
+    private string renderOutputName = "DEFAULT";
     private string id = Guid.NewGuid().ToString();
 
     public bool FlipX
@@ -55,6 +56,16 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
             OnPropertyChanged(nameof(FlipY));
         }
     }
+    
+    public string RenderOutputName
+    {
+        get => renderOutputName;
+        set
+        {
+            renderOutputName = value;
+            OnPropertyChanged(nameof(RenderOutputName));
+        }
+    }
 
     private ViewportColorChannels _channels = ViewportColorChannels.Default;
     
@@ -64,6 +75,18 @@ internal class ViewportWindowViewModel : SubViewModel<WindowViewModel>, IDockabl
         set => SetProperty(ref _channels, value);
     }
 
+    private bool hudVisible = true;
+
+    public bool HudVisible
+    {
+        get => hudVisible;
+        set
+        {
+            hudVisible = value;
+            OnPropertyChanged(nameof(HudVisible));
+        }
+    }
+
     private PreviewPainterControl previewPainterControl;
 
     public void IndexChanged()

+ 32 - 12
src/PixiEditor/ViewModels/SubViewModels/WindowViewModel.cs

@@ -31,6 +31,7 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>
     public event Action<ViewportWindowViewModel> ViewportClosed;
 
     private object? activeWindow;
+
     public object? ActiveWindow
     {
         get => activeWindow;
@@ -62,16 +63,29 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>
             return;
         CreateNewViewport(doc);
     }
-    
-    [Commands_Command.Basic("PixiEditor.Window.CenterActiveViewport", "CENTER_ACTIVE_VIEWPORT", "CENTER_ACTIVE_VIEWPORT", CanExecute = "PixiEditor.HasDocument",
+
+    [Commands_Command.Basic("PixiEditor.Window.CenterActiveViewport", "CENTER_ACTIVE_VIEWPORT",
+        "CENTER_ACTIVE_VIEWPORT", CanExecute = "PixiEditor.HasDocument",
         Icon = PixiPerfectIcons.Center, AnalyticsTrack = true)]
     public void CenterCurrentViewport()
     {
         if (ActiveWindow is ViewportWindowViewModel viewport)
             viewport.CenterViewportTrigger.Execute(this, viewport.Document.SizeBindable);
     }
-    
-    [Commands_Command.Basic("PixiEditor.Window.FlipHorizontally", "FLIP_VIEWPORT_HORIZONTALLY", "FLIP_VIEWPORT_HORIZONTALLY", CanExecute = "PixiEditor.HasDocument",
+
+
+    [Command.Basic("PixiEditor.Viewport.ToggleHud", "TOGGLE_HUD", "TOGGLE_HUD_DESCRIPTION",
+        AnalyticsTrack = true, Key = Key.H, Modifiers = KeyModifiers.Shift, MenuItemPath = "VIEW/TOGGLE_HUD")]
+    public void ToggleHudOfCurrentViewport()
+    {
+        if (ActiveWindow is ViewportWindowViewModel viewport)
+        {
+            viewport.HudVisible = !viewport.HudVisible;
+        }
+    }
+
+    [Commands_Command.Basic("PixiEditor.Window.FlipHorizontally", "FLIP_VIEWPORT_HORIZONTALLY",
+        "FLIP_VIEWPORT_HORIZONTALLY", CanExecute = "PixiEditor.HasDocument",
         Icon = PixiPerfectIcons.YFlip, AnalyticsTrack = true)]
     public void FlipViewportHorizontally()
     {
@@ -80,8 +94,9 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>
             viewport.FlipX = !viewport.FlipX;
         }
     }
-    
-    [Commands_Command.Basic("PixiEditor.Window.FlipVertically", "FLIP_VIEWPORT_VERTICALLY", "FLIP_VIEWPORT_VERTICALLY", CanExecute = "PixiEditor.HasDocument",
+
+    [Commands_Command.Basic("PixiEditor.Window.FlipVertically", "FLIP_VIEWPORT_VERTICALLY", "FLIP_VIEWPORT_VERTICALLY",
+        CanExecute = "PixiEditor.HasDocument",
         Icon = PixiPerfectIcons.XFlip, AnalyticsTrack = true)]
     public void FlipViewportVertically()
     {
@@ -154,7 +169,8 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>
         }
     }
 
-    [Commands_Command.Basic("PixiEditor.Window.OpenSettingsWindow", "OPEN_SETTINGS", "OPEN_SETTINGS_DESCRIPTIVE", Key = Key.OemComma, Modifiers = KeyModifiers.Control,
+    [Commands_Command.Basic("PixiEditor.Window.OpenSettingsWindow", "OPEN_SETTINGS", "OPEN_SETTINGS_DESCRIPTIVE",
+        Key = Key.OemComma, Modifiers = KeyModifiers.Control,
         MenuItemPath = "EDIT/SETTINGS", MenuItemOrder = 16, Icon = PixiPerfectIcons.Settings, AnalyticsTrack = true)]
     public static void OpenSettingsWindow(int page)
     {
@@ -168,21 +184,24 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>
     }
 
     [Commands_Command.Basic("PixiEditor.Window.OpenStartupWindow", "OPEN_STARTUP_WINDOW", "OPEN_STARTUP_WINDOW",
-        Icon = PixiPerfectIcons.Home, MenuItemPath = "VIEW/OPEN_STARTUP_WINDOW", MenuItemOrder = 1, AnalyticsTrack = true)]
+        Icon = PixiPerfectIcons.Home, MenuItemPath = "VIEW/OPEN_STARTUP_WINDOW", MenuItemOrder = 1,
+        AnalyticsTrack = true)]
     public void OpenHelloThereWindow()
     {
         new HelloTherePopup(Owner.FileSubViewModel).Show(MainWindow.Current);
     }
 
-    [Commands_Command.Basic("PixiEditor.Window.OpenShortcutWindow", "OPEN_SHORTCUT_WINDOW", "OPEN_SHORTCUT_WINDOW", Key = Key.F1,
-        Icon = PixiPerfectIcons.Book, MenuItemPath = "VIEW/OPEN_SHORTCUT_WINDOW", MenuItemOrder = 2, AnalyticsTrack = true)]
+    [Commands_Command.Basic("PixiEditor.Window.OpenShortcutWindow", "OPEN_SHORTCUT_WINDOW", "OPEN_SHORTCUT_WINDOW",
+        Key = Key.F1,
+        Icon = PixiPerfectIcons.Book, MenuItemPath = "VIEW/OPEN_SHORTCUT_WINDOW", MenuItemOrder = 2,
+        AnalyticsTrack = true)]
     public void ShowShortcutWindow()
     {
         var popup = new ShortcutsPopup(commandController);
         popup.Show();
         popup.Activate();
     }
-        
+
     [Commands_Command.Basic("PixiEditor.Window.OpenAboutWindow", "OPEN_ABOUT_WINDOW", "OPEN_ABOUT_WINDOW",
         Icon = PixiPerfectIcons.Info, MenuItemPath = "HELP/ABOUT", MenuItemOrder = 5, AnalyticsTrack = true)]
     public void OpenAboutWindow()
@@ -191,7 +210,8 @@ internal class WindowViewModel : SubViewModel<ViewModelMain>
     }
 
     [Commands_Command.Internal("PixiEditor.Window.ShowDockWindow")]
-    [Commands_Command.Basic("PixiEditor.Window.OpenNavigationWindow", "Navigator", "OPEN_NAVIGATION_WINDOW", "OPEN_NAVIGATION_WINDOW")]
+    [Commands_Command.Basic("PixiEditor.Window.OpenNavigationWindow", "Navigator", "OPEN_NAVIGATION_WINDOW",
+        "OPEN_NAVIGATION_WINDOW")]
     public void ShowDockWindow(string id)
     {
         Owner.LayoutSubViewModel.LayoutManager.ShowDockable(id);

+ 3 - 0
src/PixiEditor/Views/Dock/DocumentTemplate.axaml

@@ -37,6 +37,9 @@
         Channels="{Binding Channels, Mode=TwoWay}"
         SnappingViewModel="{Binding ActiveDocument.SnappingViewModel, Source={viewModels1:MainVM DocumentManagerSVM}}"
         SnappingEnabled="{Binding ViewportSubViewModel.SnappingEnabled, Source={viewModels1:MainVM}, Mode=TwoWay}"
+        AvailableRenderOutputs="{Binding ActiveDocument.NodeGraph.AvailableRenderOutputs, Source={viewModels1:MainVM DocumentManagerSVM}}"
+        ViewportRenderOutput="{Binding RenderOutputName, Mode=TwoWay}"
+        HudVisible="{Binding HudVisible}"
         Document="{Binding Document}">
     </viewportControls:Viewport>
 </UserControl>

+ 1 - 11
src/PixiEditor/Views/Dock/DocumentTemplate.axaml.cs

@@ -1,14 +1,4 @@
-using System.ComponentModel;
-using Avalonia.Controls;
-using Avalonia.Controls.Primitives;
-using Avalonia.Input;
-using Avalonia.Interactivity;
-using PixiEditor.Models.Preferences;
-using PixiEditor.ViewModels.Dock;
-using PixiEditor.Views.Palettes;
-using PixiEditor.Extensions.CommonApi.UserPreferences.Settings.PixiEditor;
-using PixiEditor.ViewModels.SubViewModels;
-using PixiEditor.ViewModels.Tools.Tools;
+using Avalonia.Controls;
 
 namespace PixiEditor.Views.Dock;
 

+ 2 - 1
src/PixiEditor/Views/LoadingWindow.axaml

@@ -7,7 +7,7 @@
         xmlns:indicators="clr-namespace:PixiEditor.Views.Indicators"
         mc:Ignorable="d" ShowInTaskbar="False" SystemDecorations="None"
         CanResize="False" WindowStartupLocation="CenterScreen"
-        Title="LoadingWindow" Height="180" Width="160"
+        Title="LoadingWindow" MinHeight="180" MinWidth="160" MaxHeight="180" MaxWidth="160"
         Background="Transparent"
         x:Name="Window">
     <Border Background="{StaticResource ThemeBackgroundBrush}"
@@ -17,6 +17,7 @@
            <indicators:LoadingIndicator/>
             <TextBlock Foreground="White" Text="PixiEditor"
                        FontFamily="Roboto" FontWeight="900" FontSize="28"
+                       HorizontalAlignment="Center" 
                        Margin="0,10,0,0"/>
         </StackPanel>
     </Border>

+ 2 - 2
src/PixiEditor/Views/LoadingWindow.axaml.cs

@@ -15,10 +15,10 @@ public partial class LoadingWindow : Window
 
     public static void ShowInNewThread()
     {
-        /*var thread = new Thread(ThreadStart) { IsBackground = true };
+        var thread = new Thread(ThreadStart) { IsBackground = true };
 
         thread.SetApartmentState(ApartmentState.STA);
-        thread.Start();*/
+        thread.Start();
     }
 
     public void SafeClose()

+ 2 - 0
src/PixiEditor/Views/Main/ViewportControls/FixedViewport.axaml.cs

@@ -128,6 +128,8 @@ internal partial class FixedViewport : UserControl, INotifyPropertyChanged
         {
             newDoc.SizeChanged += viewport.DocSizeChanged;
         }
+        
+        viewport.ForceRefreshFinalImage();
     }
 
     private void DocSizeChanged(object? sender, DocumentSizeChangedEventArgs e)

+ 30 - 13
src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml

@@ -42,7 +42,8 @@
                                         PassEventArgsToCommand="True"/>
             </EventTriggerBehavior>-->
         </Interaction.Behaviors>
-        <overlays:TogglableFlyout Margin="5" Icon="{DynamicResource icon-tool}"
+        <overlays:TogglableFlyout IsVisible="{Binding HudVisible, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}}" 
+                                  Margin="5" Icon="{DynamicResource icon-tool}"
                                   ZIndex="2" HorizontalAlignment="Right" VerticalAlignment="Top">
             <overlays:TogglableFlyout.Child>
                 <Border Padding="5"
@@ -51,8 +52,8 @@
                         BorderThickness="{DynamicResource ThemeBorderThickness}"
                         Background="{DynamicResource ThemeBackgroundBrush1}" ZIndex="2">
                     <StackPanel Orientation="Vertical">
-                        <StackPanel Orientation="Horizontal">
-                            <Border Width="35" Height="35" Margin="5 0"
+                        <StackPanel Orientation="Horizontal" Spacing="10" HorizontalAlignment="Center">
+                            <Border Width="35" Height="35"
                                     BorderBrush="{DynamicResource ThemeBorderMidBrush}"
                                     BorderThickness="{DynamicResource ThemeBorderThickness}"
                                     CornerRadius="{DynamicResource ControlCornerRadius}"
@@ -74,14 +75,14 @@
                                     Cursor="Hand" />
                         </StackPanel>
                         <Separator />
-                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
+                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="10">
                             <ToggleButton Width="32" Height="32" ui:Translator.TooltipKey="HIGH_RES_PREVIEW"
                                           Classes="OverlayButton pixi-icon"
                                           BorderBrush="{DynamicResource ThemeBorderMidBrush}"
                                           Content="{DynamicResource icon-circle}"
                                           IsChecked="{Binding HighResPreview, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=TwoWay}"
                                           Cursor="Hand" />
-                            <ToggleButton Margin="10 0 0 0" Width="32" Height="32"
+                            <ToggleButton Width="32" Height="32"
                                           ui:Translator.TooltipKey="LOW_RES_PREVIEW"
                                           Classes="OverlayButton pixi-icon"
                                           BorderBrush="{DynamicResource ThemeBorderMidBrush}"
@@ -90,13 +91,13 @@
                                           Cursor="Hand" />
                         </StackPanel>
                         <Separator />
-                        <StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
+                        <StackPanel HorizontalAlignment="Center" Orientation="Horizontal" Spacing="10">
                             <ToggleButton Width="32" Height="32" ui:Translator.TooltipKey="TOGGLE_VERTICAL_SYMMETRY"
                                           Classes="OverlayToggleButton pixi-icon"
                                           IsChecked="{Binding Document.VerticalSymmetryAxisEnabledBindable, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=TwoWay}"
                                           Content="{DynamicResource icon-y-symmetry}"
                                           Cursor="Hand" />
-                            <ToggleButton Margin="10 0 0 0" Width="32" Height="32"
+                            <ToggleButton Width="32" Height="32"
                                           ui:Translator.TooltipKey="TOGGLE_HORIZONTAL_SYMMETRY"
                                           Classes="OverlayToggleButton pixi-icon"
                                           IsChecked="{Binding Document.HorizontalSymmetryAxisEnabledBindable, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=TwoWay}"
@@ -104,13 +105,13 @@
                                           Cursor="Hand" />
                         </StackPanel>
                         <Separator />
-                        <StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
+                        <StackPanel HorizontalAlignment="Center" Orientation="Horizontal" Spacing="10">
                             <ToggleButton Width="32" Height="32" ui:Translator.TooltipKey="FLIP_VIEWPORT_HORIZONTALLY"
                                           Classes="OverlayToggleButton pixi-icon"
                                           IsChecked="{Binding FlipX, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=TwoWay}"
                                           Content="{DynamicResource icon-y-flip}"
                                           Cursor="Hand" />
-                            <ToggleButton Margin="10 0 0 0" Width="32" Height="32"
+                            <ToggleButton  Width="32" Height="32"
                                           ui:Translator.TooltipKey="FLIP_VIEWPORT_VERTICALLY"
                                           Classes="OverlayToggleButton pixi-icon"
                                           IsChecked="{Binding FlipY, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=TwoWay}"
@@ -118,24 +119,39 @@
                                           Cursor="Hand" />
                         </StackPanel>
                         <Separator />
-                        <StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
-                            <ToggleButton Margin="10 0 0 0" Width="32" Height="32"
+                        <StackPanel Spacing="10" HorizontalAlignment="Center" Orientation="Horizontal">
+                            <ToggleButton Width="32" Height="32"
                                           ui:Translator.TooltipKey="TOGGLE_GRIDLINES"
                                           Classes="OverlayToggleButton pixi-icon"
                                           Content="{DynamicResource icon-gridlines}"
                                           IsChecked="{Binding GridLinesVisible, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=TwoWay}" />
 
-                            <ToggleButton Margin="10 0 0 0" Width="32" Height="32"
+                            <ToggleButton Width="32" Height="32"
                                           ui:Translator.TooltipKey="TOGGLE_SNAPPING"
                                           Classes="OverlayToggleButton pixi-icon"
                                           Content="{DynamicResource icon-snapping}"
                                           IsChecked="{Binding SnappingEnabled, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=TwoWay}" />
                         </StackPanel>
+                        <Separator />
+                        <TextBlock ui:Translator.Key="RENDER_PREVIEW" />
+                        <ComboBox
+                            ItemsSource="{Binding AvailableRenderOutputs, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}}"
+                            SelectedValue="{Binding ViewportRenderOutput, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
+                            SelectionChanged="RenderOutput_SelectionChanged"
+                            ui:Translator.TooltipKey="RENDER_OUTPUT"
+                            Margin="0 10 0 0">
+                            <ComboBox.ItemTemplate>
+                                <DataTemplate>
+                                    <TextBlock Cursor="Arrow" ui:Translator.Key="{Binding}" />
+                                </DataTemplate>
+                            </ComboBox.ItemTemplate>
+                        </ComboBox>
                     </StackPanel>
                 </Border>
             </overlays:TogglableFlyout.Child>
         </overlays:TogglableFlyout>
-        <Grid ZIndex="100" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="10">
+        <Grid IsVisible="{Binding HudVisible, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}}"
+            ZIndex="100" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="10">
             <Grid.RowDefinitions>
                 <RowDefinition MinHeight="40" MaxHeight="120" />
                 <RowDefinition Height="35" />
@@ -179,6 +195,7 @@
             AllOverlays="{Binding ElementName=vpUc, Path=ActiveOverlays}"
             FadeOut="{Binding Source={viewModels:ToolVM ColorPickerToolViewModel}, Path=PickOnlyFromReferenceLayer, Mode=OneWay}"
             DefaultCursor="{Binding Source={viewModels:MainVM}, Path=ToolsSubViewModel.ToolCursor, Mode=OneWay}"
+            RenderOutput="{Binding ViewportRenderOutput, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=viewportControls:Viewport}, Mode=OneWay}"
             CheckerImagePath="/Images/CheckerTile.png"
             PointerPressed="Scene_OnContextMenuOpening"
             ui:RenderOptionsBindable.BitmapInterpolationMode="{Binding Scale, Converter={converters:ScaleToBitmapScalingModeConverter}, RelativeSource={RelativeSource Self}}">

+ 43 - 2
src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml.cs

@@ -5,6 +5,7 @@ using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Input;
 using Avalonia.Interactivity;
+using Avalonia.VisualTree;
 using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.Helpers;
@@ -224,6 +225,15 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
         set => SetValue(FlipYProperty, value);
     }
 
+    public static readonly StyledProperty<bool> HudVisibleProperty = AvaloniaProperty.Register<Viewport, bool>(
+        nameof(HudVisible), true);
+
+    public bool HudVisible
+    {
+        get => GetValue(HudVisibleProperty);
+        set => SetValue(HudVisibleProperty, value);
+    }
+
     public ViewportColorChannels Channels
     {
         get => GetValue(ChannelsProperty);
@@ -288,6 +298,18 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
         }
     }
 
+    public static readonly StyledProperty<ObservableCollection<string>> AvailableRenderOutputsProperty =
+        AvaloniaProperty.Register<Viewport, ObservableCollection<string>>(nameof(AvailableRenderOutputs));
+
+    public static readonly StyledProperty<string> ViewportRenderOutputProperty = AvaloniaProperty.Register<Viewport, string>(
+        nameof(ViewportRenderOutput), "DEFAULT");
+
+    public string ViewportRenderOutput
+    {
+        get => GetValue(ViewportRenderOutputProperty);
+        set => SetValue(ViewportRenderOutputProperty, value);
+    }
+
     public ObservableCollection<Overlay> ActiveOverlays { get; } = new();
 
     public Guid GuidValue { get; } = Guid.NewGuid();
@@ -350,6 +372,12 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
         set { SetValue(SnappingEnabledProperty, value); }
     }
 
+    public ObservableCollection<string> AvailableRenderOutputs
+    {
+        get { return (ObservableCollection<string>)GetValue(AvailableRenderOutputsProperty); }
+        set { SetValue(AvailableRenderOutputsProperty, value); }
+    }
+
     private void ForceRefreshFinalImage()
     {
         Scene.InvalidateVisual();
@@ -521,8 +549,8 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
 
     private void Scene_OnContextMenuOpening(object? sender, PointerPressedEventArgs e)
     {
-        if(e.GetMouseButton(this) != MouseButton.Right) return;
-        
+        if (e.GetMouseButton(this) != MouseButton.Right) return;
+
         ViewportWindowViewModel vm = ((ViewportWindowViewModel)DataContext);
         var tools = vm.Owner.Owner.ToolsSubViewModel;
 
@@ -548,4 +576,17 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
         Scene?.ContextFlyout?.ShowAt(Scene);
         e.Handled = true;
     }
+
+    private void RenderOutput_SelectionChanged(object? sender, SelectionChangedEventArgs e)
+    {
+        if (e.Source is ComboBox comboBox)
+        {
+            if(!comboBox.IsAttachedToVisualTree()) return;
+
+            if (e.AddedItems.Count > 0)
+            {
+                ViewportRenderOutput = (string)e.AddedItems[0];
+            }
+        }
+    }
 }

+ 12 - 0
src/PixiEditor/Views/Nodes/NodeGraphView.cs

@@ -254,6 +254,18 @@ internal class NodeGraphView : Zoombox.Zoombox
                 nodeView.PropertyChanged += NodeView_PropertyChanged;
             }
         }
+        
+        if(e.Property == Canvas.LeftProperty || e.Property == Canvas.TopProperty)
+        {
+            if (e.Sender is ContentPresenter presenter && presenter.Child is NodeView nodeView)
+            {
+                Dispatcher.UIThread.Post(
+                    () =>
+                {
+                    UpdateConnections(nodeView);
+                }, DispatcherPriority.Render);
+            }
+        }
     }
 
     private void CreateNodeType(NodeTypeInfo nodeType)

+ 23 - 0
src/PixiEditor/Views/Nodes/Properties/StringPropertyView.axaml

@@ -0,0 +1,23 @@
+<properties:NodePropertyView xmlns="https://github.com/avaloniaui"
+                             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+                             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+                             xmlns:properties="clr-namespace:PixiEditor.Views.Nodes.Properties"
+                             xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
+                             xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
+                             xmlns:behaviours="clr-namespace:PixiEditor.Helpers.Behaviours"
+                             xmlns:system="clr-namespace:System;assembly=System.Runtime"
+                             xmlns:properties1="clr-namespace:PixiEditor.ViewModels.Nodes.Properties"
+                             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+                             x:DataType="properties1:StringPropertyViewModel"
+                             x:Class="PixiEditor.Views.Nodes.Properties.StringPropertyView">
+    <DockPanel LastChildFill="True"
+        HorizontalAlignment="{Binding IsInput, Converter={converters:BoolToValueConverter FalseValue='Right', TrueValue='Stretch'}}">
+        <TextBlock VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}" />
+        <TextBox Text="{CompiledBinding StringValue, Mode=TwoWay}" IsVisible="{Binding ShowInputField}">
+            <Interaction.Behaviors>
+                <behaviours:GlobalShortcutFocusBehavior />
+            </Interaction.Behaviors>
+        </TextBox>
+    </DockPanel>
+</properties:NodePropertyView>

+ 14 - 0
src/PixiEditor/Views/Nodes/Properties/StringPropertyView.axaml.cs

@@ -0,0 +1,14 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace PixiEditor.Views.Nodes.Properties;
+
+public partial class StringPropertyView : NodePropertyView
+{
+    public StringPropertyView()
+    {
+        InitializeComponent();
+    }
+}
+

+ 8 - 1
src/PixiEditor/Views/Rendering/Scene.cs

@@ -106,6 +106,12 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         set => SetValue(ChannelsProperty, value);
     }
 
+    public string RenderOutput
+    {
+        get { return (string)GetValue(RenderOutputProperty); }
+        set { SetValue(RenderOutputProperty, value); }
+    }
+
 
     private Bitmap? checkerBitmap;
 
@@ -134,6 +140,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
 
     private PixelSize lastSize = PixelSize.Empty;
     private Cursor lastCursor;
+    public static readonly StyledProperty<string> RenderOutputProperty = AvaloniaProperty.Register<Scene, string>("RenderOutput");
 
     static Scene()
     {
@@ -254,7 +261,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
     {
         DrawCheckerboard(renderTexture.DrawingSurface, bounds);
         DrawOverlays(renderTexture.DrawingSurface, bounds, OverlayRenderSorting.Background);
-        SceneRenderer.RenderScene(renderTexture.DrawingSurface, CalculateResolution());
+        SceneRenderer.RenderScene(renderTexture.DrawingSurface, CalculateResolution(), RenderOutput == "DEFAULT" ? null : RenderOutput);
         DrawOverlays(renderTexture.DrawingSurface, bounds, OverlayRenderSorting.Foreground);
     }