Skip to content

Feature requests: textures and materials for data-driven rendering #506

Open

Description

@flowhorn

Hey @bdero, I really love flutter_scene and i am experimenting with it the last months a lot. I feel like it has so much potential and wanted to share a few things I was having issues with, that would really help working with flutter_scene.

Happy to help with any of these.

--- AI GENERATED ---

flutter_scene works very well for meshes and PBR materials. These requests come from using it for data-driven rendering: volume visualisation (scientific, simulation and voxel data), large image stacks, height fields and look-up-table effects. Each item below has a short rationale and an example API sketch; the sketches are suggestions, not requirements.

Checked against flutter_scene 0.24.3, on native (Impeller) and the WebGL2 backend.

Summary

Texture types and formats

  • 2D-array and 3D textures in the public API
  • sampler2DArray / sampler3D parameters in .fmat materials
  • Array and 3D textures in the WebGL2 backend
  • Pixel formats beyond RGBA8 (single-channel, 16-bit, float)

Resource control

  • Device limits in GpuCapabilities
  • Partial texture updates (region and layer)
  • Explicit texture disposal and memory accounting

Authoring

  • Shared code (#include) between .fmat files
  • A way to wait for or pre-warm material pipelines
  • Exact texel reads in materials

Performance

  • Render scale per SceneView
  • GPU timing in render stats
  • Compute shaders where the backend supports them
  • Depth and blend control for custom unlit materials

1. 2D-array and 3D textures in the public API

Problem. Texture2D is the only texture you can create from raw data through the public API (fromPixels, fromImage, fromEncodedBytes, fromAsset). package:flutter_scene/gpu.dart exports Texture but no way to create one, so array and 3D textures can't be made without importing src/.

Use cases.

  • Volume rendering: CT- or MRI-like scans, fluid and smoke simulations, signed distance fields.
  • Texture arrays for terrain splatting, sprite animation frames or decals, without atlas bleeding.
  • 3D look-up tables for colour grading and noise volumes.

Today's workaround. Pack slices into a 2D atlas and reconstruct 3D addressing in the shader. That means manual trilinear filtering (8 fetches instead of 1), tile-edge handling, and keeping within the maximum texture size by splitting the data across several textures.

Suggested API.

final layers = Texture2DArray.fromLayers(
  [layer0, layer1, layer2], // Uint8List or ByteData per layer
  width: 512,
  height: 512,
  format: PixelFormat.r16UNormInt,
  sampling: const TextureSampling(mipmaps: false),
);

final volume = Texture3D.fromVoxels(
  voxels, // width * height * depth texels
  width: 256,
  height: 256,
  depth: 256,
  format: PixelFormat.r16Float,
);

Native Flutter GPU already supports TextureType.texture2DArray with createTexture(layerCount:); 3D textures would also need engine support.

2. sampler2DArray / sampler3D parameters in .fmat

Problem. .fmat parameters support float, int, vec2–vec4, mat4, sampler2D and samplerCube only, so even with array or 3D textures available, a custom material couldn't sample them.

Suggested syntax.

parameters: [
  { type: sampler3d, name: density, hint: default_black },
  { type: sampler2darray, name: layers, hint: default_black },
]
float d = texture(density, uvw).r;
vec4 c = texture(layers, vec3(uv, layerIndex));
material.parameters.setTexture('density', volume, sampler: linearClamp);
3. Array and 3D textures in the WebGL2 backend

Problem. The WebGL2 backend's texture types are only texture2D, texture2DMultisample, textureCube and textureExternalOES, and its textures bind only TEXTURE_2D or TEXTURE_CUBE_MAP. So array and 3D textures are unavailable on web even internally.

Why it's cheap. WebGL2 supports both natively: TEXTURE_2D_ARRAY and TEXTURE_3D through texImage3D / texSubImage3D, with at least 256 layers and 256³ voxels guaranteed. Supporting them would keep the same material code working across all backends, which is one of flutter_scene's strengths.

4. Pixel formats beyond RGBA8

Problem. Texture2D.fromPixels takes RGBA8888 only. Scalar or high-precision data has to be packed into 8-bit channels, for example a 16-bit value split across red and green, and unpacked in the shader. That has two costs:

  • It wastes memory: four bytes per texel for one value.
  • Hardware filtering can't be used on the packed bytes.

Use cases. Height maps, scientific scalar fields, depth or distance data, HDR intermediates, single-channel masks.

Suggested API.

Texture2D.fromData(
  ByteData data,
  width,
  height, {
  PixelFormat format = PixelFormat.r8g8b8a8UNormInt,
  TextureSampling sampling = const TextureSampling(),
});

Formats wanted: r8, rg8, r16, r16Float, r32Float, rgba16Float. Expose PixelFormat from gpu.dart, with a capability check for formats that aren't filterable or available everywhere.

5. Device limits in GpuCapabilities

Problem. Apps that size textures to the data can't ask the device what it supports. They hard-code conservative values such as 4096 texels, which wastes capability on desktop or fails on older mobile GPUs.

Suggested fields.

final caps = gpuCapabilities;
caps.maxTextureSize;       // e.g. 16384 on desktop, 4096 on some mobile GPUs
caps.maxArrayLayers;
caps.max3DTextureSize;
caps.maxSamplersPerStage;
caps.supportsFloatFiltering;
6. Partial texture updates

Problem. gpu.Texture.overwrite replaces a whole mip level (or one slice). Streaming data, such as tiles arriving from the network, frames decoded in the background or a simulation updating a region, either needs a full re-upload or many separate textures.

Suggested API.

texture.writeRegion(
  bytes,
  x: 256, y: 0, width: 256, height: 256,
  layer: 12, // array layer or 3D depth slice
  mipLevel: 0,
);

This makes progressive loading possible: show what has arrived and fill in the rest without reallocating.

7. Explicit texture disposal and memory accounting

Problem. GPU textures are released when garbage-collected, so apps that hold hundreds of megabytes of data textures can't free memory deterministically. When switching datasets, the old and new data briefly coexist.

Suggested API.

texture.dispose();           // release GPU memory now
texture.byteSize;            // size of this texture
takeMemoryReport().textures; // already partly there; per-texture breakdown would help
8. Shared code between .fmat files

Problem. .fmat has no include mechanism. Materials that share sampling, decoding or colour-mapping functions either duplicate the code or merge into one large material with a mode switch.

Suggested syntax.

fragment {
  #include "shaders/common/colormap.glsl"
  #include "shaders/common/sampling.glsl"
  ...
}

Resolved at build time by buildMaterials, relative to the discovery root.

9. A way to wait for or pre-warm material pipelines

Problem. On WebGL2 a material's pipeline can be built asynchronously on first use, and that frame is skipped. With SceneView(autoTick: false) the scene may then not be presented until something else triggers a frame. Apps work around this by ticking for a while after creation.

Suggested API.

final material = await loadFmatMaterial('shaders/heatmap.fmat');
await material.ready;            // pipeline compiled and linked
// or
await Scene.prewarm([material]); // compile ahead of first use
10. Exact texel reads in materials

Problem. Data textures often need unfiltered reads at integer coordinates, for example look-up tables, packed values or index maps. Today that is done with texture(sampler, (floor(p) + 0.5) / size) and nearest sampling, which is fragile under precision limits and easy to get wrong.

Suggestion. Document or guarantee that texelFetch(sampler, ivec2(p), 0) works in .fmat on every backend (or provide a TexelFetch() helper that maps to the right construct per backend), plus integer formats where supported.

11. Render scale per SceneView

Problem. Expensive fragment work, such as ray marching, screen-space effects or high sample counts, scales with the number of pixels. There's no simple way to render one view at reduced resolution while it's moving and at full resolution when it's still.

Suggested API.

SceneView(
  scene,
  camera: camera,
  renderScale: interacting ? 0.5 : 1.0, // upscaled when presented
);
12. GPU timing in render stats

Problem. Flutter's FrameTiming covers the CPU side (build, raster thread). Flutter GPU work runs asynchronously on the GPU, so a heavy material can be slow without showing up in any metric the app can read.

Suggested API.

final stats = scene.renderStats.lastFrame;
stats.gpuTime;                      // Duration?, null where unsupported
stats.passes.map((p) => p.gpuTime); // per pass

Backed by Metal/Vulkan timestamp queries and EXT_disjoint_timer_query_webgl2 where available.

13. Compute shaders where the backend supports them

Problem. GpuCapabilities.compute exists, but there's no public way to dispatch compute work. Many data workflows need a preprocessing pass on the GPU.

Use cases. Histograms and min/max for automatic exposure or contrast, gradient or normal precomputation for volumes, mip or occupancy pyramids for empty-space skipping, particle and simulation updates.

Suggested API.

if (gpuCapabilities.compute) {
  final pass = ComputePass(shader: library['Histogram']!)
    ..bindTexture('source', volume)
    ..bindBuffer('bins', histogram);
  pass.dispatch(groupsX, groupsY, groupsZ);
}
14. Depth and blend control for custom unlit materials

Problem. Some effects draw proxy geometry and composite with what is already in the scene, for example volume bounding boxes, soft particles, or clipping planes that intersect meshes. That needs access to scene depth and control over blending and depth writes from a custom material.

Suggestion.

  • Allow blending, depth_write and depth_test on unlit materials (as for lit ones).
  • Optionally, a scene_depth sampler that custom materials can read (where the render graph allows it).

Activity

  1. flowhorn commented on Oct 24, 2026

    @flowhorn
    Author

    One more request that isn't in the list above, and that would take pressure off several of the items in it.

    15. Public access to the GPU layer in package:flutter_scene/gpu.dart

    Problem. flutter_scene already has a complete GPU layer that works on both backends: Flutter GPU on native and its own WebGL2 implementation on web. But gpu.dart exports only Shader, ShaderLibrary, Texture, the sampler types and a few vertex types. GpuContext, createTexture, PixelFormat, TextureType, CommandBuffer, RenderPass and render pipelines are only reachable through src/.

    So for anything the high-level API doesn't cover yet, an app has two options:

    • import src/, which breaks on any release, or
    • depend on flutter_gpu directly, which loses web.

    Suggestion. Export the GPU layer from gpu.dart (or a separate gpu_advanced.dart), documented as lower level and less stable than scene.dart:

    import 'package:flutter_scene/gpu.dart' as gpu;
    
    final texture = gpu.gpuContext.createTexture(
      gpu.StorageMode.hostVisible, width, height,
      textureType: gpu.TextureType.texture2DArray, // where the backend supports it
      layerCount: depth,
      format: gpu.PixelFormat.r8g8UNormInt,
    );

    On web, the WebGL2 createTexture would need to accept layerCount too, even if it reports array textures as unsupported until item 3 is done.

    Then apps could try out items 1, 4 and 5 themselves on the backends that already support them, while the high-level APIs settle. It also gives a natural place for compute (item 13) once flutter/flutter#193805 lands: the same calls on native, and GpuCapabilities.compute false on web.

    My case. My volume renderer only uses flutter_scene for one quad per viewport with a custom material. With this escape hatch I could use array textures on native today and keep the atlas path for web.

    Engine side

    The parts that need Flutter GPU itself, not flutter_scene, are now filed upstream:

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions