Browse Source

Added full screen rendering

Krzysztof Krysiński 1 month ago
parent
commit
78c5d05301

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

@@ -11,14 +11,12 @@ public class CustomOutputNode : Node, IRenderInput
     public const string OutputNamePropertyName = "OutputName";
     public const string IsDefaultExportPropertyName = "IsDefaultExport";
     public const string SizePropertyName = "Size";
+    public const string FullViewportRenderPropertyName = "FullViewportRender";
     public RenderInputProperty Input { get; }
     public InputProperty<string> OutputName { get; }
     public InputProperty<bool> IsDefaultExport { get; }
     public InputProperty<VecI> Size { get; }
-
-    private VecI? lastDocumentSize;
-
-    private TextureCache textureCache = new TextureCache();
+    public InputProperty<bool> FullViewportRender { get; }
 
     public CustomOutputNode()
     {
@@ -28,6 +26,7 @@ public class CustomOutputNode : Node, IRenderInput
         OutputName = CreateInput(OutputNamePropertyName, "OUTPUT_NAME", "");
         IsDefaultExport = CreateInput(IsDefaultExportPropertyName, "IS_DEFAULT_EXPORT", false);
         Size = CreateInput(SizePropertyName, "SIZE", VecI.Zero);
+        FullViewportRender = CreateInput(FullViewportRenderPropertyName, "FULL_VIEWPORT_RENDER", false);
     }
 
     public override Node CreateCopy()
@@ -43,8 +42,6 @@ public class CustomOutputNode : Node, IRenderInput
                 ? context.RenderOutputSize
                 : (VecI)(Size.Value * context.ChunkResolution.Multiplier());
 
-            lastDocumentSize = targetSize;
-
             Canvas targetSurface = context.RenderSurface;
 
             int saved = targetSurface.Save();
@@ -57,34 +54,46 @@ public class CustomOutputNode : Node, IRenderInput
             {
                 context.RenderSurface.DrawSurface(targetSurface.Surface, 0, 0);
             }
+
+            RenderPreviews(context);
         }
     }
 
     RenderInputProperty IRenderInput.Background => Input;
 
-    public RectD? GetPreviewBounds(int frame, string elementToRenderName = "")
+    protected void RenderPreviews(RenderContext ctx)
     {
-        if (lastDocumentSize == null)
+        var previewToRender = ctx.GetPreviewTexturesForNode(Id);
+        if (previewToRender == null || previewToRender.Count == 0)
+            return;
+
+        foreach (var preview in previewToRender)
         {
-            return null;
-        }
+            if (preview.Texture == null)
+                continue;
 
-        return new RectD(0, 0, lastDocumentSize.Value.X, lastDocumentSize.Value.Y);
-    }
+            int saved = preview.Texture.DrawingSurface.Canvas.Save();
+            preview.Texture.DrawingSurface.Canvas.Clear();
 
-    public bool RenderPreview(DrawingSurface renderOn, RenderContext context, string elementToRenderName)
-    {
-        // TODO: Implement preview rendering
-        /*if (Input.Value == null)
-        {
-            return false;
-        }
+            var bounds = new RectD(0, 0, ctx.RenderOutputSize.X, ctx.RenderOutputSize.Y);
 
-        int saved = renderOn.Canvas.Save();
-        Input.Value.Paint(context, renderOn);
+            VecD scaling = PreviewUtility.CalculateUniformScaling(bounds.Size, preview.Texture.Size);
+            VecD offset = PreviewUtility.CalculateCenteringOffset(bounds.Size, preview.Texture.Size, scaling);
+            RenderContext adjusted =
+                PreviewUtility.CreatePreviewContext(ctx, scaling, bounds.Size, preview.Texture.Size);
 
-        renderOn.Canvas.RestoreToCount(saved);*/
+            preview.Texture.DrawingSurface.Canvas.Translate((float)offset.X, (float)offset.Y);
+            preview.Texture.DrawingSurface.Canvas.Scale((float)scaling.X, (float)scaling.Y);
+            preview.Texture.DrawingSurface.Canvas.Translate((float)-bounds.X, (float)-bounds.Y);
 
-        return true;
+            adjusted.RenderSurface = preview.Texture.DrawingSurface.Canvas;
+            RenderPreview(preview.Texture.DrawingSurface.Canvas, adjusted);
+            preview.Texture.DrawingSurface.Canvas.RestoreToCount(saved);
+        }
+    }
+
+    protected virtual void RenderPreview(Canvas surface, RenderContext context)
+    {
+        Input.Value?.Paint(context, surface);
     }
 }

+ 38 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Workspace/ViewportInfoNode.cs

@@ -0,0 +1,38 @@
+using Drawie.Backend.Core.Numerics;
+using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Rendering;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Workspace;
+
+[NodeInfo("ViewportInfo")]
+public class ViewportInfoNode : Node
+{
+    public OutputProperty<Matrix3X3> Transform { get; }
+    public OutputProperty<VecD> PanPosition { get; }
+    public OutputProperty<double> Zoom { get; }
+    public OutputProperty<bool> FlipX { get; }
+    public OutputProperty<bool> FlipY { get; }
+
+    public ViewportInfoNode()
+    {
+        Transform = CreateOutput<Matrix3X3>("Transform", "TRANSFORM", Matrix3X3.Identity);
+        PanPosition = CreateOutput<VecD>("PanPosition", "PAN_POSITION", VecD.Zero);
+        Zoom = CreateOutput<double>("Zoom", "ZOOM", 1.0);
+        FlipX = CreateOutput<bool>("FlipX", "FLIP_X", false);
+        FlipY = CreateOutput<bool>("FlipY", "FLIP_Y", false);
+    }
+
+    protected override void OnExecute(RenderContext context)
+    {
+        Transform.Value = context.ViewportData.Transform;
+        PanPosition.Value = context.ViewportData.Translation;
+        Zoom.Value = context.ViewportData.Zoom;
+        FlipX.Value = context.ViewportData.FlipX;
+        FlipY.Value = context.ViewportData.FlipY;
+    }
+
+    public override Node CreateCopy()
+    {
+        return new ViewportInfoNode();
+    }
+}

+ 26 - 0
src/PixiEditor.ChangeableDocument/Rendering/ContextData/ViewportData.cs

@@ -0,0 +1,26 @@
+using Drawie.Backend.Core.Numerics;
+using Drawie.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Rendering.ContextData;
+
+public struct ViewportData
+{
+    public Matrix3X3 Transform { get; set; } = Matrix3X3.Identity;
+    public VecD Translation { get; set; }
+    public double Zoom { get; set; } = 1.0;
+    public bool FlipX { get; set; }
+    public bool FlipY { get; set; }
+
+    public ViewportData()
+    {
+    }
+
+    public ViewportData(Matrix3X3 toMatrix3X3, VecD sceneCanvasPos, double sceneScale, bool flipX, bool flipY)
+    {
+        Transform = toMatrix3X3;
+        Translation = sceneCanvasPos;
+        Zoom = sceneScale;
+        FlipX = flipX;
+        FlipY = flipY;
+    }
+}

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

@@ -28,6 +28,7 @@ public class RenderContext
     public PointerInfo PointerInfo { get; set; }
     public KeyboardInfo KeyboardInfo { get; set; }
     public EditorData EditorData { get; set; }
+    public ViewportData ViewportData { get; set; }
     public ColorSpace ProcessingColorSpace { get; set; }
     public string? TargetOutput { get; set; }
     public AffectedArea AffectedArea { get; set; }
@@ -95,6 +96,7 @@ public class RenderContext
             PointerInfo = PointerInfo,
             EditorData = EditorData,
             KeyboardInfo = KeyboardInfo,
+            ViewportData = ViewportData
         };
     }
 }

+ 2 - 0
src/PixiEditor.Zoombox/Operations/ManipulationOperation.cs

@@ -64,8 +64,10 @@ internal class ManipulationOperation
         VecD newPos = owner.ToZoomboxSpace(screenOrigin);
         VecD centerTranslation = originalPos - newPos;
         owner.Center += centerTranslation;
+        //owner.Pan += centerTranslation;
 
         VecD translatedZoomboxPos = owner.ToZoomboxSpace(screenOrigin + screenTranslation);
         owner.Center -= translatedZoomboxPos - originalPos;
+        owner.Pan -= translatedZoomboxPos - originalPos;
     }
 }

+ 3 - 1
src/PixiEditor.Zoombox/Operations/MoveDragOperation.cs

@@ -27,7 +27,9 @@ internal class MoveDragOperation : IDragOperation
     public void Update(PointerEventArgs e)
     {
         var curMousePos = Zoombox.ToVecD(e.GetPosition(parent));
-        parent.Center += parent.ToZoomboxSpace(prevMousePos) - parent.ToZoomboxSpace(curMousePos);
+        var delta = parent.ToZoomboxSpace(prevMousePos) - parent.ToZoomboxSpace(curMousePos);
+        parent.Center += delta;
+        parent.Pan += prevMousePos - curMousePos;
         prevMousePos = curMousePos;
     }
 

+ 3 - 0
src/PixiEditor.Zoombox/Zoombox.cs

@@ -131,6 +131,8 @@ public partial class Zoombox : ContentControl, INotifyPropertyChanged
     public double CanvasX => ToScreenSpace(VecD.Zero).X;
     public double CanvasY => ToScreenSpace(VecD.Zero).Y;
 
+    public VecD Pan { get; internal set; } = VecD.Zero;
+
     public TransformGroup CanvasTransform => new TransformGroup
     {
         Children =
@@ -319,6 +321,7 @@ public partial class Zoombox : ContentControl, INotifyPropertyChanged
         FlipY = false;
         Scale = Math.Clamp(1 / scaleFactor, MinScale, MaxScale);
         Center = newSize / 2;
+        Pan = VecD.Zero;
     }
 
     public void ZoomIntoCenter(double delta)

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

@@ -1215,5 +1215,10 @@
   "UNLINK_DOCUMENT_MESSAGE": "Are you sure you want to unlink this document? Layer will stay in its current state.",
   "RELINK_DOCUMENT_TITLE": "Relink Document",
   "ERROR_RELINKING_DOCUMENT": "Error relinking document: {0}",
-  "RELINK": "Relink"
+  "RELINK": "Relink",
+  "FULL_VIEWPORT_RENDER": "Full Viewport Render",
+  "PAN_POSITION": "Pan Position",
+  "ZOOM": "Zoom",
+  "FLIP_X": "Flip X",
+  "FLIP_Y": "Flip Y"
 }

+ 1 - 1
src/PixiEditor/Helpers/Behaviours/DocumentTextBindingBehavior.cs

@@ -53,7 +53,7 @@ public class DocumentTextBindingBehavior : Behavior<TextEditor>
         {
             var caretOffset = _textEditor.CaretOffset;
             _textEditor.Document.Text = text;
-            _textEditor.CaretOffset = caretOffset;
+            _textEditor.CaretOffset = Math.Min(caretOffset, text.Length);
         }
     }
 }

+ 1 - 1
src/PixiEditor/Models/Position/ViewportInfo.cs

@@ -13,7 +13,7 @@ internal readonly record struct ViewportInfo(
     double Angle,
     VecD Center,
     VecD RealDimensions,
-    Matrix3X3 Transform,
+    ViewportData ViewportData,
     PointerInfo PointerInfo,
     KeyboardInfo KeyboardInfo,
     EditorData EditorData,

+ 58 - 22
src/PixiEditor/Models/Rendering/SceneRenderer.cs

@@ -103,7 +103,7 @@ internal class SceneRenderer
         ViewportInfo previewGenerationViewport = new()
         {
             RealDimensions = new VecD(1, 1),
-            Transform = Matrix3X3.Identity,
+            ViewportData = new ViewportData(Matrix3X3.Identity, new VecD(1, 1), 0, false, false),
             Id = Guid.NewGuid(),
             Resolution = ChunkResolution.Full,
             Sampling = SamplingOptions.Bilinear,
@@ -130,7 +130,7 @@ internal class SceneRenderer
          */
 
         VecI renderTargetSize = (VecI)viewport.RealDimensions;
-        Matrix3X3 targetMatrix = viewport.Transform;
+        Matrix3X3 targetMatrix = viewport.ViewportData.Transform;
         Guid viewportId = viewport.Id;
         ChunkResolution resolution = viewport.Resolution;
         SamplingOptions samplingOptions = viewport.Sampling;
@@ -141,9 +141,25 @@ internal class SceneRenderer
         string? targetOutput = viewport.RenderOutput.Equals("DEFAULT", StringComparison.InvariantCultureIgnoreCase)
             ? null
             : viewport.RenderOutput;
+        bool isFullViewportRender = false;
 
         IReadOnlyNodeGraph finalGraph = RenderingUtils.SolveFinalNodeGraph(targetOutput, Document);
 
+        if (targetOutput != null)
+        {
+            var outputNode = finalGraph.AllNodes.FirstOrDefault(n =>
+                n is CustomOutputNode outputNode && outputNode.OutputName.Value == targetOutput);
+
+            if (outputNode is CustomOutputNode customOutputNode)
+            {
+                isFullViewportRender = customOutputNode.FullViewportRender.Value;
+                visibleDocumentRegion = isFullViewportRender
+                    ? null
+                    : viewport.VisibleDocumentRegion;
+                resolution = ChunkResolution.Full;
+            }
+        }
+
         float oversizeFactor = 1;
         if (visibleDocumentRegion != null && viewport.IsScene &&
             visibleDocumentRegion.Value != new RectI(0, 0, Document.Size.X, Document.Size.Y))
@@ -154,7 +170,7 @@ internal class SceneRenderer
         }
 
         bool shouldRerender =
-            ShouldRerender(renderTargetSize, targetMatrix, resolution, viewportId, targetOutput, finalGraph,
+            ShouldRerender(renderTargetSize, isFullViewportRender ? Matrix3X3.Identity : targetMatrix, resolution, viewportId, targetOutput, finalGraph,
                 previewTextures, visibleDocumentRegion, oversizeFactor, out bool fullAffectedArea);
 
         if (shouldRerender)
@@ -165,7 +181,7 @@ internal class SceneRenderer
                 : affectedArea;
             return RenderGraph(renderTargetSize, targetMatrix, viewportId, resolution, samplingOptions, affectedArea,
                 visibleDocumentRegion, targetOutput, viewport.IsScene, oversizeFactor,
-                pointerInfo, keyboardInfo, editorData, finalGraph, previewTextures);
+                pointerInfo, keyboardInfo, editorData, viewport.ViewportData, finalGraph, previewTextures);
         }
 
         var cachedTexture = DocumentViewModel.SceneTextures[viewportId];
@@ -183,36 +199,47 @@ internal class SceneRenderer
         PointerInfo pointerInfo,
         KeyboardInfo keyboardInfo,
         EditorData editorData,
+        ViewportData viewportData,
         IReadOnlyNodeGraph finalGraph, Dictionary<Guid, List<PreviewRenderRequest>>? previewTextures)
     {
         DrawingSurface renderTarget = null;
         Texture? renderTexture = null;
         int restoreCanvasTo;
 
-        VecI finalSize = SolveRenderOutputSize(targetOutput, finalGraph, Document.Size);
-        if (RenderInOutputSize(finalGraph, renderTargetSize, finalSize))
+        VecI finalSize = SolveRenderOutputSize(targetOutput, finalGraph, Document.Size, renderTargetSize, out bool isFullViewportRender);
+        if (isFullViewportRender)
         {
-            finalSize = (VecI)(finalSize * resolution.Multiplier());
-
             renderTexture =
-                textureCache.RequestTexture(viewportId.GetHashCode(), finalSize, Document.ProcessingColorSpace);
+                textureCache.RequestTexture(viewportId.GetHashCode(), renderTargetSize, Document.ProcessingColorSpace);
             renderTarget = renderTexture.DrawingSurface;
             renderTarget.Canvas.Save();
-            renderTexture.DrawingSurface.Canvas.Save();
-            renderTexture.DrawingSurface.Canvas.Scale((float)resolution.Multiplier());
         }
         else
         {
-            var bufferedSize = (VecI)(renderTargetSize * oversizeFactor);
-            renderTexture = textureCache.RequestTexture(viewportId.GetHashCode(), bufferedSize,
-                Document.ProcessingColorSpace);
+            if (RenderInOutputSize(finalGraph, renderTargetSize, finalSize))
+            {
+                finalSize = (VecI)(finalSize * resolution.Multiplier());
+
+                renderTexture =
+                    textureCache.RequestTexture(viewportId.GetHashCode(), finalSize, Document.ProcessingColorSpace);
+                renderTarget = renderTexture.DrawingSurface;
+                renderTarget.Canvas.Save();
+                renderTexture.DrawingSurface.Canvas.Save();
+                renderTexture.DrawingSurface.Canvas.Scale((float)resolution.Multiplier());
+            }
+            else
+            {
+                var bufferedSize = (VecI)(renderTargetSize * oversizeFactor);
+                renderTexture = textureCache.RequestTexture(viewportId.GetHashCode(), bufferedSize,
+                    Document.ProcessingColorSpace);
 
-            var bufferedMatrix = targetMatrix.PostConcat(Matrix3X3.CreateTranslation(
-                (bufferedSize.X - renderTargetSize.X) / 2.0,
-                (bufferedSize.Y - renderTargetSize.Y) / 2.0));
+                var bufferedMatrix = targetMatrix.PostConcat(Matrix3X3.CreateTranslation(
+                    (bufferedSize.X - renderTargetSize.X) / 2.0,
+                    (bufferedSize.Y - renderTargetSize.Y) / 2.0));
 
-            renderTarget = renderTexture.DrawingSurface;
-            renderTarget.Canvas.SetMatrix(bufferedMatrix);
+                renderTarget = renderTexture.DrawingSurface;
+                renderTarget.Canvas.SetMatrix(bufferedMatrix);
+            }
         }
 
         bool renderOnionSkinning = canRenderOnionSkinning &&
@@ -236,6 +263,7 @@ internal class SceneRenderer
                     Document.ProcessingColorSpace, samplingOptions, Document.NodeGraph, finalOpacity);
                 onionContext.TargetOutput = targetOutput;
                 onionContext.VisibleDocumentRegion = visibleDocumentRegion;
+                onionContext.ViewportData = viewportData;
                 finalGraph.Execute(onionContext);
             }
         }
@@ -250,6 +278,7 @@ internal class SceneRenderer
         context.AffectedArea = area;
         context.VisibleDocumentRegion = visibleDocumentRegion;
         context.PreviewTextures = previewTextures;
+        context.ViewportData = viewportData;
         finalGraph.Execute(context);
         ExecuteBrushOutputPreviews(finalGraph, previewTextures, context);
 
@@ -268,6 +297,7 @@ internal class SceneRenderer
                     Document.ProcessingColorSpace, samplingOptions, Document.NodeGraph, finalOpacity);
                 onionContext.TargetOutput = targetOutput;
                 onionContext.VisibleDocumentRegion = visibleDocumentRegion;
+                onionContext.ViewportData = viewportData;
                 finalGraph.Execute(onionContext);
             }
         }
@@ -300,9 +330,10 @@ internal class SceneRenderer
     }
 
     private static VecI SolveRenderOutputSize(string? targetOutput, IReadOnlyNodeGraph finalGraph,
-        VecI documentSize)
+        VecI documentSize, VecI viewportSize, out bool isFullViewportRender)
     {
         VecI finalSize = documentSize;
+        isFullViewportRender = false;
         if (targetOutput != null)
         {
             var outputNode = finalGraph.AllNodes.FirstOrDefault(n =>
@@ -310,7 +341,12 @@ internal class SceneRenderer
 
             if (outputNode is CustomOutputNode customOutputNode)
             {
-                if (customOutputNode.Size.Value.ShortestAxis > 0)
+                if (customOutputNode.FullViewportRender.Value)
+                {
+                    finalSize = viewportSize;
+                    isFullViewportRender = true;
+                }
+                else if (customOutputNode.Size.Value.ShortestAxis > 0)
                 {
                     finalSize = customOutputNode.Size.Value;
                 }
@@ -378,7 +414,7 @@ internal class SceneRenderer
             return true;
         }
 
-        VecI finalSize = SolveRenderOutputSize(targetOutput, finalGraph, Document.Size);
+        VecI finalSize = SolveRenderOutputSize(targetOutput, finalGraph, Document.Size, targetSize, out _);
         bool renderInDocumentSize = RenderInOutputSize(finalGraph, targetSize, finalSize);
         VecI compareSize = renderInDocumentSize
             ? (VecI)(Document.Size * resolution.Multiplier())

+ 10 - 1
src/PixiEditor/ViewModels/Document/Nodes/Workspace/CustomOutputNodeViewModel.cs

@@ -5,4 +5,13 @@ using PixiEditor.ViewModels.Nodes;
 namespace PixiEditor.ViewModels.Document.Nodes.Workspace;
 
 [NodeViewModel("CUSTOM_OUTPUT_NODE", "WORKSPACE", PixiPerfectIcons.Surveillance)]
-internal class CustomOutputNodeViewModel : NodeViewModel<CustomOutputNode>;
+internal class CustomOutputNodeViewModel : NodeViewModel<CustomOutputNode>
+{
+    public override void OnInitialized()
+    {
+        InputPropertyMap[CustomOutputNode.FullViewportRenderPropertyName].ValueChanged += (property, args) =>
+        {
+            InputPropertyMap[CustomOutputNode.SizePropertyName].IsVisible = !(bool)args.NewValue!;
+        };
+    }
+}

+ 7 - 0
src/PixiEditor/ViewModels/Document/Nodes/Workspace/ViewportInfoNodeViewModel.cs

@@ -0,0 +1,7 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Workspace;
+using PixiEditor.ViewModels.Nodes;
+
+namespace PixiEditor.ViewModels.Document.Nodes.Workspace;
+
+[NodeViewModel("VIEWPORT_INFO_NODE", "WORKSPACE", null)]
+internal class ViewportInfoNodeViewModel : NodeViewModel<ViewportInfoNode>;

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

@@ -142,7 +142,7 @@ internal partial class FixedViewport : UserControl, INotifyPropertyChanged
             0,
             docSize / 2,
             new VecD(Bounds.Width, Bounds.Height),
-            scaling,
+            new ViewportData(scaling, VecD.Zero, 1, false, false),
             new PointerInfo(),
             new KeyboardInfo(),
             ViewModelMain.Current.GetEditorData(), // TODO: Remove singleton

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

@@ -534,7 +534,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
     private ViewportInfo GetLocation()
     {
         return new(AngleRadians, Center, RealDimensions,
-            Scene.CalculateTransformMatrix().ToSKMatrix().ToMatrix3X3(),
+            new ViewportData(Scene.CalculateTransformMatrix().ToSKMatrix().ToMatrix3X3(), Scene.Pan, Scene.Scale, FlipX, FlipY),
             Scene.LastPointerInfo,
             Scene.LastKeyboardInfo,
             EditorDataFunc(),

+ 14 - 13
src/PixiEditor/Views/Rendering/Scene.cs

@@ -346,17 +346,17 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         texture.Canvas.Save();
         var matrix = CalculateTransformMatrix();
 
-        texture.Canvas.SetMatrix(matrix.ToSKMatrix().ToMatrix3X3());
+        VecI outputSize = FindOutputSize(out var isFullscreen);
 
-        VecI outputSize = FindOutputSize();
+        texture.Canvas.SetMatrix(isFullscreen ? Matrix3X3.Identity : matrix.ToSKMatrix().ToMatrix3X3());
 
         RectD dirtyBounds = new RectD(0, 0, outputSize.X, outputSize.Y);
-        RenderScene(texture, dirtyBounds);
+        RenderScene(texture, dirtyBounds, isFullscreen);
 
         texture.Canvas.Restore();
     }
 
-    private void RenderScene(DrawingSurface texture, RectD bounds)
+    private void RenderScene(DrawingSurface texture, RectD bounds, bool isFullscreenRender)
     {
         var renderOutput = RenderOutput == "DEFAULT" ? null : RenderOutput;
         DrawCheckerboard(texture, bounds);
@@ -373,7 +373,7 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
             if(!Document.SceneTextures.TryGetValue(ViewportId, out var cachedTexture))
                 return;
 
-            Matrix3X3 matrixDiff = SolveMatrixDiff(matrix, cachedTexture);
+            Matrix3X3 matrixDiff = isFullscreenRender ? Matrix3X3.Identity : SolveMatrixDiff(matrix, cachedTexture);
             var target = cachedTexture.DrawingSurface;
 
             if (tex.Size == (VecI)RealDimensions || tex.Size == (VecI)(RealDimensions * SceneRenderer.OversizeFactor))
@@ -499,9 +499,10 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         }
     }
 
-    private VecI FindOutputSize()
+    private VecI FindOutputSize(out bool isFullscreen)
     {
         VecI outputSize = Document.SizeBindable;
+        isFullscreen = false;
 
         if (!string.IsNullOrEmpty(RenderOutput))
         {
@@ -515,11 +516,17 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
                     {
                         outputSize = size;
                     }
+
+                    var fullScreenProp = node?.Inputs.FirstOrDefault(x => x.PropertyName == CustomOutputNode.FullViewportRenderPropertyName);
+                    if (fullScreenProp != null)
+                    {
+                        isFullscreen = Document.NodeGraph.GetComputedPropertyValue<bool>(fullScreenProp);
+                    }
                 }
             }
         }
 
-        return outputSize;
+        return isFullscreen ? new VecI((int)Bounds.Size.Width, (int)Bounds.Size.Height) : outputSize;
     }
 
     protected override void OnPointerMoved(PointerEventArgs e)
@@ -882,12 +889,6 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         return transform;
     }
 
-    private float CalculateResolutionScale()
-    {
-        var resolution = CalculateResolution();
-        return (float)resolution.InvertedMultiplier();
-    }
-
     private void CaptureOverlay(Overlay? overlay, IPointer pointer)
     {
         if (AllOverlays == null) return;