Skip to content

Shaders crash on Windows GLES V2: float_type not populated in ShaderStructMemberMetadata #184953

Description

@Bleak84

flutter_gpu shaders crash on Windows GLES V2: float_type not populated in ShaderStructMemberMetadata

Description

All flutter_gpu apps with uniform blocks crash on Windows (ANGLE/GLES V2 backend) with the error:

[ERROR:flutter/impeller/renderer/backend/gles/buffer_bindings_gles.cc(409)]
Float uniform should have a float type.

This is a regression introduced by engine commit 41e819290c2 (PR #182229). That commit replaced the size-based uniform dispatch (switch(member.size)) in BindUniformBufferV2() with a new ShaderFloatType-based dispatch (switch(member.float_type.value())) to resolve the ambiguity between vec4 and mat2 (both 16 bytes). The commit populated float_type for builtin shaders and runtime effect shaders, but did not update lib/gpu/shader_library.cc — the deserialization path for flutter_gpu shader bundles. As a result, all ShaderStructMemberMetadata structs created by MakeFromFlatbuffer() have float_type = std::nullopt, which triggers the hard crash on the GLES V2 path.

Steps to Reproduce

  1. Create a Flutter app that uses flutter_gpu with any shader containing a uniform block (e.g., uniform FrameInfo { mat4 mvp; })
  2. Load the shader via gpu.ShaderLibrary.fromAsset('my_shaders.shaderbundle')
  3. Create a render pipeline, bind the uniform buffer, and call renderPass.draw()
  4. Run on Windows: flutter run -d windows

Environment

Detail Value
Flutter 3.43.0-1.0.pre-572 (master)
Dart 3.13.0
Platform Windows 10/11 (ANGLE/GLES V2)
Impeller Enabled (default)
GPU backend OpenGLES (via ANGLE)

Root Cause Analysis

What PR #182229 changed

Commit 41e819290c2 (2026-02-13) modified BindUniformBufferV2() in impeller/renderer/backend/gles/buffer_bindings_gles.cc:

Before (size-based dispatch):

switch (member.size) {
  case sizeof(Matrix):   gl.UniformMatrix4fv(...); continue;
  case sizeof(Vector4):  gl.Uniform4fv(...);        continue;
  case sizeof(Vector3):  gl.Uniform3fv(...);        continue;
  case sizeof(Vector2):  gl.Uniform2fv(...);        continue;
  case sizeof(Scalar):   gl.Uniform1fv(...);        continue;
}

After (ShaderFloatType-based dispatch):

if (!member.float_type.has_value()) {
  VALIDATION_LOG << "Float uniform should have a float type.";
  return false;  // ← CRASH for flutter_gpu shaders
}
switch (member.float_type.value()) {
  case ShaderFloatType::kFloat: gl.Uniform1fv(...);  break;
  case ShaderFloatType::kVec2:  gl.Uniform2fv(...);  break;
  case ShaderFloatType::kVec3:  gl.Uniform3fv(...);  break;
  case ShaderFloatType::kVec4:  gl.Uniform4fv(...);  break;
  case ShaderFloatType::kMat4:  gl.UniformMatrix4fv(...); break;
  // ...
}

The same commit added std::optional<ShaderFloatType> float_type to ShaderStructMemberMetadata in impeller/core/shader_types.h:

struct ShaderStructMemberMetadata {
  ShaderType type;
  std::string name;
  size_t offset;
  size_t size;
  size_t byte_length;
  std::optional<size_t> array_elements;
  std::optional<ShaderFloatType> float_type;  // ← new field
};

What was updated

The commit correctly populated float_type in two places:

  1. Builtin shaders — hardcoded C++ metadata structs (e.g., FrameInfo, FragInfo) now include /*float_type=*/ShaderFloatType::kMat4

  2. Runtime effect shadersimpeller/entity/contents/runtime_effect_contents.cc was updated to infer float_type from the runtime stage's RuntimeUniformType metadata

What was NOT updated

lib/gpu/shader_library.cc — the deserialization path for flutter_gpu shader bundles loaded via gpu.ShaderLibrary.fromAsset(). The MakeFromFlatbuffer() function creates ShaderStructMemberMetadata structs without initializing float_type, leaving it as std::nullopt:

// lib/gpu/shader_library.cc — MakeFromFlatbuffer()
members.push_back(impeller::ShaderStructMemberMetadata{
    .type = FromUniformType(struct_member->type()),
    .name = struct_member->name()->c_str(),
    .offset = static_cast<size_t>(struct_member->offset_in_bytes()),
    .size =
        static_cast<size_t>(struct_member->element_size_in_bytes()),
    .byte_length =
        static_cast<size_t>(struct_member->total_size_in_bytes()),
    .array_elements =
        struct_member->array_elements() == 0
            ? std::optional<size_t>(std::nullopt)
            : static_cast<size_t>(struct_member->array_elements()),
    // float_type is NOT set — defaults to std::nullopt
});

Verified: git log --oneline 41e819290c2..HEAD -- lib/gpu/shader_library.cc returns empty — no subsequent commit has fixed this.

Why only Windows is affected

The GLES V2 path (BindUniformBufferV2) is only used when the GL context reports a version below 3.0. Windows/ANGLE with Flutter's hardcoded EGL_CONTEXT_CLIENT_VERSION = 2 always gets GLES 2.0, forcing the V2 path. The V3 path (BindUniformBufferV3) uses UBOs and does not inspect float_type. Metal and Vulkan backends bind uniform buffers as opaque byte ranges without per-member type inspection.

Related

Impact

This blocks all flutter_gpu usage on Windows. Any app using gpu.ShaderLibrary.fromAsset() with shaders containing uniform blocks will crash at draw time. This includes third-party 3D packages that build on flutter_gpu.

Expected results

Expected results

The shader uniform is bound via glUniform*fv calls. 3D geometry renders normally. This works correctly on macOS/iOS (Metal backend) and Android/Linux with GLES 3.0+ (V3/UBO path).

Actual results

Actual results

The app crashes during the first draw call that involves a flutter_gpu uniform buffer. No rendering occurs.

Crash log

[ERROR:flutter/impeller/renderer/backend/gles/buffer_bindings_gles.cc(409)] Float uniform should have a float type.

Code sample

Code sample
// shaders/simple.vert
// Minimal vertex shader with uniform block that triggers the crash.
// The mat4 uniform member causes BindUniformBufferV2() to check
// member.float_type, which is std::nullopt for flutter_gpu shaders.

uniform FrameInfo {
  mat4 mvp;
} frame_info;

in vec3 position;

void main() {
  gl_Position = frame_info.mvp * vec4(position, 1.0);
}
// shaders/simple.frag"
// Minimal fragment shader - outputs solid color.

uniform FragInfo {
  vec4 color;
} frag_info;

out vec4 frag_color;

void main() {
  frag_color = frag_info.color;
}
{
 /* shaders/simple.shaderbundle.json
 */
  "SimpleVertex": {
    "type": "vertex",
    "file": "shaders/simple.vert"
  },
  "SimpleFragment": {
    "type": "fragment",
    "file": "shaders/simple.frag"
  }
}
// hook/build.dart
import 'package:native_assets_cli/native_assets_cli.dart';
import 'package:flutter_gpu_shaders/build.dart';

void main(List<String> args) async {
  await build(args, (input, output) async {
    await buildShaderBundleJson(
      buildInput: input,
      buildOutput: output,
      manifestFileName: 'shaders/simple.shaderbundle.json',
    );
  });
}
name: flutter_gpu_crash_repro
description: Minimal reproduction of flutter_gpu GLES V2 crash on Windows.
publish_to: 'none'
version: 1.0.0

environment:
  sdk: '>=3.4.0 <4.0.0'

dependencies:
  flutter:
    sdk: flutter
  flutter_gpu:
    sdk: flutter
  flutter_gpu_shaders: ^0.3.0

flutter:
  assets:
    - build/shaderbundles/
// main.dart
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:flutter_gpu/gpu.dart' as gpu;

void main() {
  runApp(const _CrashReproApp());
}

class _CrashReproApp extends StatelessWidget {
  const _CrashReproApp();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('flutter_gpu Crash Repro')),
        body: const Center(
          child: _GpuDrawWidget(),
        ),
      ),
    );
  }
}

class _GpuDrawWidget extends StatefulWidget {
  const _GpuDrawWidget();

  @override
  State<_GpuDrawWidget> createState() => _GpuDrawWidgetState();
}

class _GpuDrawWidgetState extends State<_GpuDrawWidget> {
  String _status = 'Initializing...';

  @override
  void initState() {
    super.initState();
    // Schedule the GPU draw after the first frame so the widget tree is ready.
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _attemptGpuDraw();
    });
  }

  void _attemptGpuDraw() {
    try {
      // ----------------------------------------------------------
      // 1. Load the shader bundle compiled by hook/build.dart.
      // ----------------------------------------------------------
      final library = gpu.ShaderLibrary.fromAsset(
        'build/shaderbundles/simple.shaderbundle',
      );
      if (library == null) {
        setState(() => _status =
            'Failed to load shader bundle.\n'
            'Did you run: flutter pub get && flutter build?');
        return;
      }

      // ----------------------------------------------------------
      // 2. Get the vertex and fragment shaders.
      // ----------------------------------------------------------
      final vertShader = library['SimpleVertex'];
      final fragShader = library['SimpleFragment'];
      if (vertShader == null || fragShader == null) {
        setState(() => _status =
            'Shaders not found in bundle.\n'
            'Got vertex: ${vertShader != null}, '
            'fragment: ${fragShader != null}');
        return;
      }

      // ----------------------------------------------------------
      // 3. Create the render pipeline.
      // ----------------------------------------------------------
      final pipeline = gpu.gpuContext.createRenderPipeline(
        vertShader,
        fragShader,
      );

      // ----------------------------------------------------------
      // 4. Get the uniform slot for "FrameInfo" — the uniform block
      //    declared as: uniform FrameInfo { mat4 mvp; }
      // ----------------------------------------------------------
      final frameInfoSlot =
          pipeline.vertexShader.getUniformSlot('FrameInfo');

      // ----------------------------------------------------------
      // 5. Create a host buffer and write an identity matrix.
      //    16 floats = 1 mat4.
      // ----------------------------------------------------------
      final hostBuffer = gpu.gpuContext.createHostBuffer();
      final mvpData = Float32List.fromList(<double>[
        1, 0, 0, 0, //
        0, 1, 0, 0, //
        0, 0, 1, 0, //
        0, 0, 0, 1, //
      ]);
      final mvpView = hostBuffer.emplace(mvpData.buffer.asByteData());

      // ----------------------------------------------------------
      // 6. Create a 1×1 render target (minimal resource usage).
      // ----------------------------------------------------------
      final colorTexture = gpu.gpuContext.createTexture(
        gpu.StorageMode.devicePrivate,
        1,
        1,
      );
      if (colorTexture == null) {
        setState(() => _status = 'Failed to create color texture.');
        return;
      }
      final renderTarget = gpu.RenderTarget.singleColor(
        gpu.ColorAttachment(texture: colorTexture),
      );

      // ----------------------------------------------------------
      // 7. Create a command buffer and render pass.
      // ----------------------------------------------------------
      final commandBuffer = gpu.gpuContext.createCommandBuffer();
      final renderPass = commandBuffer.createRenderPass(renderTarget);

      // ----------------------------------------------------------
      // 8. Bind the uniform buffer.
      //
      //    This populates ShaderStructMemberMetadata with
      //    float_type = std::nullopt (the bug) for the mat4 member,
      //    because lib/gpu/shader_library.cc never sets float_type
      //    when deserializing flutter_gpu shader bundles.
      // ----------------------------------------------------------
      renderPass.bindUniform(frameInfoSlot, mvpView);

      // ----------------------------------------------------------
      // 9. Bind a minimal vertex buffer (a single triangle).
      // ----------------------------------------------------------
      final vertexData = Float32List.fromList(<double>[
        0.0, 0.5, 0.0, // vertex 0 position (vec3)
        -0.5, -0.5, 0.0, // vertex 1 position (vec3)
        0.5, -0.5, 0.0, // vertex 2 position (vec3)
      ]);
      final vertexView =
          hostBuffer.emplace(vertexData.buffer.asByteData());
      renderPass.bindVertexBuffer(vertexView, 3);

      // ----------------------------------------------------------
      // 10. Draw. On Windows GLES V2 this calls BindUniformBufferV2()
      //     which checks member.float_type.has_value(). Since
      //     float_type is std::nullopt for flutter_gpu shaders, this
      //     crashes with: "Float uniform should have a float type."
      // ----------------------------------------------------------
      renderPass.draw();

      // ----------------------------------------------------------
      // 11. Submit the command buffer.
      // ----------------------------------------------------------
      commandBuffer.submit();

      setState(() => _status =
          'GPU draw succeeded!\n\n'
          'If you see this, the bug has been fixed\n'
          'or you are not on Windows GLES V2.');
    } catch (e, st) {
      setState(() => _status =
          'CRASH (expected on Windows GLES V2):\n\n'
          '$e\n\nStack trace:\n$st');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(16),
      child: SelectableText(
        _status,
        style: const TextStyle(fontFamily: 'monospace', fontSize: 12),
      ),
    );
  }
}

Screenshots or Video

No response

Logs

Logs

Flutter Doctor output

Doctor output
[√] Flutter (Channel master, 3.43.0-1.0.pre-572, on Microsoft Windows [Version 10.0.26200.8037], locale en-US) [5.7s]
    • Flutter version 3.43.0-1.0.pre-572 on channel master at C:\src\flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision a04cdec60e (3 hours ago), 2026-04-12 19:10:19 -0400
    • Engine revision a04cdec60e
    • Dart version 3.13.0 (build 3.13.0-11.0.dev)
    • DevTools version 2.57.0
    • Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android,
      enable-ios, cli-animations, enable-native-assets, enable-swift-package-manager, omit-legacy-version-file,
      enable-lldb-debugging, enable-uiscene-migration, enable-riscv64

[√] Windows Version (11 Home 64-bit, 25H2, 2009) [4.1s]

[√] Android toolchain - develop for Android devices (Android SDK version 36.1.0) [5.4s]
    • Android SDK at C:\Users\sbrow\AppData\Local\Android\sdk
    • Emulator version 36.3.10.0 (build_id 14472402) (CL:N/A)
    • Platform android-36, build-tools 36.1.0
    • Java binary at: C:\Program Files\Android\Android Studio\jbr\bin\java
      This is the JDK bundled with the latest Android Studio installation on this machine.
      To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.
    • Java version OpenJDK Runtime Environment (build 21.0.8+-14196175-b1038.72)
    • All Android licenses accepted.

[√] Chrome - develop for the web [332ms]
    • Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe

[√] Visual Studio - develop Windows apps (Visual Studio Build Tools 2022 17.14.25 (January 2026)) [328ms]
    • Visual Studio at C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools
    • Visual Studio Build Tools 2022 version 17.14.36915.13
    • Windows 10 SDK version 10.0.17763.0

[√] Connected device (3 available) [497ms]
    • Windows (desktop) • windows • windows-x64    • Microsoft Windows [Version 10.0.26200.8037]
    • Chrome (web)      • chrome  • web-javascript • Google Chrome 146.0.7680.178
    • Edge (web)        • edge    • web-javascript • Microsoft Edge 146.0.3856.62

[√] Network resources [441ms]
    • All expected network resources are available.

• No issues found!

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work listc: regressionIt was better in the past than it is nowe: impellerImpeller rendering backend issues and features requestsengineflutter/engine related. See also e: labels.flutter-gpuhas reproducible stepsThe issue has been confirmed reproducible and is ready to work onplatform-windowsBuilding on or for Windows specificallyteam-engineOwned by Engine teamtriaged-engineTriaged by Engine team

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    ✅ Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions