Texture improvements: generateMipmaps(), add levels to createTexture, tests to check auto mip level selection

This commit is contained in:
Nick Fisher
2025-05-29 18:38:57 +08:00
parent 11f7ac459b
commit 94eacec27e
14 changed files with 517 additions and 855 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 B

View File

@@ -617,6 +617,7 @@ external ffi.Pointer<TTexture> Texture_build(
ffi.Pointer<TTexture>,
ffi.Pointer<TLinearImage>,
ffi.UnsignedInt,
ffi.UnsignedInt,
ffi.UnsignedInt)>(isLeaf: true)
external bool Texture_loadImage(
ffi.Pointer<TEngine> tEngine,
@@ -624,6 +625,7 @@ external bool Texture_loadImage(
ffi.Pointer<TLinearImage> tImage,
int bufferFormat,
int pixelDataType,
int level,
);
@ffi.Native<
@@ -684,6 +686,12 @@ external bool Texture_setImageWithDepth(
int pixelDataType,
);
@ffi.Native<ffi.Uint32 Function(ffi.Pointer<TTexture>)>(
isLeaf: true)
external int Texture_getLevels(
ffi.Pointer<TTexture> tTexture,
);
@ffi.Native<ffi.Uint32 Function(ffi.Pointer<TTexture>, ffi.Uint32)>(
isLeaf: true)
external int Texture_getWidth(
@@ -705,6 +713,13 @@ external int Texture_getDepth(
int level,
);
@ffi.Native<ffi.Void Function(ffi.Pointer<TTexture>, ffi.Pointer<TEngine>,)>(
isLeaf: true)
external void Texture_generateMipMaps(
ffi.Pointer<TTexture> tTexture,
ffi.Pointer<TEngine> tEngine,
);
@ffi.Native<ffi.UnsignedInt Function(ffi.Pointer<TTexture>, ffi.Uint32)>(
isLeaf: true)
external int Texture_getUsage(
@@ -2419,6 +2434,7 @@ external void Image_getChannelsRenderThread(
ffi.Pointer<TLinearImage>,
ffi.UnsignedInt,
ffi.UnsignedInt,
ffi.UnsignedInt,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Bool)>>)>(
isLeaf: true)
external void Texture_loadImageRenderThread(
@@ -2427,6 +2443,7 @@ external void Texture_loadImageRenderThread(
ffi.Pointer<TLinearImage> tImage,
int bufferFormat,
int pixelDataType,
int level,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Bool)>> onComplete,
);

View File

@@ -242,7 +242,7 @@ sealed class Struct extends NativeType {
Struct(this._address);
static create<T extends Struct>() {
static T create<T extends Struct>() {
switch (T) {
case double4x4:
final ptr = double4x4.stackAlloc();
@@ -255,6 +255,9 @@ sealed class Struct extends NativeType {
final arr4 =
Array<Float64>._((numElements: 4, addr: ptr.cast<Float64>() + 96));
return double4x4(arr1, arr2, arr3, arr4, ptr) as T;
case TFogOptions:
final ptr = TFogOptions.stackAlloc();
return ptr.toDart() as T;
}
throw Exception();
}
@@ -412,11 +415,11 @@ extension type NativeLibrary(JSObject _) implements JSObject {
external Pointer<TMaterial> _Material_createGizmoMaterial(
Pointer<TEngine> tEngine,
);
external bool _Material_hasParameter(
external int _Material_hasParameter(
Pointer<TMaterial> tMaterial,
Pointer<Char> propertyName,
);
external bool _MaterialInstance_isStencilWriteEnabled(
external int _MaterialInstance_isStencilWriteEnabled(
Pointer<TMaterialInstance> materialInstance,
);
external void _MaterialInstance_setStencilWrite(
@@ -692,14 +695,14 @@ extension type NativeLibrary(JSObject _) implements JSObject {
Pointer<TView> tView,
bool enabled,
);
external bool _View_isStencilBufferEnabled(
external int _View_isStencilBufferEnabled(
Pointer<TView> tView,
);
external void _View_setDitheringEnabled(
Pointer<TView> tView,
bool enabled,
);
external bool _View_isDitheringEnabled(
external int _View_isDitheringEnabled(
Pointer<TView> tView,
);
external void _View_setScene(
@@ -741,14 +744,15 @@ extension type NativeLibrary(JSObject _) implements JSObject {
int sampler,
int format,
);
external bool _Texture_loadImage(
external int _Texture_loadImage(
Pointer<TEngine> tEngine,
Pointer<TTexture> tTexture,
Pointer<TLinearImage> tImage,
int bufferFormat,
int pixelDataType,
int level
);
external bool _Texture_setImage(
external int _Texture_setImage(
Pointer<TEngine> tEngine,
Pointer<TTexture> tTexture,
int level,
@@ -760,7 +764,7 @@ extension type NativeLibrary(JSObject _) implements JSObject {
int bufferFormat,
int pixelDataType,
);
external bool _Texture_setImageWithDepth(
external int _Texture_setImageWithDepth(
Pointer<TEngine> tEngine,
Pointer<TTexture> tTexture,
int level,
@@ -776,6 +780,9 @@ extension type NativeLibrary(JSObject _) implements JSObject {
int bufferFormat,
int pixelDataType,
);
external int _Texture_getLevels(
Pointer<TTexture> tTexture,
);
external int _Texture_getWidth(
Pointer<TTexture> tTexture,
int level,
@@ -784,6 +791,10 @@ extension type NativeLibrary(JSObject _) implements JSObject {
Pointer<TTexture> tTexture,
int level,
);
external void _Texture_generateMipmaps(
Pointer<TTexture> tTexture,
Pointer<TEngine> tEngine
);
external int _Texture_getDepth(
Pointer<TTexture> tTexture,
int level,
@@ -1075,7 +1086,7 @@ extension type NativeLibrary(JSObject _) implements JSObject {
EntityId entityId,
Pointer<double4x4> transformPtr,
);
external bool _TransformManager_transformToUnitCube(
external int _TransformManager_transformToUnitCube(
Pointer<TTransformManager> tTransformManager,
EntityId entityId,
Pointer<Aabb3> boundingBoxPtr,
@@ -1108,7 +1119,7 @@ extension type NativeLibrary(JSObject _) implements JSObject {
bool clear,
bool discard,
);
external bool _Renderer_beginFrame(
external int _Renderer_beginFrame(
Pointer<TRenderer> tRenderer,
Pointer<TSwapChain> tSwapChain,
JSBigInt frameTimeInNanos,
@@ -1690,6 +1701,7 @@ extension type NativeLibrary(JSObject _) implements JSObject {
Pointer<TLinearImage> tImage,
int bufferFormat,
int pixelDataType,
int level,
Pointer<self.NativeFunction<void Function(bool)>> onComplete,
);
external void _Texture_setImageRenderThread(
@@ -1899,7 +1911,7 @@ extension type NativeLibrary(JSObject _) implements JSObject {
Pointer<TEngine> tEngine,
Pointer<TGltfResourceLoader> tGltfResourceLoader,
);
external bool _GltfResourceLoader_asyncBeginLoad(
external int _GltfResourceLoader_asyncBeginLoad(
Pointer<TGltfResourceLoader> tGltfResourceLoader,
Pointer<TFilamentAsset> tFilamentAsset,
);
@@ -1915,11 +1927,11 @@ extension type NativeLibrary(JSObject _) implements JSObject {
Pointer<Uint8> data,
size_t length,
);
external bool _GltfResourceLoader_loadResources(
external int _GltfResourceLoader_loadResources(
Pointer<TGltfResourceLoader> tGltfResourceLoader,
Pointer<TFilamentAsset> tFilamentAsset,
);
external bool _RenderableManager_setMaterialInstanceAt(
external int _RenderableManager_setMaterialInstanceAt(
Pointer<TRenderableManager> tRenderableManager,
EntityId entityId,
int primitiveIndex,
@@ -1934,23 +1946,23 @@ extension type NativeLibrary(JSObject _) implements JSObject {
Pointer<TRenderableManager> tRenderableManager,
EntityId entityId,
);
external bool _RenderableManager_isRenderable(
external int _RenderableManager_isRenderable(
Pointer<TRenderableManager> tRenderableManager,
EntityId entityId,
);
external bool _RenderableManager_hasComponent(
external int _RenderableManager_hasComponent(
Pointer<TRenderableManager> tRenderableManager,
EntityId entityId,
);
external bool _RenderableManager_empty(
external int _RenderableManager_empty(
Pointer<TRenderableManager> tRenderableManager,
);
external bool _RenderableManager_getLightChannel(
external int _RenderableManager_getLightChannel(
Pointer<TRenderableManager> tRenderableManager,
EntityId entityId,
int channel,
);
external bool _RenderableManager_isShadowCaster(
external int _RenderableManager_isShadowCaster(
Pointer<TRenderableManager> tRenderableManager,
EntityId entityId,
);
@@ -1964,11 +1976,11 @@ extension type NativeLibrary(JSObject _) implements JSObject {
EntityId entityId,
bool receiveShadows,
);
external bool _RenderableManager_isShadowReceiver(
external int _RenderableManager_isShadowReceiver(
Pointer<TRenderableManager> tRenderableManager,
EntityId entityId,
);
external bool _RenderableManager_getFogEnabled(
external int _RenderableManager_getFogEnabled(
Pointer<TRenderableManager> tRenderableManager,
EntityId entityId,
);
@@ -2071,11 +2083,11 @@ extension type NativeLibrary(JSObject _) implements JSObject {
Pointer<TAnimationManager> tAnimationManager,
JSBigInt frameTimeInNanos,
);
external bool _AnimationManager_addGltfAnimationComponent(
external int _AnimationManager_addGltfAnimationComponent(
Pointer<TAnimationManager> tAnimationManager,
Pointer<TSceneAsset> tSceneAsset,
);
external bool _AnimationManager_removeGltfAnimationComponent(
external int _AnimationManager_removeGltfAnimationComponent(
Pointer<TAnimationManager> tAnimationManager,
Pointer<TSceneAsset> tSceneAsset,
);
@@ -2087,15 +2099,15 @@ extension type NativeLibrary(JSObject _) implements JSObject {
Pointer<TAnimationManager> tAnimationManager,
EntityId entityId,
);
external bool _AnimationManager_addBoneAnimationComponent(
external int _AnimationManager_addBoneAnimationComponent(
Pointer<TAnimationManager> tAnimationManager,
Pointer<TSceneAsset> tSceneAsset,
);
external bool _AnimationManager_removeBoneAnimationComponent(
external int _AnimationManager_removeBoneAnimationComponent(
Pointer<TAnimationManager> tAnimationManager,
Pointer<TSceneAsset> tSceneAsset,
);
external bool _AnimationManager_setMorphAnimation(
external int _AnimationManager_setMorphAnimation(
Pointer<TAnimationManager> tAnimationManager,
EntityId entityId,
Pointer<Float32> morphData,
@@ -2104,7 +2116,7 @@ extension type NativeLibrary(JSObject _) implements JSObject {
int numFrames,
double frameLengthInMs,
);
external bool _AnimationManager_clearMorphAnimation(
external int _AnimationManager_clearMorphAnimation(
Pointer<TAnimationManager> tAnimationManager,
EntityId entityId,
);
@@ -2112,7 +2124,7 @@ extension type NativeLibrary(JSObject _) implements JSObject {
Pointer<TAnimationManager> tAnimationManager,
Pointer<TSceneAsset> sceneAsset,
);
external bool _AnimationManager_addBoneAnimation(
external int _AnimationManager_addBoneAnimation(
Pointer<TAnimationManager> tAnimationManager,
Pointer<TSceneAsset> tSceneAsset,
int skinIndex,
@@ -2144,7 +2156,7 @@ extension type NativeLibrary(JSObject _) implements JSObject {
int boneIndex,
Pointer<Float32> out,
);
external bool _AnimationManager_playGltfAnimation(
external int _AnimationManager_playGltfAnimation(
Pointer<TAnimationManager> tAnimationManager,
Pointer<TSceneAsset> tSceneAsset,
int index,
@@ -2154,7 +2166,7 @@ extension type NativeLibrary(JSObject _) implements JSObject {
double crossfade,
double startOffset,
);
external bool _AnimationManager_stopGltfAnimation(
external int _AnimationManager_stopGltfAnimation(
Pointer<TAnimationManager> tAnimationManager,
Pointer<TSceneAsset> sceneAsset,
int index,
@@ -2197,17 +2209,17 @@ extension type NativeLibrary(JSObject _) implements JSObject {
Pointer<Char> outPtr,
int index,
);
external bool _AnimationManager_updateBoneMatrices(
external int _AnimationManager_updateBoneMatrices(
Pointer<TAnimationManager> tAnimationManager,
Pointer<TSceneAsset> sceneAsset,
);
external bool _AnimationManager_setMorphTargetWeights(
external int _AnimationManager_setMorphTargetWeights(
Pointer<TAnimationManager> tAnimationManager,
EntityId entityId,
Pointer<Float32> morphData,
int numWeights,
);
external bool _AnimationManager_setGltfAnimationFrame(
external int _AnimationManager_setGltfAnimationFrame(
Pointer<TAnimationManager> tAnimationManager,
Pointer<TSceneAsset> tSceneAsset,
int animationIndex,
@@ -2284,7 +2296,7 @@ bool Material_hasParameter(
self.Pointer<Char> propertyName,
) {
final result = _lib._Material_hasParameter(tMaterial.cast(), propertyName);
return result;
return result == 1;
}
bool MaterialInstance_isStencilWriteEnabled(
@@ -2292,7 +2304,7 @@ bool MaterialInstance_isStencilWriteEnabled(
) {
final result =
_lib._MaterialInstance_isStencilWriteEnabled(materialInstance.cast());
return result;
return result == 1;
}
void MaterialInstance_setStencilWrite(
@@ -2842,7 +2854,7 @@ bool View_isStencilBufferEnabled(
self.Pointer<TView> tView,
) {
final result = _lib._View_isStencilBufferEnabled(tView.cast());
return result;
return result == 1;
}
void View_setDitheringEnabled(
@@ -2857,7 +2869,7 @@ bool View_isDitheringEnabled(
self.Pointer<TView> tView,
) {
final result = _lib._View_isDitheringEnabled(tView.cast());
return result;
return result == 1;
}
void View_setScene(
@@ -2941,10 +2953,11 @@ bool Texture_loadImage(
self.Pointer<TLinearImage> tImage,
int bufferFormat,
int pixelDataType,
int level,
) {
final result = _lib._Texture_loadImage(tEngine.cast(), tTexture.cast(),
tImage.cast(), bufferFormat, pixelDataType);
return result;
tImage.cast(), bufferFormat, pixelDataType, level);
return result == 1;
}
bool Texture_setImage(
@@ -2961,7 +2974,7 @@ bool Texture_setImage(
) {
final result = _lib._Texture_setImage(tEngine.cast(), tTexture.cast(), level,
data, size, width, height, channels, bufferFormat, pixelDataType);
return result;
return result == 1;
}
bool Texture_setImageWithDepth(
@@ -2995,6 +3008,13 @@ bool Texture_setImageWithDepth(
depth,
bufferFormat,
pixelDataType);
return result == 1;
}
int Texture_getLevels(
self.Pointer<TTexture> tTexture,
) {
final result = _lib._Texture_getLevels(tTexture.cast());
return result;
}
@@ -3014,6 +3034,14 @@ int Texture_getHeight(
return result;
}
void Texture_generateMipmaps(
self.Pointer<TTexture> tTexture,
self.Pointer<TEngine> tEngine,
) {
final result = _lib._Texture_generateMipmaps(tTexture.cast(), tEngine.cast());
return result;
}
int Texture_getDepth(
self.Pointer<TTexture> tTexture,
int level,
@@ -3617,7 +3645,7 @@ bool TransformManager_transformToUnitCube(
final boundingBoxPtr = boundingBox._address;
final result = _lib._TransformManager_transformToUnitCube(
tTransformManager.cast(), entityId, boundingBoxPtr.cast());
return result;
return result == 1;
}
void TransformManager_setParent(
@@ -3680,7 +3708,7 @@ bool Renderer_beginFrame(
) {
final result = _lib._Renderer_beginFrame(
tRenderer.cast(), tSwapChain.cast(), frameTimeInNanos.toJSBigInt);
return result;
return result == 1;
}
void Renderer_endFrame(
@@ -4871,6 +4899,7 @@ void Texture_loadImageRenderThread(
self.Pointer<TLinearImage> tImage,
int bufferFormat,
int pixelDataType,
int level,
self.Pointer<self.NativeFunction<void Function(bool)>> onComplete,
) {
final result = _lib._Texture_loadImageRenderThread(
@@ -4879,6 +4908,7 @@ void Texture_loadImageRenderThread(
tImage.cast(),
bufferFormat,
pixelDataType,
level,
onComplete.cast());
return result;
}
@@ -5324,7 +5354,7 @@ bool GltfResourceLoader_asyncBeginLoad(
) {
final result = _lib._GltfResourceLoader_asyncBeginLoad(
tGltfResourceLoader.cast(), tFilamentAsset.cast());
return result;
return result == 1;
}
void GltfResourceLoader_asyncUpdateLoad(
@@ -5360,7 +5390,7 @@ bool GltfResourceLoader_loadResources(
) {
final result = _lib._GltfResourceLoader_loadResources(
tGltfResourceLoader.cast(), tFilamentAsset.cast());
return result;
return result == 1;
}
bool RenderableManager_setMaterialInstanceAt(
@@ -5374,7 +5404,7 @@ bool RenderableManager_setMaterialInstanceAt(
entityId,
primitiveIndex,
tMaterialInstance.cast());
return result;
return result == 1;
}
self.Pointer<TMaterialInstance> RenderableManager_getMaterialInstanceAt(
@@ -5402,7 +5432,7 @@ bool RenderableManager_isRenderable(
) {
final result =
_lib._RenderableManager_isRenderable(tRenderableManager.cast(), entityId);
return result;
return result == 1;
}
bool RenderableManager_hasComponent(
@@ -5411,14 +5441,14 @@ bool RenderableManager_hasComponent(
) {
final result =
_lib._RenderableManager_hasComponent(tRenderableManager.cast(), entityId);
return result;
return result == 1;
}
bool RenderableManager_empty(
self.Pointer<TRenderableManager> tRenderableManager,
) {
final result = _lib._RenderableManager_empty(tRenderableManager.cast());
return result;
return result == 1;
}
bool RenderableManager_getLightChannel(
@@ -5428,7 +5458,7 @@ bool RenderableManager_getLightChannel(
) {
final result = _lib._RenderableManager_getLightChannel(
tRenderableManager.cast(), entityId, channel);
return result;
return result == 1;
}
bool RenderableManager_isShadowCaster(
@@ -5437,7 +5467,7 @@ bool RenderableManager_isShadowCaster(
) {
final result = _lib._RenderableManager_isShadowCaster(
tRenderableManager.cast(), entityId);
return result;
return result == 1;
}
void RenderableManager_setCastShadows(
@@ -5466,7 +5496,7 @@ bool RenderableManager_isShadowReceiver(
) {
final result = _lib._RenderableManager_isShadowReceiver(
tRenderableManager.cast(), entityId);
return result;
return result == 1;
}
bool RenderableManager_getFogEnabled(
@@ -5475,7 +5505,7 @@ bool RenderableManager_getFogEnabled(
) {
final result = _lib._RenderableManager_getFogEnabled(
tRenderableManager.cast(), entityId);
return result;
return result == 1;
}
Aabb3 RenderableManager_getAabb(
@@ -5695,7 +5725,7 @@ bool AnimationManager_addGltfAnimationComponent(
) {
final result = _lib._AnimationManager_addGltfAnimationComponent(
tAnimationManager.cast(), tSceneAsset.cast());
return result;
return result == 1;
}
bool AnimationManager_removeGltfAnimationComponent(
@@ -5704,7 +5734,7 @@ bool AnimationManager_removeGltfAnimationComponent(
) {
final result = _lib._AnimationManager_removeGltfAnimationComponent(
tAnimationManager.cast(), tSceneAsset.cast());
return result;
return result == 1;
}
void AnimationManager_addMorphAnimationComponent(
@@ -5731,7 +5761,7 @@ bool AnimationManager_addBoneAnimationComponent(
) {
final result = _lib._AnimationManager_addBoneAnimationComponent(
tAnimationManager.cast(), tSceneAsset.cast());
return result;
return result == 1;
}
bool AnimationManager_removeBoneAnimationComponent(
@@ -5740,7 +5770,7 @@ bool AnimationManager_removeBoneAnimationComponent(
) {
final result = _lib._AnimationManager_removeBoneAnimationComponent(
tAnimationManager.cast(), tSceneAsset.cast());
return result;
return result == 1;
}
bool AnimationManager_setMorphAnimation(
@@ -5760,7 +5790,7 @@ bool AnimationManager_setMorphAnimation(
numMorphTargets,
numFrames,
frameLengthInMs);
return result;
return result == 1;
}
bool AnimationManager_clearMorphAnimation(
@@ -5769,7 +5799,7 @@ bool AnimationManager_clearMorphAnimation(
) {
final result = _lib._AnimationManager_clearMorphAnimation(
tAnimationManager.cast(), entityId);
return result;
return result == 1;
}
void AnimationManager_resetToRestPose(
@@ -5804,7 +5834,7 @@ bool AnimationManager_addBoneAnimation(
fadeOutInSecs,
fadeInInSecs,
maxDelta);
return result;
return result == 1;
}
DartEntityId AnimationManager_getBone(
@@ -5861,7 +5891,7 @@ bool AnimationManager_playGltfAnimation(
replaceActive,
crossfade,
startOffset);
return result;
return result == 1;
}
bool AnimationManager_stopGltfAnimation(
@@ -5871,7 +5901,7 @@ bool AnimationManager_stopGltfAnimation(
) {
final result = _lib._AnimationManager_stopGltfAnimation(
tAnimationManager.cast(), sceneAsset.cast(), index);
return result;
return result == 1;
}
double AnimationManager_getGltfAnimationDuration(
@@ -5953,7 +5983,7 @@ bool AnimationManager_updateBoneMatrices(
) {
final result = _lib._AnimationManager_updateBoneMatrices(
tAnimationManager.cast(), sceneAsset.cast());
return result;
return result == 1;
}
bool AnimationManager_setMorphTargetWeights(
@@ -5964,7 +5994,7 @@ bool AnimationManager_setMorphTargetWeights(
) {
final result = _lib._AnimationManager_setMorphTargetWeights(
tAnimationManager.cast(), entityId, morphData, numWeights);
return result;
return result == 1;
}
bool AnimationManager_setGltfAnimationFrame(
@@ -5975,7 +6005,7 @@ bool AnimationManager_setGltfAnimationFrame(
) {
final result = _lib._AnimationManager_setGltfAnimationFrame(
tAnimationManager.cast(), tSceneAsset.cast(), animationIndex, frame);
return result;
return result == 1;
}
extension TMaterialInstanceExt on Pointer<TMaterialInstance> {
@@ -6349,7 +6379,6 @@ final class TScene extends self.Struct {
}
/// Copied from FogOptions in View.h
extension TFogOptionsExt on Pointer<TFogOptions> {
TFogOptions toDart() {
var distance = _lib.getValue(this + 0, "float").toDartDouble;
@@ -6401,76 +6430,109 @@ extension TFogOptionsExt on Pointer<TFogOptions> {
}
final class TFogOptions extends self.Struct {
final double distance;
double _distance = 0.0;
double get distance => _distance;
set distance(double val) {
_distance = val;
_lib.setValue(_address, val.toJS, "double");
}
final double cutOffDistance;
double _cutOffDistance = 0.0;
double get cutOffDistance => _cutOffDistance;
set cutOffDistance(double val) {
_cutOffDistance = val;
_lib.setValue(_address + 8, val.toJS, "double");
}
final double maximumOpacity;
double _maximumOpacity = 0.0;
double get maximumOpacity => _maximumOpacity;
set maximumOpacity(double val) {
_maximumOpacity = val;
_lib.setValue(_address + 16, val.toJS, "double");
}
final double height;
double _height = 0.0;
double get height => _height;
set height(double val) {
_height = val;
_lib.setValue(_address + 24, val.toJS, "double");
}
final double heightFalloff;
double _heightFalloff = 0.0;
double get heightFalloff => _heightFalloff;
set heightFalloff(double val) {
_heightFalloff = val;
_lib.setValue(_address + 32, val.toJS, "double");
}
final double3 linearColor;
late double3 _linearColor = double3(0.0, 0.0, 0.0, _address + 40);
double3 get linearColor => _linearColor;
set linearColor(double3 val) {
_linearColor = val;
// Assuming double3 is stored as 24 bytes (3 * 8 bytes)
_lib.setValue(_address + 40, val._address.toJS, "pointer");
}
final double density;
double _density = 0.0;
double get density => _density;
set density(double val) {
_density = val;
_lib.setValue(_address + 64, val.toJS, "double");
}
final double inScatteringStart;
double _inScatteringStart = 0.0;
double get inScatteringStart => _inScatteringStart;
set inScatteringStart(double val) {
_inScatteringStart = val;
_lib.setValue(_address + 72, val.toJS, "double");
}
final double inScatteringSize;
double _inScatteringSize = 0.0;
double get inScatteringSize => _inScatteringSize;
set inScatteringSize(double val) {
_inScatteringSize = val;
_lib.setValue(_address + 80, val.toJS, "double");
}
final bool fogColorFromIbl;
bool _fogColorFromIbl = false;
bool get fogColorFromIbl => _fogColorFromIbl;
set fogColorFromIbl(bool val) {
_fogColorFromIbl = val;
_lib.setValue(_address + 88, (val ? 1 : 0).toJS, "i8");
}
final self.Pointer<TTexture> skyColor;
self.Pointer<TTexture> _skyColor = self.Pointer<TTexture>(0);
self.Pointer<TTexture> get skyColor => _skyColor;
set skyColor(self.Pointer<TTexture> val) {
_skyColor = val;
_lib.setValue(_address + 96, val.toJS, "*");
}
final bool enabled;
bool _enabled = false;
bool get enabled => _enabled;
set enabled(bool val) {
_enabled = val;
_lib.setValue(_address + 104, (val ? 1 : 0).toJS, "i8");
}
TFogOptions(
this.distance,
this.cutOffDistance,
this.maximumOpacity,
this.height,
this.heightFalloff,
this.linearColor,
this.density,
this.inScatteringStart,
this.inScatteringSize,
this.fogColorFromIbl,
this.skyColor,
this.enabled,
this._distance,
this._cutOffDistance,
this._maximumOpacity,
this._height,
this._heightFalloff,
this._linearColor,
this._density,
this._inScatteringStart,
this._inScatteringSize,
this._fogColorFromIbl,
this._skyColor,
this._enabled,
super._address);
static Pointer<TFogOptions> stackAlloc() {
return Pointer<TFogOptions>(_lib._stackAlloc<TFogOptions>(62));
}
}
extension double3Ext on Pointer<double3> {
double3 toDart() {
var x = _lib.getValue(this + 0, "double").toDartDouble;
var y = _lib.getValue(this + 8, "double").toDartDouble;
var z = _lib.getValue(this + 16, "double").toDartDouble;
return double3(x, y, z, this);
}
void setFrom(double3 dartType) {
_lib.setValue(this + 0, dartType.x.toJS, "double");
_lib.setValue(this + 8, dartType.y.toJS, "double");
_lib.setValue(this + 16, dartType.z.toJS, "double");
}
}
final class double3 extends self.Struct {
final double x;
final double y;
final double z;
double3(this.x, this.y, this.z, super._address);
static Pointer<double3> stackAlloc() {
return Pointer<double3>(_lib._stackAlloc<double3>(24));
return Pointer<TFogOptions>(
_lib._stackAlloc<TFogOptions>(112)); // Updated size
}
}
@@ -6903,6 +6965,50 @@ final class double4x4 extends self.Struct {
}
}
extension double3Ext on Pointer<double3> {
double3 toDart() {
var x = _lib.getValue(this + 0, "double").toDartDouble;
var y = _lib.getValue(this + 8, "double").toDartDouble;
var z = _lib.getValue(this + 16, "double").toDartDouble;
return double3(x, y, z, this);
}
void setFrom(double3 dartType) {
_lib.setValue(this + 0, dartType.x.toJS, "double");
_lib.setValue(this + 8, dartType.y.toJS, "double");
_lib.setValue(this + 16, dartType.z.toJS, "double");
}
}
final class double3 extends self.Struct {
double _x = 0.0;
double get x => _x;
set x(double val) {
_x = val;
_lib.setValue(_address, x.toJS, "double");
}
double _y = 0.0;
double get y => _y;
set y(double val) {
_y = val;
_lib.setValue(_address + 8, x.toJS, "double");
}
double _z = 0.0;
double get z => _z;
set z(double val) {
_z = val;
_lib.setValue(_address + 16, x.toJS, "double");
}
double3(this._x, this._y, this._z, super._address);
static Pointer<double3> stackAlloc() {
return Pointer<double3>(_lib._stackAlloc<double3>(24));
}
}
sealed class TProjection {
static const Perspective = 0;
static const Orthographic = 1;

View File

@@ -10,17 +10,13 @@ class FFITexture extends Texture {
FFITexture(this._engine, this.pointer);
Future<void> setLinearImage(covariant FFILinearImage image,
PixelDataFormat format, PixelDataType type) async {
PixelDataFormat format, PixelDataType type,
{int level = 0}) async {
final tPixelDataFormat = format.value;
final tPixelDataType = type.value;
final result = await withBoolCallback((cb) {
Texture_loadImageRenderThread(
_engine,
pointer,
image.pointer,
tPixelDataFormat,
tPixelDataType,
cb);
Texture_loadImageRenderThread(_engine, pointer, image.pointer,
tPixelDataFormat, tPixelDataType, level, cb);
});
if (!result) {
@@ -31,14 +27,13 @@ class FFITexture extends Texture {
@override
Future<void> dispose() async {
await withVoidCallback((requestId, cb) {
Engine_destroyTextureRenderThread(_engine, pointer, requestId,cb);
Engine_destroyTextureRenderThread(_engine, pointer, requestId, cb);
});
}
@override
Future<void> generateMipmaps() {
// TODO: implement generateMipmaps
throw UnimplementedError();
Future<void> generateMipmaps() async {
Texture_generateMipMaps(pointer, _engine);
}
@override
@@ -58,9 +53,8 @@ class FFITexture extends Texture {
}
@override
Future<int> getLevels() {
// TODO: implement getLevels
throw UnimplementedError();
Future<int> getLevels() async {
return Texture_getLevels(pointer);
}
@override
@@ -116,7 +110,7 @@ class FFITexture extends Texture {
Uint8List buffer,
PixelDataFormat format,
PixelDataType type) async {
throw UnimplementedError();
throw UnimplementedError();
// final success = await withBoolCallback((cb) {
// Texture_setImageWithDepthRenderThread(
// _engine,
@@ -316,8 +310,9 @@ class FFITextureSampler extends TextureSampler {
// }
Future<void> setAnisotropy(double anisotropy) async {
await withVoidCallback((requestId,cb) {
TextureSampler_setAnisotropyRenderThread(pointer, anisotropy, requestId,cb);
await withVoidCallback((requestId, cb) {
TextureSampler_setAnisotropyRenderThread(
pointer, anisotropy, requestId, cb);
});
}
@@ -334,8 +329,8 @@ class FFITextureSampler extends TextureSampler {
@override
Future dispose() async {
await withVoidCallback((requestId,cb) {
TextureSampler_destroyRenderThread(pointer, requestId,cb);
await withVoidCallback((requestId, cb) {
TextureSampler_destroyRenderThread(pointer, requestId, cb);
});
}
}

View File

@@ -330,8 +330,10 @@ abstract class Texture {
/// Returns the internal format of this texture
Future<TextureFormat> getFormat();
/// Sets the given [image] as the source data for this texture.
///
Future setLinearImage(
covariant LinearImage image, PixelDataFormat format, PixelDataType type);
covariant LinearImage image, PixelDataFormat format, PixelDataType type, {int level = 0});
/// Sets the image data for a 2D texture or a texture level
Future setImage(int level, Uint8List buffer, int width, int height,
@@ -485,13 +487,14 @@ abstract class LinearImage {
///
///
///
static Future<Texture> decodeToTexture(Uint8List data, { TextureFormat textureFormat = TextureFormat.RGB32F, PixelDataFormat pixelDataFormat = PixelDataFormat.RGB, PixelDataType pixelDataType = PixelDataType.FLOAT}) async {
static Future<Texture> decodeToTexture(Uint8List data, { TextureFormat textureFormat = TextureFormat.RGB32F, PixelDataFormat pixelDataFormat = PixelDataFormat.RGB, PixelDataType pixelDataType = PixelDataType.FLOAT, int levels = 1}) async {
final decodedImage = await FilamentApp.instance!.decodeImage(data);
final texture = await FilamentApp.instance!.createTexture(
await decodedImage.getWidth(),
await decodedImage.getHeight(),
textureFormat: textureFormat,
levels:levels
);
await texture.setLinearImage(

View File

@@ -218,12 +218,14 @@ EMSCRIPTEN_KEEPALIVE TTexture *Texture_build(TEngine *engine,
intptr_t import,
TTextureSamplerType sampler,
TTextureFormat format);
EMSCRIPTEN_KEEPALIVE size_t Texture_getLevels(TTexture *tTexture);
EMSCRIPTEN_KEEPALIVE bool Texture_loadImage(
TEngine *tEngine,
TTexture *tTexture,
TLinearImage *tImage,
TPixelDataFormat bufferFormat,
TPixelDataType pixelDataType
TPixelDataType pixelDataType,
int level
);
EMSCRIPTEN_KEEPALIVE bool Texture_setImage(
TEngine *tEngine,
@@ -257,6 +259,7 @@ EMSCRIPTEN_KEEPALIVE uint32_t Texture_getWidth(TTexture *tTexture, uint32_t leve
EMSCRIPTEN_KEEPALIVE uint32_t Texture_getHeight(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 void Texture_generateMipMaps(TTexture *tTexture, TEngine *tEngine);
EMSCRIPTEN_KEEPALIVE TLinearImage *Image_createEmpty(uint32_t width,uint32_t height,uint32_t channel);
EMSCRIPTEN_KEEPALIVE TLinearImage *Image_decode(uint8_t* data, size_t length, const char* name);

View File

@@ -186,6 +186,7 @@ namespace thermion
TLinearImage *tImage,
TPixelDataFormat bufferFormat,
TPixelDataType pixelDataType,
int level,
void (*onComplete)(bool)
);
void Texture_setImageRenderThread(

View File

@@ -269,7 +269,7 @@ namespace thermion
TTextureFormat tFormat
)
{
TRACE("Creating texture %dx%d (depth %d), sampler type %d, format %d tUsage %d", width, height, depth, static_cast<int>(tSamplerType), static_cast<int>(tFormat), tUsage);
TRACE("Creating texture %dx%d (depth %d), sampler type %d, format %d tUsage %d, %d levels", width, height, depth, static_cast<int>(tSamplerType), static_cast<int>(tFormat), tUsage, levels);
auto *engine = reinterpret_cast<::filament::Engine *>(tEngine);
auto format = convertToFilamentFormat(tFormat);
auto samplerType = static_cast<::filament::Texture::Sampler>(static_cast<int>(tSamplerType));
@@ -295,7 +295,7 @@ namespace thermion
TRACE("BLIT_SRC");
}
auto builder = ::filament::Texture::Builder()
auto &builder = ::filament::Texture::Builder()
.width(width)
.height(height)
.depth(depth)
@@ -310,7 +310,7 @@ namespace thermion
auto *texture = builder
.build(*engine);
if(texture) {
TRACE("Texture successfully created");
TRACE("Texture successfully created with %d levels", texture->getLevels());
} else {
Log("Error: failed to created texture");
}
@@ -318,7 +318,12 @@ namespace thermion
return reinterpret_cast<TTexture *>(texture);
}
EMSCRIPTEN_KEEPALIVE bool Texture_loadImage(TEngine *tEngine, TTexture *tTexture, TLinearImage *tImage, TPixelDataFormat tBufferFormat, TPixelDataType tPixelDataType)
EMSCRIPTEN_KEEPALIVE size_t Texture_getLevels(TTexture *tTexture) {
auto texture = reinterpret_cast<filament::Texture *>(tTexture);
return texture->getLevels();
}
EMSCRIPTEN_KEEPALIVE bool Texture_loadImage(TEngine *tEngine, TTexture *tTexture, TLinearImage *tImage, TPixelDataFormat tBufferFormat, TPixelDataType tPixelDataType, int level)
{
auto engine = reinterpret_cast<filament::Engine *>(tEngine);
auto image = reinterpret_cast<::image::LinearImage *>(tImage);
@@ -354,7 +359,7 @@ namespace thermion
bufferFormat,
pixelDataType);
texture->setImage(*engine, 0, std::move(buffer));
texture->setImage(*engine, level, std::move(buffer));
return true;
}
@@ -531,6 +536,12 @@ namespace thermion
return texture->getDepth();
}
EMSCRIPTEN_KEEPALIVE void Texture_generateMipMaps(TTexture *tTexture, TEngine *tEngine) {
auto *texture = reinterpret_cast<filament::Texture *>(tTexture);
auto *engine = reinterpret_cast<filament::Engine *>(tEngine);
texture->generateMipmaps(*engine);
}
EMSCRIPTEN_KEEPALIVE TLinearImage *Image_createEmpty(uint32_t width, uint32_t height, uint32_t channel)
{
auto *image = new ::image::LinearImage(width, height, channel);
@@ -588,12 +599,9 @@ namespace thermion
TTextureSampler *sampler,
TSamplerMinFilter filter)
{
if (sampler)
{
auto *textureSampler = reinterpret_cast<filament::TextureSampler *>(sampler);
textureSampler->setMinFilter(static_cast<filament::TextureSampler::MinFilter>(filter));
}
auto *textureSampler = reinterpret_cast<filament::TextureSampler *>(sampler);
textureSampler->setMinFilter(static_cast<filament::TextureSampler::MinFilter>(filter));
TRACE("Set TextureSampler min filter to %d", filter);
}
EMSCRIPTEN_KEEPALIVE void TextureSampler_setAnisotropy(

View File

@@ -844,12 +844,13 @@ extern "C"
// Texture methods
EMSCRIPTEN_KEEPALIVE void Texture_loadImageRenderThread(TEngine *tEngine, TTexture *tTexture, TLinearImage *tImage,
TPixelDataFormat bufferFormat, TPixelDataType pixelDataType,
int level,
void (*onComplete)(bool))
{
std::packaged_task<void()> lambda(
[=]() mutable
{
bool result = Texture_loadImage(tEngine, tTexture, tImage, bufferFormat, pixelDataType);
bool result = Texture_loadImage(tEngine, tTexture, tImage, bufferFormat, pixelDataType, level);
PROXY(onComplete(result));
});
auto fut = _renderThread->add_task(lambda);

View File

@@ -32,150 +32,160 @@ void main() async {
}, bg: kRed);
});
test('create 2D texture and set image from raw buffer', () async {
await testHelper.withViewer((viewer) async {
var imageData = File(
"${testHelper.testDir}/assets/cube_texture_512x512.png",
).readAsBytesSync();
final image = await FilamentApp.instance!.decodeImage(imageData);
expect(await image.getChannels(), 4);
expect(await image.getWidth(), 512);
expect(await image.getHeight(), 512);
test('generate mipmaps', () async {
await testHelper.withViewer((viewer) async {
var imageData = File(
"${testHelper.testDir}/assets/cube_texture_512x512.png",
).readAsBytesSync();
final texture = await LinearImage.decodeToTexture(imageData, levels: 4);
expect(await texture.getLevels(), 4);
await texture.generateMipmaps();
await texture.dispose();
}, bg: kRed);
});
final texture = await FilamentApp.instance!.createTexture(
await image.getWidth(),
await image.getHeight(),
textureFormat: TextureFormat.RGBA32F,
);
var data = await image.getData();
test('create 2D texture and set image from raw buffer', () async {
await testHelper.withViewer((viewer) async {
var imageData = File(
"${testHelper.testDir}/assets/cube_texture_512x512.png",
).readAsBytesSync();
final image = await FilamentApp.instance!.decodeImage(imageData);
expect(await image.getChannels(), 4);
expect(await image.getWidth(), 512);
expect(await image.getHeight(), 512);
await texture.setImage(
final texture = await FilamentApp.instance!.createTexture(
await image.getWidth(),
await image.getHeight(),
textureFormat: TextureFormat.RGBA32F,
);
var data = await image.getData();
await texture.setImage(
0,
data.buffer.asUint8List(data.offsetInBytes),
512,
512,
4,
PixelDataFormat.RGBA,
PixelDataType.FLOAT,
);
await texture.dispose();
}, bg: kRed);
});
test('create 3D texture and set image from buffers', () async {
await testHelper.withViewer((viewer) async {
final width = 128;
final height = 128;
final channels = 4;
final depth = 5;
final texture = await FilamentApp.instance!.createTexture(
width,
height,
depth: depth,
textureSamplerType: TextureSamplerType.SAMPLER_3D,
textureFormat: TextureFormat.RGBA32F,
);
for (int i = 0; i < depth; i++) {
final buffer = Uint8List(width * height * channels * sizeOf<Float>());
await texture.setImage3D(
0,
data.buffer.asUint8List(data.offsetInBytes),
512,
512,
4,
0,
0,
i,
width,
height,
channels,
1,
buffer,
PixelDataFormat.RGBA,
PixelDataType.FLOAT,
);
await texture.dispose();
}, bg: kRed);
});
test('create 3D texture and set image from buffers', () async {
await testHelper.withViewer((viewer) async {
final width = 128;
final height = 128;
final channels = 4;
final depth = 5;
final texture = await FilamentApp.instance!.createTexture(
width,
height,
depth: depth,
textureSamplerType: TextureSamplerType.SAMPLER_3D,
textureFormat: TextureFormat.RGBA32F,
);
for (int i = 0; i < depth; i++) {
final buffer = Uint8List(width * height * channels * sizeOf<Float>());
await texture.setImage3D(
0,
0,
0,
i,
width,
height,
channels,
1,
buffer,
PixelDataFormat.RGBA,
PixelDataType.FLOAT,
);
}
await texture.dispose();
}, bg: kRed);
});
test('apply 3D texture material ', () async {
await testHelper.withViewer((viewer) async {
final material = await FilamentApp.instance!.createMaterial(
File(
"/Users/nickfisher/Documents/thermion/materials/texture_array.filamat",
).readAsBytesSync(),
);
final materialInstance = await material.createInstance();
final sampler = await FilamentApp.instance!.createTextureSampler();
final cube = await viewer.createGeometry(
GeometryHelper.cube(),
materialInstances: [materialInstance],
);
final width = 1;
final height = 1;
final channels = 4;
final numTextures = 2;
final texture = await FilamentApp.instance!.createTexture(
width,
height,
depth: numTextures,
textureSamplerType: TextureSamplerType.SAMPLER_3D,
textureFormat: TextureFormat.RGBA32F,
);
for (int i = 0; i < numTextures; i++) {
var pixelBuffer = Float32List.fromList([
i == 0 ? 1.0 : 0.0,
i == 1 ? 1.0 : 0.0,
0.0,
1.0,
]);
var byteBuffer = pixelBuffer.buffer.asUint8List(
pixelBuffer.offsetInBytes,
);
await texture.setImage3D(
0,
0,
0,
i,
width,
height,
channels,
1,
byteBuffer,
PixelDataFormat.RGBA,
PixelDataType.FLOAT,
);
}
await materialInstance.setParameterTexture(
"textures",
texture,
sampler,
);
await materialInstance.setParameterInt("activeTexture", 0);
await testHelper.capture(viewer.view, "3d_texture_0");
await materialInstance.setParameterInt("activeTexture", 1);
await testHelper.capture(viewer.view, "3d_texture_1");
await viewer.destroyAsset(cube);
await materialInstance.destroy();
await material.destroy();
await texture.dispose();
});
});
}
await texture.dispose();
}, bg: kRed);
});
group("sampler", () {
test('create sampler', () async {
await testHelper.withViewer((viewer) async {
final sampler = FilamentApp.instance!.createTextureSampler();
}, bg: kRed);
test('apply 3D texture material ', () async {
await testHelper.withViewer((viewer) async {
final material = await FilamentApp.instance!.createMaterial(
File(
"/Users/nickfisher/Documents/thermion/materials/texture_array.filamat",
).readAsBytesSync(),
);
final materialInstance = await material.createInstance();
final sampler = await FilamentApp.instance!.createTextureSampler();
final cube = await viewer.createGeometry(
GeometryHelper.cube(),
materialInstances: [materialInstance],
);
final width = 1;
final height = 1;
final channels = 4;
final numTextures = 2;
final texture = await FilamentApp.instance!.createTexture(
width,
height,
depth: numTextures,
textureSamplerType: TextureSamplerType.SAMPLER_3D,
textureFormat: TextureFormat.RGBA32F,
);
for (int i = 0; i < numTextures; i++) {
var pixelBuffer = Float32List.fromList([
i == 0 ? 1.0 : 0.0,
i == 1 ? 1.0 : 0.0,
0.0,
1.0,
]);
var byteBuffer = pixelBuffer.buffer.asUint8List(
pixelBuffer.offsetInBytes,
);
await texture.setImage3D(
0,
0,
0,
i,
width,
height,
channels,
1,
byteBuffer,
PixelDataFormat.RGBA,
PixelDataType.FLOAT,
);
}
await materialInstance.setParameterTexture(
"textures",
texture,
sampler,
);
await materialInstance.setParameterInt("activeTexture", 0);
await testHelper.capture(viewer.view, "3d_texture_0");
await materialInstance.setParameterInt("activeTexture", 1);
await testHelper.capture(viewer.view, "3d_texture_1");
await viewer.destroyAsset(cube);
await materialInstance.destroy();
await material.destroy();
await texture.dispose();
});
});
});
group("sampler", () {
test('create sampler', () async {
await testHelper.withViewer((viewer) async {
final sampler = FilamentApp.instance!.createTextureSampler();
}, bg: kRed);
});
});
}

View File

@@ -4,595 +4,113 @@ import 'package:thermion_dart/thermion_dart.dart';
import 'package:test/test.dart';
import 'helpers.dart';
Future<
({
ThermionAsset blueCube,
MaterialInstance blueMaterialInstance,
ThermionAsset greenCube,
MaterialInstance greenMaterialInstance
})> setup(ThermionViewer viewer) async {
var blueMaterialInstance =
await FilamentApp.instance!.createUnlitMaterialInstance();
final blueCube = await viewer.createGeometry(GeometryHelper.cube(),
materialInstances: [blueMaterialInstance]);
await blueMaterialInstance.setParameterFloat4(
"baseColorFactor", 0.0, 0.0, 1.0, 1.0);
// Position blue cube slightly behind and to the right
await blueCube.setTransform(Matrix4.translation(Vector3(1.0, 0.0, -1.0)));
var greenMaterialInstance =
await FilamentApp.instance!.createUnlitMaterialInstance();
final greenCube = await viewer.createGeometry(GeometryHelper.cube(),
materialInstances: [greenMaterialInstance]);
await greenMaterialInstance.setParameterFloat4(
"baseColorFactor", 0.0, 1.0, 0.0, 1.0);
return (
blueCube: blueCube,
blueMaterialInstance: blueMaterialInstance,
greenCube: greenCube,
greenMaterialInstance: greenMaterialInstance
);
}
void main() async {
final testHelper = TestHelper("material");
await testHelper.setup();
group("ubershader material tests", () {
test('ubershader material with color only', () async {
await testHelper.withViewer((viewer) async {
var materialInstance =
await FilamentApp.instance!.createUbershaderMaterialInstance();
await viewer
.loadIbl("file://${testHelper.testDir}/assets/default_env_ibl.ktx");
var cube = await viewer.createGeometry(
GeometryHelper.cube(normals: true, uvs: true),
materialInstances: [materialInstance]);
await materialInstance.setParameterFloat4(
"baseColorFactor", 0.0, 1.0, 0.0, 1.0);
await materialInstance.setParameterInt("baseColorIndex", -1);
await testHelper.capture(viewer.view, "ubershader_material_base_color");
await materialInstance.destroy();
}, bg: kRed, postProcessing: true);
});
test('ubershader + baseColorMap texture', () async {
await testHelper.withViewer((viewer) async {
var materialInstance = await FilamentApp.instance!
.createUbershaderMaterialInstance(unlit: true);
final cube = await viewer.createGeometry(GeometryHelper.cube(),
materialInstances: [materialInstance]);
var data = File(
"${testHelper.testDir}/assets/cube_texture_512x512_flipped.png")
.readAsBytesSync();
final image = await FilamentApp.instance!.decodeImage(data);
final texture = await FilamentApp.instance!.createTexture(
await image.getWidth(), await image.getHeight(),
textureFormat: TextureFormat.RGBA32F);
await texture.setLinearImage(
image, PixelDataFormat.RGBA, PixelDataType.FLOAT);
final sampler = await FilamentApp.instance!.createTextureSampler();
await materialInstance.setParameterFloat4(
"baseColorFactor", 1.0, 1.0, 1.0, 0.0);
await materialInstance.setParameterInt("baseColorIndex", 0);
await materialInstance.setParameterTexture(
"baseColorMap", texture, sampler);
await testHelper.capture(viewer.view,
"geometry_cube_with_custom_material_ubershader_texture");
await viewer.destroyAsset(cube);
await materialInstance.destroy();
await texture.dispose();
});
});
test('create cube with custom material instance (unlit)', () async {
var viewer = await testHelper.createViewer();
await viewer.setCameraPosition(0, 2, 6);
await viewer
.setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 8));
await viewer.setBackgroundColor(1.0, 0.0, 0.0, 1.0);
await viewer.setPostProcessing(true);
await viewer.setToneMapping(ToneMapper.LINEAR);
test('ubershader material with color only', () async {
await testHelper.withViewer((viewer) async {
var materialInstance =
await FilamentApp.instance!.createUnlitMaterialInstance();
var cube = await viewer.createGeometry(GeometryHelper.cube(),
await FilamentApp.instance!.createUbershaderMaterialInstance();
await viewer
.loadIbl("file://${testHelper.testDir}/assets/default_env_ibl.ktx");
var cube = await viewer.createGeometry(
GeometryHelper.cube(normals: true, uvs: true),
materialInstances: [materialInstance]);
var textureData =
File("${testHelper.testDir}/assets/cube_texture_512x512.png")
await materialInstance.setParameterFloat4(
"baseColorFactor", 0.0, 1.0, 0.0, 1.0);
await materialInstance.setParameterInt("baseColorIndex", -1);
await testHelper.capture(viewer.view, "ubershader_material_base_color");
await materialInstance.destroy();
}, bg: kRed, postProcessing: true);
});
test('ubershader + baseColorMap texture', () async {
await testHelper.withViewer((viewer) async {
var materialInstance = await FilamentApp.instance!
.createUbershaderMaterialInstance(unlit: true);
final cube = await viewer.createGeometry(GeometryHelper.cube(),
materialInstances: [materialInstance]);
var data =
File("${testHelper.testDir}/assets/cube_texture_512x512_flipped.png")
.readAsBytesSync();
throw UnimplementedError();
// var texture = await FilamentApp.instance!.createTexture(textureData);
// await viewer.applyTexture(texture, cube.entity);
// await testHelper.capture(
// viewer, "geometry_cube_with_custom_material_unlit_texture_only");
// await viewer.destroyAsset(cube);
final image = await FilamentApp.instance!.decodeImage(data);
final texture = await FilamentApp.instance!.createTexture(
await image.getWidth(), await image.getHeight(),
textureFormat: TextureFormat.RGBA32F);
await texture.setLinearImage(
image, PixelDataFormat.RGBA, PixelDataType.FLOAT);
final sampler = await FilamentApp.instance!.createTextureSampler();
await materialInstance.setParameterFloat4(
"baseColorFactor", 1.0, 1.0, 1.0, 0.0);
await materialInstance.setParameterInt("baseColorIndex", 0);
await materialInstance.setParameterTexture(
"baseColorMap", texture, sampler);
// cube = await viewer.createGeometry(GeometryHelper.cube(),
// materialInstances: [materialInstance]);
// // reusing same material instance, so set baseColorIndex to -1 to disable the texture
// await materialInstance.setParameterInt("baseColorIndex", -1);
// await materialInstance.setParameterFloat4(
// "baseColorFactor", 0.0, 1.0, 0.0, 1.0);
// await testHelper.capture(
// viewer, "geometry_cube_with_custom_material_unlit_color_only");
// await viewer.destroyAsset(cube);
// cube = await viewer.createGeometry(GeometryHelper.cube(),
// materialInstances: [materialInstance]);
// // now set baseColorIndex to 0 to enable the texture and the base color
// await materialInstance.setParameterInt("baseColorIndex", 0);
// await materialInstance.setParameterFloat4(
// "baseColorFactor", 0.0, 1.0, 0.0, 0.5);
// await viewer.applyTexture(texture, cube.entity);
// await testHelper.capture(
// viewer, "geometry_cube_with_custom_material_unlit_color_and_texture");
// await viewer.destroyAsset(cube);
// await viewer.destroyTexture(texture);
// await materialInstance.destroy();
// await viewer.dispose();
await testHelper.capture(
viewer.view, "geometry_cube_with_custom_material_ubershader_texture");
await viewer.destroyAsset(cube);
await materialInstance.destroy();
await texture.dispose();
});
});
group('depth & stencil', () {
test('set depth func to always', () async {
await testHelper.withViewer((viewer) async {
final (
:blueCube,
:blueMaterialInstance,
:greenCube,
:greenMaterialInstance
) = await setup(viewer);
// with default depth func, blue cube renders behind the green cube
await testHelper.capture(
viewer.view, "material_instance_depth_func_default");
test('baseColorMap texture with mip levels', () async {
await testHelper.withViewer((viewer) async {
var materialInstance = await FilamentApp.instance!
.createUbershaderMaterialInstance(unlit: true);
final cube = await viewer.createGeometry(GeometryHelper.cube(),
materialInstances: [materialInstance]);
await greenMaterialInstance.setDepthFunc(SamplerCompareFunction.A);
final red = await FilamentApp.instance!.decodeImage(
File("${testHelper.testDir}/assets/red_24x24.png").readAsBytesSync());
final green = await FilamentApp.instance!.decodeImage(
File("${testHelper.testDir}/assets/green_12x12.png").readAsBytesSync());
// with green material depth func set to always pass, green cube will render in front of blue cube
await testHelper.capture(
viewer.view, "material_instance_depth_func_always");
});
});
final texture = await FilamentApp.instance!
.createTexture(24, 24, levels: 2, textureFormat: TextureFormat.RGB32F);
test('disable depth write', () async {
await testHelper.withViewer((viewer) async {
final (
:blueCube,
:blueMaterialInstance,
:greenCube,
:greenMaterialInstance
) = await setup(viewer);
expect(await texture.getLevels(), 2);
// With depth write enabled on both materials, green cube renders behind the blue cube
await testHelper.capture(
viewer.view, "material_instance_depth_write_enabled");
final redF32 = await red.getData();
final greenF32 = await green.getData();
// Disable depth write on green cube, blue cube will always appear in front (green cube renders behind everything, including the image material, so not it's not visible at all)
await greenMaterialInstance.setDepthWriteEnabled(false);
await testHelper.capture(
viewer.view, "material_instance_depth_write_disabled");
await texture.setImage(
0,
redF32.buffer.asUint8List(redF32.offsetInBytes),
24,
24,
await red.getChannels(),
PixelDataFormat.RGB,
PixelDataType.FLOAT);
await texture.setImage(
1,
greenF32.buffer.asUint8List(greenF32.offsetInBytes),
12,
12,
await green.getChannels(),
PixelDataFormat.RGB,
PixelDataType.FLOAT);
// Set priority for greenCube to render last, making it appear in front
await viewer.setPriority(greenCube.entity, 7);
await testHelper.capture(viewer.view,
"material_instance_depth_write_disabled_with_priority");
});
});
final sampler = await FilamentApp.instance!.createTextureSampler(minFilter: TextureMinFilter.NEAREST_MIPMAP_LINEAR);
test('enable stencil write', () async {
await testHelper.withViewer((viewer) async {
final (
:blueCube,
:blueMaterialInstance,
:greenCube,
:greenMaterialInstance
) = await setup(viewer);
await materialInstance.setParameterFloat4(
"baseColorFactor", 1.0, 1.0, 1.0, 0.0);
await materialInstance.setParameterInt("baseColorIndex", 0);
await materialInstance.setParameterTexture(
"baseColorMap", texture, sampler);
// force depth to always pass so we're just comparing stencil test
await greenMaterialInstance.setDepthFunc(SamplerCompareFunction.A);
await blueMaterialInstance.setDepthFunc(SamplerCompareFunction.A);
await testHelper.capture(viewer.view, "mip_level_0");
await testHelper.capture(
viewer.view, "material_instance_depth_pass_stencil_disabled");
assert(await greenMaterialInstance.isStencilWriteEnabled() == false);
assert(await blueMaterialInstance.isStencilWriteEnabled() == false);
await greenMaterialInstance.setStencilWriteEnabled(true);
await blueMaterialInstance.setStencilWriteEnabled(true);
assert(await greenMaterialInstance.isStencilWriteEnabled() == true);
assert(await blueMaterialInstance.isStencilWriteEnabled() == true);
// just a sanity check, no difference from the last
await testHelper.capture(
viewer.view, "material_instance_depth_pass_stencil_enabled");
}, postProcessing: true, bg: null);
});
test('stencil always fail', () async {
await testHelper.withViewer((viewer) async {
final (
:blueCube,
:blueMaterialInstance,
:greenCube,
:greenMaterialInstance
) = await setup(viewer);
// force depth to always pass so we're just comparing stencil test
await greenMaterialInstance.setDepthFunc(SamplerCompareFunction.A);
await blueMaterialInstance.setDepthFunc(SamplerCompareFunction.A);
await greenMaterialInstance.setStencilWriteEnabled(true);
assert(await greenMaterialInstance.isStencilWriteEnabled() == true);
await greenMaterialInstance
.setStencilCompareFunction(SamplerCompareFunction.N);
// green cube isn't rendered
await testHelper.capture(
viewer.view, "material_instance_stencil_always_fail");
}, postProcessing: true, bg: null);
});
test('fail stencil not equal', () async {
await testHelper.withViewer((viewer) async {
final (
:blueCube,
:blueMaterialInstance,
:greenCube,
:greenMaterialInstance
) = await setup(viewer);
// this ensures the blue cube is rendered before the green cube
await viewer.setPriority(blueCube.entity, 0);
await viewer.setPriority(greenCube.entity, 1);
await blueMaterialInstance.setStencilWriteEnabled(true);
await blueMaterialInstance.setStencilReferenceValue(1);
await blueMaterialInstance
.setStencilCompareFunction(SamplerCompareFunction.A);
await blueMaterialInstance
.setStencilOpDepthStencilPass(StencilOperation.REPLACE);
await greenMaterialInstance.setStencilReferenceValue(1);
await greenMaterialInstance
.setStencilCompareFunction(SamplerCompareFunction.E);
// green cube is only rendered where it intersects with the blue cube
await testHelper.capture(viewer.view, "fail_stencil_ne");
}, postProcessing: true);
final camera = await viewer.getActiveCamera();
await viewer.view.setFrustumCullingEnabled(false);
await camera.lookAt(Vector3(0, 0, 600));
await testHelper.capture(viewer.view, "mip_level_1");
await viewer.destroyAsset(cube);
await materialInstance.destroy();
await texture.dispose();
});
});
}
Float32List unprojectTexture({
required Float32List renderTarget,
required Float32List uvCoordinates,
required int renderTargetWidth,
required int renderTargetHeight,
required int renderTargetChannels,
required int uvWidth,
required int uvHeight,
required int uvChannels,
required int outputWidth,
required int outputHeight,
int uChannel = 0,
int vChannel = 1,
}) {
// Create output texture (initially transparent/zero)
final outputSize = outputWidth * outputHeight * renderTargetChannels;
final outputTexture = Float32List(outputSize);
// Make sure the input dimensions match
assert(renderTargetWidth == uvWidth && renderTargetHeight == uvHeight,
'Render target and UV texture dimensions must match');
// For each pixel in the render target
for (int y = 0; y < renderTargetHeight; y++) {
for (int x = 0; x < renderTargetWidth; x++) {
// Calculate index in the source textures
final srcIndex = (y * renderTargetWidth + x);
final renderPixelIndex = srcIndex * renderTargetChannels;
final uvPixelIndex = srcIndex * uvChannels;
// Read UV coordinates directly from UV texture
// Since we're using Float32List, values should already be in 0-1 range
final u = uvCoordinates[uvPixelIndex + uChannel];
final v = uvCoordinates[uvPixelIndex + vChannel];
// Skip invalid UVs (e.g., background or out of bounds)
if (u < 0.0 || u > 1.0 || v < 0.0 || v > 1.0) {
continue;
}
// final u = x / renderTargetWidth;
// final v = y / renderTargetHeight;
// Convert UV to output texture coordinates
final outX = (u * (outputWidth - 1)).round();
final outY = (v * (outputHeight - 1)).round();
// Calculate the destination index in the output texture
final outIndex = (outY * outputWidth + outX) * renderTargetChannels;
// Copy color data from render target to output at the UV position
for (int c = 0; c < renderTargetChannels; c++) {
outputTexture[outIndex + c] = renderTarget[renderPixelIndex + c];
}
}
}
return outputTexture;
}
// // Rotate the camera in 30-degree increments and capture at each position
// for (int i = 0; i <= 180; i += 30) {
// final angle = i * (pi / 180); // Convert to radians
// // Calculate camera position
// // Start at (0, 1, 5) (facing the sphere from +z) and rotate around to (-5, 1, 0)
// final radius = 5.0;
// final x = -radius * sin(angle);
// final z = radius * cos(angle);
// // Create view matrix for this camera position
// final matrix = makeViewMatrix(
// Vector3(x, 1, z),
// Vector3.zero(), // Looking at origin
// Vector3(0, 1, 0) // Up vector
// )
// ..invert();
// await viewer.setCameraModelMatrix4(matrix);
// // Take a snapshot at this position
// await testHelper.capture(viewer.view, "projection_${i}deg");
// }
// group("MaterialInstance", () {
// test('disable depth write', () async {
// var viewer = await testHelper.createViewer();
// await viewer.setBackgroundColor(1.0, 0.0, 0.0, 1.0);
// await viewer.setCameraPosition(0, 0, 6);
// await viewer.addDirectLight(
// DirectLight.sun(direction: Vector3(0, 0, -1)..normalize()));
// final cube1 = await viewer.createGeometry(GeometryHelper.cube());
// var materialInstance = await viewer.getMaterialInstanceAt(cube1, 0);
// final cube2 = await viewer.createGeometry(GeometryHelper.cube());
// await viewer.setMaterialPropertyFloat4(
// cube2, "baseColorFactor", 0, 0, 1, 0, 1);
// await viewer.setPosition(cube2, 1.0, 0.0, -1.0);
// expect(materialInstance, isNotNull);
// // with depth write enabled on both materials, cube2 renders behind the white cube
// await testHelper.capture(viewer.view, "material_instance_depth_write_enabled");
// // if we disable depth write on cube1, then cube2 will always appear in front
// // (relying on insertion order)
// materialInstance!.setDepthWriteEnabled(false);
// await testHelper.capture(
// viewer, "material_instance_depth_write_disabled");
// // set priority for the cube1 cube to 7 (render) last, cube1 renders in front
// await viewer.setPriority(cube1, 7);
// await testHelper.capture(
// viewer, "material_instance_depth_write_disabled_with_priority");
// await viewer.dispose();
// });
// test('set uv scaling (unlit)', () async {
// var viewer = await testHelper.createViewer();
// await viewer.setBackgroundColor(1.0, 0.0, 0.0, 1.0);
// await viewer.setCameraPosition(0, 0, 6);
// await viewer.addDirectLight(
// DirectLight.sun(direction: Vector3(0, 0, -1)..normalize()));
// final unlitMaterialInstance = await FilamentApp.instance!.createUnlitMaterialInstance();
// final cube = await viewer.createGeometry(GeometryHelper.cube(),
// materialInstance: unlitMaterialInstance);
// await viewer.setMaterialPropertyFloat4(
// cube, 'baseColorFactor', 0, 1, 1, 1, 1);
// await viewer.setMaterialPropertyInt(cube, 'baseColorIndex', 0, 1);
// unlitMaterialInstance.setParameterFloat2("uvScale", 2.0, 4.0);
// var textureData =
// File("${testHelper.testDir}/assets/cube_texture_512x512.png")
// .readAsBytesSync();
// var texture = await FilamentApp.instance!.createTexture(textureData);
// await viewer.applyTexture(texture, cube);
// await testHelper.capture(viewer.view, "set_uv_scaling");
// await viewer.dispose();
// });
// });
// group("texture", () {
// test("create/apply/dispose texture", () async {
// var viewer = await testHelper.createViewer();
// var textureData =
// File("${testHelper.testDir}/assets/cube_texture_512x512.png")
// .readAsBytesSync();
// var texture = await FilamentApp.instance!.createTexture(textureData);
// await viewer.setBackgroundColor(0.0, 0.0, 0.0, 1.0);
// await viewer.addDirectLight(
// DirectLight.sun(direction: Vector3(0, -10, -1)..normalize()));
// await viewer.addDirectLight(DirectLight.spot(
// intensity: 1000000,
// position: Vector3(0, 0, 1.5),
// direction: Vector3(0, 0, -1)..normalize(),
// falloffRadius: 10,
// spotLightConeInner: 1,
// spotLightConeOuter: 1));
// await viewer.setCameraPosition(0, 2, 6);
// await viewer
// .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 8));
// var materialInstance =
// await FilamentApp.instance!.createUbershaderMaterialInstance(unlit: true);
// var cube = await viewer.createGeometry(GeometryHelper.cube(),
// materialInstances: [materialInstance]);
// await viewer.setPostProcessing(true);
// await viewer.setToneMapping(ToneMapper.LINEAR);
// await viewer.applyTexture(texture, cube,
// materialIndex: 0, parameterName: "baseColorMap");
// await testHelper.capture(viewer.view, "texture_applied_to_geometry");
// await viewer.destroyAsset(cube);
// await viewer.destroyTexture(texture);
// await viewer.dispose();
// });
// });
// group("unproject", () {
// test("unproject", () async {
// final dimensions = (width: 1280, height: 768);
// var viewer = await testHelper.createViewer(viewportDimensions: dimensions);
// await viewer.setPostProcessing(false);
// // await viewer.setToneMapping(ToneMapper.LINEAR);
// await viewer.setBackgroundColor(1.0, 1.0, 1.0, 1.0);
// // await viewer.createIbl(1.0, 1.0, 1.0, 100000);
// await viewer.addLight(LightType.SUN, 6500, 100000, -2, 0, 0, 1, -1, 0);
// await viewer.addLight(LightType.SPOT, 6500, 500000, 0, 0, 2, 0, 0, -1,
// falloffRadius: 10, spotLightConeInner: 1.0, spotLightConeOuter: 2.0);
// await viewer.setCameraPosition(-3, 4, 6);
// await viewer.setCameraRotation(
// Quaternion.axisAngle(Vector3(0, 1, 0), -pi / 8) *
// Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 6));
// var cube =
// await viewer.createGeometry(GeometryHelper.cube(), keepData: true);
// await viewer.setMaterialPropertyFloat4(
// cube, "baseColorFactor", 0, 1.0, 1.0, 1.0, 1.0);
// var textureData =
// File("${testHelper.testDir}/assets/cube_texture_512x512.png").readAsBytesSync();
// var texture = await FilamentApp.instance!.createTexture(textureData);
// await viewer.applyTexture(texture, cube,
// materialIndex: 0, parameterName: "baseColorMap");
// var numFrames = 60;
// // first do the render
// for (int i = 0; i < numFrames; i++) {
// await viewer.setCameraPosition(-3 + (i / numFrames * 2), 4, 6);
// await viewer.setCameraRotation(
// Quaternion.axisAngle(Vector3(0, 1, 0), -pi / 8) *
// Quaternion.axisAngle(
// Vector3(1, 0, 0), -pi / 6 - (i / numFrames * pi / 6)));
// var rendered = await testHelper.capture(viewer.view, "unproject_render$i");
// var renderPng =
// await pixelsToPng(rendered, dimensions.width, dimensions.height);
// File("${outDir.path}/unproject_render${i}.png")
// .writeAsBytesSync(renderPng);
// }
// // then go off and convert the video
// // now unproject the render back onto the geometry
// final textureSize = (width: 1280, height: 768);
// var pixels = <Uint8List>[];
// // note we skip the first frame
// for (int i = 0; i < numFrames; i++) {
// await viewer.setCameraPosition(-3 + (i / numFrames * 2), 4, 6);
// await viewer.setCameraRotation(
// Quaternion.axisAngle(Vector3(0, 1, 0), -pi / 8) *
// Quaternion.axisAngle(
// Vector3(1, 0, 0), -pi / 6 - (i / numFrames * pi / 6)));
// var input = pngToPixelBuffer(File(
// "${outDir.path}/a8c317af-6081-4848-8a06-f6b69bc57664_${i + 1}.png")
// .readAsBytesSync());
// var pixelBuffer = await (await viewer as ThermionViewerFFI).unproject(
// cube,
// input,
// dimensions.width,
// dimensions.height,
// textureSize.width,
// textureSize.height);
// // var png = await pixelsToPng(Uint8List.fromList(pixelBuffer),
// // dimensions.width, dimensions.height);
// await savePixelBufferToBmp(
// pixelBuffer,
// textureSize.width,
// textureSize.height,
// p.join(outDir.path, "unprojected_texture${i}.bmp"));
// pixels.add(pixelBuffer);
// if (i > 10) {
// break;
// }
// }
// // }
// final aggregatePixelBuffer = medianImages(pixels);
// await savePixelBufferToBmp(aggregatePixelBuffer, textureSize.width,
// textureSize.height, "unproject_texture.bmp");
// var pixelBufferPng = await pixelsToPng(
// Uint8List.fromList(aggregatePixelBuffer),
// dimensions.width,
// dimensions.height);
// File("${outDir.path}/unproject_texture.png")
// .writeAsBytesSync(pixelBufferPng);
// await viewer.setPostProcessing(true);
// await viewer.setToneMapping(ToneMapper.LINEAR);
// final unlit = await FilamentApp.instance!.createUnlitMaterialInstance();
// await viewer.destroyAsset(cube);
// cube = await viewer.createGeometry(GeometryHelper.cube(),
// materialInstance: unlit);
// var reconstructedTexture = await FilamentApp.instance!.createTexture(pixelBufferPng);
// await viewer.applyTexture(reconstructedTexture, cube);
// await viewer.setCameraRotation(
// Quaternion.axisAngle(Vector3(0, 1, 0), -pi / 8) *
// Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 6));
// await testHelper.capture(viewer.view, "unproject_reconstruct");
// // now re-render
// for (int i = 0; i < numFrames; i++) {
// await viewer.setCameraPosition(-3 + (i / numFrames * 2), 4, 6);
// await viewer.setCameraRotation(
// Quaternion.axisAngle(Vector3(0, 1, 0), -pi / 8) *
// Quaternion.axisAngle(
// Vector3(1, 0, 0), -pi / 6 - (i / numFrames * pi / 6)));
// var rendered = await testHelper.capture(viewer.view, "unproject_rerender$i");
// var renderPng =
// await pixelsToPng(rendered, dimensions.width, dimensions.height);
// File("${outDir.path}/unproject_rerender${i}.png")
// .writeAsBytesSync(renderPng);
// }
// }, timeout: Timeout(Duration(minutes: 2)));
// });
// }