|
32 | 32 | #include <filament/Camera.h> |
33 | 33 | #include <filament/Color.h> |
34 | 34 | #include <filament/ColorGrading.h> |
| 35 | +#include <filament/ColorSpace.h> |
35 | 36 | #include <filament/Engine.h> |
36 | 37 | #include <filament/FrameHistoryStream.h> |
37 | 38 | #include <filament/Frustum.h> |
|
66 | 67 |
|
67 | 68 | using namespace filament; |
68 | 69 | using namespace filament::math; |
| 70 | +using namespace filament::color; |
69 | 71 | using namespace utils; |
70 | 72 |
|
71 | 73 | static bool isGray(float3 const v) { |
@@ -1269,6 +1271,135 @@ TEST(FilamentTest, ColorGradingMediumNeonValidation) { |
1269 | 1271 | Engine::destroy((Engine**)&engine); |
1270 | 1272 | } |
1271 | 1273 |
|
| 1274 | + |
| 1275 | +TEST(FilamentTest, ColorGradingNeonLinearValidation) { |
| 1276 | + Engine* engine = Engine::Builder() |
| 1277 | + .backend(Engine::Backend::NOOP) |
| 1278 | + .feature("engine.color_grading.use_optimized_default_builder", true) |
| 1279 | + .build(); |
| 1280 | + ASSERT_NE(engine, nullptr); |
| 1281 | + |
| 1282 | + struct LutData { |
| 1283 | + std::vector<uint32_t> pixels; |
| 1284 | + uint32_t width, height, depth; |
| 1285 | + }; |
| 1286 | + |
| 1287 | + auto exporter = [](void const* data, size_t const size, backend::PixelDataFormat format, |
| 1288 | + backend::PixelDataType type, uint32_t const w, uint32_t const h, uint32_t const d, void* user) { |
| 1289 | + auto* target = static_cast<LutData*>(user); |
| 1290 | + target->width = w; target->height = h; target->depth = d; |
| 1291 | + target->pixels.resize(size / sizeof(uint32_t)); |
| 1292 | + memcpy(target->pixels.data(), data, size); |
| 1293 | + }; |
| 1294 | + |
| 1295 | + LutData neonLut, scalarLut; |
| 1296 | + |
| 1297 | + // 1. Generate Vectorized Linear LUT (fastMath = true) |
| 1298 | + ColorGrading const* cgNeon = ColorGrading::Builder() |
| 1299 | + .fastMath(true) |
| 1300 | + .outputColorSpace(Rec709-Linear-D65) |
| 1301 | + .exportLut(exporter, &neonLut) |
| 1302 | + .build(*engine); |
| 1303 | + |
| 1304 | + // 2. Generate Scalar Linear LUT (fastMath = false) |
| 1305 | + ColorGrading const* cgScalar = ColorGrading::Builder() |
| 1306 | + .fastMath(false) |
| 1307 | + .outputColorSpace(Rec709-Linear-D65) |
| 1308 | + .exportLut(exporter, &scalarLut) |
| 1309 | + .build(*engine); |
| 1310 | + |
| 1311 | + // 3. Verify Metadata and Pixels |
| 1312 | + ASSERT_EQ(neonLut.width, scalarLut.width); |
| 1313 | + ASSERT_EQ(neonLut.pixels.size(), scalarLut.pixels.size()); |
| 1314 | + |
| 1315 | + for (size_t i = 0; i < neonLut.pixels.size(); i++) { |
| 1316 | + uint32_t const cn = neonLut.pixels[i]; |
| 1317 | + uint32_t const cs = scalarLut.pixels[i]; |
| 1318 | + |
| 1319 | + // Unpack 10-bit channels and verify with ±5 tolerance |
| 1320 | + EXPECT_NEAR(int(cn & 0x3FF), int(cs & 0x3FF), 5); |
| 1321 | + EXPECT_NEAR(int((cn >> 10) & 0x3FF), int((cs >> 10) & 0x3FF), 5); |
| 1322 | + EXPECT_NEAR(int((cn >> 20) & 0x3FF), int((cs >> 20) & 0x3FF), 5); |
| 1323 | + } |
| 1324 | + |
| 1325 | + engine->destroy(cgNeon); |
| 1326 | + engine->destroy(cgScalar); |
| 1327 | + Engine::destroy((Engine**)&engine); |
| 1328 | +} |
| 1329 | + |
| 1330 | +TEST(FilamentTest, ColorGradingMediumNeonLinearValidation) { |
| 1331 | + Engine* engine = Engine::Builder() |
| 1332 | + .backend(Engine::Backend::NOOP) |
| 1333 | + .feature("engine.color_grading.use_optimized_default_builder", true) |
| 1334 | + .build(); |
| 1335 | + ASSERT_NE(engine, nullptr); |
| 1336 | + |
| 1337 | + struct LutData { |
| 1338 | + std::vector<uint32_t> pixels; |
| 1339 | + uint32_t width, height, depth; |
| 1340 | + }; |
| 1341 | + |
| 1342 | + auto exporter = [](void const* data, size_t const size, backend::PixelDataFormat format, |
| 1343 | + backend::PixelDataType type, uint32_t const w, uint32_t const h, uint32_t const d, void* user) { |
| 1344 | + auto* target = static_cast<LutData*>(user); |
| 1345 | + target->width = w; target->height = h; target->depth = d; |
| 1346 | + target->pixels.resize(size / sizeof(uint32_t)); |
| 1347 | + memcpy(target->pixels.data(), data, size); |
| 1348 | + }; |
| 1349 | + |
| 1350 | + LutData neonLut, scalarLut; |
| 1351 | + |
| 1352 | + // 1. Generate Vectorized Medium Linear LUT (fastMath = true, hasAdjustments = true) |
| 1353 | + ColorGrading const* cgNeon = ColorGrading::Builder() |
| 1354 | + .fastMath(true) |
| 1355 | + .outputColorSpace(Rec709-Linear-D65) |
| 1356 | + .contrast(1.2f) |
| 1357 | + .saturation(1.1f) |
| 1358 | + .vibrance(1.15f) |
| 1359 | + .exposure(0.1f) |
| 1360 | + .shadowsMidtonesHighlights( |
| 1361 | + {0.95f, 1.0f, 1.05f, 0.0f}, |
| 1362 | + {1.0f, 1.05f, 1.0f, 0.0f}, |
| 1363 | + {1.05f, 1.0f, 0.95f, 0.0f}, |
| 1364 | + {0.0f, 0.33f, 0.55f, 1.0f}) |
| 1365 | + .exportLut(exporter, &neonLut) |
| 1366 | + .build(*engine); |
| 1367 | + |
| 1368 | + // 2. Generate Scalar Medium Linear LUT (fastMath = false, hasAdjustments = true) |
| 1369 | + ColorGrading const* cgScalar = ColorGrading::Builder() |
| 1370 | + .fastMath(false) |
| 1371 | + .outputColorSpace(Rec709-Linear-D65) |
| 1372 | + .contrast(1.2f) |
| 1373 | + .saturation(1.1f) |
| 1374 | + .vibrance(1.15f) |
| 1375 | + .exposure(0.1f) |
| 1376 | + .shadowsMidtonesHighlights( |
| 1377 | + {0.95f, 1.0f, 1.05f, 0.0f}, |
| 1378 | + {1.0f, 1.05f, 1.0f, 0.0f}, |
| 1379 | + {1.05f, 1.0f, 0.95f, 0.0f}, |
| 1380 | + {0.0f, 0.33f, 0.55f, 1.0f}) |
| 1381 | + .exportLut(exporter, &scalarLut) |
| 1382 | + .build(*engine); |
| 1383 | + |
| 1384 | + // 3. Verify Metadata and Pixels |
| 1385 | + ASSERT_EQ(neonLut.width, scalarLut.width); |
| 1386 | + ASSERT_EQ(neonLut.pixels.size(), scalarLut.pixels.size()); |
| 1387 | + |
| 1388 | + for (size_t i = 0; i < neonLut.pixels.size(); i++) { |
| 1389 | + uint32_t const cn = neonLut.pixels[i]; |
| 1390 | + uint32_t const cs = scalarLut.pixels[i]; |
| 1391 | + |
| 1392 | + // Unpack 10-bit channels and verify with ±125 tolerance for chained Remez vs exact math |
| 1393 | + EXPECT_NEAR(int(cn & 0x3FF), int(cs & 0x3FF), 125); |
| 1394 | + EXPECT_NEAR(int((cn >> 10) & 0x3FF), int((cs >> 10) & 0x3FF), 125); |
| 1395 | + EXPECT_NEAR(int((cn >> 20) & 0x3FF), int((cs >> 20) & 0x3FF), 125); |
| 1396 | + } |
| 1397 | + |
| 1398 | + engine->destroy(cgNeon); |
| 1399 | + engine->destroy(cgScalar); |
| 1400 | + Engine::destroy((Engine**)&engine); |
| 1401 | +} |
| 1402 | + |
1272 | 1403 | TEST(FilamentTest, ColorGradingAdvancedNeonValidation) { |
1273 | 1404 | Engine* engine = Engine::Builder() |
1274 | 1405 | .backend(Engine::Backend::NOOP) |
|
0 commit comments