Skip to content

Shared-texture RiveWidget mispositioned when an ancestor MediaQuery overrides devicePixelRatio #640

@joao-ello

Description

@joao-ello

Submission checklist

  • I have confirmed the issue is present in the latest version of the rive Flutter package
  • I have searched the documentation and forums and could not find an answer
  • I have searched existing issues and this is not a duplicate

Description

A RiveWidget(useSharedTexture: true) rendered into a RivePanel is drawn at the wrong position when both of these hold:

  1. the RiveWidget is offset within its RivePanel (it does not fill the panel), and
  2. an ancestor MediaQuery overrides devicePixelRatio to a value different from the real view dpr (window.devicePixelRatio).

Expected: the artwork renders centered in its panel (as it does when the painter fills the panel, or when the MediaQuery dpr matches the view dpr).
Actual: the artwork is offset by a factor of mqDpr / realDpr.

No ancestor transform is required. This is common in real apps: device-preview/inspector shells and "UI scaler" widgets routinely wrap the app in MediaQuery(devicePixelRatio: realDpr / scale) so logical layout renders at a virtual size — any shared-texture RiveWidget under such a scaler mispositions.

Cause: SharedTextureViewRenderObject.paintIntoSharedTexture scales its panel-relative transform by devicePixelRatio, which SharedTextureView.build() sources from MediaQuery.devicePixelRatioOf(context). The shared-texture canvas, however, is sized by the real view dpr (window.devicePixelRatio). When an app overrides MediaQuery.devicePixelRatio, the two diverge and a non-zero panel-relative offset is scaled by mqDpr / realDpr. The same MediaQuery-sourced dpr is used in rive_panel.dart (RiveSurface.buildSharedRenderTexture.devicePixelRatio).

Reproduction steps / code

Minimal repro repo (3 panels; the coyote should be centered in all three): https://github.com/joao-ello/rive-shared-texture-dpr-repro

  • main branch = stock rive → panel B is mispositioned.
  • fix branch = the suggested fix → all panels correct.

The essence:

MediaQuery(
  data: MediaQuery.of(context).copyWith(
    devicePixelRatio: MediaQuery.of(context).devicePixelRatio * 1.4, // mismatch vs real dpr
  ),
  child: SizedBox(
    width: 100, height: 100,
    child: DecoratedBox(
      decoration: BoxDecoration(border: Border.all(color: Colors.red)),
      child: RivePanel(
        backgroundColor: const Color(0x3300AAFF),
        child: Align(
          alignment: Alignment.center,
          child: SizedBox(
            width: 44, height: 44, // smaller than the panel -> offset painter
            child: RiveWidgetBuilder(
              fileLoader: fileLoader,
              builder: (context, state) => switch (state) {
                RiveLoaded() => RiveWidget(
                    controller: state.controller, fit: Fit.contain,
                    useSharedTexture: true),
                _ => const SizedBox.shrink(),
              },
            ),
          ),
        ),
      ),
    ),
  ),
)
Panel painter MediaQuery dpr result
A offset (44 in a 100 panel) == real dpr correct
B offset real dpr × 1.4 mispositioned
C fills panel real dpr × 1.4 correct (offset is 0)

Source .riv / .rev file

The repro uses your own example asset (example/assets/coyote.riv), bundled in the repro repo as assets/repro.riv. Any .riv works — the bug is positional.

Screenshots / video

Before/after are in the repo README: bug-screenshot.png (B mispositioned) and fix-screenshot.png (all correct).

Rive Flutter package version

0.14.7 (latest). Also reproduces on 0.14.6, and on current master (cc5147f) — the dpr source there is still MediaQuery.devicePixelRatioOf(context), even after the recent shared-texture transform reworks (#12675, #12688).

Flutter version

Flutter 3.44.0 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 559ffa3f75 • 2026-05-15
Engine • hash fcf463a2242790d1fdcd9d044f533080f5022e18 (revision 4c525dac5e)
Tools • Dart 3.12.0 • DevTools 2.57.0

Device

Web (Chrome, on a retina Mac) and iPad (iPadOS). Platform-agnostic — the ×1.4 is a relative mismatch, so it reproduces at any window.devicePixelRatio.

OS version

macOS (web / Chrome, CanvasKit & skwasm) and iPadOS 17.

Additional context

Suggested fix — source the dpr from the real view, not the overridable MediaQuery, in shared_texture_view.dart (SharedTextureView.build) and rive_panel.dart (RiveSurface.build):

- MediaQuery.devicePixelRatioOf(context)
+ View.of(context).devicePixelRatio

View.of(context).devicePixelRatio is the real, non-overridable view dpr, which matches the canvas sizing, and keeps the relative getTransformTo transform intact. (Retain a MediaQuery.devicePixelRatioOf(context) dependency so a runtime dpr change — e.g. moving the window to a different-DPI monitor — still triggers a rebuild, since View.of only notifies on view-identity changes.)

Full diff on a fork of current master: joao-ello/rive-flutter@master...shared-texture-dpr-fix (verified — it makes panel B correct; this is what the repro's fix branch uses).

Happy to open a PR if that's a useful channel for you.

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