add Dart IndirectLight and Ktx1Bundle classes, and separate KTX decoding from skybox/IBL creation

This commit is contained in:
Nick Fisher
2025-06-12 11:31:44 +08:00
parent f252c86152
commit ad26fc4563
16 changed files with 424 additions and 104 deletions

View File

@@ -1,3 +1,4 @@
import 'package:thermion_dart/src/filament/src/interface/ktx1_bundle.dart';
import 'package:vector_math/vector_math_64.dart' as v64; import 'package:vector_math/vector_math_64.dart' as v64;
import 'package:animation_tools_dart/src/bone_animation_data.dart'; import 'package:animation_tools_dart/src/bone_animation_data.dart';
import 'package:animation_tools_dart/src/morph_animation_data.dart'; import 'package:animation_tools_dart/src/morph_animation_data.dart';
@@ -19,6 +20,9 @@ class BackgroundImage extends ThermionAsset {
final FFIScene scene; final FFIScene scene;
int? width;
int? height;
BackgroundImage._( BackgroundImage._(
this.asset, this.scene, this.texture, this.sampler, this.mi); this.asset, this.scene, this.texture, this.sampler, this.mi);
@@ -45,9 +49,8 @@ class BackgroundImage extends ThermionAsset {
await viewer.createGeometry(GeometryHelper.fullscreenQuad()); await viewer.createGeometry(GeometryHelper.fullscreenQuad());
await imageMaterialInstance.setParameterInt("showImage", 0); await imageMaterialInstance.setParameterInt("showImage", 0);
var transform = Matrix4.identity(); var transform = Matrix4.identity();
await imageMaterialInstance.setParameterMat4( await imageMaterialInstance.setParameterMat4("transform", transform);
"transform", transform);
await backgroundImage.setMaterialInstanceAt(imageMaterialInstance); await backgroundImage.setMaterialInstanceAt(imageMaterialInstance);
await scene.add(backgroundImage as FFIAsset); await scene.add(backgroundImage as FFIAsset);
@@ -62,28 +65,74 @@ class BackgroundImage extends ThermionAsset {
await mi.setParameterFloat4("backgroundColor", r, g, b, a); await mi.setParameterFloat4("backgroundColor", r, g, b, a);
} }
///
///
///
Future hideImage() async { Future hideImage() async {
await mi.setParameterInt("showImage", 0); await mi.setParameterInt("showImage", 0);
} }
///
///
///
Future setImageFromKtxBundle(Ktx1Bundle bundle) async {
final texture = await bundle.createTexture();
if (bundle.isCubemap()) {
sampler ??=
await FilamentApp.instance!.createTextureSampler() as FFITextureSampler;
this.texture = texture;
await mi.setParameterTexture(
"cubeMap", texture as FFITexture, sampler as FFITextureSampler);
await setBackgroundColor(1, 1, 1, 0);
await mi.setParameterInt("showImage", 1);
await mi.setParameterInt("isCubeMap", 1);
width = await texture.getWidth();
height = await texture.getHeight();
} else {
await setImageFromTexture(texture);
}
}
/// ///
/// ///
/// ///
Future setImage(Uint8List imageData) async { Future setImage(Uint8List imageData) async {
final image = await FilamentApp.instance!.decodeImage(imageData); final image = await FilamentApp.instance!.decodeImage(imageData);
final channels = await image.getChannels();
final textureFormat = channels == 4
? TextureFormat.RGBA32F
: channels == 3
? TextureFormat.RGB32F
: throw UnimplementedError();
final pixelFormat = channels == 4
? PixelDataFormat.RGBA
: channels == 3
? PixelDataFormat.RGB
: throw UnimplementedError();
texture ??= await FilamentApp.instance!.createTexture( final texture = await FilamentApp.instance!.createTexture(
await image.getWidth(), await image.getHeight(), await image.getWidth(), await image.getHeight(),
textureFormat: TextureFormat.RGBA32F); textureFormat: textureFormat);
await texture! await texture.setLinearImage(image, pixelFormat, PixelDataType.FLOAT);
.setLinearImage(image, PixelDataFormat.RGBA, PixelDataType.FLOAT); await setImageFromTexture(texture);
}
///
///
///
Future setImageFromTexture(Texture texture) async {
this.texture = texture;
sampler ??= sampler ??=
await FilamentApp.instance!.createTextureSampler() as FFITextureSampler; await FilamentApp.instance!.createTextureSampler() as FFITextureSampler;
await mi.setParameterInt("isCubeMap", 0);
await mi.setParameterTexture( await mi.setParameterTexture(
"image", texture as FFITexture, sampler as FFITextureSampler); "image", texture as FFITexture, sampler as FFITextureSampler);
await setBackgroundColor(1, 1, 1, 0); await setBackgroundColor(1, 1, 1, 0);
await mi.setParameterInt("showImage", 1); await mi.setParameterInt("showImage", 1);
width = await texture.getWidth();
height = await texture.getHeight();
} }
/// ///
@@ -128,7 +177,7 @@ class BackgroundImage extends ThermionAsset {
Future setCastShadows(bool castShadows) { Future setCastShadows(bool castShadows) {
// TODO: implement setCastShadows // TODO: implement setCastShadows
throw UnimplementedError(); throw UnimplementedError();
} }
@override @override
Future setReceiveShadows(bool castShadows) { Future setReceiveShadows(bool castShadows) {
@@ -346,6 +395,4 @@ class BackgroundImage extends ThermionAsset {
Future<v64.Aabb3> getBoundingBox() { Future<v64.Aabb3> getBoundingBox() {
throw UnimplementedError(); throw UnimplementedError();
} }
} }

View File

@@ -0,0 +1,90 @@
import 'package:thermion_dart/src/filament/src/implementation/ffi_filament_app.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_texture.dart';
import 'package:thermion_dart/thermion_dart.dart';
class FFIIndirectLight extends IndirectLight {
final Pointer<TEngine> engine;
final Pointer<TIndirectLight> pointer;
final Texture? _irradianceTexture;
final Texture? _reflectionsTexture;
FFIIndirectLight._(this.engine, this.pointer, this._irradianceTexture,
this._reflectionsTexture);
static Future<FFIIndirectLight> fromIrradianceTexture(
Texture irradianceTexture,
{Texture? reflectionsTexture,
double intensity = 30000}) async {
final engine = (FilamentApp.instance as FFIFilamentApp).engine;
var indirectLight = await withPointerCallback<TIndirectLight>((cb) {
Engine_buildIndirectLightFromIrradianceTextureRenderThread(
engine,
(reflectionsTexture as FFITexture?)?.pointer ?? nullptr,
(irradianceTexture as FFITexture).pointer,
intensity,
cb);
});
if (indirectLight == nullptr) {
throw Exception("Failed to create indirect light");
}
return FFIIndirectLight._(
engine, indirectLight, irradianceTexture, reflectionsTexture);
}
static Future<FFIIndirectLight> fromIrradianceHarmonics(
Float32List irradianceHarmonics,
{Texture? reflectionsTexture,
double intensity = 30000}) async {
final engine = (FilamentApp.instance as FFIFilamentApp).engine;
var indirectLight = await withPointerCallback<TIndirectLight>((cb) {
Engine_buildIndirectLightFromIrradianceHarmonicsRenderThread(
engine,
(reflectionsTexture as FFITexture?)?.pointer ?? nullptr,
irradianceHarmonics.address,
intensity,
cb);
});
if (indirectLight == nullptr) {
throw Exception("Failed to create indirect light");
}
return FFIIndirectLight._(engine, indirectLight, null, reflectionsTexture);
}
Future rotate(Matrix3 rotation) async {
late Pointer stackPtr;
if (FILAMENT_WASM) {
//stackPtr = stackSave();
}
IndirectLight_setRotation(this.pointer, rotation.storage.address);
if (FILAMENT_WASM) {
//stackRestore(stackPtr);
rotation.storage.free();
}
}
Future destroy() async {
await withVoidCallback(
(requestId, cb) => Engine_destroyIndirectLightRenderThread(
engine,
pointer,
requestId,
cb,
),
);
if (_irradianceTexture != null) {
await withVoidCallback((requestId, cb) =>
Engine_destroyTextureRenderThread(engine,
(_irradianceTexture as FFITexture).pointer, requestId, cb));
}
if (_reflectionsTexture != null) {
await withVoidCallback((requestId, cb) =>
Engine_destroyTextureRenderThread(engine,
(_reflectionsTexture as FFITexture).pointer, requestId, cb));
}
}
}

View File

@@ -0,0 +1,58 @@
import 'package:thermion_dart/src/filament/src/implementation/ffi_filament_app.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_texture.dart';
import 'package:thermion_dart/src/filament/src/interface/ktx1_bundle.dart';
import 'package:thermion_dart/thermion_dart.dart';
class FFIKtx1Bundle extends Ktx1Bundle {
final Pointer<TKtx1Bundle> pointer;
FFIKtx1Bundle(this.pointer);
///
///
///
bool isCubemap() {
return Ktx1Bundle_isCubemap(pointer);
}
///
///
///
Future destroy() async {
Ktx1Bundle_destroy(pointer);
}
///
///
///
Float32List getSphericalHarmonics() {
var harmonics = Float32List(27);
Ktx1Bundle_getSphericalHarmonics(pointer, harmonics.address);
return harmonics;
}
///
///
///
static Future<Ktx1Bundle> create(Uint8List data) async {
var bundle = Ktx1Bundle_create(data.address, data.length);
if (bundle == nullptr) {
throw Exception("Failed to decode KTX texture");
}
return FFIKtx1Bundle(bundle);
}
Future<Texture> createTexture() async {
final texturePtr = await withPointerCallback<TTexture>((cb) {
Ktx1Reader_createTextureRenderThread(
(FilamentApp.instance as FFIFilamentApp).engine,
pointer,
0,
nullptr,
cb);
});
return FFITexture(FilamentApp.instance!.engine, texturePtr);
}
}

View File

@@ -1,4 +1,5 @@
import 'package:thermion_dart/src/filament/src/implementation/ffi_asset.dart'; import 'package:thermion_dart/src/filament/src/implementation/ffi_asset.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_indirect_light.dart';
import 'package:thermion_dart/src/filament/src/interface/scene.dart'; import 'package:thermion_dart/src/filament/src/interface/scene.dart';
import 'package:thermion_dart/thermion_dart.dart'; import 'package:thermion_dart/thermion_dart.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
@@ -124,4 +125,27 @@ class FFIScene extends Scene {
.info("Added stencil highlight for asset (entity ${asset.entity})"); .info("Added stencil highlight for asset (entity ${asset.entity})");
} }
} }
IndirectLight? _indirectLight;
///
///
///
Future setIndirectLight(IndirectLight? indirectLight) async {
if (indirectLight == null) {
Scene_setIndirectLight(scene, nullptr);
_indirectLight = null;
} else {
Scene_setIndirectLight(
scene, (indirectLight as FFIIndirectLight).pointer);
_indirectLight = indirectLight;
}
}
///
///
///
Future<IndirectLight?> getIndirectLight() async {
return _indirectLight;
}
} }

View File

@@ -1,6 +1,3 @@
import 'dart:typed_data';
import 'package:thermion_dart/src/bindings/bindings.dart';
import 'package:thermion_dart/thermion_dart.dart'; import 'package:thermion_dart/thermion_dart.dart';
class FFITexture extends Texture { class FFITexture extends Texture {

View File

@@ -0,0 +1,24 @@
import 'package:thermion_dart/thermion_dart.dart';
abstract class Ktx1Bundle {
///
///
///
bool isCubemap();
///
///
///
Future<Texture> createTexture();
///
///
///
Float32List getSphericalHarmonics();
///
///
///
Future destroy();
}

View File

@@ -1,7 +1,19 @@
import 'package:vector_math/vector_math_64.dart';
enum LightType { enum LightType {
SUN, //!< Directional light that also draws a sun's disk in the sky. SUN, //!< Directional light that also draws a sun's disk in the sky.
DIRECTIONAL, //!< Directional light, emits light in a given direction. DIRECTIONAL, //!< Directional light, emits light in a given direction.
POINT, //!< Point light, emits light from a position, in all directions. POINT, //!< Point light, emits light from a position, in all directions.
FOCUSED_SPOT, //!< Physically correct spot light. FOCUSED_SPOT, //!< Physically correct spot light.
SPOT, SPOT,
} }
abstract class IndirectLight {
Future destroy() {
throw UnimplementedError();
}
Future rotate(Matrix3 rotation) {
throw UnimplementedError();
}
}

View File

@@ -21,18 +21,32 @@ abstract class Scene {
/// ///
Future removeEntity(ThermionEntity entity); Future removeEntity(ThermionEntity entity);
/// Renders an outline around [entity] with the given color. /// Renders an outline around [entity] with the given color.
///
/// ///
Future setStencilHighlight(ThermionAsset asset,{double r = 1.0, ///
Future setStencilHighlight(ThermionAsset asset,
{double r = 1.0,
double g = 0.0, double g = 0.0,
double b = 0.0, double b = 0.0,
int? entity, int? entity,
int primitiveIndex = 0}); int primitiveIndex = 0});
/// Removes the outline around [entity]. Noop if there was no highlight. /// Removes the outline around [entity]. Noop if there was no highlight.
/// ///
/// ///
Future removeStencilHighlight(ThermionAsset asset); Future removeStencilHighlight(ThermionAsset asset);
///
///
///
Future setIndirectLight(IndirectLight? indirectLight) {
throw UnimplementedError();
}
///
///
///
Future<IndirectLight?> getIndirectLight() {
throw UnimplementedError();
}
} }

View File

@@ -1,6 +1,10 @@
import 'dart:async'; import 'dart:async';
import 'package:thermion_dart/src/filament/src/implementation/background_image.dart'; import 'package:thermion_dart/src/filament/src/implementation/background_image.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_indirect_light.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_ktx1_bundle.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_swapchain.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_texture.dart'; import 'package:thermion_dart/src/filament/src/implementation/ffi_texture.dart';
import 'package:thermion_dart/src/filament/src/interface/ktx1_bundle.dart';
import '../../../../filament/src/implementation/ffi_asset.dart'; import '../../../../filament/src/implementation/ffi_asset.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_filament_app.dart'; import 'package:thermion_dart/src/filament/src/implementation/ffi_filament_app.dart';
import '../../../../filament/src/implementation/ffi_scene.dart'; import '../../../../filament/src/implementation/ffi_scene.dart';
@@ -204,7 +208,14 @@ class ThermionViewerFFI extends ThermionViewer {
Future setBackgroundImage(String path, {bool fillHeight = false}) async { Future setBackgroundImage(String path, {bool fillHeight = false}) async {
final imageData = await FilamentApp.instance!.loadResource(path); final imageData = await FilamentApp.instance!.loadResource(path);
_backgroundImage ??= await BackgroundImage.create(this, scene); _backgroundImage ??= await BackgroundImage.create(this, scene);
await _backgroundImage!.setImage(imageData); bool isKtx = path.endsWith(".ktx");
if (isKtx) {
final bundle = await FFIKtx1Bundle.create(imageData);
await _backgroundImage!.setImageFromKtxBundle(bundle);
} else {
await _backgroundImage!.setImage(imageData);
}
return (_backgroundImage!.width!, _backgroundImage!.height!);
} }
/// ///
@@ -250,11 +261,9 @@ class ThermionViewerFFI extends ThermionViewer {
_skyboxTextureUploadComplete = _skyboxTextureUploadComplete =
withVoidCallback((requestId, onTextureUploadComplete) async { withVoidCallback((requestId, onTextureUploadComplete) async {
var skyboxTexturePointer = await withPointerCallback<TTexture>((cb) { var bundle = await FFIKtx1Bundle.create(data);
Texture_decodeKtxRenderThread(app.engine, data.address, data.length,
nullptr, requestId, onTextureUploadComplete, cb); _skyboxTexture = await bundle.createTexture() as FFITexture;
});
_skyboxTexture = FFITexture(app.engine, skyboxTexturePointer);
_skybox = await withPointerCallback<TSkybox>((cb) { _skybox = await withPointerCallback<TSkybox>((cb) {
Engine_buildSkyboxRenderThread(app.engine, _skyboxTexture!.pointer, cb); Engine_buildSkyboxRenderThread(app.engine, _skyboxTexture!.pointer, cb);
@@ -267,8 +276,6 @@ class ThermionViewerFFI extends ThermionViewer {
await completer.future; await completer.future;
} }
Pointer<TTexture>? _iblTexture;
Pointer<TIndirectLight>? _indirectLight;
Future? _iblTextureUploadComplete; Future? _iblTextureUploadComplete;
/// ///
@@ -288,23 +295,22 @@ class ThermionViewerFFI extends ThermionViewer {
var data = await FilamentApp.instance!.loadResource(lightingPath); var data = await FilamentApp.instance!.loadResource(lightingPath);
var harmonics = Float32List(27); final bundle = await FFIKtx1Bundle.create(data);
_iblTexture = await withPointerCallback<TTexture>((cb) { final texture = await bundle.createTexture();
Texture_decodeKtxRenderThread(app.engine, data.address, data.length, final harmonics = bundle.getSphericalHarmonics();
harmonics.address, requestId, onTextureUploadComplete, cb);
}); final ibl = await FFIIndirectLight.fromIrradianceHarmonics(harmonics,
reflectionsTexture: texture, intensity: intensity);
await scene.setIndirectLight(ibl);
_indirectLight = await withPointerCallback<TIndirectLight>((cb) {
Engine_buildIndirectLightRenderThread(
app.engine, _iblTexture!, intensity, harmonics.address, cb);
});
if (FILAMENT_WASM) { if (FILAMENT_WASM) {
//stackRestore(stackPtr); //stackRestore(stackPtr);
data.free(); data.free();
} }
data.free(); data.free();
Scene_setIndirectLight(scene.scene, _indirectLight!);
completer.complete(); completer.complete();
}).then((_) { }).then((_) {
_iblTextureUploadComplete = null; _iblTextureUploadComplete = null;
@@ -317,20 +323,9 @@ class ThermionViewerFFI extends ThermionViewer {
/// ///
@override @override
Future rotateIbl(Matrix3 rotationMatrix) async { Future rotateIbl(Matrix3 rotationMatrix) async {
if (_indirectLight == null) { var ibl = await scene.getIndirectLight();
throw Exception("No IBL loaded"); if (ibl != null) {
} await ibl.rotate(rotationMatrix);
late Pointer stackPtr;
if (FILAMENT_WASM) {
//stackPtr = stackSave();
}
IndirectLight_setRotation(_indirectLight!, rotationMatrix.storage.address);
if (FILAMENT_WASM) {
//stackRestore(stackPtr);
rotationMatrix.storage.free();
} }
} }
@@ -366,24 +361,16 @@ class ThermionViewerFFI extends ThermionViewer {
/// ///
/// ///
@override @override
Future removeIbl() async { Future removeIbl({bool destroy = true}) async {
var ibl = await scene.getIndirectLight();
await scene.setIndirectLight(null);
if (ibl != null && destroy) {
await ibl.destroy();
}
if (_iblTextureUploadComplete != null) { if (_iblTextureUploadComplete != null) {
await _iblTextureUploadComplete!; await _iblTextureUploadComplete!;
_iblTextureUploadComplete = null; _iblTextureUploadComplete = null;
} }
if (_indirectLight != null) {
Scene_setIndirectLight(scene.scene, nullptr);
await withVoidCallback(
(requestId, cb) => Engine_destroyIndirectLightRenderThread(
app.engine,
_indirectLight!,
requestId,
cb,
),
);
_indirectLight = null;
_iblTexture = null;
}
} }
final _lights = <ThermionEntity>{}; final _lights = <ThermionEntity>{};

View File

@@ -89,7 +89,7 @@ abstract class ThermionViewer {
Future loadSkybox(String skyboxPath); Future loadSkybox(String skyboxPath);
/// ///
/// Removes the skybox from the scene. /// Removes the skybox from the scene and destroys all associated resources.
/// ///
Future removeSkybox(); Future removeSkybox();
@@ -106,8 +106,10 @@ abstract class ThermionViewer {
/// ///
/// Removes the image-based light from the scene. /// Removes the image-based light from the scene.
/// If [destroy] is true, the indirect light and all associated resources
/// (irradiance/reflection textures) will be destroyed.
/// ///
Future removeIbl(); Future removeIbl({bool destroy = true});
/// ///
/// Adds a direct light to the scene. /// Adds a direct light to the scene.
@@ -123,7 +125,7 @@ abstract class ThermionViewer {
Future removeLight(ThermionEntity light); Future removeLight(ThermionEntity light);
/// ///
/// Remove all lights (excluding IBL) from the scene. /// Remove all direct lights from the scene.
/// ///
Future destroyLights(); Future destroyLights();

View File

@@ -46,6 +46,7 @@ extern "C"
typedef struct TGltfResourceLoader TGltfResourceLoader; typedef struct TGltfResourceLoader TGltfResourceLoader;
typedef struct TFilamentAsset TFilamentAsset; typedef struct TFilamentAsset TFilamentAsset;
typedef struct TColorGrading TColorGrading; typedef struct TColorGrading TColorGrading;
typedef struct TKtx1Bundle TKtx1Bundle;
typedef struct { typedef struct {
double x; double x;

View File

@@ -260,11 +260,24 @@ EMSCRIPTEN_KEEPALIVE uint32_t Texture_getHeight(TTexture *tTexture, uint32_t lev
EMSCRIPTEN_KEEPALIVE uint32_t Texture_getDepth(TTexture *tTexture, uint32_t level); EMSCRIPTEN_KEEPALIVE uint32_t Texture_getDepth(TTexture *tTexture, uint32_t level);
EMSCRIPTEN_KEEPALIVE TTextureUsage Texture_getUsage(TTexture *tTexture, uint32_t level); EMSCRIPTEN_KEEPALIVE TTextureUsage Texture_getUsage(TTexture *tTexture, uint32_t level);
EMSCRIPTEN_KEEPALIVE void Texture_generateMipMaps(TTexture *tTexture, TEngine *tEngine); EMSCRIPTEN_KEEPALIVE void Texture_generateMipMaps(TTexture *tTexture, TEngine *tEngine);
EMSCRIPTEN_KEEPALIVE TTexture* Texture_decodeKtx( EMSCRIPTEN_KEEPALIVE TKtx1Bundle* Ktx1Bundle_create(
TEngine *tEngine,
uint8_t *ktxData, uint8_t *ktxData,
size_t length, size_t length
float *sphericalHarmonics, );
EMSCRIPTEN_KEEPALIVE void Ktx1Bundle_getSphericalHarmonics(
TKtx1Bundle *tBundle,
float *harmonics
);
EMSCRIPTEN_KEEPALIVE bool Ktx1Bundle_isCubemap(
TKtx1Bundle *tBundle
);
EMSCRIPTEN_KEEPALIVE void Ktx1Bundle_destroy(
TKtx1Bundle *tBundle
);
EMSCRIPTEN_KEEPALIVE TTexture* Ktx1Reader_createTexture(
TEngine *tEngine,
TKtx1Bundle *tBundle,
uint32_t requestId, uint32_t requestId,
VoidCallback onTextureUploadComplete VoidCallback onTextureUploadComplete
); );

View File

@@ -60,7 +60,7 @@ namespace thermion
void (*onComplete)(TTexture*) void (*onComplete)(TTexture*)
); );
void Texture_generateMipMapsRenderThread(TTexture *tTexture, TEngine *tEngine, uint32_t requestId, VoidCallback onComplete); void Texture_generateMipMapsRenderThread(TTexture *tTexture, TEngine *tEngine, uint32_t requestId, VoidCallback onComplete);
void Texture_decodeKtxRenderThread(TEngine *tEngine, uint8_t *ktxData, size_t length, float *sphericalHarmonics, uint32_t requestId, VoidCallback onTextureUploadComplete, void (*onComplete)(TTexture *)); void Ktx1Reader_createTextureRenderThread(TEngine *tEngine, TKtx1Bundle *tBundle, uint32_t requestId, VoidCallback onTextureUploadComplete, void (*onComplete)(TTexture *));
void Engine_destroyTextureRenderThread(TEngine *engine, TTexture* tTexture, uint32_t requestId, VoidCallback onComplete); void Engine_destroyTextureRenderThread(TEngine *engine, TTexture* tTexture, uint32_t requestId, VoidCallback onComplete);
void Engine_createFenceRenderThread(TEngine *tEngine, void (*onComplete)(TFence*)); void Engine_createFenceRenderThread(TEngine *tEngine, void (*onComplete)(TFence*));

View File

@@ -94,33 +94,49 @@ namespace thermion
return reinterpret_cast<TLinearImage *>(linearImage); return reinterpret_cast<TLinearImage *>(linearImage);
} }
EMSCRIPTEN_KEEPALIVE TTexture *Texture_decodeKtx( EMSCRIPTEN_KEEPALIVE TKtx1Bundle *Ktx1Bundle_create(uint8_t *ktxData, size_t length)
TEngine *tEngine,
uint8_t *ktxData,
size_t length,
float *sphericalHarmonics,
uint32_t requestId,
VoidCallback onTextureUploadComplete)
{ {
auto *bundle = new image::Ktx1Bundle(ktxData, static_cast<uint32_t>(length));
return reinterpret_cast<TKtx1Bundle *>(bundle);
}
auto engine = reinterpret_cast<filament::Engine *>(tEngine); EMSCRIPTEN_KEEPALIVE bool Ktx1Bundle_isCubemap(TKtx1Bundle *tBundle)
{
auto *bundle = reinterpret_cast<image::Ktx1Bundle *>(tBundle);
return bundle->isCubemap();
}
auto copy = new std::vector<uint8_t>(ktxData, ktxData + length); EMSCRIPTEN_KEEPALIVE void Ktx1Bundle_destroy(
image::Ktx1Bundle *bundle = TKtx1Bundle *tBundle)
new image::Ktx1Bundle(static_cast<const uint8_t *>(copy->data()), {
static_cast<uint32_t>(length)); auto *bundle = reinterpret_cast<image::Ktx1Bundle *>(tBundle);
delete bundle;
}
EMSCRIPTEN_KEEPALIVE void Ktx1Bundle_getSphericalHarmonics(TKtx1Bundle *tBundle, float *sphericalHarmonics)
{
auto *bundle = reinterpret_cast<image::Ktx1Bundle *>(tBundle);
filament::math::float3 harmonics[9]; filament::math::float3 harmonics[9];
if (sphericalHarmonics) if (sphericalHarmonics)
{ {
bundle->getSphericalHarmonics(harmonics); bundle->getSphericalHarmonics(harmonics);
memcpy(sphericalHarmonics, harmonics, 27 * sizeof(float)); memcpy(sphericalHarmonics, harmonics, 27 * sizeof(float));
} }
}
EMSCRIPTEN_KEEPALIVE TTexture *Ktx1Reader_createTexture(
TEngine *tEngine,
TKtx1Bundle *tBundle,
uint32_t requestId,
VoidCallback onTextureUploadComplete)
{
auto engine = reinterpret_cast<filament::Engine *>(tEngine);
auto *bundle = reinterpret_cast<image::Ktx1Bundle *>(tBundle);
std::vector<void *> *callbackData = new std::vector<void *>{ std::vector<void *> *callbackData = new std::vector<void *>{
reinterpret_cast<void *>(onTextureUploadComplete), reinterpret_cast<void *>(onTextureUploadComplete),
reinterpret_cast<void *>(bundle),
reinterpret_cast<void *>(copy),
reinterpret_cast<void *>(requestId)}; reinterpret_cast<void *>(requestId)};
auto *texture = auto *texture =
@@ -130,9 +146,7 @@ namespace thermion
std::vector<void*>* vec = (std::vector<void*>*)userdata; std::vector<void*>* vec = (std::vector<void*>*)userdata;
void *callbackPtr = vec->at(0); void *callbackPtr = vec->at(0);
image::Ktx1Bundle *bundle = reinterpret_cast<image::Ktx1Bundle *>(vec->at(1)); uintptr_t requestId = (uintptr_t)vec->at(2);
std::vector<uint8_t> *copy = reinterpret_cast<std::vector<uint8_t>*>(vec->at(2));
uintptr_t requestId = (uintptr_t)vec->at(3);
delete vec; delete vec;
@@ -140,9 +154,7 @@ namespace thermion
{ {
auto callback = ((VoidCallback)callbackPtr); auto callback = ((VoidCallback)callbackPtr);
callback(requestId); callback(requestId);
} } },
delete bundle;
delete copy; },
(void *)callbackData); (void *)callbackData);
return reinterpret_cast<TTexture *>(texture); return reinterpret_cast<TTexture *>(texture);
} }

View File

@@ -864,7 +864,9 @@ extern "C"
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
static std::unordered_map<uint32_t, std::function<void(int32_t)>> _emscriptenWrappers; static std::unordered_map<uint32_t, std::function<void(int32_t)>> _emscriptenWrappers;
static void Emscripten_voidCallback(int32_t requestId) { EMSCRIPTEN_KEEPALIVE static void Emscripten_voidCallback(int32_t requestId) {
Log("Emscripten_voidCallback: requestId %d", requestId);
auto it = _emscriptenWrappers.find(requestId); auto it = _emscriptenWrappers.find(requestId);
if (it != _emscriptenWrappers.end()) { if (it != _emscriptenWrappers.end()) {
it->second(requestId); it->second(requestId);
@@ -876,19 +878,23 @@ extern "C"
#endif #endif
EMSCRIPTEN_KEEPALIVE void Texture_decodeKtxRenderThread( EMSCRIPTEN_KEEPALIVE void Ktx1Reader_createTextureRenderThread(
TEngine *tEngine, uint8_t *ktxData, size_t length, float *sphericalHarmonics, uint32_t requestId, VoidCallback onTextureUploadComplete, void (*onComplete)(TTexture *)) { TEngine *tEngine, TKtx1Bundle *tBundle, uint32_t requestId, VoidCallback onTextureUploadComplete, void (*onComplete)(TTexture *)) {
#ifdef EMSCRIPTEN
if(onTextureUploadComplete) {
_emscriptenWrappers[requestId] = [=](int32_t requestId) {
PROXY(onTextureUploadComplete(requestId));
};
}
#endif
std::packaged_task<void()> lambda( std::packaged_task<void()> lambda(
[=]() mutable [=]() mutable
{ {
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
std::function<void(int32_t)> wrapper = [=](int32_t requestId) { auto *texture = Ktx1Reader_createTexture(tEngine, tBundle, requestId, onTextureUploadComplete ? Emscripten_voidCallback : nullptr);
PROXY(onTextureUploadComplete(requestId));
};
_emscriptenWrappers[requestId] = wrapper;
auto *texture = Texture_decodeKtx(tEngine, ktxData, length, sphericalHarmonics, requestId, Emscripten_voidCallback);
#else #else
auto *texture = Texture_decodeKtx(tEngine, ktxData, length, sphericalHarmonics, requestId, onTextureUploadComplete); auto *texture = Ktx1Reader_createTexture(tEngine, tBundle, requestId, onTextureUploadComplete);
#endif #endif
PROXY(onComplete(texture)); PROXY(onComplete(texture));
}); });

View File

@@ -0,0 +1,33 @@
import 'dart:io';
import 'package:test/test.dart';
import 'package:thermion_dart/src/filament/src/interface/filament_app.dart';
import 'helpers.dart';
void main() async {
final testHelper = TestHelper("images");
await testHelper.setup();
test('decode KTX', () async {
await testHelper.withViewer((viewer) async {
await FilamentApp.instance!.decodeKtx(
File("${testHelper.testDir}/assets/default_env_skybox.ktx").readAsBytesSync());
});
});
test('set background color', () async {
await testHelper.withViewer((viewer) async {
await viewer.setBackgroundColor(0, 1, 0, 1);
await testHelper.capture(viewer.view, "background_green");
await viewer.setBackgroundColor(1, 0, 0, 1);
await testHelper.capture(viewer.view, "background_red");
});
});
test('set background image', () async {
await testHelper.withViewer((viewer) async {
await viewer.setBackgroundImage(
"file://${testHelper.testDir}/assets/cube_texture_512x512.png");
await testHelper.capture(viewer.view, "background_image");
});
});
}