diff --git a/thermion_dart/lib/src/filament/src/implementation/background_image.dart b/thermion_dart/lib/src/filament/src/implementation/background_image.dart index d647e627..01c01da6 100644 --- a/thermion_dart/lib/src/filament/src/implementation/background_image.dart +++ b/thermion_dart/lib/src/filament/src/implementation/background_image.dart @@ -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:animation_tools_dart/src/bone_animation_data.dart'; import 'package:animation_tools_dart/src/morph_animation_data.dart'; @@ -19,6 +20,9 @@ class BackgroundImage extends ThermionAsset { final FFIScene scene; + int? width; + int? height; + BackgroundImage._( this.asset, this.scene, this.texture, this.sampler, this.mi); @@ -45,9 +49,8 @@ class BackgroundImage extends ThermionAsset { await viewer.createGeometry(GeometryHelper.fullscreenQuad()); await imageMaterialInstance.setParameterInt("showImage", 0); var transform = Matrix4.identity(); - - await imageMaterialInstance.setParameterMat4( - "transform", transform); + + await imageMaterialInstance.setParameterMat4("transform", transform); await backgroundImage.setMaterialInstanceAt(imageMaterialInstance); await scene.add(backgroundImage as FFIAsset); @@ -62,28 +65,74 @@ class BackgroundImage extends ThermionAsset { await mi.setParameterFloat4("backgroundColor", r, g, b, a); } + /// + /// + /// Future hideImage() async { 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 { 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(), - textureFormat: TextureFormat.RGBA32F); - await texture! - .setLinearImage(image, PixelDataFormat.RGBA, PixelDataType.FLOAT); + textureFormat: textureFormat); + await texture.setLinearImage(image, pixelFormat, PixelDataType.FLOAT); + await setImageFromTexture(texture); + } + + /// + /// + /// + Future setImageFromTexture(Texture texture) async { + this.texture = texture; sampler ??= await FilamentApp.instance!.createTextureSampler() as FFITextureSampler; - + await mi.setParameterInt("isCubeMap", 0); await mi.setParameterTexture( "image", texture as FFITexture, sampler as FFITextureSampler); await setBackgroundColor(1, 1, 1, 0); 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) { // TODO: implement setCastShadows throw UnimplementedError(); - } + } @override Future setReceiveShadows(bool castShadows) { @@ -346,6 +395,4 @@ class BackgroundImage extends ThermionAsset { Future getBoundingBox() { throw UnimplementedError(); } - - } diff --git a/thermion_dart/lib/src/filament/src/implementation/ffi_indirect_light.dart b/thermion_dart/lib/src/filament/src/implementation/ffi_indirect_light.dart new file mode 100644 index 00000000..9ecc2143 --- /dev/null +++ b/thermion_dart/lib/src/filament/src/implementation/ffi_indirect_light.dart @@ -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 engine; + final Pointer pointer; + final Texture? _irradianceTexture; + final Texture? _reflectionsTexture; + + FFIIndirectLight._(this.engine, this.pointer, this._irradianceTexture, + this._reflectionsTexture); + + static Future fromIrradianceTexture( + Texture irradianceTexture, + {Texture? reflectionsTexture, + double intensity = 30000}) async { + final engine = (FilamentApp.instance as FFIFilamentApp).engine; + var indirectLight = await withPointerCallback((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 fromIrradianceHarmonics( + Float32List irradianceHarmonics, + {Texture? reflectionsTexture, + double intensity = 30000}) async { + final engine = (FilamentApp.instance as FFIFilamentApp).engine; + + var indirectLight = await withPointerCallback((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)); + } + } +} diff --git a/thermion_dart/lib/src/filament/src/implementation/ffi_ktx1_bundle.dart b/thermion_dart/lib/src/filament/src/implementation/ffi_ktx1_bundle.dart new file mode 100644 index 00000000..ec770830 --- /dev/null +++ b/thermion_dart/lib/src/filament/src/implementation/ffi_ktx1_bundle.dart @@ -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 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 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 createTexture() async { + final texturePtr = await withPointerCallback((cb) { + Ktx1Reader_createTextureRenderThread( + (FilamentApp.instance as FFIFilamentApp).engine, + pointer, + 0, + nullptr, + cb); + }); + return FFITexture(FilamentApp.instance!.engine, texturePtr); + } +} diff --git a/thermion_dart/lib/src/filament/src/implementation/ffi_scene.dart b/thermion_dart/lib/src/filament/src/implementation/ffi_scene.dart index e9acd8aa..1a78024a 100644 --- a/thermion_dart/lib/src/filament/src/implementation/ffi_scene.dart +++ b/thermion_dart/lib/src/filament/src/implementation/ffi_scene.dart @@ -1,4 +1,5 @@ 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/thermion_dart.dart'; import 'package:logging/logging.dart'; @@ -124,4 +125,27 @@ class FFIScene extends Scene { .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 getIndirectLight() async { + return _indirectLight; + } } diff --git a/thermion_dart/lib/src/filament/src/implementation/ffi_texture.dart b/thermion_dart/lib/src/filament/src/implementation/ffi_texture.dart index 376f1ea7..3d22e405 100644 --- a/thermion_dart/lib/src/filament/src/implementation/ffi_texture.dart +++ b/thermion_dart/lib/src/filament/src/implementation/ffi_texture.dart @@ -1,6 +1,3 @@ -import 'dart:typed_data'; - -import 'package:thermion_dart/src/bindings/bindings.dart'; import 'package:thermion_dart/thermion_dart.dart'; class FFITexture extends Texture { diff --git a/thermion_dart/lib/src/filament/src/interface/ktx1_bundle.dart b/thermion_dart/lib/src/filament/src/interface/ktx1_bundle.dart new file mode 100644 index 00000000..14674ecc --- /dev/null +++ b/thermion_dart/lib/src/filament/src/interface/ktx1_bundle.dart @@ -0,0 +1,24 @@ +import 'package:thermion_dart/thermion_dart.dart'; + +abstract class Ktx1Bundle { + + /// + /// + /// + bool isCubemap(); + + /// + /// + /// + Future createTexture(); + + /// + /// + /// + Float32List getSphericalHarmonics(); + + /// + /// + /// + Future destroy(); +} diff --git a/thermion_dart/lib/src/filament/src/interface/light.dart b/thermion_dart/lib/src/filament/src/interface/light.dart index 9075911b..826d71dc 100644 --- a/thermion_dart/lib/src/filament/src/interface/light.dart +++ b/thermion_dart/lib/src/filament/src/interface/light.dart @@ -1,7 +1,19 @@ +import 'package:vector_math/vector_math_64.dart'; + enum LightType { SUN, //!< Directional light that also draws a sun's disk in the sky. DIRECTIONAL, //!< Directional light, emits light in a given direction. POINT, //!< Point light, emits light from a position, in all directions. FOCUSED_SPOT, //!< Physically correct spot light. SPOT, -} \ No newline at end of file +} + +abstract class IndirectLight { + Future destroy() { + throw UnimplementedError(); + } + + Future rotate(Matrix3 rotation) { + throw UnimplementedError(); + } +} diff --git a/thermion_dart/lib/src/filament/src/interface/scene.dart b/thermion_dart/lib/src/filament/src/interface/scene.dart index fc368532..04e3ed27 100644 --- a/thermion_dart/lib/src/filament/src/interface/scene.dart +++ b/thermion_dart/lib/src/filament/src/interface/scene.dart @@ -21,18 +21,32 @@ abstract class Scene { /// Future removeEntity(ThermionEntity entity); - /// 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 b = 0.0, int? entity, int primitiveIndex = 0}); - + /// Removes the outline around [entity]. Noop if there was no highlight. - /// - /// + /// + /// Future removeStencilHighlight(ThermionAsset asset); + + /// + /// + /// + Future setIndirectLight(IndirectLight? indirectLight) { + throw UnimplementedError(); + } + + /// + /// + /// + Future getIndirectLight() { + throw UnimplementedError(); + } } diff --git a/thermion_dart/lib/src/viewer/src/ffi/src/thermion_viewer_ffi.dart b/thermion_dart/lib/src/viewer/src/ffi/src/thermion_viewer_ffi.dart index bc05bec1..46fd95d5 100644 --- a/thermion_dart/lib/src/viewer/src/ffi/src/thermion_viewer_ffi.dart +++ b/thermion_dart/lib/src/viewer/src/ffi/src/thermion_viewer_ffi.dart @@ -1,6 +1,10 @@ import 'dart:async'; 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/interface/ktx1_bundle.dart'; import '../../../../filament/src/implementation/ffi_asset.dart'; import 'package:thermion_dart/src/filament/src/implementation/ffi_filament_app.dart'; import '../../../../filament/src/implementation/ffi_scene.dart'; @@ -204,7 +208,14 @@ class ThermionViewerFFI extends ThermionViewer { Future setBackgroundImage(String path, {bool fillHeight = false}) async { final imageData = await FilamentApp.instance!.loadResource(path); _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 = withVoidCallback((requestId, onTextureUploadComplete) async { - var skyboxTexturePointer = await withPointerCallback((cb) { - Texture_decodeKtxRenderThread(app.engine, data.address, data.length, - nullptr, requestId, onTextureUploadComplete, cb); - }); - _skyboxTexture = FFITexture(app.engine, skyboxTexturePointer); + var bundle = await FFIKtx1Bundle.create(data); + + _skyboxTexture = await bundle.createTexture() as FFITexture; _skybox = await withPointerCallback((cb) { Engine_buildSkyboxRenderThread(app.engine, _skyboxTexture!.pointer, cb); @@ -267,8 +276,6 @@ class ThermionViewerFFI extends ThermionViewer { await completer.future; } - Pointer? _iblTexture; - Pointer? _indirectLight; Future? _iblTextureUploadComplete; /// @@ -288,23 +295,22 @@ class ThermionViewerFFI extends ThermionViewer { var data = await FilamentApp.instance!.loadResource(lightingPath); - var harmonics = Float32List(27); + final bundle = await FFIKtx1Bundle.create(data); - _iblTexture = await withPointerCallback((cb) { - Texture_decodeKtxRenderThread(app.engine, data.address, data.length, - harmonics.address, requestId, onTextureUploadComplete, cb); - }); + final texture = await bundle.createTexture(); + final harmonics = bundle.getSphericalHarmonics(); + + final ibl = await FFIIndirectLight.fromIrradianceHarmonics(harmonics, + reflectionsTexture: texture, intensity: intensity); + + await scene.setIndirectLight(ibl); - _indirectLight = await withPointerCallback((cb) { - Engine_buildIndirectLightRenderThread( - app.engine, _iblTexture!, intensity, harmonics.address, cb); - }); if (FILAMENT_WASM) { //stackRestore(stackPtr); data.free(); } data.free(); - Scene_setIndirectLight(scene.scene, _indirectLight!); + completer.complete(); }).then((_) { _iblTextureUploadComplete = null; @@ -317,20 +323,9 @@ class ThermionViewerFFI extends ThermionViewer { /// @override Future rotateIbl(Matrix3 rotationMatrix) async { - if (_indirectLight == null) { - throw Exception("No IBL loaded"); - } - - late Pointer stackPtr; - if (FILAMENT_WASM) { - //stackPtr = stackSave(); - } - - IndirectLight_setRotation(_indirectLight!, rotationMatrix.storage.address); - - if (FILAMENT_WASM) { - //stackRestore(stackPtr); - rotationMatrix.storage.free(); + var ibl = await scene.getIndirectLight(); + if (ibl != null) { + await ibl.rotate(rotationMatrix); } } @@ -366,24 +361,16 @@ class ThermionViewerFFI extends ThermionViewer { /// /// @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) { await _iblTextureUploadComplete!; _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 = {}; diff --git a/thermion_dart/lib/src/viewer/src/thermion_viewer_base.dart b/thermion_dart/lib/src/viewer/src/thermion_viewer_base.dart index 186da226..f27fc849 100644 --- a/thermion_dart/lib/src/viewer/src/thermion_viewer_base.dart +++ b/thermion_dart/lib/src/viewer/src/thermion_viewer_base.dart @@ -89,7 +89,7 @@ abstract class ThermionViewer { Future loadSkybox(String skyboxPath); /// - /// Removes the skybox from the scene. + /// Removes the skybox from the scene and destroys all associated resources. /// Future removeSkybox(); @@ -106,8 +106,10 @@ abstract class ThermionViewer { /// /// 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. @@ -123,7 +125,7 @@ abstract class ThermionViewer { Future removeLight(ThermionEntity light); /// - /// Remove all lights (excluding IBL) from the scene. + /// Remove all direct lights from the scene. /// Future destroyLights(); diff --git a/thermion_dart/native/include/c_api/APIBoundaryTypes.h b/thermion_dart/native/include/c_api/APIBoundaryTypes.h index 43e4174e..f3cd9909 100644 --- a/thermion_dart/native/include/c_api/APIBoundaryTypes.h +++ b/thermion_dart/native/include/c_api/APIBoundaryTypes.h @@ -46,6 +46,7 @@ extern "C" typedef struct TGltfResourceLoader TGltfResourceLoader; typedef struct TFilamentAsset TFilamentAsset; typedef struct TColorGrading TColorGrading; + typedef struct TKtx1Bundle TKtx1Bundle; typedef struct { double x; diff --git a/thermion_dart/native/include/c_api/TTexture.h b/thermion_dart/native/include/c_api/TTexture.h index 2e708fe2..b088dd9b 100644 --- a/thermion_dart/native/include/c_api/TTexture.h +++ b/thermion_dart/native/include/c_api/TTexture.h @@ -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 TTextureUsage Texture_getUsage(TTexture *tTexture, uint32_t level); EMSCRIPTEN_KEEPALIVE void Texture_generateMipMaps(TTexture *tTexture, TEngine *tEngine); -EMSCRIPTEN_KEEPALIVE TTexture* Texture_decodeKtx( - TEngine *tEngine, +EMSCRIPTEN_KEEPALIVE TKtx1Bundle* Ktx1Bundle_create( uint8_t *ktxData, - size_t length, - float *sphericalHarmonics, + size_t length +); +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, VoidCallback onTextureUploadComplete ); diff --git a/thermion_dart/native/include/c_api/ThermionDartRenderThreadApi.h b/thermion_dart/native/include/c_api/ThermionDartRenderThreadApi.h index fd246315..717185bd 100644 --- a/thermion_dart/native/include/c_api/ThermionDartRenderThreadApi.h +++ b/thermion_dart/native/include/c_api/ThermionDartRenderThreadApi.h @@ -60,7 +60,7 @@ namespace thermion void (*onComplete)(TTexture*) ); 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_createFenceRenderThread(TEngine *tEngine, void (*onComplete)(TFence*)); diff --git a/thermion_dart/native/src/c_api/TTexture.cpp b/thermion_dart/native/src/c_api/TTexture.cpp index e8a7e28f..fef51807 100644 --- a/thermion_dart/native/src/c_api/TTexture.cpp +++ b/thermion_dart/native/src/c_api/TTexture.cpp @@ -94,33 +94,49 @@ namespace thermion return reinterpret_cast(linearImage); } - EMSCRIPTEN_KEEPALIVE TTexture *Texture_decodeKtx( - TEngine *tEngine, - uint8_t *ktxData, - size_t length, - float *sphericalHarmonics, - uint32_t requestId, - VoidCallback onTextureUploadComplete) + EMSCRIPTEN_KEEPALIVE TKtx1Bundle *Ktx1Bundle_create(uint8_t *ktxData, size_t length) { + auto *bundle = new image::Ktx1Bundle(ktxData, static_cast(length)); + return reinterpret_cast(bundle); + } - auto engine = reinterpret_cast(tEngine); + EMSCRIPTEN_KEEPALIVE bool Ktx1Bundle_isCubemap(TKtx1Bundle *tBundle) + { + auto *bundle = reinterpret_cast(tBundle); + return bundle->isCubemap(); + } - auto copy = new std::vector(ktxData, ktxData + length); - image::Ktx1Bundle *bundle = - new image::Ktx1Bundle(static_cast(copy->data()), - static_cast(length)); + EMSCRIPTEN_KEEPALIVE void Ktx1Bundle_destroy( + TKtx1Bundle *tBundle) + { + auto *bundle = reinterpret_cast(tBundle); + delete bundle; + } + EMSCRIPTEN_KEEPALIVE void Ktx1Bundle_getSphericalHarmonics(TKtx1Bundle *tBundle, float *sphericalHarmonics) + { + auto *bundle = reinterpret_cast(tBundle); filament::math::float3 harmonics[9]; if (sphericalHarmonics) { bundle->getSphericalHarmonics(harmonics); memcpy(sphericalHarmonics, harmonics, 27 * sizeof(float)); } + } + + EMSCRIPTEN_KEEPALIVE TTexture *Ktx1Reader_createTexture( + TEngine *tEngine, + TKtx1Bundle *tBundle, + uint32_t requestId, + VoidCallback onTextureUploadComplete) + { + + auto engine = reinterpret_cast(tEngine); + + auto *bundle = reinterpret_cast(tBundle); std::vector *callbackData = new std::vector{ reinterpret_cast(onTextureUploadComplete), - reinterpret_cast(bundle), - reinterpret_cast(copy), reinterpret_cast(requestId)}; auto *texture = @@ -130,9 +146,7 @@ namespace thermion std::vector* vec = (std::vector*)userdata; void *callbackPtr = vec->at(0); - image::Ktx1Bundle *bundle = reinterpret_cast(vec->at(1)); - std::vector *copy = reinterpret_cast*>(vec->at(2)); - uintptr_t requestId = (uintptr_t)vec->at(3); + uintptr_t requestId = (uintptr_t)vec->at(2); delete vec; @@ -140,9 +154,7 @@ namespace thermion { auto callback = ((VoidCallback)callbackPtr); callback(requestId); - } - delete bundle; - delete copy; }, + } }, (void *)callbackData); return reinterpret_cast(texture); } diff --git a/thermion_dart/native/src/c_api/ThermionDartRenderThreadApi.cpp b/thermion_dart/native/src/c_api/ThermionDartRenderThreadApi.cpp index e577b4dc..c4128a39 100644 --- a/thermion_dart/native/src/c_api/ThermionDartRenderThreadApi.cpp +++ b/thermion_dart/native/src/c_api/ThermionDartRenderThreadApi.cpp @@ -864,7 +864,9 @@ extern "C" #ifdef EMSCRIPTEN static std::unordered_map> _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); if (it != _emscriptenWrappers.end()) { it->second(requestId); @@ -876,19 +878,23 @@ extern "C" #endif - EMSCRIPTEN_KEEPALIVE void Texture_decodeKtxRenderThread( - TEngine *tEngine, uint8_t *ktxData, size_t length, float *sphericalHarmonics, uint32_t requestId, VoidCallback onTextureUploadComplete, void (*onComplete)(TTexture *)) { + EMSCRIPTEN_KEEPALIVE void Ktx1Reader_createTextureRenderThread( + 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 lambda( [=]() mutable { #ifdef EMSCRIPTEN - std::function wrapper = [=](int32_t requestId) { - PROXY(onTextureUploadComplete(requestId)); - }; - _emscriptenWrappers[requestId] = wrapper; - auto *texture = Texture_decodeKtx(tEngine, ktxData, length, sphericalHarmonics, requestId, Emscripten_voidCallback); + auto *texture = Ktx1Reader_createTexture(tEngine, tBundle, requestId, onTextureUploadComplete ? Emscripten_voidCallback : nullptr); #else - auto *texture = Texture_decodeKtx(tEngine, ktxData, length, sphericalHarmonics, requestId, onTextureUploadComplete); + auto *texture = Ktx1Reader_createTexture(tEngine, tBundle, requestId, onTextureUploadComplete); #endif PROXY(onComplete(texture)); }); diff --git a/thermion_dart/test/image_tests.dart b/thermion_dart/test/image_tests.dart new file mode 100644 index 00000000..03355a54 --- /dev/null +++ b/thermion_dart/test/image_tests.dart @@ -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"); + }); + }); +}