From f481ccecae4c2fcff103cf979d76eb4655887235 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 21 Jan 2022 20:48:04 -0500 Subject: [PATCH] Merge pull request #7409 from AvaloniaUI/fixes/wasm-resizing Butter Smooth resizing in Browser --- .../Properties/AssemblyInfo.cs | 1 + .../Rendering/DeferredRenderer.cs | 2 +- .../Avalonia.Web.Blazor/AvaloniaView.razor.cs | 27 +++++++- .../Interop/SKHtmlCanvasInterop.cs | 8 ++- .../Interop/Typescript/SKHtmlCanvas.ts | 64 +++++++++++++++---- 5 files changed, 84 insertions(+), 18 deletions(-) diff --git a/src/Avalonia.Visuals/Properties/AssemblyInfo.cs b/src/Avalonia.Visuals/Properties/AssemblyInfo.cs index 881ddfd89f..ebff097199 100644 --- a/src/Avalonia.Visuals/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Visuals/Properties/AssemblyInfo.cs @@ -11,4 +11,5 @@ using Avalonia.Metadata; [assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] [assembly: InternalsVisibleTo("Avalonia.Skia.RenderTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] [assembly: InternalsVisibleTo("Avalonia.Skia.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] +[assembly: InternalsVisibleTo("Avalonia.Web.Blazor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index fe63fdec46..92d9f05503 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -308,7 +308,7 @@ namespace Avalonia.Rendering } } - private void Render(bool forceComposite) + internal void Render(bool forceComposite) { using (var l = _lock.TryLock()) { diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs index 7644514687..dc8b091563 100644 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs +++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs @@ -3,6 +3,7 @@ using Avalonia.Controls.Embedding; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; +using Avalonia.Rendering; using Avalonia.Web.Blazor.Interop; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; @@ -299,6 +300,18 @@ namespace Avalonia.Web.Blazor _topLevel.Prepare(); _topLevel.Renderer.Start(); + + // Note: this is technically a hack, but it's a kinda unique use case when + // we want to blit the previous frame + // renderer doesn't have much control over the render target + // we render on the UI thread + // We also don't want to have it as a meaningful public API. + // Therefore we have InternalsVisibleTo hack here. + if (_topLevel.Renderer is DeferredRenderer dr) + { + dr.Render(true); + } + Invalidate(); }); } @@ -327,6 +340,11 @@ namespace Avalonia.Web.Blazor _dpi = newDpi; _topLevelImpl.SetClientSize(_canvasSize, _dpi); + + if (_topLevel.Renderer is DeferredRenderer dr) + { + dr.Render(true); + } Invalidate(); } @@ -334,9 +352,16 @@ namespace Avalonia.Web.Blazor private void OnSizeChanged(SKSize newSize) { _canvasSize = newSize; + + _interop.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi)); _topLevelImpl.SetClientSize(_canvasSize, _dpi); + if (_topLevel.Renderer is DeferredRenderer dr) + { + dr.Render(true); + } + Invalidate(); } @@ -348,7 +373,7 @@ namespace Avalonia.Web.Blazor return; } - _interop.RequestAnimationFrame(true, (int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi)); + _interop.RequestAnimationFrame(true); } public void SetActive(bool active) diff --git a/src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs index 4f5d4cdf70..9cbbf24086 100644 --- a/src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs +++ b/src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs @@ -11,6 +11,7 @@ namespace Avalonia.Web.Blazor.Interop private const string InitRasterSymbol = "SKHtmlCanvas.initRaster"; private const string DeinitSymbol = "SKHtmlCanvas.deinit"; private const string RequestAnimationFrameSymbol = "SKHtmlCanvas.requestAnimationFrame"; + private const string SetCanvasSizeSymbol = "SKHtmlCanvas.setCanvasSize"; private const string PutImageDataSymbol = "SKHtmlCanvas.putImageData"; private readonly ElementReference htmlCanvas; @@ -68,8 +69,11 @@ namespace Avalonia.Web.Blazor.Interop callbackReference?.Dispose(); } - public void RequestAnimationFrame(bool enableRenderLoop, int rawWidth, int rawHeight) => - Invoke(RequestAnimationFrameSymbol, htmlCanvas, enableRenderLoop, rawWidth, rawHeight); + public void RequestAnimationFrame(bool enableRenderLoop) => + Invoke(RequestAnimationFrameSymbol, htmlCanvas, enableRenderLoop); + + public void SetCanvasSize(int rawWidth, int rawHeight) => + Invoke(SetCanvasSizeSymbol, htmlCanvas, rawWidth, rawHeight); public void PutImageData(IntPtr intPtr, SKSizeI rawSize) => Invoke(PutImageDataSymbol, htmlCanvas, intPtr.ToInt64(), rawSize.Width, rawSize.Height); diff --git a/src/Web/Avalonia.Web.Blazor/Interop/Typescript/SKHtmlCanvas.ts b/src/Web/Avalonia.Web.Blazor/Interop/Typescript/SKHtmlCanvas.ts index 147e2a963f..04d57a7756 100644 --- a/src/Web/Avalonia.Web.Blazor/Interop/Typescript/SKHtmlCanvas.ts +++ b/src/Web/Avalonia.Web.Blazor/Interop/Typescript/SKHtmlCanvas.ts @@ -25,6 +25,8 @@ export class SKHtmlCanvas { renderFrameCallback: DotNet.DotNetObjectReference; renderLoopEnabled: boolean = false; renderLoopRequest: number = 0; + newWidth: number; + newHeight: number; public static initGL(element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObjectReference): SKGLViewInfo { var view = SKHtmlCanvas.init(true, element, elementId, callback); @@ -75,13 +77,22 @@ export class SKHtmlCanvas { htmlCanvas.SKHtmlCanvas = undefined; } - public static requestAnimationFrame(element: HTMLCanvasElement, renderLoop?: boolean, width?: number, height?: number) { + public static requestAnimationFrame(element: HTMLCanvasElement, renderLoop?: boolean) { const htmlCanvas = element as SKHtmlCanvasElement; if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) return; - htmlCanvas.SKHtmlCanvas.requestAnimationFrame(renderLoop, width, height); + htmlCanvas.SKHtmlCanvas.requestAnimationFrame(renderLoop); } + + public static setCanvasSize(element: HTMLCanvasElement, width: number, height: number) + { + const htmlCanvas = element as SKHtmlCanvasElement; + if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) + return; + + htmlCanvas.SKHtmlCanvas.setCanvasSize(width, height); + } public static setEnableRenderLoop(element: HTMLCanvasElement, enable: boolean) { const htmlCanvas = element as SKHtmlCanvasElement; @@ -128,28 +139,53 @@ export class SKHtmlCanvas { public deinit() { this.setEnableRenderLoop(false); } - - public requestAnimationFrame(renderLoop?: boolean, width?: number, height?: number) { + + public setCanvasSize(width: number, height: number) + { + this.newWidth = width; + this.newHeight = height; + + if(this.htmlCanvas.width != this.newWidth) + { + this.htmlCanvas.width = this.newWidth; + } + + if(this.htmlCanvas.height != this.newHeight) + { + this.htmlCanvas.height = this.newHeight; + } + + if (this.glInfo) { + // make current + GL.makeContextCurrent(this.glInfo.context); + } + } + + public requestAnimationFrame(renderLoop?: boolean) { // optionally update the render loop if (renderLoop !== undefined && this.renderLoopEnabled !== renderLoop) this.setEnableRenderLoop(renderLoop); - // make sure the canvas is scaled correctly for the drawing - if (width && height) { - this.htmlCanvas.width = width; - this.htmlCanvas.height = height; - } - // skip because we have a render loop if (this.renderLoopRequest !== 0) return; // add the draw to the next frame this.renderLoopRequest = window.requestAnimationFrame(() => { - if (this.glInfo) { - // make current - GL.makeContextCurrent(this.glInfo.context); - } + if (this.glInfo) { + // make current + GL.makeContextCurrent(this.glInfo.context); + } + + if(this.htmlCanvas.width != this.newWidth) + { + this.htmlCanvas.width = this.newWidth; + } + + if(this.htmlCanvas.height != this.newHeight) + { + this.htmlCanvas.height = this.newHeight; + } this.renderFrameCallback.invokeMethod('Invoke'); this.renderLoopRequest = 0;