Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
49c6c75
refactor:
nmfisher Feb 10, 2026
a2e803d
Merge 49c6c75df57d1f6838a411aab46b5b8494a064d9 into 091220de68cd3b140…
nmfisher Mar 6, 2026
17b589c
chore: update generated artifacts
github-actions[bot] Mar 6, 2026
18165d2
add Windows check for useExternalImage (if backend option is null, th…
nmfisher Mar 5, 2026
d068a39
remove superseded Windows platform/context/etc
nmfisher Mar 23, 2026
21d183b
reinstate missing Windows-specific VulkanPlatform implementation methods
nmfisher Mar 23, 2026
b062f83
Merge 21d183bdd5d08e4c043a9a2ebb117c5857d055b2 into 091220de68cd3b140…
nmfisher Mar 23, 2026
89efc1c
chore: update generated artifacts
github-actions[bot] Mar 23, 2026
869423d
revert hardcoded debug mode and log config
nmfisher Mar 23, 2026
96010f7
revert Linux test backend to OpenGL (we don't have SwiftShader suppor…
nmfisher Mar 23, 2026
16729c8
chore: update comments for asset/entity types
nmfisher Apr 2, 2026
0c95d0c
refactor: remove unnecessary T getHandle<T>() from ThermionAsset (thi…
nmfisher Apr 2, 2026
48a3f86
refactor: split geometry utilities into separate files
nmfisher Apr 2, 2026
dd7dc20
feat: add wireframe rendering via barycentric coordinates
nmfisher Apr 2, 2026
0cb4999
refactor: update Dart interfaces, bindings and build config
nmfisher Apr 2, 2026
82db349
chore: update and add tests
nmfisher Apr 2, 2026
5593537
chore: update generated artifacts
github-actions[bot] Apr 2, 2026
4601239
feat: add wireframe overlay as separate entity via createOverlayFromA…
nmfisher Apr 2, 2026
34ab44e
fix highlight overlay not visible on back face of plane/quad
nmfisher Apr 2, 2026
e08b880
feat!: rename setGltfAnimationFrame to setGltfAnimationTime
nmfisher Apr 3, 2026
4e4ddbb
feat: add pauseFrameScheduler/resumeFrameScheduler to ThermionFlutter…
nmfisher Apr 4, 2026
6726039
chore: formatting
nmfisher Apr 4, 2026
a4a6b63
fix: use Euclidean gradient of signed offset for consistent screen-sp…
nmfisher Apr 4, 2026
5ba5acb
feat: add solid shading overlay (Blender-style viewport shading)
nmfisher Apr 4, 2026
1a254d2
refactor!: replace wireframe/solid overlay with in-place geometry pre…
nmfisher Apr 4, 2026
e0f1221
refactor: remove solid.mat, use gltfio ubershader instead
nmfisher Apr 4, 2026
b4949db
feat: add typed UbershaderMaterialInstance and WireframeMaterialInsta…
nmfisher Apr 4, 2026
dcee81c
change default wireframe edge width to 1.0
nmfisher Apr 5, 2026
f60cff8
fix: preserve default shader after geometry rebuild for wireframe sup…
nmfisher Apr 5, 2026
0552777
refactor: rename preserveGeometry to rebuildVertices and move into co…
nmfisher Apr 5, 2026
11bd67c
fix: rebuildVertexBuffers entity ordering, tangent source, and ushort…
nmfisher Apr 6, 2026
da180e4
refactor: use consistent entity enumeration in rebuildVertexBuffers
nmfisher Apr 6, 2026
70b8e98
refactor: merge createWireframeMaterial into createWireframeMaterialI…
nmfisher Apr 6, 2026
cd6179d
fix: free leaked SurfaceOrientation and replace malloc with new[]
nmfisher Apr 6, 2026
c1f329e
feat: add flatShading parameter to loadGltf for per-face normals
nmfisher Apr 6, 2026
5c53fc4
refactor: replace load-time flatShading with runtime setFlatShading t…
nmfisher Apr 6, 2026
a9fe863
feat: support PixelDataFormat.R in pixel buffer conversion and capture
nmfisher Apr 6, 2026
ae40421
feat: capture() now supports PixelDataFormat.R
nmfisher Apr 6, 2026
7549901
chore: format multiline strings
nmfisher Apr 6, 2026
dbf90cc
factor out swapchain/view binding into RenderManager
nmfisher Apr 7, 2026
891a0f4
correctly return single MaterialInstance from FFITexturedQuad
nmfisher Apr 7, 2026
58a42d1
correctly attach viewer view to swapchain
nmfisher Apr 7, 2026
54e2baf
feat: add normal-color material for visualizing face normals
nmfisher Apr 10, 2026
de90cb3
feat: add sharp-edge material with dihedral angle detection
nmfisher Apr 10, 2026
37dd226
chore: update generated artifacts
github-actions[bot] Apr 10, 2026
b2347c6
feat: add option for isDepthTexture to image material (assumes R inpu…
nmfisher Apr 10, 2026
a726a8d
chore: update generated artifacts
github-actions[bot] Apr 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
43 changes: 43 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
FROM ubuntu:24.04

ENV DEBIAN_FRONTEND=noninteractive

# System dependencies for Flutter Linux desktop + Vulkan/EGL development
RUN apt-get update && apt-get install -y \
# Build essentials
git curl wget unzip xz-utils zip \
build-essential cmake ninja-build pkg-config \
# Clang/LLVM toolchain (required by thermion_dart native build)
clang lld libc++-dev libc++abi-dev libclang-dev \
# Flutter Linux desktop dependencies
libgtk-3-dev \
# EGL/GL for texture interop
libegl1-mesa-dev libgl1-mesa-dev libgles2-mesa-dev \
# Vulkan headers and loader
libvulkan-dev vulkan-tools \
# DRM headers for drm_fourcc.h
libdrm-dev \
# Headless testing (xvfb)
xvfb \
# Misc
ca-certificates \
&& rm -rf /var/lib/apt/lists/*

# Reuse the existing ubuntu user (UID 1000) so bind mounts work with podman --userns=keep-id
RUN usermod -l developer -d /home/developer -m ubuntu && \
groupmod -n developer ubuntu
USER developer
WORKDIR /home/developer

# Install Flutter SDK directly (no fvm needed in container)
RUN git clone https://github.com/flutter/flutter.git -b stable --depth 1 /home/developer/flutter

ENV PATH="/home/developer/flutter/bin:/home/developer/flutter/bin/cache/dart-sdk/bin:${PATH}"

# Precache Linux desktop artifacts and disable analytics
RUN flutter config --no-analytics && \
flutter precache --linux && \
dart --disable-analytics && \
flutter doctor -v

WORKDIR /workspace
33 changes: 33 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "Thermion Linux Dev",
"build": {
"dockerfile": "Dockerfile"
},
"features": {},
"runArgs": [
"--userns=keep-id",
"--device", "/dev/dri",
"--group-add", "keep-groups",
"--security-opt", "label=disable"
],
"containerEnv": {
"XDG_RUNTIME_DIR": "/tmp/runtime-developer",
"WAYLAND_DISPLAY": "${localEnv:WAYLAND_DISPLAY}",
"DISPLAY": "${localEnv:DISPLAY}",
"GDK_BACKEND": "wayland"
},
"mounts": [
"source=${localEnv:XDG_RUNTIME_DIR}/${localEnv:WAYLAND_DISPLAY},target=/tmp/runtime-developer/${localEnv:WAYLAND_DISPLAY},type=bind",
"source=/tmp/.X11-unix,target=/tmp/.X11-unix,type=bind"
],
"customizations": {
"vscode": {
"extensions": [
"Dart-Code.dart-code",
"Dart-Code.flutter"
]
}
},
"postCreateCommand": ".devcontainer/post-create.sh",
"remoteUser": "developer"
}
9 changes: 9 additions & 0 deletions .devcontainer/post-create.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash
set -e

echo "Running post-create setup..."

# Ensure Flutter is on PATH and working
flutter doctor -v

echo "Post-create setup complete."
43 changes: 31 additions & 12 deletions examples/dart/cli_windows/bin/cli_windows.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import 'dart:async';
import 'dart:ffi' as ffi;
import 'dart:io';
import 'dart:isolate';
import 'dart:math';
import 'package:thermion_dart/src/filament/src/implementation/ffi_filament_app.dart';
import 'package:thermion_dart/thermion_dart.dart';
Expand All @@ -7,22 +10,26 @@ import 'package:cli_windows/thermion_window.g.dart';
void main(List<String> arguments) async {
var hwnd = create_thermion_window(500, 500, 0, 0);
update();
final config = FFIFilamentConfig(loadResource: (path) async => File(path.replaceAll("file://", "")).readAsBytesSync());
final config = FFIFilamentConfig(
loadResource: (path) async =>
File(path.replaceAll("file://", "")).readAsBytesSync());
await FFIFilamentApp.create(config: config);
var viewer = ThermionViewerFFI();

await viewer.initialized;
var swapChain = await FilamentApp.instance!.createSwapChain(Pointer<Void>.fromAddress(hwnd));
var swapChain = await FilamentApp.instance!
.createSwapChain(Pointer<Void>.fromAddress(hwnd));
var view = viewer.view;
await view.setViewport(500, 500);
var camera = await viewer.getActiveCamera();
await camera.setLensProjection();
await FilamentApp.instance!.register(swapChain, view);
await FilamentApp.instance!.setRenderOrder(swapChain, view);

await viewer.setBackgroundColor(1.0, 0.0, 0.0, 1.0);

var skyboxPath = File("..\\..\\assets\\default_env_skybox.ktx").absolute;
await viewer.loadSkybox("file://${skyboxPath.uri.toFilePath(windows: true)}");
var skyboxPath = File("../../assets/default_env_skybox.ktx").absolute;
await viewer.loadSkybox(
"file://${skyboxPath.uri.toFilePath(windows: Platform.isWindows)}");

final cube = await viewer.createGeometry(GeometryHelper.cube());

Expand All @@ -33,17 +40,29 @@ void main(List<String> arguments) async {

await camera.lookAt(Vector3(0, 0, 10));

while(true) {
FrameScheduler_initDartApi(ffi.NativeApi.initializeApiDLData);

final framePort = ReceivePort();
final completer = Completer<void>();

framePort.listen((message) async {
var angle = (stopwatch.elapsedMilliseconds / 1000) * 2 * pi;
var rotation = Quaternion.axisAngle(Vector3(0,1,0), angle);
var rotation = Quaternion.axisAngle(Vector3(0, 1, 0), angle);
var position = Vector3(10 * sin(angle), 0, 10 * cos(angle));
var modelMatrix = Matrix4.compose(position, rotation, Vector3.all(1));
await camera.setModelMatrix(modelMatrix);
await FilamentApp.instance!.render();
update();
await Future.delayed(Duration(milliseconds: 17));
}

});

FrameScheduler_startWithPort(framePort.sendPort.nativePort, 60);

// Keep alive until interrupted
ProcessSignal.sigint.watch().listen((_) {
FrameScheduler_stop();
framePort.close();
completer.complete();
});


await completer.future;
}
32 changes: 27 additions & 5 deletions examples/dart/cli_windows/hook/build.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:io';
import 'package:logging/logging.dart';
import 'package:code_assets/code_assets.dart';
import 'package:hooks/hooks.dart';
Expand All @@ -10,16 +11,38 @@ void main(List<String> args) async {
..onRecord.listen((record) => print(
record.message + "\n"));
await build(args, (BuildInput input, BuildOutputBuilder output) async {
final targetOS = input.config.code.targetOS;

final sources = <String>[];
final defines = <String, String>{};
final flags = <String>[];

if (targetOS == OS.windows) {
sources.add('native/thermion_window.cpp');
defines['UNICODE'] = '1';
} else if (targetOS == OS.linux) {
sources.add('native/thermion_window_linux.cpp');
flags.add('-std=c++17');

final cflagsResult = await Process.run('pkg-config', ['--cflags', 'sdl2']);
if (cflagsResult.exitCode == 0) {
flags.addAll(cflagsResult.stdout.toString().trim().split(RegExp(r'\s+')));
}
final libsResult = await Process.run('pkg-config', ['--libs', 'sdl2']);
if (libsResult.exitCode == 0) {
flags.addAll(libsResult.stdout.toString().trim().split(RegExp(r'\s+')));
}
}

final cbuilder = CBuilder.library(
name: input.packageName,
language: Language.cpp,
assetName: 'cli_windows.dart',
sources: ['native/thermion_window.cpp'],
sources: sources,
includes: ['native', '../../../thermion_dart/native/include'],
defines: {"UNICODE":"1"},
flags:[],
defines: defines,
flags: flags,
dartBuildFiles: ['hook/build.dart'],

);

await cbuilder.run(
Expand All @@ -30,4 +53,3 @@ void main(List<String> args) async {

});
}

6 changes: 3 additions & 3 deletions examples/dart/cli_windows/native/thermion_window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ static void RenderLoop() {

extern "C" {

EMSCRIPTEN_KEEPALIVE intptr_t create_thermion_window(int width, int height, int left, int top) {
THERMION_EXPORT intptr_t create_thermion_window(int width, int height, int left, int top) {
_window = new ThermionWindow(width, height, left, top);

// Start the render thread
Expand All @@ -127,13 +127,13 @@ extern "C" {
}

// Update function can now be simplified or removed since rendering happens in the thread
EMSCRIPTEN_KEEPALIVE void update() {
THERMION_EXPORT void update() {


}

// Add a cleanup function
EMSCRIPTEN_KEEPALIVE void cleanup() {
THERMION_EXPORT void cleanup() {
_running = false;
if (_renderThread.joinable()) {
_renderThread.join();
Expand Down
19 changes: 13 additions & 6 deletions examples/dart/cli_windows/native/thermion_window.h
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
#pragma once

#ifdef IS_DLL
#define EMSCRIPTEN_KEEPALIVE __declspec(dllimport)
#include <stdint.h>

#ifdef _WIN32
#ifdef IS_DLL
#define THERMION_EXPORT __declspec(dllimport)
#else
#define THERMION_EXPORT __declspec(dllexport)
#endif
#else
#define EMSCRIPTEN_KEEPALIVE __declspec(dllexport)
#define THERMION_EXPORT
#endif

extern "C" {

intptr_t create_thermion_window(int width, int height, int left, int top);
void update();
THERMION_EXPORT intptr_t create_thermion_window(int width, int height, int left, int top);
THERMION_EXPORT void update();
THERMION_EXPORT void cleanup();

}
}
85 changes: 85 additions & 0 deletions examples/dart/cli_windows/native/thermion_window_linux.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#include <iostream>
#include <chrono>
#include <thread>
#include <atomic>

#include <SDL2/SDL.h>
#include <SDL2/SDL_syswm.h>

#include "thermion_window.h"

static SDL_Window* _window = nullptr;
static std::atomic<bool> _running{false};
static std::thread _eventThread;

static void EventLoop() {
while (_running) {
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_QUIT ||
(e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE)) {
_running = false;
return;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(4));
}
}

extern "C" {

intptr_t create_thermion_window(int width, int height, int left, int top) {
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
std::cerr << "SDL_Init failed: " << SDL_GetError() << std::endl;
return 0;
}

_window = SDL_CreateWindow(
"thermion_window",
(left == 0) ? SDL_WINDOWPOS_CENTERED : left,
(top == 0) ? SDL_WINDOWPOS_CENTERED : top,
width, height,
SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN
);

if (!_window) {
std::cerr << "Failed to create window: " << SDL_GetError() << std::endl;
SDL_Quit();
return 0;
}

SDL_SysWMinfo wmInfo;
SDL_VERSION(&wmInfo.version);
if (!SDL_GetWindowWMInfo(_window, &wmInfo)) {
std::cerr << "SDL_GetWindowWMInfo failed: " << SDL_GetError() << std::endl;
SDL_DestroyWindow(_window);
SDL_Quit();
return 0;
}

intptr_t handle = static_cast<intptr_t>(wmInfo.info.x11.window);
std::cout << "Created SDL2 OpenGL window, X11 handle: " << handle << std::endl;

_running = true;
_eventThread = std::thread(EventLoop);

return handle;
}

void update() {
// No-op: event loop runs in background thread
}

void cleanup() {
_running = false;
if (_eventThread.joinable()) {
_eventThread.join();
}
if (_window) {
SDL_DestroyWindow(_window);
_window = nullptr;
}
SDL_Quit();
}

}
12 changes: 12 additions & 0 deletions examples/flutter/quickstart/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -68,6 +70,16 @@ class _MyHomePageState extends State<MyHomePage> {
initialCameraPosition: Vector3(0, 0, 6),
background: Colors.blue,
manipulatorType: _manipulatorType,
onAssetLoaded: (viewer, asset) async {
var last = DateTime.now();
Timer.periodic(Duration(milliseconds: 16), (timer) async {
var now = DateTime.now();
await asset.setTransform(Matrix4.rotationY(
(now.millisecondsSinceEpoch.toDouble() -
last.millisecondsSinceEpoch) /
1000));
});
},
onViewerAvailable: (viewer) async {
setState(() {
_viewer = viewer;
Expand Down
2 changes: 1 addition & 1 deletion examples/flutter/quickstart/linux/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ add_subdirectory(${FLUTTER_MANAGED_DIR})
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)

add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
add_definitions(-DAPPLICATION_ID="${APPLICATION_ID} -DGDK_WINDOWING_X11=1")

# Define the application target. To change its name, change BINARY_NAME above,
# not the value here, or `flutter run` will no longer work.
Expand Down
1 change: 1 addition & 0 deletions examples/flutter/quickstart/linux/main.cc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#define GDK_WINDOWING_X11 TRUE
#include "my_application.h"

int main(int argc, char** argv) {
Expand Down
Loading
Loading