From 4d24c5b07bd6c158273aa19200e3a72e2f81be96 Mon Sep 17 00:00:00 2001 From: CI Date: Sat, 1 Aug 2026 09:36:51 -0500 Subject: [PATCH] docs(flutter): document RenderResolution and WebGL context lifecycle --- runtimes/flutter/faq.mdx | 11 +++++++++++ runtimes/flutter/flutter.mdx | 31 ++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/runtimes/flutter/faq.mdx b/runtimes/flutter/faq.mdx index 81745a31..9b52affb 100644 --- a/runtimes/flutter/faq.mdx +++ b/runtimes/flutter/faq.mdx @@ -48,6 +48,17 @@ Future main() async { } ``` +## "Too many active WebGL contexts" warnings on web + +Every `Factory.rive` widget that owns its texture holds a live WebGL context, and browsers cap those. Exhausting the cap can also cause blank or frozen Rive widgets and CanvasKit context-loss errors. See [WebGL contexts (web)](/runtimes/flutter/flutter#webgl-contexts-web) for how contexts are released and reused. + +To stay under the cap: + +- Draw multiple widgets into one shared texture with a [RivePanel](/runtimes/flutter/flutter#rivepanel) — one context in total. +- Dispose Rive widgets you no longer show — that releases their WebGL contexts. + +Set `RiveNative.debugRenderTextureLogging = true` to confirm you're hitting the cap: it logs texture create, reuse, and release with live WebGL context counts. + ## How to enable 16KB page support on Android? Rive Flutter `0.14.x` includes 16KB page support by default. diff --git a/runtimes/flutter/flutter.mdx b/runtimes/flutter/flutter.mdx index 9863ac98..6330f4a1 100644 --- a/runtimes/flutter/flutter.mdx +++ b/runtimes/flutter/flutter.mdx @@ -380,6 +380,7 @@ Follow the steps below to integrate Rive into your Flutter apps. - `useSharedTexture`: Whether to use the nearest inherited shared texture from a [RivePanel](#rivepanel). Defaults to false. Ignored when `sharedTexture` is provided. - `sharedTexture`: An explicit `SharedRenderTexture` to draw into, bypassing the ancestor-based `RivePanel` lookup. Use this with [`SharedRenderTexture` and `RiveSurface`](#sharedrendertexture-and-rivesurface) to share a texture across arbitrary parts of the widget tree. - `drawOrder`: Stacking order when drawing to a shared texture with `Factory.rive` (default: `1`). Higher values draw on top. See [Draw Order](#draw-order). +- `renderResolution`: How the widget's backing texture is sized with `Factory.rive` (default: `RenderResolution.display()`). Not valid with `useSharedTexture`/`sharedTexture`. See [Render Resolution](#render-resolution). ### `RiveWidgetBuilder` @@ -406,7 +407,7 @@ Follow the steps below to integrate Rive into your Flutter apps. - When you want to programmatically composite a scene that includes multiple Rive graphics (from multiple Rive files/artboards) - When using `Factory.rive` (will report errors with `Factory.flutter`) and want to improve performance - When you want to reduce the number of textures being drawn to -- When targeting web platforms to avoid WebGL context limitations through `Factory.rive` +- When targeting web platforms to avoid [WebGL context limits](#webgl-contexts-web) through `Factory.rive` **Performance considerations:** @@ -429,6 +430,7 @@ RivePanel( - Only works with `Factory.rive` - has no effect with `Factory.flutter` - Set `useSharedTexture: true` in your `RiveWidget`s to enable shared texture rendering - Set `drawOrder` in your `RiveWidget`s to control stacking (see [Draw Order](#draw-order)) +- Set `renderResolution` on the panel to control how its shared texture is sized (see [Render Resolution](#render-resolution)) - If you need to interleave Rive content with Flutter content, consider using separate `RivePanel`s or `Factory.flutter` - For complex scenarios, benchmark both approaches to determine the best performance strategy @@ -471,6 +473,7 @@ Widget build(BuildContext context) { - `RiveWidget.sharedTexture` takes precedence over `useSharedTexture` when both are set. - You must call `dispose()` on textures created with `SharedRenderTexture.create()` to release the native texture. - `RiveSurface` is wrapped in `IgnorePointer` by default so pointer events are handled by the individual `RiveWidget`s. Set `ignorePointer: false` if pointer events should reach the native texture widget directly. +- Set `renderResolution` on the `RiveSurface` to control how the shared texture is sized (see [Render Resolution](#render-resolution)). ### Draw Order @@ -484,6 +487,20 @@ When multiple `RiveWidget`s draw to the same shared texture - through a [RivePan `drawOrder` only applies when drawing to a shared texture with `Factory.rive`. When a widget owns its own texture, Flutter's normal paint order applies. +### Render Resolution + +`Factory.rive` draws content into a backing texture. `RiveWidget.renderResolution` (default: `RenderResolution.display()`) controls how that texture is sized - the same way on every platform, including web. It changes backing resolution only; layout, fit, alignment, and hit testing are unaffected. + +- `RenderResolution.display()` (default): layout size * device pixel ratio * any scale ancestors apply at paint time (for example a `FittedBox` or `Transform.scale`). Content stays sharp at its final on-screen size, and allocates for it. +- `RenderResolution.layout(scale: 1.0)`: layout size * device pixel ratio * `scale`. Ancestor transforms apply at composite time, so scaling the widget up stretches the texture (softer output) instead of reallocating it - render small and let a `FittedBox` upscale cheaply. +- `RenderResolution.fixed(width, height)`: an explicit size in physical pixels, independent of layout, device pixel ratio, and transforms. Never reallocated on resize. + +For shared textures the surface owns the allocation: set `renderResolution` on the [RivePanel](#rivepanel) or [RiveSurface](#sharedrendertexture-and-rivesurface). Setting it on a `RiveWidget` alongside `useSharedTexture` or `sharedTexture` throws an assertion error. + + +`renderResolution` only applies with `Factory.rive`. `Factory.flutter` paints directly into Flutter's canvas at composite resolution. + + ### `RiveWidgetController` `RiveWidgetController` manages the graphic. @@ -605,6 +622,18 @@ The exception to this is the `FileLoader`, which you control. This loader can be +### WebGL contexts (web) + +On the web, every `Factory.rive` widget that owns its texture renders into its own WebGL context, and browsers cap live contexts (around 16, shared with Flutter's own CanvasKit surfaces). + +Disposing a Rive widget releases its context. A small reuse pool keeps a few canvases alive, so churn like route transitions stays cheap. Contexts the browser has evicted are detected and destroyed, never reused. + +Set `RiveNative.debugRenderTextureLogging = true` to log texture create, reuse, and release with live WebGL context counts. + + +When showing many Rive widgets at once on web, draw them into a single shared texture with a [RivePanel](#rivepanel) - one WebGL context in total instead of one per widget. + + ## Specifying a renderer When creating a Rive `File` or `FileLoader`, you need to specify a factory to use: