Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Apps/UnitTests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ set(SOURCES
"Source/Tests.NativeEngine.DepthTextures.cpp"
"Source/Tests.NativeEngine.Teardown.cpp"
"Source/Tests.NativeEngine.TextureFormats.cpp"
"Source/Tests.NativeOptimizations.cpp"
"Source/Tests.ShaderCache.cpp"
"Source/Tests.ShaderCompilation.cpp"
"Source/Tests.ShaderCompilation.FragCoord.cpp"
Expand Down Expand Up @@ -136,6 +137,11 @@ if(BABYLON_NATIVE_PLUGIN_NATIVEMESHOPT)
target_compile_definitions(UnitTests PRIVATE HAS_NATIVE_MESHOPT)
endif()

if(BABYLON_NATIVE_PLUGIN_NATIVEOPTIMIZATIONS)
target_link_libraries(UnitTests PRIVATE NativeOptimizations)
target_compile_definitions(UnitTests PRIVATE HAS_NATIVE_OPTIMIZATIONS)
endif()

if(GRAPHICS_API STREQUAL "D3D12")
target_compile_definitions(UnitTests PRIVATE SKIP_RENDER_TESTS BABYLON_NATIVE_GRAPHICS_API_D3D12)
endif()
Expand Down
106 changes: 106 additions & 0 deletions Apps/UnitTests/Source/Tests.NativeOptimizations.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#include <gtest/gtest.h>
#ifdef HAS_NATIVE_OPTIMIZATIONS
#include <Babylon/AppRuntime.h>
#include <Babylon/Plugins/NativeOptimizations.h>
#include <napi/env.h>

#include <chrono>
#include <cstdlib>
#include <future>
#include <stdexcept>
#endif

TEST(NativeOptimizations, SplatSortingAcceptsTypedAndNumberArrayMatrices)
{
#ifndef HAS_NATIVE_OPTIMIZATIONS
GTEST_SKIP() << "NativeOptimizations is disabled";
#else
std::promise<void> completed;
auto completion = completed.get_future();
Babylon::AppRuntime runtime{};
runtime.Dispatch([&](Napi::Env env) {
try
{
Babylon::Plugins::NativeOptimizations::Initialize(env);
Napi::Eval(env, R"(
(function() {
const positions = new Float32Array([
1, 0, 0, 1,
0, 1, 0, 1,
0, 0, 1, 1,
1, 1, 1, 1,
-1, 0, -1, 1
]);
for (const typed of [true, false]) {
const matrix = typed ? new Float32Array(16) : new Array(16).fill(0);
matrix[2] = 2;
matrix[6] = -3;
matrix[10] = 5;
const modelView = { _m: matrix };
for (const rightHanded of [false, true]) {
const indices = new Float32Array(5);
_native.sortSplats(modelView, positions, indices, rightHanded);
// Distinct depths [2, -3, 5, 4, -7] give an algorithm-independent ordering.
const expected = rightHanded ? [4, 1, 0, 3, 2] : [2, 3, 0, 1, 4];
for (let index = 0; index < indices.length; ++index) {
if (indices[index] !== expected[index]) {
throw new Error("Unexpected splat order for typed=" + typed +
", rightHanded=" + rightHanded + ", index=" + index);
}
}
Comment thread
bkaradzic-microsoft marked this conversation as resolved.
const single = new Float32Array([99]);
_native.sortSplats(modelView, positions.subarray(0, 4), single, rightHanded);
if (single[0] !== 0) {
throw new Error("Unexpected single-splat index");
}
_native.sortSplats(modelView, new Float32Array(0), new Float32Array(0), rightHanded);
}
}
function expectMatrixError(matrix, message) {
let error;
try {
_native.sortSplats({ _m: matrix }, positions, new Float32Array(5), false);
} catch (caught) {
error = caught;
}
// JSI wraps exceptions thrown from host functions with this prefix.
if (!(error instanceof Error) ||
(error.message !== message && error.message !== "Exception in HostFunction: " + message)) {
throw new Error("Invalid matrix must report: " + message +
"; received: " + String(error));
}
}
for (const matrix of [undefined, null, {}, 42, new Uint8Array(16), new Float64Array(16)]) {
expectMatrixError(matrix, "sortSplats requires modelView._m to be a Float32Array or Array.");
}
for (const component of [2, 6, 10]) {
const matrix = new Array(16).fill(0);
delete matrix[component];
expectMatrixError(matrix, "sortSplats requires modelView._m[2], [6], [10] to be numbers.");
for (const value of [undefined, null, "2", false, {}]) {
matrix[component] = value;
expectMatrixError(matrix, "sortSplats requires modelView._m[2], [6], [10] to be numbers.");
}
}
})();
)", "native-splat-matrix-storage.js");
completed.set_value();
}
catch (const Napi::Error& error)
{
completed.set_exception(std::make_exception_ptr(std::runtime_error{Napi::GetErrorString(error)}));
}
catch (...)
{
completed.set_exception(std::current_exception());
}
});
if (completion.wait_for(std::chrono::seconds{30}) != std::future_status::ready)
{
// AppRuntime teardown joins the worker; returning would hang if that worker is stuck.
ADD_FAILURE() << "Timed out waiting for NativeOptimizations matrix storage regression";
std::quick_exit(1);
}
EXPECT_NO_THROW(completion.get());
#endif
}
22 changes: 20 additions & 2 deletions Plugins/NativeOptimizations/Source/NativeOptimizations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,13 @@ namespace
void sortSplats(const Napi::CallbackInfo& info)
{
const auto modelView{ info[0].As<Napi::Object>() };
const auto m{ modelView.Get("_m").As<Napi::Float32Array>() };
const auto matrixStorage{ modelView.Get("_m") };
if (!matrixStorage.IsArray() &&
!(matrixStorage.IsTypedArray() && matrixStorage.As<Napi::TypedArray>().TypedArrayType() == napi_float32_array))
{
throw Napi::TypeError::New(info.Env(), "sortSplats requires modelView._m to be a Float32Array or Array.");
}
const auto m{ matrixStorage.As<Napi::Object>() };
Comment thread
bkaradzic-microsoft marked this conversation as resolved.
Comment thread
bkaradzic-microsoft marked this conversation as resolved.

auto positions{ info[1].As<Napi::Float32Array>() };

Expand All @@ -306,7 +312,19 @@ namespace
}

const auto splatCount = indices.ElementLength();
float vp[3] = { m[2u], m[6u], m[10u] };
// High-precision Babylon matrices use ordinary number arrays.
const auto m2{m.Get(2u)};
const auto m6{m.Get(6u)};
const auto m10{m.Get(10u)};
if (!m2.IsNumber() || !m6.IsNumber() || !m10.IsNumber())
{
throw Napi::TypeError::New(info.Env(), "sortSplats requires modelView._m[2], [6], [10] to be numbers.");
}
float vp[3] = {
m2.As<Napi::Number>().FloatValue(),
m6.As<Napi::Number>().FloatValue(),
m10.As<Napi::Number>().FloatValue()
};
Comment thread
bkaradzic-microsoft marked this conversation as resolved.
static std::vector<float> depthMix;

depthMix.resize(splatCount * 2);
Expand Down
Loading