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
- Create a Flutter app that uses
flutter_gpu with any shader containing a uniform block (e.g., uniform FrameInfo { mat4 mvp; })
- Load the shader via
gpu.ShaderLibrary.fromAsset('my_shaders.shaderbundle')
- Create a render pipeline, bind the uniform buffer, and call
renderPass.draw()
- 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:
-
Builtin shaders — hardcoded C++ metadata structs (e.g., FrameInfo, FragInfo) now include /*float_type=*/ShaderFloatType::kMat4
-
Runtime effect shaders — impeller/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!
flutter_gpu shaders crash on Windows GLES V2:
float_typenot populated inShaderStructMemberMetadataDescription
All
flutter_gpuapps with uniform blocks crash on Windows (ANGLE/GLES V2 backend) with the error:This is a regression introduced by engine commit
41e819290c2(PR #182229). That commit replaced the size-based uniform dispatch (switch(member.size)) inBindUniformBufferV2()with a newShaderFloatType-based dispatch (switch(member.float_type.value())) to resolve the ambiguity betweenvec4andmat2(both 16 bytes). The commit populatedfloat_typefor builtin shaders and runtime effect shaders, but did not updatelib/gpu/shader_library.cc— the deserialization path forflutter_gpushader bundles. As a result, allShaderStructMemberMetadatastructs created byMakeFromFlatbuffer()havefloat_type = std::nullopt, which triggers the hard crash on the GLES V2 path.Steps to Reproduce
flutter_gpuwith any shader containing a uniform block (e.g.,uniform FrameInfo { mat4 mvp; })gpu.ShaderLibrary.fromAsset('my_shaders.shaderbundle')renderPass.draw()flutter run -d windowsEnvironment
Root Cause Analysis
What PR #182229 changed
Commit
41e819290c2(2026-02-13) modifiedBindUniformBufferV2()inimpeller/renderer/backend/gles/buffer_bindings_gles.cc:Before (size-based dispatch):
After (
ShaderFloatType-based dispatch):The same commit added
std::optional<ShaderFloatType> float_typetoShaderStructMemberMetadatainimpeller/core/shader_types.h:What was updated
The commit correctly populated
float_typein two places:Builtin shaders — hardcoded C++ metadata structs (e.g.,
FrameInfo,FragInfo) now include/*float_type=*/ShaderFloatType::kMat4Runtime effect shaders —
impeller/entity/contents/runtime_effect_contents.ccwas updated to inferfloat_typefrom the runtime stage'sRuntimeUniformTypemetadataWhat was NOT updated
lib/gpu/shader_library.cc— the deserialization path forflutter_gpushader bundles loaded viagpu.ShaderLibrary.fromAsset(). TheMakeFromFlatbuffer()function createsShaderStructMemberMetadatastructs without initializingfloat_type, leaving it asstd::nullopt:Verified:
git log --oneline 41e819290c2..HEAD -- lib/gpu/shader_library.ccreturns 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 hardcodedEGL_CONTEXT_CLIENT_VERSION = 2always gets GLES 2.0, forcing the V2 path. The V3 path (BindUniformBufferV3) uses UBOs and does not inspectfloat_type. Metal and Vulkan backends bind uniform buffers as opaque byte ranges without per-member type inspection.Related
41e819290c2) — introduced the regressionImpact
This blocks all
flutter_gpuusage on Windows. Any app usinggpu.ShaderLibrary.fromAsset()with shaders containing uniform blocks will crash at draw time. This includes third-party 3D packages that build onflutter_gpu.Expected results
Expected results
The shader uniform is bound via
glUniform*fvcalls. 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_gpuuniform buffer. No rendering occurs.Crash log
Code sample
Code sample
{ /* shaders/simple.shaderbundle.json */ "SimpleVertex": { "type": "vertex", "file": "shaders/simple.vert" }, "SimpleFragment": { "type": "fragment", "file": "shaders/simple.frag" } }Screenshots or Video
No response
Logs
Logs
Flutter Doctor output
Doctor output