From 7a5fdb0b7762c9484e9eb147c60b5b5286c4cb35 Mon Sep 17 00:00:00 2001 From: Nick Fisher Date: Fri, 28 Mar 2025 10:04:02 +0800 Subject: [PATCH] test updates --- thermion_dart/test/projection_tests.dart | 119 ++- thermion_dart/test/shadow_tests.dart | 78 +- thermion_dart/test/texture_tests.dart | 1059 +++------------------- 3 files changed, 254 insertions(+), 1002 deletions(-) diff --git a/thermion_dart/test/projection_tests.dart b/thermion_dart/test/projection_tests.dart index d1051a9a..8853d4ee 100644 --- a/thermion_dart/test/projection_tests.dart +++ b/thermion_dart/test/projection_tests.dart @@ -2,17 +2,9 @@ import 'dart:async'; import 'dart:io'; import 'dart:math'; +import 'dart:typed_data'; import 'package:test/test.dart'; import 'package:thermion_dart/src/utils/src/texture_projection.dart'; -import 'package:thermion_dart/src/viewer/src/ffi/src/callbacks.dart'; -import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_asset.dart'; -import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_camera.dart'; -import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_filament_app.dart'; -import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_material.dart'; -import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_render_target.dart'; -import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_scene.dart'; -import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_swapchain.dart'; -import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_view.dart'; import 'package:thermion_dart/thermion_dart.dart'; import 'helpers.dart'; @@ -49,6 +41,8 @@ void main() async { final camera = await viewer.getActiveCamera(); await viewer.view.setFrustumCullingEnabled(false); await camera.setLensProjection(near: 0.75, far: 100); + final vp = await viewer.view.getViewport(); + final (width, height) = (vp.width, vp.height); final dist = 5.0; await camera.lookAt( Vector3( @@ -74,13 +68,15 @@ void main() async { final divisions = 8; final projectedImage = - await FilamentApp.instance!.createImage(512, 512, 4); + await FilamentApp.instance!.createImage(width, height, 4); final projectedTexture = await FilamentApp.instance!.createTexture( - 512, - 512, + width, + height, textureFormat: TextureFormat.RGBA32F, ); + var images = []; + for (int i = 0; i < divisions; i++) { await camera.lookAt( Vector3( @@ -93,15 +89,17 @@ void main() async { await textureProjection.project(cube); final depth = textureProjection.getDepthWritePixelBuffer(); await savePixelBufferToBmp( - depth, 512, 512, "${testHelper.outDir.path}/depth_$i.bmp"); + depth, width, height, "${testHelper.outDir.path}/depth_$i.bmp"); final projected = textureProjection.getProjectedPixelBuffer(); - await savePixelBufferToBmp( - depth, 512, 512, "${testHelper.outDir.path}/projected_$i.bmp"); + await savePixelBufferToBmp(projected, width, height, + "${testHelper.outDir.path}/projected_$i.bmp"); await cube.setMaterialInstanceAt(ubershader); final data = await projectedImage.getData(); data.setRange(0, data.length, projected.buffer.asFloat32List()); + images.add(projected.buffer.asFloat32List()); + await projectedTexture.setLinearImage( projectedImage, PixelDataFormat.RGBA, @@ -119,6 +117,97 @@ void main() async { await ubershader.setParameterTexture( "baseColorMap", originalTexture, sampler); } + +// Improved blending - treating black pixels as transparent +final blendedImage = Float32List(width * height * 4); +final weightSums = List.filled(width * height, 0.0); + +// For each image +for (final image in images) { + // For each pixel in the image + for (int p = 0; p < width * height; p++) { + final baseIdx = p * 4; + final r = image[baseIdx]; + final g = image[baseIdx + 1]; + final b = image[baseIdx + 2]; + final alpha = image[baseIdx + 3]; + + // Check if pixel is black (all color channels near zero) + final isBlack = (r < 0.01 && g < 0.01 && b < 0.01); + + // Only include pixels that are non-black AND have non-zero alpha + if (!isBlack && alpha > 0) { + // Weight contribution by alpha value + final weight = alpha; + blendedImage[baseIdx] += r * weight; + blendedImage[baseIdx + 1] += g * weight; + blendedImage[baseIdx + 2] += b * weight; + blendedImage[baseIdx + 3] += weight; + + // Track total weights for normalization + weightSums[p] += weight; + } + } +} + +// Normalize by the accumulated weights +for (int p = 0; p < width * height; p++) { + final baseIdx = p * 4; + final weightSum = weightSums[p]; + + if (weightSum > 0) { + blendedImage[baseIdx] /= weightSum; + blendedImage[baseIdx + 1] /= weightSum; + blendedImage[baseIdx + 2] /= weightSum; + // Set alpha to full for pixels that had contributions + blendedImage[baseIdx + 3] = 1.0; + } else { + // For pixels with no contributions, ensure they're fully transparent + blendedImage[baseIdx] = 0; + blendedImage[baseIdx + 1] = 0; + blendedImage[baseIdx + 2] = 0; + blendedImage[baseIdx + 3] = 0; + } +} + // Set the blended data to the projectedImage + final data = await projectedImage.getData(); + data.setRange(0, data.length, blendedImage); + + await savePixelBufferToBmp(blendedImage.buffer.asUint8List(), width, + height, "${testHelper.outDir.path}/blended.bmp", + hasAlpha: true, isFloat: true); + + // Update the texture with the blended image + await projectedTexture.setLinearImage( + projectedImage, + PixelDataFormat.RGBA, + PixelDataType.FLOAT, + ); + + // Set the blended texture as the material parameter + await ubershader.setParameterTexture( + "baseColorMap", + projectedTexture, + sampler, + ); + + // Capture 120 frames orbiting around the cube + final orbitFrames = 120; + for (int frame = 0; frame < orbitFrames; frame++) { + // Calculate camera position based on frame + final angle = frame / orbitFrames * 2 * pi; + await camera.lookAt( + Vector3( + sin(angle) * dist, + dist * 0.8, // Slightly lower height + cos(angle) * dist, + ), + ); + + // Capture each frame with a sequential number + await testHelper.capture(viewer.view, + "capture_uv_blended_orbit_${frame.toString().padLeft(3, '0')}"); + } }, createRenderTarget: true); }); }); diff --git a/thermion_dart/test/shadow_tests.dart b/thermion_dart/test/shadow_tests.dart index 29ca63be..ce2f60e9 100644 --- a/thermion_dart/test/shadow_tests.dart +++ b/thermion_dart/test/shadow_tests.dart @@ -1,3 +1,6 @@ +import 'dart:math'; + +import 'package:thermion_dart/src/filament/src/light_options.dart'; import 'package:thermion_dart/thermion_dart.dart'; import 'package:test/test.dart'; import 'package:vector_math/vector_math_64.dart'; @@ -5,15 +8,17 @@ import 'helpers.dart'; void main() async { final testHelper = TestHelper("material"); + await testHelper.setup(); group("shadow tests", () { test('enable/disable shadows', () async { await testHelper.withViewer((viewer) async { - await viewer.setPostProcessing(true); - await viewer.setShadowsEnabled(true); + await viewer.setShadowsEnabled(false); await viewer.setShadowType(ShadowType.PCF); - var materialInsance = await viewer.createUbershaderMaterialInstance(); - await materialInsance.setParameterFloat4( + var materialInstance = + await FilamentApp.instance!.createUbershaderMaterialInstance(); + await materialInstance.setCullingMode(CullingMode.NONE); + await materialInstance.setParameterFloat4( "baseColorFactor", 0.0, 1.0, 0.0, 1.0); await viewer.addDirectLight(DirectLight.sun( intensity: 50000, @@ -23,22 +28,25 @@ void main() async { final plane = await viewer.createGeometry( GeometryHelper.plane( normals: true, uvs: true, width: 10, height: 10), - materialInstances: [materialInsance]); - expect(await viewer.isCastShadowsEnabled(plane.entity), true); - expect(await viewer.isReceiveShadowsEnabled(plane.entity), true); - await viewer.createGeometry( + materialInstances: [materialInstance]); + // await plane.setTransform(Matrix4.rotationX(pi)); + // await viewer.addToScene(plane); + expect(await plane.isCastShadowsEnabled(), true); + expect(await plane.isReceiveShadowsEnabled(), true); + final cube = await viewer.createGeometry( GeometryHelper.cube( normals: true, uvs: true, ), - materialInstances: [materialInsance]); + materialInstances: [materialInstance]); + await viewer.setShadowsEnabled(true); - await testHelper.capture(viewer, "shadows_enabled"); + await testHelper.capture(viewer.view, "shadows_enabled"); await viewer.setShadowsEnabled(false); - await testHelper.capture(viewer, "shadows_disabled"); - }, bg: kRed); + await testHelper.capture(viewer.view, "shadows_disabled"); + }, bg: kRed, createRenderTarget: true, postProcessing: true); }); test('enable/disable cast shadows', () async { @@ -46,8 +54,11 @@ void main() async { await viewer.setPostProcessing(true); await viewer.setShadowsEnabled(true); await viewer.setShadowType(ShadowType.PCF); - var materialInsance = await viewer.createUbershaderMaterialInstance(); - await materialInsance.setParameterFloat4( + var materialInstance = + await FilamentApp.instance!.createUbershaderMaterialInstance(); + await materialInstance.setCullingMode(CullingMode.NONE); + + await materialInstance.setParameterFloat4( "baseColorFactor", 0.0, 1.0, 0.0, 1.0); await viewer.addDirectLight(DirectLight.sun( intensity: 50000, @@ -57,22 +68,22 @@ void main() async { final plane = await viewer.createGeometry( GeometryHelper.plane( normals: true, uvs: true, width: 10, height: 10), - materialInstances: [materialInsance]); + materialInstances: [materialInstance]); final cube = await viewer.createGeometry( GeometryHelper.cube( normals: true, uvs: true, ), - materialInstances: [materialInsance]); + materialInstances: [materialInstance]); - expect(await viewer.isCastShadowsEnabled(cube.entity), true); - await testHelper.capture(viewer, "cast_shadows_enabled"); + expect(await cube.isCastShadowsEnabled(), true); + await testHelper.capture(viewer.view, "cast_shadows_enabled"); - await viewer.setCastShadows(cube.entity, false); - expect(await viewer.isCastShadowsEnabled(cube.entity), false); - await testHelper.capture(viewer, "cast_shadows_disabled"); - }, bg: kRed); + await cube.setCastShadows(false); + expect(await cube.isCastShadowsEnabled(), false); + await testHelper.capture(viewer.view, "cast_shadows_disabled"); + }, bg: kRed, createRenderTarget: true, postProcessing: true); }); test('enable/disable receive shadows', () async { @@ -80,9 +91,12 @@ void main() async { await viewer.setPostProcessing(true); await viewer.setShadowsEnabled(true); await viewer.setShadowType(ShadowType.PCF); - var materialInsance = await viewer.createUbershaderMaterialInstance(); - await materialInsance.setParameterFloat4( + var materialInstance = + await FilamentApp.instance!.createUbershaderMaterialInstance(); + await materialInstance.setParameterFloat4( "baseColorFactor", 0.0, 1.0, 0.0, 1.0); + await materialInstance.setCullingMode(CullingMode.NONE); + await viewer.addDirectLight(DirectLight.sun( intensity: 50000, castShadows: true, @@ -91,22 +105,22 @@ void main() async { final plane = await viewer.createGeometry( GeometryHelper.plane( normals: true, uvs: true, width: 10, height: 10), - materialInstances: [materialInsance]); + materialInstances: [materialInstance]); final cube = await viewer.createGeometry( GeometryHelper.cube( normals: true, uvs: true, ), - materialInstances: [materialInsance]); + materialInstances: [materialInstance]); - expect(await viewer.isReceiveShadowsEnabled(plane.entity), true); - await testHelper.capture(viewer, "receive_shadows_enabled"); + expect(await plane.isReceiveShadowsEnabled(), true); + await testHelper.capture(viewer.view, "receive_shadows_enabled"); - await viewer.setReceiveShadows(plane.entity, false); - expect(await viewer.isReceiveShadowsEnabled(plane.entity), false); - await testHelper.capture(viewer, "receive_shadows_disabled"); - }, bg: kRed); + await plane.setReceiveShadows(false); + expect(await plane.isReceiveShadowsEnabled(), false); + await testHelper.capture(viewer.view, "receive_shadows_disabled"); + }, bg: kRed, createRenderTarget: true, postProcessing: true); }); }); } diff --git a/thermion_dart/test/texture_tests.dart b/thermion_dart/test/texture_tests.dart index d42581ea..5eee360b 100644 --- a/thermion_dart/test/texture_tests.dart +++ b/thermion_dart/test/texture_tests.dart @@ -1,24 +1,15 @@ @Timeout(const Duration(seconds: 600)) -import 'dart:async'; import 'dart:ffi'; import 'dart:io'; -import 'dart:math'; import 'dart:typed_data'; -import 'package:image/image.dart'; import 'package:test/test.dart'; import 'package:thermion_dart/src/viewer/src/ffi/src/callbacks.dart'; -import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_camera.dart'; -import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_render_target.dart'; -import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_swapchain.dart'; -import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_texture.dart'; -import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_view.dart'; -import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_viewer_ffi.dart'; import 'package:thermion_dart/thermion_dart.dart'; -import 'package:vector_math/vector_math_64.dart'; import 'helpers.dart'; void main() async { final testHelper = TestHelper("material"); + await testHelper.setup(); group("image", () { test('create 2D texture & set from decoded image', () async { @@ -26,12 +17,12 @@ void main() async { var imageData = File( "${testHelper.testDir}/assets/cube_texture_512x512.png", ).readAsBytesSync(); - final image = await viewer.decodeImage(imageData); + final image = await FilamentApp.instance!.decodeImage(imageData); expect(await image.getChannels(), 4); expect(await image.getWidth(), 512); expect(await image.getHeight(), 512); - final texture = await viewer.createTexture( + final texture = await FilamentApp.instance!.createTexture( await image.getWidth(), await image.getHeight(), textureFormat: TextureFormat.RGBA32F, @@ -45,851 +36,107 @@ 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 viewer.decodeImage(imageData); - expect(await image.getChannels(), 4); - expect(await image.getWidth(), 512); - expect(await image.getHeight(), 512); + 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); - final texture = await viewer.createTexture( - await image.getWidth(), - await image.getHeight(), - textureFormat: TextureFormat.RGBA32F, - ); - var data = await image.getData(); + 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 viewer.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()); - await texture.setImage3D( + await texture.setImage( 0, - 0, - 0, - i, - width, - height, - channels, - 1, - buffer, + data.buffer.asUint8List(data.offsetInBytes), + 512, + 512, + 4, PixelDataFormat.RGBA, PixelDataType.FLOAT, ); - } - await texture.dispose(); - }, bg: kRed); - }); + await texture.dispose(); + }, bg: kRed); + }); - test('apply 3D texture material ', () async { - await testHelper.withViewer((viewer) async { - final material = await viewer.createMaterial( - File( - "/Users/nickfisher/Documents/thermion/materials/texture_array.filamat", - ).readAsBytesSync(), - ); - final materialInstance = await material.createInstance(); - final sampler = await viewer.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 viewer.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, + 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, - channels, - 1, - byteBuffer, - PixelDataFormat.RGBA, - PixelDataType.FLOAT, + depth: depth, + textureSamplerType: TextureSamplerType.SAMPLER_3D, + textureFormat: TextureFormat.RGBA32F, ); - } - await materialInstance.setParameterTexture( - "textures", - texture, - sampler, - ); - await materialInstance.setParameterInt("activeTexture", 0); - - await testHelper.capture(viewer, "3d_texture_0"); - - await materialInstance.setParameterInt("activeTexture", 1); - - await testHelper.capture(viewer, "3d_texture_1"); - - await viewer.destroyAsset(cube); - await materialInstance.dispose(); - await material.dispose(); - await texture.dispose(); - }); - }); - }); - - group("sampler", () { - test('create sampler', () async { - await testHelper.withViewer((viewer) async { - final sampler = viewer.createTextureSampler(); - }, bg: kRed); - }); - }); - - group('projection', () { - Future withProjectionMaterial( - ThermionViewer viewer, - Future Function( - TextureSampler sampler, - MaterialInstance mi, - RenderTarget rt, - int width, - int height, - ) fn, - ) async { - // setup render target - final view = await viewer.getViewAt(0); - final vp = await view.getViewport(); - - final rtTextureHandle = await testHelper.createTexture(512, 512); - final (viewportWidth, viewportHeight) = (vp.width, vp.height); - - final rt = await viewer.createRenderTarget( - viewportWidth, - viewportHeight, - colorTextureHandle: rtTextureHandle.metalTextureAddress, - ); - - await view.setRenderTarget(rt); - - // setup base material + geometry - final sampler = await viewer.createTextureSampler(); - - var projectionMaterial = await viewer.createMaterial( - File( - "/Users/nickfisher/Documents/thermion/materials/capture_uv.filamat", - ).readAsBytesSync(), - ); - expect(await projectionMaterial.hasParameter("flipUVs"), true); - var projectionMaterialInstance = - await projectionMaterial.createInstance(); - await projectionMaterialInstance.setParameterBool("flipUVs", true); - final colorTexture = await rt.getColorTexture(); - final depthTexture = await rt.getDepthTexture(); - final w = await depthTexture.getWidth(); - final h = await depthTexture.getHeight(); - final d = await depthTexture.getDepth(); - - final depthSampler = await viewer.createTextureSampler( - compareMode: TextureCompareMode.COMPARE_TO_TEXTURE, - ); - await projectionMaterialInstance.setParameterTexture( - "color", - colorTexture, - sampler, - ); - await projectionMaterialInstance.setParameterTexture( - "depth", - depthTexture, - depthSampler, - ); - await projectionMaterialInstance.setDepthFunc(SamplerCompareFunction.A); - - await fn( - sampler, - projectionMaterialInstance, - rt, - viewportWidth, - viewportHeight, - ); - - // cleanup - await sampler.dispose(); - await projectionMaterialInstance.dispose(); - await projectionMaterial.dispose(); - } - - Future withCube( - ThermionViewer viewer, - Future Function( - ThermionAsset asset, - MaterialInstance mi, - Future Function() resetMaterial, - ) fn, - ) async { - // var material = await viewer.createUbershaderMaterialInstance(unlit: true); - var material = await viewer.createUnlitMaterialInstance(); - final cube = await viewer.createGeometry( - GeometryHelper.cube(), - materialInstances: [material], - ); - var sampler = await viewer.createTextureSampler(); - var inputTextureData = File( - "${testHelper.testDir}/assets/cube_texture2_512x512.png", - ).readAsBytesSync(); - var inputImage = await viewer.decodeImage(inputTextureData); - var inputTexture = await viewer.createTexture( - await inputImage.getWidth(), - await inputImage.getHeight(), - textureFormat: TextureFormat.RGBA32F, - ); - await inputTexture.setLinearImage( - inputImage, - PixelDataFormat.RGBA, - PixelDataType.FLOAT, - ); - final resetMaterial = () async { - await material.setCullingMode(CullingMode.BACK); - await material.setParameterInt("baseColorIndex", 0); - await material.setParameterTexture( - "baseColorMap", - inputTexture, - sampler, - ); - await material.setParameterFloat4( - "baseColorFactor", - 1.0, - 1.0, - 1.0, - 1.0, - ); - }; - await resetMaterial(); - - await fn(cube, material, resetMaterial); - } - - test('depth visualization', () async { - RenderLoop_create(); - final engine = await withPointerCallback( - (cb) => Engine_createRenderThread(TBackend.BACKEND_METAL.index, nullptr, nullptr, 1, false, cb)); - - final gltfResourceLoader = await withPointerCallback( - (cb) => GltfResourceLoader_createRenderThread(engine, cb)); - final gltfAssetLoader = await withPointerCallback( - (cb) => GltfAssetLoader_createRenderThread(engine, nullptr, cb)); - - final renderer = await withPointerCallback( - (cb) => Engine_createRendererRenderThread(engine, cb)); - final swapchain = await withPointerCallback((cb) => - Engine_createHeadlessSwapChainRenderThread( - engine, - 500, - 500, - TSWAP_CHAIN_CONFIG_TRANSPARENT | TSWAP_CHAIN_CONFIG_READABLE, - cb)); - final camera = await withPointerCallback( - (cb) => Engine_createCameraRenderThread(engine, cb)); - - final offscreenView = await withPointerCallback( - (cb) => Engine_createViewRenderThread(engine, cb)); - final view = await withPointerCallback( - (cb) => Engine_createViewRenderThread(engine, cb)); - - final colorTexture = await withPointerCallback((cb) => - Texture_buildRenderThread( - engine, - 500, - 500, - 1, - 1, - TTextureUsage.TEXTURE_USAGE_COLOR_ATTACHMENT.value | - TTextureUsage.TEXTURE_USAGE_SAMPLEABLE.value | - TTextureUsage.TEXTURE_USAGE_BLIT_SRC.value, + for (int i = 0; i < depth; i++) { + final buffer = Uint8List(width * height * channels * sizeOf()); + await texture.setImage3D( 0, - TTextureSamplerType.SAMPLER_2D, - TTextureFormat.TEXTUREFORMAT_RGBA8, - cb)); - - final depthTexture = await withPointerCallback((cb) => - Texture_buildRenderThread( - engine, - 500, - 500, - 1, - 1, - TTextureUsage.TEXTURE_USAGE_DEPTH_ATTACHMENT.value | - TTextureUsage.TEXTURE_USAGE_SAMPLEABLE.value, 0, - TTextureSamplerType.SAMPLER_2D, - TTextureFormat.TEXTUREFORMAT_DEPTH32F, - cb)); - - final renderTarget = await withPointerCallback((cb) => - RenderTarget_createRenderThread( - engine, 500, 500, colorTexture, depthTexture, cb)); - View_setRenderTarget(offscreenView, renderTarget); - final offscreenScene = Engine_createScene(engine); - final scene = Engine_createScene(engine); - - await withVoidCallback((cb) { - Renderer_setClearOptionsRenderThread( - renderer, 1.0, 0.0, 1.0, 1.0, 0, true, true, cb); - }); - View_setFrustumCullingEnabled(offscreenView, false); - View_setFrustumCullingEnabled(view, false); - View_setScene(offscreenView, offscreenScene); - View_setScene(view, scene); - View_setCamera(offscreenView, camera); - View_setCamera(view, camera); - View_setViewport(offscreenView, 500, 500); - View_setViewport(view, 500, 500); - final eye = Struct.create() - ..x = 5.0 - ..y = 1.0 - ..z = 5.0; - Camera_lookAt( - camera, - eye, - Struct.create() - ..x = 0.0 - ..y = 0.0 - ..z = 0.0, - Struct.create() - ..x = 0.0 - ..y = 1.0 - ..z = 0.0); - View_setBloomRenderThread(offscreenView, false, 0.0); - - Camera_setLensProjection(camera, 0.05, 100000, 1.0, kFocalLength); - View_setPostProcessing(offscreenView, false); - View_setPostProcessing(view, false); - - final iblData = File("${testHelper.testDir}/assets/default_env_ibl.ktx") - .readAsBytesSync(); - final ibl = await withPointerCallback((cb) => - Engine_buildIndirectLightRenderThread( - engine, iblData.address, iblData.length, 30000, cb, nullptr)); - - Scene_setIndirectLight(offscreenScene, ibl); - Scene_setIndirectLight(scene, ibl); - - final skyboxData = - File("${testHelper.testDir}/assets/default_env_skybox.ktx") - .readAsBytesSync(); - - final skybox = await withPointerCallback((cb) => - Engine_buildSkyboxRenderThread( - engine, skyboxData.address, skyboxData.length, cb, nullptr)); - - Scene_setSkybox(offscreenScene, skybox); - Scene_setSkybox(scene, skybox); - - // final cubeData = GeometryHelper.cube(); - // final cube = await withPointerCallback((cb) => - // SceneAsset_createGeometryRenderThread( - // engine, - // cubeData.vertices.address, - // cubeData.vertices.length, - // cubeData.normals.address, - // cubeData.normals.length, - // cubeData.uvs.address, - // cubeData.uvs.length, - // cubeData.indices.address, - // cubeData.indices.length, - // TPrimitiveType.PRIMITIVETYPE_POINTS, - // nullptr, - // 0, - // cb)); - // Scene_addEntity(offscreenScene, SceneAsset_getEntity(cube)); - var cube = - File("${testHelper.testDir}/assets/cube.glb").readAsBytesSync(); - final filamentAsset = await withPointerCallback((cb) => - GltfAssetLoader_loadRenderThread(gltfAssetLoader, - cube.address, cube.length, 1, cb)); - var entities = Int32List(FilamentAsset_getEntityCount(filamentAsset)); - FilamentAsset_getEntities(filamentAsset, entities.address); - - final unlitMaData = - File("/Users/nickfisher/Documents/thermion/materials/unlit.filamat") - .readAsBytesSync(); - final unlitMa = - Engine_buildMaterial(engine, unlitMaData.address, unlitMaData.length); - final unlitMi = await withPointerCallback( - (cb) => Material_createInstanceRenderThread(unlitMa, cb)); - MaterialInstance_setParameterFloat2( - unlitMi, "uvScale".toNativeUtf8().cast(), 1.0, 1.0); - MaterialInstance_setParameterFloat4(unlitMi, - "baseColorFactor".toNativeUtf8().cast(), 1.0, 1.0, 0.0, 1.0); - MaterialInstance_setParameterInt( - unlitMi, "baseColorIndex".toNativeUtf8().cast(), -1); - for (int i = 0; i < entities.length; i++) { - RenderableManager_setMaterialInstanceAt( - Engine_getRenderableManager(engine), entities[i], 0, unlitMi); - } - - // final materialInstance = GltfAssetLoader_getMaterialInstance( - // Engine_getRenderableManager(engine), filamentAsset); - // MaterialInstance_setParameterFloat4(materialInstance, - // "baseColorFactor".toNativeUtf8().cast(), 1.0, 0, 0, 1); - - final imageData = - File("${testHelper.testDir}/assets/cube_texture2_512x512.png") - .readAsBytesSync(); - final image = await Image_decode(imageData.address, imageData.length, - "unused".toNativeUtf8().cast()); - final texture = await withPointerCallback((cb) => - Texture_buildRenderThread( - engine, - Image_getWidth(image), - Image_getHeight(image), - 1, - 1, - TTextureUsage.TEXTURE_USAGE_SAMPLEABLE.index, 0, - TTextureSamplerType.SAMPLER_2D, - TTextureFormat.TEXTUREFORMAT_RGBA32F, - cb)); - - await withBoolCallback((cb) => Texture_loadImageRenderThread( - engine, - texture, - image, - TPixelDataFormat.PIXELDATAFORMAT_RGBA, - TPixelDataType.PIXELDATATYPE_FLOAT, - cb)); - MaterialInstance_setParameterInt( - unlitMi, "baseColorIndex".toNativeUtf8().cast(), 0); - MaterialInstance_setParameterTexture( - unlitMi, - "baseColorMap".toNativeUtf8().cast(), - RenderTarget_getDepthTexture(renderTarget), - // texture, - TextureSampler_create()); - - await withVoidCallback((cb) { - Scene_addFilamentAssetRenderThread(offscreenScene, filamentAsset, cb); + i, + width, + height, + channels, + 1, + buffer, + PixelDataFormat.RGBA, + PixelDataType.FLOAT, + ); + } + await texture.dispose(); + }, bg: kRed); }); - await withVoidCallback((cb) { - Scene_addFilamentAssetRenderThread(scene, filamentAsset, cb); - }); + 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], + ); - await withVoidCallback((cb) { - Engine_flushAndWaitRenderThead(engine, cb); - }); + 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, + ); - await withBoolCallback((cb) { - Renderer_beginFrameRenderThread( - renderer, - swapchain, - 0, - cb, - ); - }); - - await withVoidCallback((cb) { - Renderer_renderRenderThread(renderer, offscreenView, cb); - }); - - await withVoidCallback((cb) { - Renderer_renderRenderThread(renderer, view, cb); - }); - - var offscreenViewOut = Uint8List(500 * 500 * 4); - - await withVoidCallback((cb) { - Renderer_readPixelsRenderThread( - renderer, - offscreenView, - renderTarget, - TPixelDataFormat.PIXELDATAFORMAT_RGBA, - TPixelDataType.PIXELDATATYPE_UBYTE, - offscreenViewOut.address, - cb, - ); - }); - - var swapchainOut = Uint8List(500 * 500 * 4); - - await withVoidCallback((cb) { - Renderer_readPixelsRenderThread( - renderer, - view, - nullptr, - TPixelDataFormat.PIXELDATAFORMAT_RGBA, - TPixelDataType.PIXELDATATYPE_UBYTE, - swapchainOut.address, - cb, - ); - }); - - await withVoidCallback((cb) { - Engine_flushAndWaitRenderThead(engine!, cb); - }); - - await savePixelBufferToPng( - offscreenViewOut, - 500, - 500, - "/tmp/view1.png", - ); - - await savePixelBufferToPng( - swapchainOut, - 500, - 500, - "/tmp/sc1.png", - ); - - await withVoidCallback((cb) => Engine_destroyIndirectLightRenderThread(engine, ibl, cb)); - await withVoidCallback((cb) => Engine_destroySkyboxRenderThread(engine, skybox, cb)); - RenderLoop_destroy(); - - // final mirrorMaterial = await viewer.createMaterial( - // File( - // "/Users/nickfisher/Documents/thermion/materials/mirror.filamat", - // ).readAsBytesSync(), - // ); - // final mirrorMi = await mirrorMaterial.createInstance(); - // await mirrorMi.setDepthWriteEnabled(false); - // final plane = await viewer.createGeometry(GeometryHelper.sphere(), - // materialInstances: [mirrorMi]); - // await viewer.setTransform( - // plane.entity, - // Matrix4.compose( - // Vector3.zero(), - // Quaternion.axisAngle(Vector3(1, 0, 0), pi / 2), - // Vector3.all(750))); - - // final renderer = bindings.renderer; - // final _engine = engine; - - // // final sampler = await viewer.createTextureSampler( - // // compareMode: TextureCompareMode.COMPARE_TO_TEXTURE, - // // ); - - // // second view - // FFIView view2 = await viewer.createView() as FFIView; - // var scene2 = Engine_createScene(engine); - // Scene_addEntity(scene2, plane.entity); - // View_setScene(view2.view, scene2); - // await view2.setPostProcessing(false); - // await view2.setViewport(vp.width, vp.height); - // await view2.setCamera(camera); - // await view2.setFrustumCullingEnabled(false); - - // await mirrorMi.setParameterTexture( - // "albedo", - // // texture as FFITexture, - // (await rt.getDepthTexture())! as FFITexture, - // (await viewer.createTextureSampler( - // compareMode: TextureCompareMode.COMPARE_TO_TEXTURE, - // compareFunc: TextureCompareFunc.LESS_EQUAL) - // as FFITextureSampler)); - - // var fence = await withPointerCallback((cb) { - // Engine_createFenceRenderThread(engine!, cb); - // }); - - // var view2Out = Uint8List(vp.width * vp.height * 4); - - // await withVoidCallback((cb) { - // Renderer_renderRenderThread(bindings.renderer, view2.view, cb); - // }); - - // await withVoidCallback((cb) { - // Renderer_readPixelsRenderThread( - // renderer, - // view2.view, - // nullptr, - // TPixelDataFormat.PIXELDATAFORMAT_RGBA.index, - // TPixelDataType.PIXELDATATYPE_UBYTE.index, - // view2Out.address, - // cb, - // ); - // }); - - // await withVoidCallback((cb) { - // Renderer_endFrameRenderThread(renderer, cb); - // }); - - // await withVoidCallback((cb) { - // Engine_flushAndWaitRenderThead(_engine!, cb); - // }); - - // await withVoidCallback((cb) { - // Engine_destroyFenceRenderThread(_engine, fence, cb); - // }); - - // await savePixelBufferToPng( - // view2Out, - // vp.width, - // vp.height, - // "/tmp/view2.png", - // ); - // while (true) { - // await Future.delayed(Duration(seconds: 1)); - // } - // // await testHelper.capture(viewer, "depth_vis", renderTarget: rt); - // // depthTextureHandle.fillColor(); - // // var data = depthTextureHandle.getTextureBytes()!; - // // var pixels = data.bytes.cast().asTypedList(data.length ~/ 4); - // // expect(pixels.where((a) => a != 0).isNotEmpty, true); - // // print(pixels); - // }); - }); - - test('project texture & UV unwrap', () async { - await testHelper.withViewer((viewer) async { - final camera = await viewer.getMainCamera(); - final depthMaterial = await viewer.createMaterial( - File( - "/Users/nickfisher/Documents/thermion/materials/depthVisualizer.filamat", - ).readAsBytesSync(), - ); - final depthMaterialInstance = await depthMaterial.createInstance(); - await viewer.setPostProcessing(false); - await withProjectionMaterial(viewer, ( - sampler, - projectionMaterialInstance, - rt, - width, - height, - ) async { - await withCube(viewer, (cube, ubershader, resetMaterial) async { - var objects = {"cube": cube}; - - await viewer.setPostProcessing(false); - - for (final entry in objects.entries) { - final object = entry.value; - final key = entry.key; - - await object.addToScene(); - - var divisions = 8; - for (int i = 0; i < divisions; i++) { - await camera.lookAt( - Vector3( - sin(i / divisions * pi) * 3, - 0, - cos(i / divisions * pi) * 3, - ), - ); - - await object.setMaterialInstanceAt(depthMaterialInstance); - - // final depthBuffer = await testHelper - // .capture(viewer, "depth_${key}_$i", renderTarget: rt); - // final floatDepthBuffer = Float32List.fromList( - // depthBuffer.map((p) => p.toDouble() / 255.0).toList()); - // final depthTexture = await viewer.createTexture(width, height); - // await depthTexture.setImage( - // 0, - // floatDepthBuffer.buffer - // .asUint8List(floatDepthBuffer.offsetInBytes), - // width, - // height, - // 4, - // PixelDataFormat.RGBA, - // PixelDataType.FLOAT); - // var depthSampler = await viewer.createTextureSampler( - // minFilter: TextureMinFilter.NEAREST, - // magFilter: TextureMagFilter.NEAREST); - // await projectionMaterialInstance.setParameterTexture( - // "depth", depthTexture, depthSampler); - - await object.setMaterialInstanceAt(ubershader); - await testHelper.capture( - viewer, - "color_${key}_$i", - renderTarget: rt, - ); - - // final view = await viewer.getViewAt(0); - // final vp = await view.getViewport(); - // final swapchain = - // await viewer.createHeadlessSwapChain(512, 512); - - // final rtTextureHandle2 = - // await testHelper.createTexture(512, 512); - // final (viewportWidth, viewportHeight) = (vp.width, vp.height); - - // final rt2 = await viewer.createRenderTarget(viewportWidth, - // viewportHeight, rtTextureHandle2.metalTextureAddress); - - // await view.setRenderTarget(rt2); - - await object.setMaterialInstanceAt(projectionMaterialInstance); - - var projectionOutput = await testHelper.capture( - viewer, - "uv_capture_${key}_$i", - renderTarget: rt, - // renderTarget: rt2, - // swapChain: swapchain - ); - - // await view.setRenderTarget(rt); - - var floatPixelBuffer = Float32List.fromList( - projectionOutput.first - .map((p) => p.toDouble() / 255.0) - .toList(), - ); - - final projectedImage = await viewer.createImage(512, 512, 4); - final data = await projectedImage.getData(); - data.setRange(0, data.length, floatPixelBuffer); - final projectedTexture = await viewer.createTexture( - 512, - 512, - textureFormat: TextureFormat.RGBA32F, - ); - await projectedTexture.setLinearImage( - projectedImage, - PixelDataFormat.RGBA, - PixelDataType.FLOAT, - ); - - await ubershader.setParameterTexture( - "baseColorMap", - projectedTexture, - sampler, - ); - await object.setMaterialInstanceAt(ubershader); - await testHelper.capture( - viewer, - "retextured_${key}_$i", - renderTarget: rt, - ); - await resetMaterial(); - } - await viewer.destroyAsset(object); - } - }); - }); - }, viewportDimensions: (width: 512, height: 512)); - }); - - Future usingVDTM( - ThermionViewer viewer, - List cameraPositions, - int width, - int height, - int channels, - Future Function(Texture texture, MaterialInstance mi) fn, - ) async { - final sampler = await viewer.createTextureSampler(); - - var texture = await viewer.createTexture( - width, - height, - textureSamplerType: TextureSamplerType.SAMPLER_3D, - depth: cameraPositions.length, - textureFormat: TextureFormat.RGBA32F, - ); - - final vdtm = await viewer.createMaterial( - File( - "/Users/nickfisher/Documents/thermion/materials/vdtm.filamat", - ).readAsBytesSync(), - ); - - final materialInstance = await vdtm.createInstance(); - - await materialInstance.setParameterFloat3Array( - "cameraPositions", - cameraPositions, - ); - await materialInstance.setParameterTexture( - "perspectives", - texture, - sampler, - ); - await fn(texture, materialInstance); - - await materialInstance.dispose(); - await vdtm.dispose(); - await texture.dispose(); - await sampler.dispose(); - } - - test('view dependent texture mapping (interpolated colors)', () async { - await testHelper.withViewer((viewer) async { - final cameraPositions = [ - Vector3(0, 0, 5), - Vector3(5, 0, 0), - Vector3(0, 0, -5), - ]; - final camera = await viewer.getMainCamera(); - - final (numCameraPositions, width, height, channels) = ( - cameraPositions.length, - 1, - 1, - 4, - ); - - await usingVDTM(viewer, cameraPositions, width, height, channels, ( - texture, - materialInstance, - ) async { - for (int i = 0; i < numCameraPositions; i++) { - final pixelBuffer = Float32List.fromList([ - 1 - (i / numCameraPositions), - i / numCameraPositions, + 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, @@ -905,132 +152,34 @@ void main() async { ); } - final cube = await viewer.createGeometry( - GeometryHelper.cube(), - materialInstances: [materialInstance], + await materialInstance.setParameterTexture( + "textures", + texture, + sampler, ); + await materialInstance.setParameterInt("activeTexture", 0); - for (int i = 0; i < 8; i++) { - final cameraPosition = Vector3( - sin(pi * (i / 7)) * 5, - 0, - cos(pi * (i / 7)) * 5, - ); - await camera.lookAt(cameraPosition); - await testHelper.capture( - viewer, - "view_dependent_texture_mapping_$i", - ); - } + 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(); }); - }, viewportDimensions: (width: 512, height: 512)); + }); }); - test('VDTM + Texture Projection', () async { - await testHelper.withViewer((viewer) async { - final cameraPositions = [ - Vector3(0, 0, 5), - Vector3(5, 0, 0), - Vector3(0, 0, -5), - ]; - - final camera = await viewer.getMainCamera(); - - await withProjectionMaterial(viewer, ( - TextureSampler projectionSampler, - MaterialInstance projectionMaterialInstance, - RenderTarget rt, - int width, - int height, - ) async { - await withCube(viewer, (cube, ubershader, resetMaterial) async { - var pixelBuffers = []; - for (int i = 0; i < cameraPositions.length; i++) { - await camera.lookAt(cameraPositions[i]); - - await testHelper.capture(viewer, "vdtm_$i", renderTarget: rt); - - await cube.setMaterialInstanceAt(projectionMaterialInstance); - - var projectionOutput = await testHelper.capture( - viewer, - "vdtm_unwrapped_$i", - renderTarget: rt, - ); - - var floatPixelBuffer = Float32List.fromList( - projectionOutput.first - .map((p) => p.toDouble() / 255.0) - .toList(), - ); - pixelBuffers.add(floatPixelBuffer); - final projectedImage = await viewer.createImage(width, height, 4); - final data = await projectedImage.getData(); - data.setRange(0, data.length, floatPixelBuffer); - final projectedTexture = await viewer.createTexture( - width, - height, - textureFormat: TextureFormat.RGBA32F, - ); - await projectedTexture.setLinearImage( - projectedImage, - PixelDataFormat.RGBA, - PixelDataType.FLOAT, - ); - - await ubershader.setParameterTexture( - "baseColorMap", - projectedTexture, - projectionSampler, - ); - await cube.setMaterialInstanceAt(ubershader); - - await testHelper.capture( - viewer, - "vdtm_projected_$i", - renderTarget: rt, - ); - - await resetMaterial(); - } - - await usingVDTM(viewer, cameraPositions, width, height, 4, ( - vdtmTexture, - vdtmMaterial, - ) async { - await cube.setMaterialInstanceAt(vdtmMaterial); - for (int i = 0; i < cameraPositions.length; i++) { - await vdtmTexture.setImage3D( - 0, - 0, - 0, - i, - width, - height, - 4, - 1, - pixelBuffers[i].buffer.asUint8List( - pixelBuffers[i].offsetInBytes, - ), - PixelDataFormat.RGBA, - PixelDataType.FLOAT, - ); - } - - for (int i = 0; i < 8; i++) { - await camera.lookAt( - Vector3(sin(pi * (i / 7)) * 5, 0, cos(pi * (i / 7)) * 5), - ); - await testHelper.capture( - viewer, - "vdtm_reprojected_$i", - renderTarget: rt, - ); - } - }); - }); - }); - }, viewportDimensions: (width: 512, height: 512)); + group("sampler", () { + test('create sampler', () async { + await testHelper.withViewer((viewer) async { + final sampler = FilamentApp.instance!.createTextureSampler(); + }, bg: kRed); + }); }); - }); + + }