Browse Source

Render size in context

Krzysztof Krysiński 3 months ago
parent
commit
1486461956
20 changed files with 114 additions and 45 deletions
  1. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/SceneObjectRenderContext.cs
  2. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CreateImageNode.cs
  3. 8 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/DocumentInfoNode.cs
  4. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Effects/OutlineNode.cs
  5. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FolderNode.cs
  6. 3 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LayerNode.cs
  7. 2 2
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs
  8. 4 4
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/RenderNode.cs
  9. 6 6
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ShaderNode.cs
  10. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/DistributePointsNode.cs
  11. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs
  12. 1 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/TileNode.cs
  13. 3 3
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Workspace/CustomOutputNode.cs
  14. 1 1
      src/PixiEditor.ChangeableDocument/Changes/NodeGraph/EvaluateGraph_Change.cs
  15. 31 5
      src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs
  16. 5 3
      src/PixiEditor.ChangeableDocument/Rendering/RenderContext.cs
  17. 2 1
      src/PixiEditor/Data/Localization/Languages/en.json
  18. 1 1
      src/PixiEditor/Models/Rendering/PreviewPainter.cs
  19. 8 7
      src/PixiEditor/Models/Rendering/SceneRenderer.cs
  20. 33 2
      src/PixiEditor/Views/Rendering/Scene.cs

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/SceneObjectRenderContext.cs

@@ -13,7 +13,7 @@ public class SceneObjectRenderContext : RenderContext
     public RenderOutputProperty TargetPropertyOutput { get; }
     public RenderOutputProperty TargetPropertyOutput { get; }
 
 
     public SceneObjectRenderContext(RenderOutputProperty targetPropertyOutput, DrawingSurface surface, RectD localBounds, KeyFrameTime frameTime,
     public SceneObjectRenderContext(RenderOutputProperty targetPropertyOutput, DrawingSurface surface, RectD localBounds, KeyFrameTime frameTime,
-        ChunkResolution chunkResolution, VecI docSize, bool renderSurfaceIsScene, ColorSpace processingColorSpace, double opacity) : base(surface, frameTime, chunkResolution, docSize, processingColorSpace, opacity)
+        ChunkResolution chunkResolution, VecI renderOutputSize, VecI documentSize, bool renderSurfaceIsScene, ColorSpace processingColorSpace, double opacity) : base(surface, frameTime, chunkResolution, renderOutputSize, documentSize, processingColorSpace, opacity)
     {
     {
         TargetPropertyOutput = targetPropertyOutput;
         TargetPropertyOutput = targetPropertyOutput;
         LocalBounds = localBounds;
         LocalBounds = localBounds;

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/CreateImageNode.cs

@@ -72,7 +72,7 @@ public class CreateImageNode : Node, IPreviewRenderable
         int saved = surface.DrawingSurface.Canvas.Save();
         int saved = surface.DrawingSurface.Canvas.Save();
 
 
         RenderContext ctx = new RenderContext(surface.DrawingSurface, context.FrameTime, context.ChunkResolution,
         RenderContext ctx = new RenderContext(surface.DrawingSurface, context.FrameTime, context.ChunkResolution,
-            context.DocumentSize, context.ProcessingColorSpace);
+            context.RenderOutputSize, context.DocumentSize, context.ProcessingColorSpace);
 
 
         surface.DrawingSurface.Canvas.SetMatrix(surface.DrawingSurface.Canvas.TotalMatrix.Concat(ContentMatrix.Value));
         surface.DrawingSurface.Canvas.SetMatrix(surface.DrawingSurface.Canvas.TotalMatrix.Concat(ContentMatrix.Value));
 
 

+ 8 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/DocumentInfoNode.cs

@@ -9,16 +9,24 @@ public class DocumentInfoNode : Node
     public OutputProperty<VecI> Size { get; }
     public OutputProperty<VecI> Size { get; }
     public OutputProperty<VecD> Center { get; }
     public OutputProperty<VecD> Center { get; }
 
 
+    public OutputProperty<VecI> RenderOutputSize { get; }
+    public OutputProperty<VecI> RenderOutputCenter { get; }
+
     public DocumentInfoNode()
     public DocumentInfoNode()
     {
     {
         Size = CreateOutput("Size", "SIZE", new VecI(0, 0));
         Size = CreateOutput("Size", "SIZE", new VecI(0, 0));
         Center = CreateOutput("Center", "CENTER", new VecD(0, 0));
         Center = CreateOutput("Center", "CENTER", new VecD(0, 0));
+        RenderOutputSize = CreateOutput("RenderOutputSize", "RENDER_OUTPUT_SIZE", new VecI(0, 0));
+        RenderOutputCenter = CreateOutput("RenderOutputCenter", "RENDER_OUTPUT_CENTER", new VecI(0, 0));
     }
     }
 
 
     protected override void OnExecute(RenderContext context)
     protected override void OnExecute(RenderContext context)
     {
     {
         Size.Value = context.DocumentSize;
         Size.Value = context.DocumentSize;
         Center.Value = new VecD(context.DocumentSize.X / 2.0, context.DocumentSize.Y / 2.0);
         Center.Value = new VecD(context.DocumentSize.X / 2.0, context.DocumentSize.Y / 2.0);
+
+        RenderOutputSize.Value = context.RenderOutputSize;
+        RenderOutputCenter.Value = new VecI(context.RenderOutputSize.X / 2, context.RenderOutputSize.Y / 2);
     }
     }
 
 
     public override Node CreateCopy()
     public override Node CreateCopy()

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Effects/OutlineNode.cs

@@ -52,7 +52,7 @@ public class OutlineNode : RenderNode, IRenderInput
     protected override void OnExecute(RenderContext context)
     protected override void OnExecute(RenderContext context)
     {
     {
         base.OnExecute(context);
         base.OnExecute(context);
-        lastDocumentSize = context.DocumentSize;
+        lastDocumentSize = context.RenderOutputSize;
 
 
         Kernel finalKernel = Type.Value switch
         Kernel finalKernel = Type.Value switch
         {
         {

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FolderNode.cs

@@ -39,7 +39,7 @@ public class FolderNode : StructureNode, IReadOnlyFolderNode, IClipSource
     protected override void OnExecute(RenderContext context)
     protected override void OnExecute(RenderContext context)
     {
     {
         base.OnExecute(context);
         base.OnExecute(context);
-        documentSize = context.DocumentSize;
+        documentSize = context.RenderOutputSize;
     }
     }
 
 
     public override void Render(SceneObjectRenderContext sceneContext)
     public override void Render(SceneObjectRenderContext sceneContext)

+ 3 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LayerNode.cs

@@ -44,7 +44,7 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource
                 blendPaint.BlendMode = RenderContext.GetDrawingBlendMode(BlendMode.Value);
                 blendPaint.BlendMode = RenderContext.GetDrawingBlendMode(BlendMode.Value);
             }
             }
 
 
-            if (AllowHighDpiRendering || renderOnto.DeviceClipBounds.Size == context.DocumentSize)
+            if (AllowHighDpiRendering || renderOnto.DeviceClipBounds.Size == context.RenderOutputSize)
             {
             {
                 DrawLayerInScene(context, renderOnto, useFilters);
                 DrawLayerInScene(context, renderOnto, useFilters);
             }
             }
@@ -56,7 +56,7 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource
                     BlendMode = Drawie.Backend.Core.Surfaces.BlendMode.SrcOver
                     BlendMode = Drawie.Backend.Core.Surfaces.BlendMode.SrcOver
                 };
                 };
 
 
-                var tempSurface = TryInitWorkingSurface(context.DocumentSize, context.ChunkResolution,
+                var tempSurface = TryInitWorkingSurface(context.RenderOutputSize, context.ChunkResolution,
                     context.ProcessingColorSpace, 22);
                     context.ProcessingColorSpace, 22);
 
 
                 DrawLayerOnTexture(context, tempSurface.DrawingSurface, context.ChunkResolution, useFilters, targetPaint);
                 DrawLayerOnTexture(context, tempSurface.DrawingSurface, context.ChunkResolution, useFilters, targetPaint);
@@ -70,7 +70,7 @@ public abstract class LayerNode : StructureNode, IReadOnlyLayerNode, IClipSource
 
 
         VecI size = AllowHighDpiRendering
         VecI size = AllowHighDpiRendering
             ? renderOnto.DeviceClipBounds.Size + renderOnto.DeviceClipBounds.Pos
             ? renderOnto.DeviceClipBounds.Size + renderOnto.DeviceClipBounds.Pos
-            : context.DocumentSize;
+            : context.RenderOutputSize;
         int saved = renderOnto.Canvas.Save();
         int saved = renderOnto.Canvas.Save();
 
 
         var adjustedResolution = AllowHighDpiRendering ? ChunkResolution.Full : context.ChunkResolution;
         var adjustedResolution = AllowHighDpiRendering ? ChunkResolution.Full : context.ChunkResolution;

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

@@ -32,10 +32,10 @@ public class OutputNode : Node, IRenderInput, IPreviewRenderable
     {
     {
         if (!string.IsNullOrEmpty(context.TargetOutput)) return;
         if (!string.IsNullOrEmpty(context.TargetOutput)) return;
 
 
-        lastDocumentSize = context.DocumentSize;
+        lastDocumentSize = context.RenderOutputSize;
 
 
         int saved = context.RenderSurface.Canvas.Save();
         int saved = context.RenderSurface.Canvas.Save();
-        context.RenderSurface.Canvas.ClipRect(new RectD(0, 0, context.DocumentSize.X, context.DocumentSize.Y));
+        context.RenderSurface.Canvas.ClipRect(new RectD(0, 0, context.RenderOutputSize.X, context.RenderOutputSize.Y));
         Input.Value?.Paint(context, context.RenderSurface);
         Input.Value?.Paint(context, context.RenderSurface);
 
 
         context.RenderSurface.Canvas.RestoreToCount(saved);
         context.RenderSurface.Canvas.RestoreToCount(saved);

+ 4 - 4
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/RenderNode.cs

@@ -39,18 +39,18 @@ public abstract class RenderNode : Node, IPreviewRenderable, IHighDpiRenderNode
             }
             }
         }
         }
 
 
-        lastDocumentSize = context.DocumentSize;
+        lastDocumentSize = context.RenderOutputSize;
     }
     }
 
 
     protected virtual void Paint(RenderContext context, DrawingSurface surface)
     protected virtual void Paint(RenderContext context, DrawingSurface surface)
     {
     {
         DrawingSurface target = surface;
         DrawingSurface target = surface;
         bool useIntermediate = !AllowHighDpiRendering
         bool useIntermediate = !AllowHighDpiRendering
-                               && context.DocumentSize is { X: > 0, Y: > 0 }
-                               && surface.DeviceClipBounds.Size != context.DocumentSize;
+                               && context.RenderOutputSize is { X: > 0, Y: > 0 }
+                               && surface.DeviceClipBounds.Size != context.RenderOutputSize;
         if (useIntermediate)
         if (useIntermediate)
         {
         {
-            Texture intermediate = textureCache.RequestTexture(-6451, context.DocumentSize, context.ProcessingColorSpace);
+            Texture intermediate = textureCache.RequestTexture(-6451, context.RenderOutputSize, context.ProcessingColorSpace);
             target = intermediate.DrawingSurface;
             target = intermediate.DrawingSurface;
         }
         }
 
 

+ 6 - 6
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ShaderNode.cs

@@ -48,7 +48,7 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
     {
     {
         base.OnExecute(context);
         base.OnExecute(context);
 
 
-        lastDocumentSize = context.DocumentSize;
+        lastDocumentSize = context.RenderOutputSize;
 
 
         if (lastShaderCode != ShaderCode.Value)
         if (lastShaderCode != ShaderCode.Value)
         {
         {
@@ -88,7 +88,7 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
         Uniforms uniforms;
         Uniforms uniforms;
         uniforms = new Uniforms();
         uniforms = new Uniforms();
 
 
-        uniforms.Add("iResolution", new Uniform("iResolution", (VecD)context.DocumentSize));
+        uniforms.Add("iResolution", new Uniform("iResolution", (VecD)context.RenderOutputSize));
         uniforms.Add("iNormalizedTime", new Uniform("iNormalizedTime", (float)context.FrameTime.NormalizedTime));
         uniforms.Add("iNormalizedTime", new Uniform("iNormalizedTime", (float)context.FrameTime.NormalizedTime));
         uniforms.Add("iFrame", new Uniform("iFrame", context.FrameTime.Frame));
         uniforms.Add("iFrame", new Uniform("iFrame", context.FrameTime.Frame));
 
 
@@ -101,7 +101,7 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
             return uniforms;
             return uniforms;
         }
         }
 
 
-        Texture texture = RequestTexture(50, context.DocumentSize, context.ProcessingColorSpace);
+        Texture texture = RequestTexture(50, context.RenderOutputSize, context.ProcessingColorSpace);
         Background.Value.Paint(context, texture.DrawingSurface);
         Background.Value.Paint(context, texture.DrawingSurface);
 
 
         var snapshot = texture.DrawingSurface.Snapshot();
         var snapshot = texture.DrawingSurface.Snapshot();
@@ -128,17 +128,17 @@ public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
         {
         {
             if (ColorSpace.Value == ColorSpaceType.Srgb && !context.ProcessingColorSpace.IsSrgb)
             if (ColorSpace.Value == ColorSpaceType.Srgb && !context.ProcessingColorSpace.IsSrgb)
             {
             {
-                targetSurface = RequestTexture(51, context.DocumentSize,
+                targetSurface = RequestTexture(51, context.RenderOutputSize,
                     Drawie.Backend.Core.Surfaces.ImageData.ColorSpace.CreateSrgb()).DrawingSurface;
                     Drawie.Backend.Core.Surfaces.ImageData.ColorSpace.CreateSrgb()).DrawingSurface;
             }
             }
             else if (ColorSpace.Value == ColorSpaceType.LinearSrgb && context.ProcessingColorSpace.IsSrgb)
             else if (ColorSpace.Value == ColorSpaceType.LinearSrgb && context.ProcessingColorSpace.IsSrgb)
             {
             {
-                targetSurface = RequestTexture(51, context.DocumentSize,
+                targetSurface = RequestTexture(51, context.RenderOutputSize,
                     Drawie.Backend.Core.Surfaces.ImageData.ColorSpace.CreateSrgbLinear()).DrawingSurface;
                     Drawie.Backend.Core.Surfaces.ImageData.ColorSpace.CreateSrgbLinear()).DrawingSurface;
             }
             }
         }
         }
 
 
-        targetSurface.Canvas.DrawRect(0, 0, context.DocumentSize.X, context.DocumentSize.Y, paint);
+        targetSurface.Canvas.DrawRect(0, 0, context.RenderOutputSize.X, context.RenderOutputSize.Y, paint);
 
 
         if (targetSurface != surface)
         if (targetSurface != surface)
         {
         {

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/DistributePointsNode.cs

@@ -22,7 +22,7 @@ public class DistributePointsNode : ShapeNode<PointsVectorData>
 
 
     protected override PointsVectorData? GetShapeData(RenderContext context)
     protected override PointsVectorData? GetShapeData(RenderContext context)
     {
     {
-        return GetPointsRandomly(context.DocumentSize);
+        return GetPointsRandomly(context.RenderOutputSize);
     }
     }
 
 
     private PointsVectorData GetPointsRandomly(VecI size)
     private PointsVectorData GetPointsRandomly(VecI size)

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/StructureNode.cs

@@ -182,7 +182,7 @@ public abstract class StructureNode : RenderNode, IReadOnlyStructureNode, IRende
         RectD localBounds = new RectD(0, 0, sceneSize.X, sceneSize.Y);
         RectD localBounds = new RectD(0, 0, sceneSize.X, sceneSize.Y);
 
 
         SceneObjectRenderContext renderObjectContext = new SceneObjectRenderContext(output, renderTarget, localBounds,
         SceneObjectRenderContext renderObjectContext = new SceneObjectRenderContext(output, renderTarget, localBounds,
-            context.FrameTime, context.ChunkResolution, context.DocumentSize, renderTarget == context.RenderSurface,
+            context.FrameTime, context.ChunkResolution, context.RenderOutputSize, context.DocumentSize, renderTarget == context.RenderSurface,
             context.ProcessingColorSpace,
             context.ProcessingColorSpace,
             context.Opacity);
             context.Opacity);
         renderObjectContext.FullRerender = context.FullRerender;
         renderObjectContext.FullRerender = context.FullRerender;

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/TileNode.cs

@@ -62,7 +62,7 @@ public class TileNode : RenderNode
         if (paint == null)
         if (paint == null)
             return;
             return;
 
 
-        surface.Canvas.DrawRect(0, 0, context.DocumentSize.X, context.DocumentSize.Y, paint);
+        surface.Canvas.DrawRect(0, 0, context.RenderOutputSize.X, context.RenderOutputSize.Y, paint);
     }
     }
 
 
     public override Node CreateCopy()
     public override Node CreateCopy()

+ 3 - 3
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Workspace/CustomOutputNode.cs

@@ -40,14 +40,14 @@ public class CustomOutputNode : Node, IRenderInput, IPreviewRenderable
         if (context.TargetOutput == OutputName.Value)
         if (context.TargetOutput == OutputName.Value)
         {
         {
             VecI targetSize = Size.Value.ShortestAxis <= 0
             VecI targetSize = Size.Value.ShortestAxis <= 0
-                ? context.DocumentSize
+                ? context.RenderOutputSize
                 : Size.Value;
                 : Size.Value;
 
 
             lastDocumentSize = targetSize;
             lastDocumentSize = targetSize;
 
 
             DrawingSurface targetSurface = context.RenderSurface;
             DrawingSurface targetSurface = context.RenderSurface;
 
 
-            if(context.DocumentSize != targetSize)
+            if(context.RenderOutputSize != targetSize)
             {
             {
                 targetSurface = textureCache.RequestTexture(0, targetSize, context.ProcessingColorSpace).DrawingSurface;
                 targetSurface = textureCache.RequestTexture(0, targetSize, context.ProcessingColorSpace).DrawingSurface;
             }
             }
@@ -56,7 +56,7 @@ public class CustomOutputNode : Node, IRenderInput, IPreviewRenderable
             targetSurface.Canvas.ClipRect(new RectD(0, 0, targetSize.X, targetSize.Y));
             targetSurface.Canvas.ClipRect(new RectD(0, 0, targetSize.X, targetSize.Y));
 
 
             RenderContext outputContext = new RenderContext(context.RenderSurface, context.FrameTime, context.ChunkResolution,
             RenderContext outputContext = new RenderContext(context.RenderSurface, context.FrameTime, context.ChunkResolution,
-                targetSize, context.ProcessingColorSpace, context.Opacity) { TargetOutput = OutputName.Value, };
+                targetSize, context.DocumentSize, context.ProcessingColorSpace, context.Opacity) { TargetOutput = OutputName.Value, };
 
 
             Input.Value?.Paint(outputContext, targetSurface);
             Input.Value?.Paint(outputContext, targetSurface);
 
 

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/EvaluateGraph_Change.cs

@@ -32,7 +32,7 @@ internal class EvaluateGraph_Change : Change
 
 
         using Texture renderTexture = Texture.ForProcessing(target.Size, target.ProcessingColorSpace);
         using Texture renderTexture = Texture.ForProcessing(target.Size, target.ProcessingColorSpace);
         RenderContext context =
         RenderContext context =
-            new(renderTexture.DrawingSurface, frameTime, ChunkResolution.Full, target.Size,
+            new(renderTexture.DrawingSurface, frameTime, ChunkResolution.Full, target.Size, target.Size,
                 target.ProcessingColorSpace) { FullRerender = true };
                 target.ProcessingColorSpace) { FullRerender = true };
         foreach (var nodeToEvaluate in queue)
         foreach (var nodeToEvaluate in queue)
         {
         {

+ 31 - 5
src/PixiEditor.ChangeableDocument/Rendering/DocumentRenderer.cs

@@ -12,6 +12,7 @@ using Drawie.Backend.Core.Surfaces.ImageData;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Text;
 using Drawie.Backend.Core.Text;
 using Drawie.Numerics;
 using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Workspace;
 
 
 namespace PixiEditor.ChangeableDocument.Rendering;
 namespace PixiEditor.ChangeableDocument.Rendering;
 
 
@@ -65,7 +66,7 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         toRenderOn.Canvas.Save();
         toRenderOn.Canvas.Save();
         toRenderOn.Canvas.SetMatrix(Matrix3X3.Identity);
         toRenderOn.Canvas.SetMatrix(Matrix3X3.Identity);
 
 
-        RenderContext context = new(renderTexture.DrawingSurface, frame, resolution, Document.Size,
+        RenderContext context = new(renderTexture.DrawingSurface, frame, resolution, Document.Size, Document.Size,
             Document.ProcessingColorSpace);
             Document.ProcessingColorSpace);
         context.FullRerender = true;
         context.FullRerender = true;
         IReadOnlyNodeGraph membersOnlyGraph = ConstructMembersOnlyGraph(layersToCombine, Document.NodeGraph);
         IReadOnlyNodeGraph membersOnlyGraph = ConstructMembersOnlyGraph(layersToCombine, Document.NodeGraph);
@@ -111,7 +112,7 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         toRenderOn.Canvas.Save();
         toRenderOn.Canvas.Save();
         toRenderOn.Canvas.SetMatrix(Matrix3X3.Identity);
         toRenderOn.Canvas.SetMatrix(Matrix3X3.Identity);
 
 
-        RenderContext context = new(renderTexture.DrawingSurface, frameTime, resolution, Document.Size,
+        RenderContext context = new(renderTexture.DrawingSurface, frameTime, resolution, Document.Size, Document.Size,
             Document.ProcessingColorSpace);
             Document.ProcessingColorSpace);
         context.FullRerender = true;
         context.FullRerender = true;
 
 
@@ -225,9 +226,6 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         toRenderOn.Canvas.Save();
         toRenderOn.Canvas.Save();
         toRenderOn.Canvas.SetMatrix(Matrix3X3.Identity);
         toRenderOn.Canvas.SetMatrix(Matrix3X3.Identity);
 
 
-        RenderContext context =
-            new(renderTexture.DrawingSurface, frameTime, ChunkResolution.Full, Document.Size,
-                Document.ProcessingColorSpace) { FullRerender = true };
 
 
         bool hasCustomOutput = !string.IsNullOrEmpty(customOutput) && customOutput != "DEFAULT";
         bool hasCustomOutput = !string.IsNullOrEmpty(customOutput) && customOutput != "DEFAULT";
 
 
@@ -235,6 +233,10 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
             ? RenderingUtils.SolveFinalNodeGraph(customOutput, Document)
             ? RenderingUtils.SolveFinalNodeGraph(customOutput, Document)
             : Document.NodeGraph;
             : Document.NodeGraph;
 
 
+        RenderContext context =
+            new(renderTexture.DrawingSurface, frameTime, ChunkResolution.Full, SolveRenderOutputSize(customOutput, graph, Document.Size),
+                Document.Size, Document.ProcessingColorSpace) { FullRerender = true };
+
         if (hasCustomOutput)
         if (hasCustomOutput)
         {
         {
             context.TargetOutput = customOutput;
             context.TargetOutput = customOutput;
@@ -334,6 +336,30 @@ public class DocumentRenderer : IPreviewRenderable, IDisposable
         return found ?? (membersOnlyGraph.OutputNode as IRenderInput)?.Background;
         return found ?? (membersOnlyGraph.OutputNode as IRenderInput)?.Background;
     }
     }
 
 
+    private static VecI SolveRenderOutputSize(string? targetOutput, IReadOnlyNodeGraph finalGraph, VecI documentSize)
+    {
+        VecI finalSize = documentSize;
+        if (targetOutput != null)
+        {
+            var outputNode = finalGraph.AllNodes.FirstOrDefault(n =>
+                n is CustomOutputNode outputNode && outputNode.OutputName.Value == targetOutput);
+
+            if (outputNode is CustomOutputNode customOutputNode)
+            {
+                if (customOutputNode.Size.Value.ShortestAxis > 0)
+                {
+                    finalSize = customOutputNode.Size.Value;
+                }
+            }
+            else
+            {
+                finalSize = documentSize;
+            }
+        }
+
+        return finalSize;
+    }
+
     public void Dispose()
     public void Dispose()
     {
     {
         renderTexture?.Dispose();
         renderTexture?.Dispose();

+ 5 - 3
src/PixiEditor.ChangeableDocument/Rendering/RenderContext.cs

@@ -13,8 +13,9 @@ public class RenderContext
 
 
     public KeyFrameTime FrameTime { get; }
     public KeyFrameTime FrameTime { get; }
     public ChunkResolution ChunkResolution { get; }
     public ChunkResolution ChunkResolution { get; }
+    public VecI RenderOutputSize { get; set; }
+
     public VecI DocumentSize { get; set; }
     public VecI DocumentSize { get; set; }
-    
     public DrawingSurface RenderSurface { get; set; }
     public DrawingSurface RenderSurface { get; set; }
     public bool FullRerender { get; set; } = false;
     public bool FullRerender { get; set; } = false;
     
     
@@ -23,14 +24,15 @@ public class RenderContext
 
 
 
 
     public RenderContext(DrawingSurface renderSurface, KeyFrameTime frameTime, ChunkResolution chunkResolution,
     public RenderContext(DrawingSurface renderSurface, KeyFrameTime frameTime, ChunkResolution chunkResolution,
-        VecI docSize, ColorSpace processingColorSpace, double opacity = 1) 
+        VecI renderOutputSize, VecI documentSize, ColorSpace processingColorSpace, double opacity = 1)
     {
     {
         RenderSurface = renderSurface;
         RenderSurface = renderSurface;
         FrameTime = frameTime;
         FrameTime = frameTime;
         ChunkResolution = chunkResolution;
         ChunkResolution = chunkResolution;
-        DocumentSize = docSize;
+        RenderOutputSize = renderOutputSize;
         Opacity = opacity;
         Opacity = opacity;
         ProcessingColorSpace = processingColorSpace;
         ProcessingColorSpace = processingColorSpace;
+        DocumentSize = documentSize;
     }
     }
 
 
     public static DrawingApiBlendMode GetDrawingBlendMode(BlendMode blendMode)
     public static DrawingApiBlendMode GetDrawingBlendMode(BlendMode blendMode)

+ 2 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -1045,5 +1045,6 @@
   "EXPORT_ZONE_NODE": "Export Zone",
   "EXPORT_ZONE_NODE": "Export Zone",
   "IS_DEFAULT_EXPORT": "Is Default Export",
   "IS_DEFAULT_EXPORT": "Is Default Export",
   "EXPORT_OUTPUT": "Export Output",
   "EXPORT_OUTPUT": "Export Output",
-  "EXPORT_SIZE": "Export Size"
+  "RENDER_OUTPUT_SIZE": "Render Output Size",
+  "RENDER_OUTPUT_CENTER": "Render Output Center",
 }
 }

+ 1 - 1
src/PixiEditor/Models/Rendering/PreviewPainter.cs

@@ -154,7 +154,7 @@ public class PreviewPainter : IDisposable
 
 
             renderTexture.DrawingSurface.Canvas.SetMatrix(matrix ?? Matrix3X3.Identity);
             renderTexture.DrawingSurface.Canvas.SetMatrix(matrix ?? Matrix3X3.Identity);
 
 
-            RenderContext context = new(renderTexture.DrawingSurface, FrameTime, ChunkResolution.Full, DocumentSize,
+            RenderContext context = new(renderTexture.DrawingSurface, FrameTime, ChunkResolution.Full, DocumentSize, DocumentSize,
                 ProcessingColorSpace);
                 ProcessingColorSpace);
 
 
             dirtyTextures.Remove(texture);
             dirtyTextures.Remove(texture);

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

@@ -93,9 +93,9 @@ internal class SceneRenderer : IDisposable
             restoreCanvas = true;
             restoreCanvas = true;
         }
         }
 
 
-        VecI finalSize = SolveDocSize(targetOutput, finalGraph);
+        VecI finalSize = SolveRenderOutputSize(targetOutput, finalGraph, Document.Size);
         RenderContext context = new(renderTarget, DocumentViewModel.AnimationHandler.ActiveFrameTime,
         RenderContext context = new(renderTarget, DocumentViewModel.AnimationHandler.ActiveFrameTime,
-            resolution, finalSize, Document.ProcessingColorSpace);
+            resolution, finalSize, Document.Size, Document.ProcessingColorSpace);
         context.TargetOutput = targetOutput;
         context.TargetOutput = targetOutput;
         finalGraph.Execute(context);
         finalGraph.Execute(context);
 
 
@@ -112,9 +112,9 @@ internal class SceneRenderer : IDisposable
         return renderTexture;
         return renderTexture;
     }
     }
 
 
-    private VecI SolveDocSize(string? targetOutput, IReadOnlyNodeGraph finalGraph)
+    private static VecI SolveRenderOutputSize(string? targetOutput, IReadOnlyNodeGraph finalGraph, VecI documentSize)
     {
     {
-        VecI finalSize = Document.Size;
+        VecI finalSize = documentSize;
         if (targetOutput != null)
         if (targetOutput != null)
         {
         {
             var outputNode = finalGraph.AllNodes.FirstOrDefault(n =>
             var outputNode = finalGraph.AllNodes.FirstOrDefault(n =>
@@ -129,7 +129,7 @@ internal class SceneRenderer : IDisposable
             }
             }
             else
             else
             {
             {
-                finalSize = Document.Size;
+                finalSize = documentSize;
             }
             }
         }
         }
 
 
@@ -250,6 +250,7 @@ internal class SceneRenderer : IDisposable
         double alphaFalloffMultiplier = 1.0 / animationData.OnionFrames;
         double alphaFalloffMultiplier = 1.0 / animationData.OnionFrames;
 
 
         var finalGraph = RenderingUtils.SolveFinalNodeGraph(targetOutput, Document);
         var finalGraph = RenderingUtils.SolveFinalNodeGraph(targetOutput, Document);
+        var renderOutputSize = SolveRenderOutputSize(targetOutput, finalGraph, Document.Size);
 
 
         // Render previous frames'
         // Render previous frames'
         for (int i = 1; i <= animationData.OnionFrames; i++)
         for (int i = 1; i <= animationData.OnionFrames; i++)
@@ -262,7 +263,7 @@ internal class SceneRenderer : IDisposable
 
 
             double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
             double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
 
 
-            RenderContext onionContext = new(target, frame, resolution, Document.Size, Document.ProcessingColorSpace,
+            RenderContext onionContext = new(target, frame, resolution, renderOutputSize, Document.Size, Document.ProcessingColorSpace,
                 finalOpacity);
                 finalOpacity);
             onionContext.TargetOutput = targetOutput;
             onionContext.TargetOutput = targetOutput;
             finalGraph.Execute(onionContext);
             finalGraph.Execute(onionContext);
@@ -278,7 +279,7 @@ internal class SceneRenderer : IDisposable
             }
             }
 
 
             double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
             double finalOpacity = onionOpacity * alphaFalloffMultiplier * (animationData.OnionFrames - i + 1);
-            RenderContext onionContext = new(target, frame, resolution, Document.Size, Document.ProcessingColorSpace,
+            RenderContext onionContext = new(target, frame, resolution, renderOutputSize, Document.Size, Document.ProcessingColorSpace,
                 finalOpacity);
                 finalOpacity);
             onionContext.TargetOutput = targetOutput;
             onionContext.TargetOutput = targetOutput;
             finalGraph.Execute(onionContext);
             finalGraph.Execute(onionContext);

+ 33 - 2
src/PixiEditor/Views/Rendering/Scene.cs

@@ -195,6 +195,8 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         CustomBackgroundScaleXProperty.Changed.AddClassHandler<Scene>(Refresh);
         CustomBackgroundScaleXProperty.Changed.AddClassHandler<Scene>(Refresh);
         CustomBackgroundScaleYProperty.Changed.AddClassHandler<Scene>(Refresh);
         CustomBackgroundScaleYProperty.Changed.AddClassHandler<Scene>(Refresh);
         BackgroundBitmapProperty.Changed.AddClassHandler<Scene>(Refresh);
         BackgroundBitmapProperty.Changed.AddClassHandler<Scene>(Refresh);
+        RenderOutputProperty.Changed.AddClassHandler<Scene>(Refresh);
+        RenderOutputProperty.Changed.AddClassHandler<Scene>(UpdateRenderOutput);
     }
     }
 
 
     private static void Refresh(Scene scene, AvaloniaPropertyChangedEventArgs args)
     private static void Refresh(Scene scene, AvaloniaPropertyChangedEventArgs args)
@@ -848,13 +850,42 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         if (e.NewValue is DocumentViewModel documentViewModel)
         if (e.NewValue is DocumentViewModel documentViewModel)
         {
         {
             documentViewModel.SizeChanged += scene.DocumentViewModelOnSizeChanged;
             documentViewModel.SizeChanged += scene.DocumentViewModelOnSizeChanged;
-            scene.ContentDimensions = documentViewModel.SizeBindable;
+            scene.ContentDimensions = scene.GetRenderOutputSize();
         }
         }
     }
     }
 
 
     private void DocumentViewModelOnSizeChanged(object? sender, DocumentSizeChangedEventArgs e)
     private void DocumentViewModelOnSizeChanged(object? sender, DocumentSizeChangedEventArgs e)
     {
     {
-        ContentDimensions = e.NewSize;
+        ContentDimensions = GetRenderOutputSize();
+    }
+
+    private VecI GetRenderOutputSize()
+    {
+        VecI outputSize = Document.SizeBindable;
+
+        if (!string.IsNullOrEmpty(RenderOutput))
+        {
+            if (Document.NodeGraph.CustomRenderOutputs.TryGetValue(RenderOutput, out var node))
+            {
+                var prop = node?.Inputs.FirstOrDefault(x => x.PropertyName == CustomOutputNode.SizePropertyName);
+                if (prop != null)
+                {
+                    VecI size = Document.NodeGraph.GetComputedPropertyValue<VecI>(prop);
+                    outputSize = size;
+                }
+            }
+        }
+
+        return outputSize;
+    }
+
+    private static void UpdateRenderOutput(Scene scene, AvaloniaPropertyChangedEventArgs e)
+    {
+        if (e.NewValue is string newValue)
+        {
+            scene.ContentDimensions = scene.GetRenderOutputSize();
+            scene.CenterContent();
+        }
     }
     }
 
 
     private static void DefaultCursorChanged(Scene scene, AvaloniaPropertyChangedEventArgs e)
     private static void DefaultCursorChanged(Scene scene, AvaloniaPropertyChangedEventArgs e)