Skip to content

Add additional materials for rendering normals and edges#135

Open
nmfisher wants to merge 47 commits into
developfrom
feature/normal-material
Open

Add additional materials for rendering normals and edges#135
nmfisher wants to merge 47 commits into
developfrom
feature/normal-material

Conversation

@nmfisher
Copy link
Copy Markdown
Owner

No description provided.

nmfisher and others added 30 commits March 6, 2026 19:52
This PR implements support for Linux/Desktop rendering in Flutter applications with EGL (Wayland + OpenGL) or DMA-BUF (Wayland + Vulkan or X11 + OpenGL).

Windows/Linux/Vulkan namespaces are now standardized:

- Base vulkan files: thermion::vulkan
- Windows files: thermion::vulkan::windows
- Linux files: thermion::vulkan::linux (changed from thermion::linux_platform::vulkan)
- Wrap VulkanUtils and linux_vulkan_utils in appropriate namespaces

Use block-linear DRM modifiers for VkImage creation when they support
COLOR_ATTACHMENT (NVIDIA GPUs). The driver picks the optimal modifier
from the list. If no COLOR_ATTACHMENT modifiers exist, fall back to a
two-image blit approach (render to TILING_OPTIMAL, blit to LINEAR
DMA-BUF export).

- Add queryColorAttachmentModifiers() to filter DRM modifiers
- createExportable() now uses block-linear modifiers with dedicated alloc
- Add createWithBlit() factory for render+export image pair
- Add BlitToExport() with command buffer on queue 1
- Pass actual DRM modifier to EGL (lo/hi split)
- Fix double-free: pass VK_NULL_HANDLE for memory to Filament
- Remove VK_KHR_swapchain (not needed for offscreen rendering)

NOTES ON VSYNC:

- drmWaitVBlank doesn't work on NVIDIA.
- we can't use Gdk events to synchronize with Flutter, these are completely disconnected.
- On Linux, we use a Flutter-synchronized render loop. Flutter's SchedulerBinding drives
frame timing via addPersistentFrameCallback, which calls the new
FrameScheduler_requestRender FFI to queue a non-blocking render on
the native render thread. After each render, a post-render callback
(FrameScheduler_setPostRenderCallback) marks textures as frame-available.

refactor: previously we needed to duplicate Filament headers (along with EGL, GLES2, GLES3, etc) for earlier implementations. For the former, we can now use Dart build hooks to propagate headers through from the Dart package to the Flutter package. For the latter, none of these are used any more, so they are safe to delete

refactor: use Dart build hooks to propagate headers through from the Dart package to the Flutter package
🤖 Generated with GitHub Actions
🤖 Generated with GitHub Actions
…t for tests on GitHub runners yet, so running tests with the Vulkan backend would only work with an actual Linux desktop
…s now extends NativeHandle<T> so we can use the T getNativeHandle() instead
Break the monolithic geometry.dart into individual files per shape
(camera, cube, cylinder, pyramid, quad, sphere) with shared utils.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace old wireframe implementation with applyWireframeBarycentrics on
GltfSceneAsset, which unwelds mesh vertices and assigns per-triangle
barycentric coordinates to CUSTOM0 for edge detection in the shader.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update FFI bindings, Dart interfaces and implementations for asset
management, gizmo, view, and scene APIs. Add link hook and
analysis_options.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update existing tests for API changes and add new tests for cascade
validation, gizmo rotation, input pipeline and quad rendering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
🤖 Generated with GitHub Actions
…sset

Instead of mutating existing glTF renderables in-place, extract mesh data
from GltfSceneAsset (with world transforms), unweld vertices, assign
barycentric coords, and create a new GeometrySceneAsset overlay entity.
This allows toggling wireframe visibility by adding/removing from scene.

Key changes:
- GeometrySceneAsset: add TransformManager component, disable culling/shadows
- GltfSceneAsset: add extractMeshData() and createWireframeOverlay() C++ methods
- C API: SceneAsset_getMeshDataSize, SceneAsset_getMeshData,
  SceneAsset_createWireframeOverlayRenderThread
- FFIWireframeAsset.createOverlayFromAsset() with color/width params
- wireframe.mat: fix alpha premultiplication (hardcode alpha=1.0 in output)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Disable face culling in silhouette material so the highlight outline
renders from both sides. Also fix plane geometry winding order to
match its declared normals, and add a test for both-sides visibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BREAKING CHANGE: setGltfAnimationFrame(index, frame) is replaced by
setGltfAnimationTime(index, timeInSeconds). The old implementation
incorrectly computed the time offset passed to Filament's
applyAnimation (60 * frame * 1000 instead of frame / frameRate).
The new API accepts time in seconds, consistent with Filament's
gltfio Animator and getGltfAnimationDuration. Callers must convert
frames to seconds before calling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ace axis line width

Replaces fwidth(abs(dist)) with sqrt(dFdx²+dFdy²) on the raw signed
offset to avoid the derivative discontinuity at the line center and
fwidth's L1 overestimation at oblique camera angles. Adds a guard
against near-zero division when the plane is viewed edge-on.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Create a separate solid-shaded overlay entity from a glTF asset using
Lambertian diffuse lighting with configurable base color, light direction
and intensity. Mirrors the wireframe overlay architecture but uses
POSITION + TANGENTS (normals converted to tangent quaternions via
SurfaceOrientation) instead of POSITION + CUSTOM0 (barycentrics).

- Add solid.mat material (unlit with manual N·L in fragment shader)
- Add extractMeshDataWithNormals() to extract positions + normals
- Add createSolidOverlay() C++ method with SurfaceOrientation pipeline
- Add FFISolidOverlayAsset Dart wrapper with createOverlayFromAsset()
- Add solid to build.sh and hook/build.dart material sources
- Fix wireframe.mat alpha premultiplication (hardcode alpha=1.0)
- Improve wireframe anti-aliasing (Euclidean gradient + transition band)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…servation

Replace the separate overlay entity approach (createWireframeOverlay,
createSolidOverlay, applyWireframeBarycentrics) with a load-time
preserveGeometry flag that rebuilds vertex buffers in-place with a superset
of attributes (POSITION, TANGENTS, UV0, CUSTOM0 barycentrics, and optional
BONE_INDICES/BONE_WEIGHTS). This preserves the full glTF feature set
(animations, skeleton, instancing) while enabling free material swapping.

Breaking changes:
- createWireframeOverlay, createSolidOverlay, applyWireframeBarycentrics removed
- extractMeshData, extractMeshDataWithNormals removed
- FFIWireframeAsset, FFISolidOverlayAsset removed
- loadGltf/loadGltfFromBuffer now accept preserveGeometry parameter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace custom solid.mat with gltfio's built-in ubershader material
(baseColorFactor, metallicFactor, roughnessFactor). This eliminates
a compiled material and its C API surface while getting IBL, shadows,
and proper specular for free.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nce wrappers

Adds convenience methods (createUbershaderMaterial, createWireframeMaterial)
on FilamentApp that return typed wrappers with named setters instead of
raw uniform names. Also adds geometrySource parameter to stencil highlight
for preserved-geometry instances, and implements the FFI wireframe material
factory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…port

- Pass UV coordinates to SurfaceOrientation for correct tangent generation
  (matching gltfio's TangentsJob Lengyel's method)
- Add dummy COLOR attribute (FLOAT4, all 1.0) so original materials don't
  read black vertex colors after vertex buffer rebuild
- Propagate material changes to all instances in setMaterialInstanceForAll
- Apply preserved geometry to freshly-allocated instances in createInstance
- Add preserved vertex/index buffer accessors for C API

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nstructor

Move rebuildVertexBuffers() call into GltfSceneAsset constructor so geometry
rebuild happens before instance creation. Rename preserveGeometry to
rebuildVertices across C API, C++ implementation, Dart interface, and tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
nmfisher and others added 17 commits April 6, 2026 16:35
…3 overflow

Three bugs in rebuildVertexBuffers:

1. Instance update loop overwrote correctly-matched geometry with wrong
   buffers. The primary loop iterated getRenderableEntities() (order A)
   but the instance update loop iterated getAssetInstances()[0]->getEntities()
   (order B). Different orders caused each entity to get the wrong mesh's
   vertex data — correct shapes but garbled textures. Fix: skip instance 0
   in the update loop (already handled by primary loop) and use name-based
   matching for instances 1+.

2. Original TANGENT vectors from glTF were ignored; tangent quaternions
   were recomputed from geometry, producing wrong tangent frames and
   broken normal mapping. Fix: read cgltf_attribute_type_tangent and
   pass to SurfaceOrientation::Builder::tangents().

3. Triangle indices for SurfaceOrientation used ushort3 (uint16), which
   overflows for meshes with >65535 unwelded vertices (FlightHelmet's
   RubberWood_low has 131,574). Fix: use uint3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace name-based entity matching with consistent use of
getEntities() across primary and instance update loops. This
fixes the nameless entity case and simplifies the code:

- Primary loop now iterates getAssetInstances()[0]->getEntities()
  instead of getRenderableEntities(), matching the instance loops
- Instance update uses sequential indexing (safe since all instances
  share the same scene graph and getEntities() order)
- Remove _entityBufferMap and name-based matching
- Fix dangling pointer: tris vector outlives orientBuilder.build()
- Remove debug logging

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nstance

Remove the separate createWireframeMaterialInstance (returning raw
MaterialInstance) and createWireframeMaterial (returning typed wrapper).
Now createWireframeMaterialInstance directly returns
WireframeMaterialInstance.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Delete SurfaceOrientation* after extracting tangent quaternions
- Replace malloc/free with new uint8_t[]/delete[] for buffer data
- Consolidate wireframe test cases

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When flatShading is true (requires rebuildVertices: true), vertex
normals are replaced with face normals computed from triangle edge
cross products, giving a faceted/low-poly look with any material.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…oggle

Remove the flatShading parameter from loadGltf/loadGltfFromBuffer and
rebuildVertexBuffers. Instead, always pre-compute both smooth and flat
tangent quaternion BufferObjects during rebuildVertexBuffers, and add
a setFlatShading(bool) method that swaps slot 1 (TANGENTS) on all
preserved VertexBuffers. This is a GPU pointer swap with near-zero
runtime cost.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add single-channel (R) support to pixelBufferToPng/pixelBufferToBmp and
thread numChannels through capture helpers so R32F depth textures render
correctly as grayscale images.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Unlit material that maps world-space normals to RGB. Includes
useAbsoluteValue toggle, opacity parameter, typed Dart wrapper,
full C/render-thread plumbing, and tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Single-pass material that highlights only geometrically sharp edges
using barycentric coordinates filtered by a precomputed sharp-edge
mask. rebuildVertices now computes edge adjacency, dihedral angles,
smooth normals, and encodes a 3-bit mask in CUSTOM0.w per triangle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
🤖 Generated with GitHub Actions
🤖 Generated with GitHub Actions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant