Skip to content

RiveCanvasRenderer.MatchCanvasResolution silently fails at startup due to OnEnable ordering (URP) #159

@malard

Description

@malard

Summary

When RiveCanvasRenderer.MatchCanvasResolution is set to true in the inspector, it silently fails to take effect at startup on URP. The render texture is allocated at the panel's logical rect size (e.g. 1920×1080) instead of canvas pixel size, then nearest-neighbor upscaled to the display because RTHandleSystem.Alloc defaults filterMode = FilterMode.Point. At 4K this produces visibly blocky / "8-bit" vector text and aliased diagonal strokes.

A side-by-side at 4K UHD (3840×2160), same scene, same settings, only difference is whether the workaround below is active. Left = bug, right = expected:

Environment

  • rive-unity v0.4.2 (latest at time of filing)
  • Unity 6000.3.5f2 with URP
  • Windows 10/11, D3D11 (also reproduces on D3D12)
  • Canvas: ScreenSpaceOverlay
  • CanvasScaler: ScaleWithScreenSize, reference 1920×1080, match 0.5

Repro

  1. URP project with a Canvas (ScreenSpaceOverlay) + CanvasScaler (ScaleWithScreenSize, ref 1920×1080).
  2. On the same GameObject, add components in this declaration order: RiveCanvasRenderer → CanvasRendererRawImage → RivePanel. Set MatchCanvasResolution = true.
  3. Add some RiveWidgets with vector text.
  4. Run the game / enter Play mode at a screen resolution higher than the CanvasScaler reference (e.g. 4K display, or Game View set to 4K UHD).

Observed: Vector text and diagonals are heavily aliased / pixelated. Looks like nearest-neighbor upscale.

Expected: Text crisp because the RT should track display resolution.

Confirmation: Manually toggling MatchCanvasResolution off→on in the inspector at runtime fixes it for the rest of the session. Restarting brings the bug back.

Root cause

Component OnEnable order on the same GameObject follows declaration order. With RiveCanvasRenderer declared before RivePanel:

  1. RiveCanvasRenderer.OnEnable (RiveCanvasRenderer.cs:84) calls AttachCanvasProviders(RivePanel).
  2. AttachCanvasProviders early-exits at if (strategy == null) return; (RiveCanvasRenderer.cs:122) because the panel hasn't yet initialised its SimpleRenderTargetStrategy.
  3. RivePanel.OnEnable (RivePanel.cs:367) runs immediately afterwards and calls InitializeDefaultRenderTargetStrategyIfNeeded (line 371), creating the strategy.
  4. By this point AttachCanvasProviders has already returned. ExternalPixelSizeProvider is never set, so SimpleRenderTargetStrategy.RefreshRenderTexture falls through to new Vector2Int((int)panel.WidgetContainer.rect.width, (int)panel.WidgetContainer.rect.height) — the panel's logical canvas rect, not the display pixel size.

The result is allocated as an RTHandle via RTHandleSystem.Alloc(...) in UniversalRenderPipelineHandler.AllocateRenderTexture without specifying filterMode, so it inherits the system default FilterMode.Point. Sampling that undersized RT to the on-screen canvas at 4K is therefore nearest-neighbor — hence the visibly blocky output.

The reason "toggle in inspector" or "drop to 1080p then back to 4K" fixes it is that any property change forces AttachCanvasProviders to re-run, by which point the strategy is initialised.

Suggested fix

Any one of:

  1. Move if (m_matchCanvasResolution) AttachCanvasProviders(RivePanel); from RiveCanvasRenderer.OnEnable to Start.
  2. Have RivePanel eagerly initialise its strategy in Awake rather than OnEnable.
  3. Have RivePanel raise an event when its strategy becomes non-null and have RiveCanvasRenderer re-attach there.
  4. As a defensive measure for the secondary symptom: pass filterMode: FilterMode.Bilinear in the RTHandleSystem.Alloc call in UniversalRenderPipelineHandler.AllocateRenderTexture (UniversalRenderPipelineHandler.cs:234). The Built-In pipeline handler doesn't have this issue because new RenderTexture(descriptor) defaults to Bilinear, but URP inherits FilterMode.Point from the RTHandle default.

Workaround for users hitting this today

A small static class with a [RuntimeInitializeOnLoadMethod(AfterSceneLoad)] hook that finds every RiveCanvasRenderer and toggles MatchCanvasResolution false→true after scene load. Confirmed fix on our project at 4K, both in editor and in the standalone Steam build:

using Rive.Components;
using UnityEngine;
using UnityEngine.SceneManagement;

public static class RiveCanvasResolutionFix
{
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
    private static void Install()
    {
        SceneManager.sceneLoaded += (scene, mode) => FixScene(scene);
        for (int i = 0; i < SceneManager.sceneCount; i++)
            FixScene(SceneManager.GetSceneAt(i));
    }

    private static void FixScene(UnityEngine.SceneManagement.Scene scene)
    {
        if (!scene.isLoaded) return;
        foreach (var root in scene.GetRootGameObjects())
        {
            foreach (var renderer in root.GetComponentsInChildren<RiveCanvasRenderer>(true))
            {
                if (!renderer.MatchCanvasResolution) continue;
                renderer.MatchCanvasResolution = false;
                renderer.MatchCanvasResolution = true;
            }
        }
    }
}

Happy to open a PR if helpful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions