Skip to content

Textured PBR materials render black on Intel iMac with AMD Radeon Pro 570X (macOS 15) #509

Open

Description

@OAConsulenze

On an Intel iMac with an AMD Radeon Pro 570X, every mesh whose material has a
base color texture renders solid black. Untextured materials render correctly
(lighting, shadows and the environment all look right). The same app works
correctly on an Intel MacBook Pro (Intel UHD 630). Metal validation reports
no errors.

The same .glb files render correctly on the same iMac when loaded in Unity
6.5 (URP + glTFast, macOS standalone build), so the GPU and driver can sample
these textures.

Environment

  • iMac 27" 2019, AMD Radeon Pro 570X 4 GB, Intel Core i5
  • macOS Sequoia 15.7.9
  • Flutter 3.50.0-0.2.pre (channel master, engine e737c8145d), Impeller Metal
  • flutter_scene 0.20.0, also reproduced with 0.24.3
  • Working machine: MacBook Pro 16" 2019 (Intel UHD 630 + AMD 5300M; Flutter
    uses the integrated GPU), same build

What we tried on the iMac (profile builds)

Variant Result
Original glb materials black
Base color + normal textures removed (metallicRoughness texture kept) renders correctly
Base color replaced with a plain JPEG via Texture2D.fromAsset, no normal map, with and without mipmaps black
Base color texture created manually, StorageMode.devicePrivate, RGBA8 black
Base color texture created manually, hostVisible, RGBA16F black
Sampler without anisotropic filtering (GpuTextureSource defaults) black
Shadows disabled black
Different environment (HDR) black
MTL_DEBUG_LAYER=1 and MTL_SHADER_VALIDATION=1 black, no errors reported
flutter_scene 0.24.3 instead of 0.20.0 black

The only log output is the usual Impeller startup line ("Using the Impeller
rendering backend"); no warnings or errors.

So the problem seems specific to the base color texture path in the PBR
shader on this AMD GPU. The metallicRoughness texture, sampled by the same
shader, does not cause it.

Screenshots

Image Image

I can share the .glb files and run test builds on the iMac if that helps.

Activity

  1. bdero commented on Oct 26, 2026

    @bdero
    Owner

    Thanks for the thorough matrix, it rules out a lot. One thing it can't rule out is that every texture sample comes back as zero on this GPU, not just base color. A metallicRoughness texture that reads as zero gives a smooth dielectric, which still looks plausible, so that run may hide the same failure.

    Could you share the .glb files, and try two things on the iMac?

    1. An UnlitMaterial with the same base color texture on a plain PlaneGeometry. If that's black too, the problem is texture sampling in general rather than the PBR shader.
    2. The surface debug views. Add this to the app's pubspec, rebuild, and set scene.debug.view = const DebugView(channel: SurfaceDebugChannel.uv0Checker);, then the same with SurfaceDebugChannel.baseColor. A screenshot of each tells us whether the UVs reach the fragment shader intact.
    hooks:
      user_defines:
        flutter_scene:
          debug_views: true

    flutter doctor -v output from the iMac would help too.

  2. added
    bugSomething isn't working.
    needs reproAwaiting a minimal reproduction from the reporter.
    on Oct 26, 2026
  3. OAConsulenze commented on Oct 26, 2026

    @OAConsulenze
    Author

    Thanks! Your hunch was right that every texture sample is zero, but the cause seems to be upstream of flutter_scene: on this iMac, reading back any ui.Image returns all zeros, even in plain Flutter.

    I built a small test app with flutter_scene 0.24.3 and debug_views: true, and ran it on the iMac (Radeon Pro 570X, Metal 2, macOS 15.7.9) and on a MacBook Pro 2019 (Intel UHD 630, where Flutter runs, macOS 26.7.1):

    Scene MacBook iMac
    UnlitMaterial + PlaneGeometry, texture from JPG asset (Texture2D.fromAsset) OK invisible (transparent)
    UnlitMaterial + PlaneGeometry, texture from in-memory pixels (Texture2D.fromPixels) OK OK
    Board .glb, normal OK black
    Board .glb, DebugView uv0 OK OK, identical
    Board .glb, DebugView uv0Checker OK OK, identical
    Board .glb, DebugView baseColor OK black

    So the UVs reach the fragment shader intact, and a texture uploaded from raw pixels samples fine. Only textures decoded from images fail.

    Plain Flutter readback test (no flutter_scene), checking non-zero bytes from Image.toByteData (rawRgba and rawStraightRgba):

    • decoded JPG (instantiateImageCodec): MacBook 100% / iMac 0%
    • PictureRecorder red rect → Picture.toImage: MacBook OK / iMac all zeros
    • decodeImageFromPixels (solid red): MacBook OK / iMac all zeros

    Since Texture2D.fromImage reads the image back with toByteData before uploading, every decoded texture ends up zero on this GPU. My guess is a missing synchronize on managed storage for discrete GPUs on Intel Macs, but I haven't verified it. I'll report the readback to flutter/flutter as well. Would wrapping the image's GPU texture (the gpu.Texture.fromImage path) be a viable workaround here?

    Screenshots attached. Flutter 3.50.0-0.2.pre (master, 3594a63245); flutter doctor -v from the build machine below (Flutter isn't installed on the iMac).

    flutter_doctor_macbook.txt
    Image
    Image
    Image
    Image
    Image
    Image
    imac_gpu.txt
    imac_log.txt
    Image
    Image
    Image
    Image
    Image
    Image
    macbook_log.txt

  4. bdero commented on Oct 27, 2026

    @bdero
    Owner

    Great digging, and your guess is right. On a Mac without unified memory, Impeller allocates the readback buffer with MTLStorageModeManaged, and the texture-to-buffer blit in BlitPassMTL::OnCopyTextureToBufferCommand never calls synchronizeResource: on it, so the CPU reads whatever was there before the copy. Apple Silicon and Intel integrated GPUs use shared storage, which is why only the Radeon shows it. I'll put up the engine fix; link your flutter/flutter issue here when you file it and I'll reference it.

    Until then, Texture2D.fromImage(image, opaque: true, sampling: const TextureSampling(mipmaps: false)) wraps the image's GPU texture instead of reading it back, which works for opaque textures that can do without mipmaps. Decoding with package:image and passing the pixels to Texture2D.fromPixels avoids the readback for everything else, at the cost of a slower decode. Neither helps textures loaded from a .glb, which always read back.

  5. OAConsulenze commented on Oct 27, 2026

    @OAConsulenze
    Author

    Filed upstream flutter/flutter#195402

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working.needs reproAwaiting a minimal reproduction from the reporter.platform:macos

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions