From d21a99f49bd83da0a33244faec087462335f335e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 27 Aug 2020 11:40:23 +0200 Subject: [PATCH 001/388] tests/api: adjust clear color value 0.3 * 255 = 76.5, which can round to 0x4C or 0x4D depending on the implementation. Use the unambigous 0.4 (0x66) instead. --- tests/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/api.py b/tests/api.py index 75e59ea169..1178a01d82 100644 --- a/tests/api.py +++ b/tests/api.py @@ -67,9 +67,9 @@ def api_reconfigure_clearcolor(width=16, height=16): assert viewer.draw(0) == 0 assert zlib.crc32(capture_buffer) == 0xb4bd32fa assert viewer.configure(offscreen=1, width=width, height=height, backend=_backend, capture_buffer=capture_buffer, - clear_color=(0.3, 0.3, 0.3, 1.0)) == 0 + clear_color=(0.4, 0.4, 0.4, 1.0)) == 0 assert viewer.draw(0) == 0 - assert zlib.crc32(capture_buffer) == 0xfeb0bb01 + assert zlib.crc32(capture_buffer) == 0x05c44869 del capture_buffer del viewer From ba57a258660a1404c3a40a021223bc0e5f2cca6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 27 Jul 2020 16:46:08 +0200 Subject: [PATCH 002/388] drawutils: make font declaration more compact (style) --- libnodegl/drawutils.c | 192 ++++++++++++++---------------------------- 1 file changed, 64 insertions(+), 128 deletions(-) diff --git a/libnodegl/drawutils.c b/libnodegl/drawutils.c index 05c864833e..5215dbb3f4 100644 --- a/libnodegl/drawutils.c +++ b/libnodegl/drawutils.c @@ -24,134 +24,70 @@ #define BYTES_PER_PIXEL 4 /* RGBA */ static const uint8_t font8[128][8] = { - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08, 0x00}, - {0x00, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x14, 0x3e, 0x14, 0x14, 0x3e, 0x14, 0x00}, - {0x08, 0x3c, 0x0a, 0x1c, 0x28, 0x28, 0x1e, 0x08}, - {0x00, 0x00, 0x22, 0x10, 0x08, 0x04, 0x22, 0x00}, - {0x00, 0x0c, 0x12, 0x0c, 0x52, 0x32, 0x6c, 0x00}, - {0x00, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x18, 0x04, 0x04, 0x04, 0x04, 0x18, 0x00}, - {0x00, 0x0c, 0x10, 0x10, 0x10, 0x10, 0x0c, 0x00}, - {0x08, 0x2a, 0x1c, 0x7f, 0x1c, 0x2a, 0x08, 0x00}, - {0x00, 0x00, 0x08, 0x08, 0x3e, 0x08, 0x08, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x02}, - {0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00}, - {0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00}, - {0x00, 0x1c, 0x32, 0x2a, 0x2a, 0x26, 0x1c, 0x00}, - {0x00, 0x08, 0x0c, 0x08, 0x08, 0x08, 0x1c, 0x00}, - {0x00, 0x1c, 0x22, 0x20, 0x1c, 0x02, 0x3e, 0x00}, - {0x00, 0x1c, 0x22, 0x18, 0x20, 0x22, 0x1c, 0x00}, - {0x00, 0x18, 0x14, 0x12, 0x3e, 0x10, 0x38, 0x00}, - {0x00, 0x3e, 0x02, 0x1e, 0x20, 0x22, 0x1c, 0x00}, - {0x00, 0x3c, 0x02, 0x1e, 0x22, 0x22, 0x1c, 0x00}, - {0x00, 0x3e, 0x20, 0x10, 0x08, 0x04, 0x04, 0x00}, - {0x00, 0x1c, 0x22, 0x1c, 0x22, 0x22, 0x1c, 0x00}, - {0x00, 0x1c, 0x22, 0x22, 0x3c, 0x20, 0x1e, 0x00}, - {0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00}, - {0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x02}, - {0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, 0x00}, - {0x00, 0x00, 0x3e, 0x00, 0x00, 0x3e, 0x00, 0x00}, - {0x02, 0x04, 0x08, 0x10, 0x08, 0x04, 0x02, 0x00}, - {0x00, 0x1c, 0x22, 0x20, 0x18, 0x04, 0x00, 0x04}, - {0x00, 0x3c, 0x22, 0x3a, 0x1a, 0x42, 0x3c, 0x00}, - {0x00, 0x1c, 0x22, 0x22, 0x3e, 0x22, 0x22, 0x00}, - {0x00, 0x1e, 0x22, 0x1e, 0x22, 0x22, 0x1e, 0x00}, - {0x00, 0x3c, 0x02, 0x02, 0x02, 0x02, 0x3c, 0x00}, - {0x00, 0x1e, 0x22, 0x22, 0x22, 0x22, 0x1e, 0x00}, - {0x00, 0x3e, 0x02, 0x1e, 0x02, 0x02, 0x3e, 0x00}, - {0x00, 0x3e, 0x02, 0x1e, 0x02, 0x02, 0x02, 0x00}, - {0x00, 0x1c, 0x22, 0x02, 0x32, 0x22, 0x3c, 0x00}, - {0x00, 0x22, 0x22, 0x3e, 0x22, 0x22, 0x22, 0x00}, - {0x00, 0x1c, 0x08, 0x08, 0x08, 0x08, 0x1c, 0x00}, - {0x00, 0x20, 0x20, 0x20, 0x20, 0x22, 0x1c, 0x00}, - {0x00, 0x12, 0x0a, 0x06, 0x0a, 0x12, 0x22, 0x00}, - {0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x3e, 0x00}, - {0x00, 0x22, 0x36, 0x2a, 0x22, 0x22, 0x22, 0x00}, - {0x00, 0x22, 0x26, 0x2a, 0x32, 0x22, 0x22, 0x00}, - {0x00, 0x1c, 0x22, 0x22, 0x22, 0x22, 0x1c, 0x00}, - {0x00, 0x1e, 0x22, 0x22, 0x1e, 0x02, 0x02, 0x00}, - {0x00, 0x1c, 0x22, 0x22, 0x22, 0x1c, 0x30, 0x00}, - {0x00, 0x1e, 0x22, 0x22, 0x1e, 0x22, 0x22, 0x00}, - {0x00, 0x3c, 0x02, 0x1c, 0x20, 0x20, 0x1e, 0x00}, - {0x00, 0x3e, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00}, - {0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c, 0x00}, - {0x00, 0x22, 0x22, 0x22, 0x14, 0x14, 0x08, 0x00}, - {0x00, 0x22, 0x22, 0x22, 0x2a, 0x36, 0x22, 0x00}, - {0x22, 0x22, 0x14, 0x08, 0x14, 0x22, 0x22, 0x00}, - {0x00, 0x22, 0x22, 0x22, 0x14, 0x08, 0x08, 0x00}, - {0x00, 0x3e, 0x10, 0x08, 0x04, 0x02, 0x3e, 0x00}, - {0x00, 0x1c, 0x04, 0x04, 0x04, 0x04, 0x1c, 0x00}, - {0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00}, - {0x00, 0x1c, 0x10, 0x10, 0x10, 0x10, 0x1c, 0x00}, - {0x00, 0x08, 0x14, 0x22, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00}, - {0x00, 0x02, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x3c, 0x22, 0x22, 0x22, 0x5c, 0x00}, - {0x02, 0x02, 0x1e, 0x22, 0x22, 0x22, 0x1e, 0x00}, - {0x00, 0x00, 0x1c, 0x22, 0x02, 0x02, 0x3c, 0x00}, - {0x20, 0x20, 0x3c, 0x22, 0x22, 0x22, 0x3c, 0x00}, - {0x00, 0x00, 0x1c, 0x22, 0x3e, 0x02, 0x3c, 0x00}, - {0x00, 0x38, 0x04, 0x1e, 0x04, 0x04, 0x04, 0x00}, - {0x00, 0x00, 0x3c, 0x22, 0x22, 0x3c, 0x20, 0x1e}, - {0x02, 0x02, 0x02, 0x1e, 0x22, 0x22, 0x22, 0x00}, - {0x00, 0x04, 0x00, 0x06, 0x04, 0x04, 0x0c, 0x00}, - {0x00, 0x10, 0x00, 0x10, 0x10, 0x10, 0x12, 0x0c}, - {0x02, 0x02, 0x12, 0x0a, 0x06, 0x0a, 0x12, 0x00}, - {0x0c, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1c, 0x00}, - {0x00, 0x00, 0x1e, 0x2a, 0x2a, 0x2a, 0x2a, 0x00}, - {0x00, 0x00, 0x1a, 0x26, 0x22, 0x22, 0x22, 0x00}, - {0x00, 0x00, 0x1c, 0x22, 0x22, 0x22, 0x1c, 0x00}, - {0x00, 0x00, 0x1e, 0x22, 0x22, 0x1e, 0x02, 0x02}, - {0x00, 0x00, 0x3c, 0x22, 0x22, 0x3c, 0x20, 0x20}, - {0x00, 0x00, 0x1a, 0x26, 0x02, 0x02, 0x02, 0x00}, - {0x00, 0x00, 0x3c, 0x02, 0x1c, 0x20, 0x1e, 0x00}, - {0x04, 0x04, 0x1e, 0x04, 0x04, 0x04, 0x18, 0x00}, - {0x00, 0x00, 0x22, 0x22, 0x22, 0x32, 0x2c, 0x00}, - {0x00, 0x00, 0x22, 0x22, 0x14, 0x14, 0x08, 0x00}, - {0x00, 0x00, 0x22, 0x2a, 0x2a, 0x2a, 0x14, 0x00}, - {0x00, 0x00, 0x22, 0x14, 0x08, 0x14, 0x22, 0x00}, - {0x00, 0x00, 0x22, 0x22, 0x22, 0x3c, 0x20, 0x1e}, - {0x00, 0x00, 0x3e, 0x10, 0x08, 0x04, 0x3e, 0x00}, - {0x18, 0x04, 0x04, 0x02, 0x04, 0x04, 0x18, 0x00}, - {0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00}, - {0x0c, 0x10, 0x10, 0x20, 0x10, 0x10, 0x0c, 0x00}, - {0x00, 0x00, 0x00, 0x2c, 0x1a, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08, 0x00}, + {0x00, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x14, 0x3e, 0x14, 0x14, 0x3e, 0x14, 0x00}, + {0x08, 0x3c, 0x0a, 0x1c, 0x28, 0x28, 0x1e, 0x08}, {0x00, 0x00, 0x22, 0x10, 0x08, 0x04, 0x22, 0x00}, + {0x00, 0x0c, 0x12, 0x0c, 0x52, 0x32, 0x6c, 0x00}, {0x00, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0x00, 0x18, 0x04, 0x04, 0x04, 0x04, 0x18, 0x00}, {0x00, 0x0c, 0x10, 0x10, 0x10, 0x10, 0x0c, 0x00}, + {0x08, 0x2a, 0x1c, 0x7f, 0x1c, 0x2a, 0x08, 0x00}, {0x00, 0x00, 0x08, 0x08, 0x3e, 0x08, 0x08, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x02}, {0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00}, + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00}, {0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00}, + {0x00, 0x1c, 0x32, 0x2a, 0x2a, 0x26, 0x1c, 0x00}, {0x00, 0x08, 0x0c, 0x08, 0x08, 0x08, 0x1c, 0x00}, + {0x00, 0x1c, 0x22, 0x20, 0x1c, 0x02, 0x3e, 0x00}, {0x00, 0x1c, 0x22, 0x18, 0x20, 0x22, 0x1c, 0x00}, + {0x00, 0x18, 0x14, 0x12, 0x3e, 0x10, 0x38, 0x00}, {0x00, 0x3e, 0x02, 0x1e, 0x20, 0x22, 0x1c, 0x00}, + {0x00, 0x3c, 0x02, 0x1e, 0x22, 0x22, 0x1c, 0x00}, {0x00, 0x3e, 0x20, 0x10, 0x08, 0x04, 0x04, 0x00}, + {0x00, 0x1c, 0x22, 0x1c, 0x22, 0x22, 0x1c, 0x00}, {0x00, 0x1c, 0x22, 0x22, 0x3c, 0x20, 0x1e, 0x00}, + {0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00}, {0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x02}, + {0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, 0x00}, {0x00, 0x00, 0x3e, 0x00, 0x00, 0x3e, 0x00, 0x00}, + {0x02, 0x04, 0x08, 0x10, 0x08, 0x04, 0x02, 0x00}, {0x00, 0x1c, 0x22, 0x20, 0x18, 0x04, 0x00, 0x04}, + {0x00, 0x3c, 0x22, 0x3a, 0x1a, 0x42, 0x3c, 0x00}, {0x00, 0x1c, 0x22, 0x22, 0x3e, 0x22, 0x22, 0x00}, + {0x00, 0x1e, 0x22, 0x1e, 0x22, 0x22, 0x1e, 0x00}, {0x00, 0x3c, 0x02, 0x02, 0x02, 0x02, 0x3c, 0x00}, + {0x00, 0x1e, 0x22, 0x22, 0x22, 0x22, 0x1e, 0x00}, {0x00, 0x3e, 0x02, 0x1e, 0x02, 0x02, 0x3e, 0x00}, + {0x00, 0x3e, 0x02, 0x1e, 0x02, 0x02, 0x02, 0x00}, {0x00, 0x1c, 0x22, 0x02, 0x32, 0x22, 0x3c, 0x00}, + {0x00, 0x22, 0x22, 0x3e, 0x22, 0x22, 0x22, 0x00}, {0x00, 0x1c, 0x08, 0x08, 0x08, 0x08, 0x1c, 0x00}, + {0x00, 0x20, 0x20, 0x20, 0x20, 0x22, 0x1c, 0x00}, {0x00, 0x12, 0x0a, 0x06, 0x0a, 0x12, 0x22, 0x00}, + {0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x3e, 0x00}, {0x00, 0x22, 0x36, 0x2a, 0x22, 0x22, 0x22, 0x00}, + {0x00, 0x22, 0x26, 0x2a, 0x32, 0x22, 0x22, 0x00}, {0x00, 0x1c, 0x22, 0x22, 0x22, 0x22, 0x1c, 0x00}, + {0x00, 0x1e, 0x22, 0x22, 0x1e, 0x02, 0x02, 0x00}, {0x00, 0x1c, 0x22, 0x22, 0x22, 0x1c, 0x30, 0x00}, + {0x00, 0x1e, 0x22, 0x22, 0x1e, 0x22, 0x22, 0x00}, {0x00, 0x3c, 0x02, 0x1c, 0x20, 0x20, 0x1e, 0x00}, + {0x00, 0x3e, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00}, {0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c, 0x00}, + {0x00, 0x22, 0x22, 0x22, 0x14, 0x14, 0x08, 0x00}, {0x00, 0x22, 0x22, 0x22, 0x2a, 0x36, 0x22, 0x00}, + {0x22, 0x22, 0x14, 0x08, 0x14, 0x22, 0x22, 0x00}, {0x00, 0x22, 0x22, 0x22, 0x14, 0x08, 0x08, 0x00}, + {0x00, 0x3e, 0x10, 0x08, 0x04, 0x02, 0x3e, 0x00}, {0x00, 0x1c, 0x04, 0x04, 0x04, 0x04, 0x1c, 0x00}, + {0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00}, {0x00, 0x1c, 0x10, 0x10, 0x10, 0x10, 0x1c, 0x00}, + {0x00, 0x08, 0x14, 0x22, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00}, + {0x00, 0x02, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x3c, 0x22, 0x22, 0x22, 0x5c, 0x00}, + {0x02, 0x02, 0x1e, 0x22, 0x22, 0x22, 0x1e, 0x00}, {0x00, 0x00, 0x1c, 0x22, 0x02, 0x02, 0x3c, 0x00}, + {0x20, 0x20, 0x3c, 0x22, 0x22, 0x22, 0x3c, 0x00}, {0x00, 0x00, 0x1c, 0x22, 0x3e, 0x02, 0x3c, 0x00}, + {0x00, 0x38, 0x04, 0x1e, 0x04, 0x04, 0x04, 0x00}, {0x00, 0x00, 0x3c, 0x22, 0x22, 0x3c, 0x20, 0x1e}, + {0x02, 0x02, 0x02, 0x1e, 0x22, 0x22, 0x22, 0x00}, {0x00, 0x04, 0x00, 0x06, 0x04, 0x04, 0x0c, 0x00}, + {0x00, 0x10, 0x00, 0x10, 0x10, 0x10, 0x12, 0x0c}, {0x02, 0x02, 0x12, 0x0a, 0x06, 0x0a, 0x12, 0x00}, + {0x0c, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1c, 0x00}, {0x00, 0x00, 0x1e, 0x2a, 0x2a, 0x2a, 0x2a, 0x00}, + {0x00, 0x00, 0x1a, 0x26, 0x22, 0x22, 0x22, 0x00}, {0x00, 0x00, 0x1c, 0x22, 0x22, 0x22, 0x1c, 0x00}, + {0x00, 0x00, 0x1e, 0x22, 0x22, 0x1e, 0x02, 0x02}, {0x00, 0x00, 0x3c, 0x22, 0x22, 0x3c, 0x20, 0x20}, + {0x00, 0x00, 0x1a, 0x26, 0x02, 0x02, 0x02, 0x00}, {0x00, 0x00, 0x3c, 0x02, 0x1c, 0x20, 0x1e, 0x00}, + {0x04, 0x04, 0x1e, 0x04, 0x04, 0x04, 0x18, 0x00}, {0x00, 0x00, 0x22, 0x22, 0x22, 0x32, 0x2c, 0x00}, + {0x00, 0x00, 0x22, 0x22, 0x14, 0x14, 0x08, 0x00}, {0x00, 0x00, 0x22, 0x2a, 0x2a, 0x2a, 0x14, 0x00}, + {0x00, 0x00, 0x22, 0x14, 0x08, 0x14, 0x22, 0x00}, {0x00, 0x00, 0x22, 0x22, 0x22, 0x3c, 0x20, 0x1e}, + {0x00, 0x00, 0x3e, 0x10, 0x08, 0x04, 0x3e, 0x00}, {0x18, 0x04, 0x04, 0x02, 0x04, 0x04, 0x18, 0x00}, + {0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00}, {0x0c, 0x10, 0x10, 0x20, 0x10, 0x10, 0x0c, 0x00}, + {0x00, 0x00, 0x00, 0x2c, 0x1a, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, }; static inline void set_color(uint8_t *p, uint32_t rgba) From f75439d9c1e747feb8e9609267b7bdfef6cb6d99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 27 Jul 2020 17:01:58 +0200 Subject: [PATCH 003/388] drawutils: reduce font8 size by removing the 32 first chars This saves 256B of zeroes. --- libnodegl/drawutils.c | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/libnodegl/drawutils.c b/libnodegl/drawutils.c index 5215dbb3f4..c7650116cc 100644 --- a/libnodegl/drawutils.c +++ b/libnodegl/drawutils.c @@ -23,23 +23,9 @@ #define BYTES_PER_PIXEL 4 /* RGBA */ -static const uint8_t font8[128][8] = { - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, +#define FONT_OFFSET 32 + +static const uint8_t font8[128 - FONT_OFFSET][8] = { {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08, 0x00}, {0x00, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x14, 0x3e, 0x14, 0x14, 0x3e, 0x14, 0x00}, {0x08, 0x3c, 0x0a, 0x1c, 0x28, 0x28, 0x1e, 0x08}, {0x00, 0x00, 0x22, 0x10, 0x08, 0x04, 0x22, 0x00}, @@ -123,6 +109,8 @@ void ngli_drawutils_print(struct canvas *canvas, int x, int y, const char *str, px = 0; continue; } + + const uint8_t *c = str[i] < FONT_OFFSET || str[i] > 127 ? font8[0] : font8[str[i] - FONT_OFFSET]; for (int char_y = 0; char_y < NGLI_FONT_H; char_y++) { for (int char_x = 0; char_x < NGLI_FONT_W; char_x++) { const int pix_x = x + px * NGLI_FONT_W + char_x; @@ -130,7 +118,7 @@ void ngli_drawutils_print(struct canvas *canvas, int x, int y, const char *str, if (pix_x < 0 || pix_y < 0 || pix_x >= canvas->w || pix_y >= canvas->h) continue; uint8_t *p = get_pixel_pos_buf(canvas, pix_x, pix_y); - if (font8[str[i] & 0x7f][char_y] & (1 << char_x)) + if (c[char_y] & (1 << char_x)) set_color(p, color); } } From 894e5f27622d50693d4126d6772f99173c545e67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 28 Jul 2020 10:44:15 +0200 Subject: [PATCH 004/388] text: remove mipmap_filter parameter This is too much implementation specific and might not make much sense with the switch to an atlas. The node will make sure to provide the best output no matter what. --- libnodegl/doc/libnodegl.md | 17 ++++++++--------- libnodegl/node_text.c | 6 +----- libnodegl/nodes.specs | 1 - 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/libnodegl/doc/libnodegl.md b/libnodegl/doc/libnodegl.md index 5e3fc1cdaf..ef014345d6 100644 --- a/libnodegl/doc/libnodegl.md +++ b/libnodegl/doc/libnodegl.md @@ -582,7 +582,6 @@ Parameter | Live-chg. | Type | Description | Default `aspect_ratio` | | [`rational`](#parameter-types) | box aspect ratio | `min_filter` | | [`filter`](#filter-choices) | rasterized text texture minifying function | `linear` `mag_filter` | | [`filter`](#filter-choices) | rasterized text texture magnification function | `nearest` -`mipmap_filter` | | [`mipmap_filter`](#mipmap_filter-choices) | rasterized text texture minifying mipmap function | `linear` **Source**: [node_text.c](/libnodegl/node_text.c) @@ -1456,14 +1455,6 @@ Constant | Description `nearest` | nearest filtering `linear` | linear filtering -## mipmap_filter choices - -Constant | Description --------- | ----------- -`none` | no mipmap generation -`nearest` | nearest filtering -`linear` | linear filtering - ## format choices Constant | Description @@ -1519,6 +1510,14 @@ Constant | Description `auto_depth` | select automatically the preferred depth format `auto_depth_stencil` | select automatically the preferred depth + stencil format +## mipmap_filter choices + +Constant | Description +-------- | ----------- +`none` | no mipmap generation +`nearest` | nearest filtering +`linear` | linear filtering + ## wrap choices Constant | Description diff --git a/libnodegl/node_text.c b/libnodegl/node_text.c index 9e4c264aa2..c30e225e56 100644 --- a/libnodegl/node_text.c +++ b/libnodegl/node_text.c @@ -55,7 +55,6 @@ struct text_priv { int aspect_ratio[2]; int min_filter; int mag_filter; - int mipmap_filter; struct texture *texture; struct canvas canvas; @@ -124,9 +123,6 @@ static const struct node_param text_params[] = { {"mag_filter", PARAM_TYPE_SELECT, OFFSET(mag_filter), {.i64=NGLI_FILTER_NEAREST}, .choices=&ngli_filter_choices, .desc=NGLI_DOCSTRING("rasterized text texture magnification function")}, - {"mipmap_filter", PARAM_TYPE_SELECT, OFFSET(mipmap_filter), {.i64=NGLI_MIPMAP_FILTER_LINEAR}, - .choices=&ngli_mipmap_filter_choices, - .desc=NGLI_DOCSTRING("rasterized text texture minifying mipmap function")}, {NULL} }; @@ -261,7 +257,7 @@ static int text_init(struct ngl_node *node) tex_params.format = NGLI_FORMAT_R8G8B8A8_UNORM; tex_params.min_filter = s->min_filter; tex_params.mag_filter = s->mag_filter; - tex_params.mipmap_filter = s->mipmap_filter; + tex_params.mipmap_filter = NGLI_MIPMAP_FILTER_LINEAR; s->texture = ngli_texture_create(gctx); if (!s->texture) return NGL_ERROR_MEMORY; diff --git a/libnodegl/nodes.specs b/libnodegl/nodes.specs index ab987b116e..cabbe9b1f5 100644 --- a/libnodegl/nodes.specs +++ b/libnodegl/nodes.specs @@ -341,7 +341,6 @@ - [aspect_ratio, rational] - [min_filter, select] - [mag_filter, select] - - [mipmap_filter, select] - Texture2D: - [format, select] From 65594798a39e1fb2ab39e1e11d53c1fcdf4b3524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 28 Jul 2020 10:46:42 +0200 Subject: [PATCH 005/388] text: remove min/mag filter parameter Similar to the previous commit, this is too close too much tied to the current implementation. If we're switching to distance field approach in the future, this option will typically get in the way. --- libnodegl/doc/libnodegl.md | 16 +++++++--------- libnodegl/node_text.c | 12 ++---------- libnodegl/nodes.specs | 2 -- 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/libnodegl/doc/libnodegl.md b/libnodegl/doc/libnodegl.md index ef014345d6..b79bdff92d 100644 --- a/libnodegl/doc/libnodegl.md +++ b/libnodegl/doc/libnodegl.md @@ -580,8 +580,6 @@ Parameter | Live-chg. | Type | Description | Default `valign` | | [`valign`](#valign-choices) | vertical alignment of the text in the box | `center` `halign` | | [`halign`](#halign-choices) | horizontal alignment of the text in the box | `center` `aspect_ratio` | | [`rational`](#parameter-types) | box aspect ratio | -`min_filter` | | [`filter`](#filter-choices) | rasterized text texture minifying function | `linear` -`mag_filter` | | [`filter`](#filter-choices) | rasterized text texture magnification function | `nearest` **Source**: [node_text.c](/libnodegl/node_text.c) @@ -1448,13 +1446,6 @@ Constant | Description `right` | right positioned `left` | left positioned -## filter choices - -Constant | Description --------- | ----------- -`nearest` | nearest filtering -`linear` | linear filtering - ## format choices Constant | Description @@ -1510,6 +1501,13 @@ Constant | Description `auto_depth` | select automatically the preferred depth format `auto_depth_stencil` | select automatically the preferred depth + stencil format +## filter choices + +Constant | Description +-------- | ----------- +`nearest` | nearest filtering +`linear` | linear filtering + ## mipmap_filter choices Constant | Description diff --git a/libnodegl/node_text.c b/libnodegl/node_text.c index c30e225e56..40789b0c4d 100644 --- a/libnodegl/node_text.c +++ b/libnodegl/node_text.c @@ -53,8 +53,6 @@ struct text_priv { double font_scale; int valign, halign; int aspect_ratio[2]; - int min_filter; - int mag_filter; struct texture *texture; struct canvas canvas; @@ -117,12 +115,6 @@ static const struct node_param text_params[] = { .desc=NGLI_DOCSTRING("horizontal alignment of the text in the box")}, {"aspect_ratio", PARAM_TYPE_RATIONAL, OFFSET(aspect_ratio), .desc=NGLI_DOCSTRING("box aspect ratio")}, - {"min_filter", PARAM_TYPE_SELECT, OFFSET(min_filter), {.i64=NGLI_FILTER_LINEAR}, - .choices=&ngli_filter_choices, - .desc=NGLI_DOCSTRING("rasterized text texture minifying function")}, - {"mag_filter", PARAM_TYPE_SELECT, OFFSET(mag_filter), {.i64=NGLI_FILTER_NEAREST}, - .choices=&ngli_filter_choices, - .desc=NGLI_DOCSTRING("rasterized text texture magnification function")}, {NULL} }; @@ -255,8 +247,8 @@ static int text_init(struct ngl_node *node) tex_params.width = s->canvas.w; tex_params.height = s->canvas.h; tex_params.format = NGLI_FORMAT_R8G8B8A8_UNORM; - tex_params.min_filter = s->min_filter; - tex_params.mag_filter = s->mag_filter; + tex_params.min_filter = NGLI_FILTER_LINEAR; + tex_params.mag_filter = NGLI_FILTER_NEAREST; tex_params.mipmap_filter = NGLI_MIPMAP_FILTER_LINEAR; s->texture = ngli_texture_create(gctx); if (!s->texture) diff --git a/libnodegl/nodes.specs b/libnodegl/nodes.specs index cabbe9b1f5..0b441b85b7 100644 --- a/libnodegl/nodes.specs +++ b/libnodegl/nodes.specs @@ -339,8 +339,6 @@ - [valign, select] - [halign, select] - [aspect_ratio, rational] - - [min_filter, select] - - [mag_filter, select] - Texture2D: - [format, select] From d727554a1ccf81a85683b4117f70a581dfd03b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 28 Jul 2020 16:45:34 +0200 Subject: [PATCH 006/388] utils/examples: add toys to instrument specific nodes Currently only used for the Text node, this placeholder is meant for scenes that expose a maximum of settings for a given node in order to play with all the parameters. --- .../pynodegl_utils/examples/toys.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 pynodegl-utils/pynodegl_utils/examples/toys.py diff --git a/pynodegl-utils/pynodegl_utils/examples/toys.py b/pynodegl-utils/pynodegl_utils/examples/toys.py new file mode 100644 index 0000000000..c996947a87 --- /dev/null +++ b/pynodegl-utils/pynodegl_utils/examples/toys.py @@ -0,0 +1,62 @@ +# +# Copyright 2020 GoPro Inc. +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import pynodegl as ngl +from pynodegl_utils.misc import scene +from pynodegl_utils.toolbox.colors import COLORS + + +@scene( + txt=scene.Text(), + fg_color=scene.Color(), + bg_color=scene.Color(), + box_corner=scene.Vector(n=3, minv=(-1, -1, -1), maxv=(1, 1, 1)), + box_width=scene.Vector(n=3, minv=(-10, -10, -10), maxv=(10, 10, 10)), + box_height=scene.Vector(n=3, minv=(-10, -10, -10), maxv=(10, 10, 10)), + padding=scene.Range(range=[0, 100]), + font_scale=scene.Range(range=[0, 15], unit_base=100), + valign=scene.List(choices=('top', 'center', 'bottom')), + halign=scene.List(choices=('left', 'center', 'right')), +) +def text(cfg, + txt='the quick brown fox\njumps over the lazy dog', + fg_color=COLORS['cgreen'], + bg_color=(0.3, 0.3, 0.3, 1.0), + box_corner=(-1+.25, -1+.25, 0), + box_width=(1.5, 0, 0), + box_height=(0, 1.5, 0), + padding=2, + font_scale=1.3, + valign='center', + halign='center', +): + return ngl.Text(txt, + fg_color=fg_color, + bg_color=bg_color, + box_corner=box_corner, + box_width=box_width, + box_height=box_height, + padding=padding, + font_scale=font_scale, + valign=valign, + halign=halign, + aspect_ratio=cfg.aspect_ratio + ) From 27130f0cdbe4c5fc086add196b75167ef9ea7685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 28 Jul 2020 23:56:56 +0200 Subject: [PATCH 007/388] math_utils: add ngli_vec3_scale() --- libnodegl/math_utils.c | 7 +++++++ libnodegl/math_utils.h | 1 + 2 files changed, 8 insertions(+) diff --git a/libnodegl/math_utils.c b/libnodegl/math_utils.c index f278f991b3..2c65f16064 100644 --- a/libnodegl/math_utils.c +++ b/libnodegl/math_utils.c @@ -32,6 +32,13 @@ float ngli_vec3_length(const float *v) return sqrtf(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); } +void ngli_vec3_scale(float *dst, const float *v, float s) +{ + dst[0] = v[0] * s; + dst[1] = v[1] * s; + dst[2] = v[2] * s; +} + void ngli_vec3_sub(float *dst, const float *v1, const float *v2) { dst[0] = v1[0] - v2[0]; diff --git a/libnodegl/math_utils.h b/libnodegl/math_utils.h index 4b48107084..eeb5398e4e 100644 --- a/libnodegl/math_utils.h +++ b/libnodegl/math_utils.h @@ -29,6 +29,7 @@ #define NGLI_MIX(x, y, a) ((x)*(1.-(a)) + (y)*(a)) float ngli_vec3_length(const float *v); +void ngli_vec3_scale(float *dst, const float *v, const float s); void ngli_vec3_sub(float *dst, const float *v1, const float *v2); void ngli_vec3_norm(float *dst, const float *v); void ngli_vec3_cross(float *dst, const float *v1, const float *v2); From 63d1f9f4262ad35d6a21afba3ae1d89e023ed258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 27 Jul 2020 18:15:29 +0200 Subject: [PATCH 008/388] text: switch to an atlas based implementation This new implementation is motivated by two main reasons: - the texture can now be shared between all the Text instances - live changing becomes possible without reconstructing a texture The ref difference is because of precision inaccuracies in the current implementation: In test-text-align_cc, if we remove the font_scale and set the padding to 0, we will observe a 1280x800 export with a vertically centered text area of 1280x392. In this situation, the y offset happens to be 196 instead of 204 for a properly vertically centered text (204+392+204=800). This is due to the rounding in the padh / 2 operation: the texture is much smaller that the export (padh=25), so padh/2 will lead to either 12 (194 in the export) or 13 (212 in the export) if we were using (padh+1)/2. This is the root cause of the differences in the references, and it can not be adressed easily in the current implementation. The new implementation is not affected by this precision issue. Another notable difference is that now the text can overflow the specified bounding box (with a font scale > 1.0), while it was previously cropped. A stencil buffer could be used to crop, but it may alter the state in an unexpected way. For the record, here are the appropriate stencil settings (thanks Matthieu) for the background: stencil_test = 1; stencil_write_mask = 0xFF; stencil_func = NGLI_COMPARE_OP_ALWAYS; stencil_ref = 1; stencil_read_mask = 0xFF; stencil_fail = NGLI_STENCIL_OP_REPLACE; stencil_depth_fail = NGLI_STENCIL_OP_REPLACE; stencil_depth_pass = NGLI_STENCIL_OP_REPLACE; And for the foreground: stencil_test = 1; stencil_write_mask = 0; stencil_func = NGLI_COMPARE_OP_EQUAL; stencil_ref = 1; stencil_read_mask = 0xFF; stencil_fail = NGLI_STENCIL_OP_KEEP; stencil_depth_fail = NGLI_STENCIL_OP_KEEP; stencil_depth_pass = NGLI_STENCIL_OP_KEEP; --- libnodegl/Makefile | 2 +- libnodegl/drawutils.c | 59 +++++ libnodegl/drawutils.h | 2 + libnodegl/node_text.c | 480 ++++++++++++++++++++++++++--------- tests/refs/text_align_bc.ref | 2 +- tests/refs/text_align_cc.ref | 2 +- tests/refs/text_align_tc.ref | 2 +- tests/refs/text_align_tl.ref | 2 +- tests/refs/text_align_tr.ref | 2 +- tests/refs/text_colors.ref | 2 +- 10 files changed, 433 insertions(+), 122 deletions(-) diff --git a/libnodegl/Makefile b/libnodegl/Makefile index 7049f331fe..ce16b4d0dc 100644 --- a/libnodegl/Makefile +++ b/libnodegl/Makefile @@ -361,7 +361,7 @@ test_asm: test_asm.o math_utils.o $(LIB_OBJS_ARCH_$(ARCH)) test_colorconv: LDLIBS = $(PROJECT_LDLIBS) -lm test_colorconv: test_colorconv.o colorconv.o log.o test_darray: test_darray.o darray.o memory.o -test_draw: test_draw.o drawutils.o +test_draw: test_draw.o drawutils.o memory.o test_hmap: test_hmap.o utils.o memory.o test_utils: test_utils.o utils.o memory.o diff --git a/libnodegl/drawutils.c b/libnodegl/drawutils.c index c7650116cc..450cbad67d 100644 --- a/libnodegl/drawutils.c +++ b/libnodegl/drawutils.c @@ -19,7 +19,11 @@ * under the License. */ +#include + #include "drawutils.h" +#include "memory.h" +#include "nodegl.h" #define BYTES_PER_PIXEL 4 /* RGBA */ @@ -125,3 +129,58 @@ void ngli_drawutils_print(struct canvas *canvas, int x, int y, const char *str, px++; } } + +#define ATLAS_COLS 16 +#define ATLAS_ROWS 6 +#define ATLAS_CHAR_PAD 1 // prevent interpolation overlap issues with texture picking +#define ATLAS_CHR_W (NGLI_FONT_W + 2 * ATLAS_CHAR_PAD) +#define ATLAS_CHR_H (NGLI_FONT_H + 2 * ATLAS_CHAR_PAD) +#define ATLAS_W (ATLAS_COLS * ATLAS_CHR_W) +#define ATLAS_H (ATLAS_ROWS * ATLAS_CHR_H) + +int ngli_drawutils_get_font_atlas(struct canvas *c_dst) +{ + struct canvas c = {.w = ATLAS_W, .h = ATLAS_H}; + c.buf = ngli_calloc(c.w * c.h, 1); + if (!c.buf) + return NGL_ERROR_MEMORY; + + uint8_t chr = 0; + for (int y = 0; y < ATLAS_ROWS; y++) { + for (int x = 0; x < ATLAS_COLS; x++) { + for (int char_y = 0; char_y < NGLI_FONT_H; char_y++) { + for (int char_x = 0; char_x < NGLI_FONT_W; char_x++) { + const int pix_x = x * ATLAS_CHR_W + ATLAS_CHAR_PAD + char_x; + const int pix_y = y * ATLAS_CHR_H + ATLAS_CHAR_PAD + char_y; + uint8_t *p = c.buf + (pix_y * c.w + pix_x); + if (font8[chr][char_y] & (1 << char_x)) + *p = 0xff; + } + } + chr++; + } + } + + *c_dst = c; + return 0; +} + +void ngli_drawutils_get_atlas_uvcoords(uint8_t chr, float *dst) +{ + const int c_id = chr < FONT_OFFSET || chr > 127 ? 0 : chr - FONT_OFFSET; + const float scale_w = 1.f / ATLAS_W; + const float scale_h = 1.f / ATLAS_H; + const int col = c_id % ATLAS_COLS; + const int row = c_id / ATLAS_COLS; // from the top of the buffer + const float cx = (col * ATLAS_CHR_W + ATLAS_CHAR_PAD) * scale_w; + const float cy = (ATLAS_H - (row + 1) * ATLAS_CHR_H + ATLAS_CHAR_PAD) * scale_h; + const float cw = NGLI_FONT_W * scale_w; + const float ch = NGLI_FONT_H * scale_h; + const float chr_uvs[] = { + cx, 1.f - cy, + cx + cw, 1.f - cy, + cx + cw, 1.f - cy - ch, + cx, 1.f - cy - ch, + }; + memcpy(dst, chr_uvs, sizeof(chr_uvs)); +} diff --git a/libnodegl/drawutils.h b/libnodegl/drawutils.h index a357d21f4d..963898f91b 100644 --- a/libnodegl/drawutils.h +++ b/libnodegl/drawutils.h @@ -43,5 +43,7 @@ struct rect { void ngli_drawutils_draw_rect(struct canvas *canvas, const struct rect *rect, uint32_t color); void ngli_drawutils_print(struct canvas *canvas, int x, int y, const char *str, uint32_t color); +int ngli_drawutils_get_font_atlas(struct canvas *c_dst); +void ngli_drawutils_get_atlas_uvcoords(uint8_t chr, float *dst); #endif diff --git a/libnodegl/node_text.c b/libnodegl/node_text.c index 40789b0c4d..90b316885c 100644 --- a/libnodegl/node_text.c +++ b/libnodegl/node_text.c @@ -35,13 +35,18 @@ #include "topology.h" #include "utils.h" -struct pipeline_desc { +struct pipeline_subdesc { struct pgcraft *crafter; struct pipeline *pipeline; int modelview_matrix_index; int projection_matrix_index; }; +struct pipeline_desc { + struct pipeline_subdesc bg; /* Background (bounding box) */ + struct pipeline_subdesc fg; /* Foreground (characters) */ +}; + struct text_priv { char *text; float fg_color[4]; @@ -55,9 +60,15 @@ struct text_priv { int aspect_ratio[2]; struct texture *texture; - struct canvas canvas; struct buffer *vertices; struct buffer *uvcoords; + struct buffer *indices; + int nb_indices; + + struct buffer *bg_vertices; + struct buffer *bg_indices; + int nb_bg_indices; + struct darray pipeline_descs; }; @@ -118,163 +129,388 @@ static const struct node_param text_params[] = { {NULL} }; -static void set_canvas_dimensions(struct canvas *canvas, const char *s) +static const char * const bg_vertex_data = + "void main()" "\n" + "{" "\n" + " ngl_out_pos = projection_matrix * modelview_matrix * position;" "\n" + "}"; + +static const char * const bg_fragment_data = + "void main()" "\n" + "{" "\n" + " ngl_out_color = color;" "\n" + "}"; + +static const char * const vertex_data = + "void main()" "\n" + "{" "\n" + " ngl_out_pos = projection_matrix * modelview_matrix * position;" "\n" + " var_tex_coord = uvcoord;" "\n" + "}"; + +static const char * const fragment_data = + "void main()" "\n" + "{" "\n" + " float v = ngl_tex2d(tex, var_tex_coord).r;" "\n" + " ngl_out_color = vec4(color.rgb, color.a * v);" "\n" + "}"; + +static const struct pgcraft_iovar vert_out_vars[] = { + {.name = "var_tex_coord", .type = NGLI_TYPE_VEC2}, +}; + +#define BC(index) s->box_corner[index] +#define BW(index) s->box_width[index] +#define BH(index) s->box_height[index] + +#define C(index) chr_corner[index] +#define W(index) chr_width[index] +#define H(index) chr_height[index] + +static void get_char_box_dim(const char *s, int *wp, int *hp, int *np) { - canvas->w = 0; - canvas->h = NGLI_FONT_H; + int w = 0, h = 1; int cur_w = 0; + int n = 0; for (int i = 0; s[i]; i++) { if (s[i] == '\n') { cur_w = 0; - canvas->h += NGLI_FONT_H; + h++; } else { - cur_w += NGLI_FONT_W; - canvas->w = NGLI_MAX(canvas->w, cur_w); + cur_w++; + w = NGLI_MAX(w, cur_w); + n++; } } + *wp = w; + *hp = h; + *np = n; } -static int prepare_canvas(struct text_priv *s) +static int update_character_geometries(struct ngl_node *node) { - /* Set canvas dimensions according to text (and user padding settings) */ - set_canvas_dimensions(&s->canvas, s->text); - s->canvas.w += 2 * s->padding; - s->canvas.h += 2 * s->padding; + struct ngl_ctx *ctx = node->ctx; + struct gctx *gctx = ctx->gctx; + struct text_priv *s = node->priv_data; - /* Pad it to match container ratio */ + int ret = 0; + const char *str = s->text; + + int text_cols, text_rows, text_nbchr; + get_char_box_dim(str, &text_cols, &text_rows, &text_nbchr); + if (!text_nbchr) { + s->nb_indices = 0; + return 0; + } + + const int nb_vertices = text_nbchr * 4 * 3; + const int nb_uvcoords = text_nbchr * 4 * 2; + const int nb_indices = text_nbchr * 6; + float *vertices = ngli_calloc(nb_vertices, sizeof(*vertices)); + float *uvcoords = ngli_calloc(nb_uvcoords, sizeof(*uvcoords)); + short *indices = ngli_calloc(nb_indices, sizeof(*indices)); + if (!vertices || !uvcoords || !indices) { + ret = NGL_ERROR_MEMORY; + goto end; + } + + /* Text/Box ratio */ const float box_width_len = ngli_vec3_length(s->box_width); const float box_height_len = ngli_vec3_length(s->box_height); static const int default_ar[2] = {1, 1}; const int *ar = s->aspect_ratio[1] ? s->aspect_ratio : default_ar; const float box_ratio = ar[0] * box_width_len / (float)(ar[1] * box_height_len); - const float tex_ratio = s->canvas.w / (float)s->canvas.h; - const int aspect_padw = (tex_ratio < box_ratio ? s->canvas.h * box_ratio - s->canvas.w : 0); - const int aspect_padh = (tex_ratio < box_ratio ? 0 : s->canvas.w / box_ratio - s->canvas.h); - - /* Adjust canvas size to impact text size */ - const int texw = (s->canvas.w + aspect_padw) / s->font_scale; - const int texh = (s->canvas.h + aspect_padh) / s->font_scale; - const int padw = texw - s->canvas.w; - const int padh = texh - s->canvas.h; - s->canvas.w = NGLI_MAX(1, texw); - s->canvas.h = NGLI_MAX(1, texh); + + const int text_width = text_cols * NGLI_FONT_W + 2 * s->padding; + const int text_height = text_rows * NGLI_FONT_H + 2 * s->padding; + const float text_ratio = text_width / (float)(text_height); + + float ratio_w, ratio_h; + if (text_ratio < box_ratio) { + ratio_w = text_ratio / box_ratio; + ratio_h = 1.0; + } else { + ratio_w = 1.0; + ratio_h = box_ratio / text_ratio; + } + + /* Apply aspect ratio and font scaling */ + float width[3]; + float height[3]; + ngli_vec3_scale(width, s->box_width, ratio_w * s->font_scale); + ngli_vec3_scale(height, s->box_height, ratio_h * s->font_scale); + + /* User padding */ + float padw[3]; + float padh[3]; + ngli_vec3_scale(padw, width, s->padding / (float)text_width); + ngli_vec3_scale(padh, height, s->padding / (float)text_height); + + /* Width and height of 1 character */ + const float chr_width[3] = { + (width[0] - 2 * padw[0]) / (float)text_cols, + (width[1] - 2 * padw[1]) / (float)text_cols, + (width[2] - 2 * padw[2]) / (float)text_cols, + }; + const float chr_height[3] = { + (height[0] - 2 * padh[0]) / (float)text_rows, + (height[1] - 2 * padh[1]) / (float)text_rows, + (height[2] - 2 * padh[2]) / (float)text_rows, + }; /* Adjust text position according to alignment settings */ - const int tx = (s->halign == HALIGN_CENTER ? padw / 2 : - s->halign == HALIGN_RIGHT ? padw : - 0) + s->padding; - const int ty = (s->valign == VALIGN_CENTER ? padh / 2 : - s->valign == VALIGN_BOTTOM ? padh : - 0) + s->padding; - - /* Allocate, draw background, print text */ - s->canvas.buf = ngli_calloc(s->canvas.w * s->canvas.h, sizeof(*s->canvas.buf) * 4); - if (!s->canvas.buf) - return NGL_ERROR_MEMORY; - const uint32_t fg = NGLI_COLOR_VEC4_TO_U32(s->fg_color); - const uint32_t bg = NGLI_COLOR_VEC4_TO_U32(s->bg_color); - struct rect rect = {.w = s->canvas.w, .h = s->canvas.h}; - ngli_drawutils_draw_rect(&s->canvas, &rect, bg); - ngli_drawutils_print(&s->canvas, tx, ty, s->text, fg); - return 0; -} + float align_padw[3]; + float align_padh[3]; + ngli_vec3_sub(align_padw, s->box_width, width); + ngli_vec3_sub(align_padh, s->box_height, height); + + const float spx = (s->halign == HALIGN_CENTER ? .5f : + s->halign == HALIGN_RIGHT ? 1.f : + 0.f); + const float spy = (s->valign == VALIGN_CENTER ? .5f : + s->valign == VALIGN_TOP ? 1.f : + 0.f); + + float corner[3] = { + BC(0) + align_padw[0] * spx + align_padh[0] * spy + padw[0] + padh[0], + BC(1) + align_padw[1] * spx + align_padh[1] * spy + padw[1] + padh[1], + BC(2) + align_padw[2] * spx + align_padh[2] * spy + padw[2] + padh[2], + }; -static const char * const vertex_data = - "void main()" "\n" - "{" "\n" - " ngl_out_pos = projection_matrix * modelview_matrix * position;" "\n" - " var_tex_coord = uvcoord;" "\n" - "}"; + int px = 0, py = 0; + int n = 0; -static const char * const fragment_data = - "void main()" "\n" - "{" "\n" - " ngl_out_color = ngl_tex2d(tex, var_tex_coord);" "\n" - "}"; + for (int i = 0; str[i]; i++) { + if (str[i] == '\n') { + py++; + px = 0; + continue; + } -static const struct pgcraft_iovar vert_out_vars[] = { - {.name = "var_tex_coord", .type = NGLI_TYPE_VEC2}, -}; + /* quad vertices */ + const float chr_corner[3] = { + corner[0] + chr_width[0] * px + chr_height[0] * (text_rows - py - 1), + corner[1] + chr_width[1] * px + chr_height[1] * (text_rows - py - 1), + corner[2] + chr_width[2] * px + chr_height[2] * (text_rows - py - 1), + }; + const float chr_vertices[] = { + C(0), C(1), C(2), + C(0) + W(0), C(1) + W(1), C(2) + W(2), + C(0) + H(0) + W(0), C(1) + H(1) + W(1), C(2) + H(2) + W(2), + C(0) + H(0), C(1) + H(1), C(2) + H(2), + }; + memcpy(vertices + 4 * 3 * n, chr_vertices, sizeof(chr_vertices)); + + /* focus uvcoords on the character in the atlas texture */ + ngli_drawutils_get_atlas_uvcoords(str[i], uvcoords + 4 * 2 * n); + + /* quad for each character is made of 2 triangles */ + const short chr_indices[] = { n*4 + 0, n*4 + 1, n*4 + 2, n*4 + 0, n*4 + 2, n*4 + 3 }; + memcpy(indices + n * NGLI_ARRAY_NB(chr_indices), chr_indices, sizeof(chr_indices)); + + n++; + px++; + } -#define C(index) s->box_corner[index] -#define W(index) s->box_width[index] -#define H(index) s->box_height[index] + s->vertices = ngli_buffer_create(gctx); + s->uvcoords = ngli_buffer_create(gctx); + s->indices = ngli_buffer_create(gctx); + if (!s->vertices || !s->uvcoords || !s->indices) { + ret = NGL_ERROR_MEMORY; + goto end; + } -static int text_init(struct ngl_node *node) + if ((ret = ngli_buffer_init(s->vertices, nb_vertices * sizeof(*vertices), NGLI_BUFFER_USAGE_STATIC)) < 0 || + (ret = ngli_buffer_init(s->uvcoords, nb_uvcoords * sizeof(*uvcoords), NGLI_BUFFER_USAGE_STATIC)) < 0 || + (ret = ngli_buffer_init(s->indices, nb_indices * sizeof(*indices), NGLI_BUFFER_USAGE_STATIC)) < 0) + goto end; + + if ((ret = ngli_buffer_upload(s->vertices, vertices, nb_vertices * sizeof(*vertices))) < 0 || + (ret = ngli_buffer_upload(s->uvcoords, uvcoords, nb_uvcoords * sizeof(*uvcoords))) < 0 || + (ret = ngli_buffer_upload(s->indices, indices, nb_indices * sizeof(*indices))) < 0) + goto end; + + s->nb_indices = nb_indices; + +end: + ngli_free(vertices); + ngli_free(uvcoords); + ngli_free(indices); + return ret; +} + +static int init_bounding_box_geometry(struct ngl_node *node) { struct ngl_ctx *ctx = node->ctx; struct gctx *gctx = ctx->gctx; struct text_priv *s = node->priv_data; - int ret = prepare_canvas(s); - if (ret < 0) - return ret; - + static const short indices[] = { 0, 1, 2, 0, 2, 3 }; const float vertices[] = { - C(0), C(1), C(2), - C(0) + W(0), C(1) + W(1), C(2) + W(2), - C(0) + H(0) + W(0), C(1) + H(1) + W(1), C(2) + H(2) + W(2), - C(0) + H(0), C(1) + H(1), C(2) + H(2), + BC(0), BC(1), BC(2), + BC(0) + BW(0), BC(1) + BW(1), BC(2) + BW(2), + BC(0) + BH(0) + BW(0), BC(1) + BH(1) + BW(1), BC(2) + BH(2) + BW(2), + BC(0) + BH(0), BC(1) + BH(1), BC(2) + BH(2), }; - static const float uvs[] = {0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0}; - - s->vertices = ngli_buffer_create(gctx); - if (!s->vertices) + s->bg_vertices = ngli_buffer_create(gctx); + s->bg_indices = ngli_buffer_create(gctx); + if (!s->bg_vertices || !s->bg_indices) return NGL_ERROR_MEMORY; - ret = ngli_buffer_init(s->vertices, sizeof(vertices), NGLI_BUFFER_USAGE_STATIC); - if (ret < 0) + int ret; + if ((ret = ngli_buffer_init(s->bg_vertices, sizeof(vertices), NGLI_BUFFER_USAGE_STATIC)) < 0 || + (ret = ngli_buffer_init(s->bg_indices, sizeof(indices), NGLI_BUFFER_USAGE_STATIC)) < 0) return ret; - ret = ngli_buffer_upload(s->vertices, vertices, sizeof(vertices)); - if (ret < 0) + if ((ret = ngli_buffer_upload(s->bg_vertices, vertices, sizeof(vertices))) < 0 || + (ret = ngli_buffer_upload(s->bg_indices, indices, sizeof(indices))) < 0) return ret; - s->uvcoords = ngli_buffer_create(gctx); - if (!s->uvcoords) - return NGL_ERROR_MEMORY; + s->nb_bg_indices = NGLI_ARRAY_NB(indices); - ret = ngli_buffer_init(s->uvcoords, sizeof(uvs), NGLI_BUFFER_USAGE_STATIC); - if (ret < 0) - return ret; + return 0; +} - ret = ngli_buffer_upload(s->uvcoords, uvs, sizeof(uvs)); +static int atlas_create(struct ngl_node *node) +{ + struct ngl_ctx *ctx = node->ctx; + struct gctx *gctx = ctx->gctx; + struct text_priv *s = node->priv_data; + + struct canvas canvas = {0}; + int ret = ngli_drawutils_get_font_atlas(&canvas); if (ret < 0) - return ret; + goto end; struct texture_params tex_params = NGLI_TEXTURE_PARAM_DEFAULTS; - tex_params.width = s->canvas.w; - tex_params.height = s->canvas.h; - tex_params.format = NGLI_FORMAT_R8G8B8A8_UNORM; + tex_params.width = canvas.w; + tex_params.height = canvas.h; + tex_params.format = NGLI_FORMAT_R8_UNORM; tex_params.min_filter = NGLI_FILTER_LINEAR; tex_params.mag_filter = NGLI_FILTER_NEAREST; tex_params.mipmap_filter = NGLI_MIPMAP_FILTER_LINEAR; s->texture = ngli_texture_create(gctx); - if (!s->texture) - return NGL_ERROR_MEMORY; + if (!s->texture) { + ret = NGL_ERROR_MEMORY; + goto end; + } + ret = ngli_texture_init(s->texture, &tex_params); if (ret < 0) - return ret; + goto end; + + ret = ngli_texture_upload(s->texture, canvas.buf, 0); + if (ret < 0) + goto end; + +end: + ngli_free(canvas.buf); + return ret; +} + +static int text_init(struct ngl_node *node) +{ + struct text_priv *s = node->priv_data; - ret = ngli_texture_upload(s->texture, s->canvas.buf, 0); + int ret = atlas_create(node); if (ret < 0) return ret; ngli_darray_init(&s->pipeline_descs, sizeof(struct pipeline_desc), 0); + ret = init_bounding_box_geometry(node); + if (ret < 0) + return ret; + + ret = update_character_geometries(node); + if (ret < 0) + return ret; + return 0; } -static int text_prepare(struct ngl_node *node) +static int init_subdesc(struct ngl_node *node, + struct pipeline_subdesc *desc, + struct pipeline_params *pipeline_params, + const struct pgcraft_params *crafter_params) { struct ngl_ctx *ctx = node->ctx; struct gctx *gctx = ctx->gctx; + + desc->crafter = ngli_pgcraft_create(ctx); + if (!desc->crafter) + return NGL_ERROR_MEMORY; + + int ret = ngli_pgcraft_craft(desc->crafter, pipeline_params, crafter_params); + if (ret < 0) + return ret; + + desc->pipeline = ngli_pipeline_create(gctx); + if (!desc->pipeline) + return NGL_ERROR_MEMORY; + + ret = ngli_pipeline_init(desc->pipeline, pipeline_params); + if (ret < 0) + return ret; + + desc->modelview_matrix_index = ngli_pgcraft_get_uniform_index(desc->crafter, "modelview_matrix", NGLI_PROGRAM_SHADER_VERT); + desc->projection_matrix_index = ngli_pgcraft_get_uniform_index(desc->crafter, "projection_matrix", NGLI_PROGRAM_SHADER_VERT); + + return 0; +} + +static int bg_prepare(struct ngl_node *node, struct pipeline_subdesc *desc) +{ + struct ngl_ctx *ctx = node->ctx; + struct text_priv *s = node->priv_data; + + const struct pgcraft_uniform uniforms[] = { + {.name = "modelview_matrix", .type = NGLI_TYPE_MAT4, .stage = NGLI_PROGRAM_SHADER_VERT, .data = NULL}, + {.name = "projection_matrix", .type = NGLI_TYPE_MAT4, .stage = NGLI_PROGRAM_SHADER_VERT, .data = NULL}, + {.name = "color", .type = NGLI_TYPE_VEC4, .stage = NGLI_PROGRAM_SHADER_FRAG, .data = s->bg_color}, + }; + + const struct pgcraft_attribute attributes[] = { + { + .name = "position", + .type = NGLI_TYPE_VEC4, + .format = NGLI_FORMAT_R32G32B32_SFLOAT, + .stride = 3 * 4, + .buffer = s->bg_vertices, + }, + }; + + struct pipeline_params pipeline_params = { + .type = NGLI_PIPELINE_TYPE_GRAPHICS, + .graphics = { + .topology = NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + .state = ctx->graphicstate, + .rt_desc = *ctx->rendertarget_desc, + } + }; + + const struct pgcraft_params crafter_params = { + .vert_base = bg_vertex_data, + .frag_base = bg_fragment_data, + .uniforms = uniforms, + .nb_uniforms = NGLI_ARRAY_NB(uniforms), + .attributes = attributes, + .nb_attributes = NGLI_ARRAY_NB(attributes), + }; + + return init_subdesc(node, desc, &pipeline_params, &crafter_params); +} + +static int fg_prepare(struct ngl_node *node, struct pipeline_subdesc *desc) +{ + struct ngl_ctx *ctx = node->ctx; struct text_priv *s = node->priv_data; const struct pgcraft_uniform uniforms[] = { {.name = "modelview_matrix", .type = NGLI_TYPE_MAT4, .stage = NGLI_PROGRAM_SHADER_VERT, .data = NULL}, {.name = "projection_matrix", .type = NGLI_TYPE_MAT4, .stage = NGLI_PROGRAM_SHADER_VERT, .data = NULL}, + {.name = "color", .type = NGLI_TYPE_VEC4, .stage = NGLI_PROGRAM_SHADER_FRAG, .data = s->fg_color}, }; const struct pgcraft_texture textures[] = { @@ -303,12 +539,20 @@ static int text_prepare(struct ngl_node *node) }, }; + /* This controls how the characters blend onto the background */ + struct graphicstate state = ctx->graphicstate; + state.blend = 1; + state.blend_src_factor = NGLI_BLEND_FACTOR_SRC_ALPHA; + state.blend_dst_factor = NGLI_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + state.blend_src_factor_a = NGLI_BLEND_FACTOR_SRC_ALPHA; + state.blend_dst_factor_a = NGLI_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + struct pipeline_params pipeline_params = { .type = NGLI_PIPELINE_TYPE_GRAPHICS, .graphics = { - .topology = NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, - .state = ctx->graphicstate, - .rt_desc = *ctx->rendertarget_desc, + .topology = NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + .state = state, + .rt_desc = *ctx->rendertarget_desc, } }; @@ -325,6 +569,14 @@ static int text_prepare(struct ngl_node *node) .nb_vert_out_vars = NGLI_ARRAY_NB(vert_out_vars), }; + return init_subdesc(node, desc, &pipeline_params, &crafter_params); +} + +static int text_prepare(struct ngl_node *node) +{ + struct ngl_ctx *ctx = node->ctx; + struct text_priv *s = node->priv_data; + struct pipeline_desc *desc = ngli_darray_push(&s->pipeline_descs, NULL); if (!desc) return NGL_ERROR_MEMORY; @@ -332,25 +584,14 @@ static int text_prepare(struct ngl_node *node) memset(desc, 0, sizeof(*desc)); - desc->crafter = ngli_pgcraft_create(ctx); - if (!desc->crafter) - return NGL_ERROR_MEMORY; - - int ret = ngli_pgcraft_craft(desc->crafter, &pipeline_params, &crafter_params); + int ret = bg_prepare(node, &desc->bg); if (ret < 0) return ret; - desc->pipeline = ngli_pipeline_create(gctx); - if (!desc->pipeline) - return NGL_ERROR_MEMORY; - - ret = ngli_pipeline_init(desc->pipeline, &pipeline_params); + ret = fg_prepare(node, &desc->fg); if (ret < 0) return ret; - desc->modelview_matrix_index = ngli_pgcraft_get_uniform_index(desc->crafter, "modelview_matrix", NGLI_PROGRAM_SHADER_VERT); - desc->projection_matrix_index = ngli_pgcraft_get_uniform_index(desc->crafter, "projection_matrix", NGLI_PROGRAM_SHADER_VERT); - return 0; } @@ -365,10 +606,17 @@ static void text_draw(struct ngl_node *node) struct pipeline_desc *descs = ngli_darray_data(&s->pipeline_descs); struct pipeline_desc *desc = &descs[ctx->rnode_pos->id]; - ngli_pipeline_update_uniform(desc->pipeline, desc->modelview_matrix_index, modelview_matrix); - ngli_pipeline_update_uniform(desc->pipeline, desc->projection_matrix_index, projection_matrix); + struct pipeline_subdesc *bg_desc = &desc->bg; + ngli_pipeline_update_uniform(bg_desc->pipeline, bg_desc->modelview_matrix_index, modelview_matrix); + ngli_pipeline_update_uniform(bg_desc->pipeline, bg_desc->projection_matrix_index, projection_matrix); + ngli_pipeline_draw_indexed(bg_desc->pipeline, s->bg_indices, NGLI_FORMAT_R16_UNORM, s->nb_bg_indices, 1); - ngli_pipeline_draw(desc->pipeline, 4, 1); + if (s->nb_indices) { + struct pipeline_subdesc *fg_desc = &desc->fg; + ngli_pipeline_update_uniform(fg_desc->pipeline, fg_desc->modelview_matrix_index, modelview_matrix); + ngli_pipeline_update_uniform(fg_desc->pipeline, fg_desc->projection_matrix_index, projection_matrix); + ngli_pipeline_draw_indexed(fg_desc->pipeline, s->indices, NGLI_FORMAT_R16_UNORM, s->nb_indices, 1); + } } static void text_uninit(struct ngl_node *node) @@ -378,14 +626,16 @@ static void text_uninit(struct ngl_node *node) const int nb_descs = ngli_darray_count(&s->pipeline_descs); for (int i = 0; i < nb_descs; i++) { struct pipeline_desc *desc = &descs[i]; - ngli_pipeline_freep(&desc->pipeline); - ngli_pgcraft_freep(&desc->crafter); + ngli_pipeline_freep(&desc->bg.pipeline); + ngli_pipeline_freep(&desc->fg.pipeline); + ngli_pgcraft_freep(&desc->bg.crafter); + ngli_pgcraft_freep(&desc->fg.crafter); } ngli_darray_reset(&s->pipeline_descs); ngli_texture_freep(&s->texture); ngli_buffer_freep(&s->vertices); ngli_buffer_freep(&s->uvcoords); - ngli_free(s->canvas.buf); + ngli_buffer_freep(&s->indices); } const struct node_class ngli_text_class = { diff --git a/tests/refs/text_align_bc.ref b/tests/refs/text_align_bc.ref index a6f5441c9d..5aa7cf69a7 100644 --- a/tests/refs/text_align_bc.ref +++ b/tests/refs/text_align_bc.ref @@ -1 +1 @@ -000000000400200005543740B554A822 000000000400200005543740B554A822 000000000400200005543740B554A822 00000000000000000D402740215C2823 +0000000000000000055475402554A822 0000000000000000055475402554A822 0000000000000000055475402554A822 000000000000000005402540355C2823 diff --git a/tests/refs/text_align_cc.ref b/tests/refs/text_align_cc.ref index e4bb05d354..824c5c30d9 100644 --- a/tests/refs/text_align_cc.ref +++ b/tests/refs/text_align_cc.ref @@ -1 +1 @@ -014008001540B514A174A82205442020 014008001540B514A174A82205442020 014008001540B514A174A82205442020 0000000005C025142174282305CC0000 +0140080045403514A974A82205442020 0140080045403514A974A82205442020 0140080045403514A974A82205442020 0000000005C025142874282305CC0000 diff --git a/tests/refs/text_align_tc.ref b/tests/refs/text_align_tc.ref index b8b66bb19d..01e12616ef 100644 --- a/tests/refs/text_align_tc.ref +++ b/tests/refs/text_align_tc.ref @@ -1 +1 @@ -B740B554A82228000100080000000000 B740B554A82228000100080000000000 B740B554A82228000100080000000000 2540255C28232820015C000000000000 +75402554A8222A200000000000000000 75402554A8222A200000000000000000 75402554A8222A200000000000000000 2540255C28232800015C000000000000 diff --git a/tests/refs/text_align_tl.ref b/tests/refs/text_align_tl.ref index 4a950b6838..1e9593b7f3 100644 --- a/tests/refs/text_align_tl.ref +++ b/tests/refs/text_align_tl.ref @@ -1 +1 @@ -D500D5518208A2000440220000000000 D500D5518208A2000440220000000000 D500D5518208A2000440220000000000 9500D5D0820CA20005C0000000000000 +D500D5518208A2000440220000000000 D500D5518208A2000440220000000000 D500D5518208A2000440220000000000 9400D5D0820CA2000CC0000000000000 diff --git a/tests/refs/text_align_tr.ref b/tests/refs/text_align_tr.ref index b21c1cd06b..e1a9eaa9a3 100644 --- a/tests/refs/text_align_tr.ref +++ b/tests/refs/text_align_tr.ref @@ -1 +1 @@ -1B548B558A080A880051020800000000 1B548B558A080A880051020800000000 1B548B558A080A880051020800000000 0B500B570E080A880073000000000000 +1B548B558A080A880011008800000000 1B548B558A080A880011008800000000 1B548B558A080A880011008800000000 0B500B570E080A880033000000000000 diff --git a/tests/refs/text_colors.ref b/tests/refs/text_colors.ref index 322a0fff10..b56c4221ea 100644 --- a/tests/refs/text_colors.ref +++ b/tests/refs/text_colors.ref @@ -1 +1 @@ -0000000005C0251C3174A823075C0000 000001C000000AC00E8817DC000005CC 000000001540A514A174A82000000000 00000000000000000000000000000000 +0000000005C0351C2974282305DC0000 000001C00000CAC0068817DC000005CC 0000000005403514A874282000000000 00000000000000000000000000000000 From 0ae90999b078edcc0b2cc69eeea18edcb99ac139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 27 Aug 2020 13:51:47 +0200 Subject: [PATCH 009/388] api: call cmd_stop() from cmd_configure() This will make sure the context is properly cleaned up on reconfigure when cmd_stop() is extended. --- libnodegl/api.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/libnodegl/api.c b/libnodegl/api.c index b0d2d97345..32af476637 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -67,6 +67,13 @@ static int get_default_platform(void) #endif } +static int cmd_stop(struct ngl_ctx *s, void *arg) +{ + ngli_gctx_freep(&s->gctx); + + return 0; +} + static int cmd_configure(struct ngl_ctx *s, void *arg) { struct ngl_config *config = arg; @@ -75,7 +82,7 @@ static int cmd_configure(struct ngl_ctx *s, void *arg) ngli_node_detach_ctx(s->scene, s); ngli_rnode_clear(&s->rnode); - ngli_gctx_freep(&s->gctx); + cmd_stop(s, arg); if (config->backend == NGL_BACKEND_AUTO) config->backend = DEFAULT_BACKEND; @@ -96,7 +103,7 @@ static int cmd_configure(struct ngl_ctx *s, void *arg) int ret = ngli_gctx_init(s->gctx); if (ret < 0) { LOG(ERROR, "unable to initialize gpu context"); - ngli_gctx_freep(&s->gctx); + cmd_stop(s, arg); return ret; } @@ -105,7 +112,7 @@ static int cmd_configure(struct ngl_ctx *s, void *arg) if (ret < 0) { ngli_node_detach_ctx(s->scene, s); ngl_node_unrefp(&s->scene); - ngli_gctx_freep(&s->gctx); + cmd_stop(s, arg); return ret; } } @@ -185,13 +192,6 @@ static int cmd_draw(struct ngl_ctx *s, void *arg) return ngli_gctx_draw(s->gctx, t); } -static int cmd_stop(struct ngl_ctx *s, void *arg) -{ - ngli_gctx_freep(&s->gctx); - - return 0; -} - static int dispatch_cmd(struct ngl_ctx *s, cmd_func_type cmd_func, void *arg) { pthread_mutex_lock(&s->lock); From e3f9942ea78c23dc906d45cb8329b6be4fc51680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Wed, 29 Jul 2020 09:31:28 +0200 Subject: [PATCH 010/388] text: share the font atlas between text nodes at context level This way, the atlas is only present once in GPU memory, and only if a text node has been set as scene for this context. --- libnodegl/api.c | 1 + libnodegl/node_text.c | 17 +++++++++-------- libnodegl/nodes.h | 1 + 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/libnodegl/api.c b/libnodegl/api.c index 32af476637..dcc1324aee 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -69,6 +69,7 @@ static int get_default_platform(void) static int cmd_stop(struct ngl_ctx *s, void *arg) { + ngli_texture_freep(&s->font_atlas); // allocated by the first node text ngli_gctx_freep(&s->gctx); return 0; diff --git a/libnodegl/node_text.c b/libnodegl/node_text.c index 90b316885c..b6b3799c30 100644 --- a/libnodegl/node_text.c +++ b/libnodegl/node_text.c @@ -59,7 +59,6 @@ struct text_priv { int valign, halign; int aspect_ratio[2]; - struct texture *texture; struct buffer *vertices; struct buffer *uvcoords; struct buffer *indices; @@ -376,7 +375,9 @@ static int atlas_create(struct ngl_node *node) { struct ngl_ctx *ctx = node->ctx; struct gctx *gctx = ctx->gctx; - struct text_priv *s = node->priv_data; + + if (ctx->font_atlas) + return 0; struct canvas canvas = {0}; int ret = ngli_drawutils_get_font_atlas(&canvas); @@ -390,17 +391,18 @@ static int atlas_create(struct ngl_node *node) tex_params.min_filter = NGLI_FILTER_LINEAR; tex_params.mag_filter = NGLI_FILTER_NEAREST; tex_params.mipmap_filter = NGLI_MIPMAP_FILTER_LINEAR; - s->texture = ngli_texture_create(gctx); - if (!s->texture) { + + ctx->font_atlas = ngli_texture_create(gctx); // freed at context reconfiguration/destruction + if (!ctx->font_atlas) { ret = NGL_ERROR_MEMORY; goto end; } - ret = ngli_texture_init(s->texture, &tex_params); + ret = ngli_texture_init(ctx->font_atlas, &tex_params); if (ret < 0) goto end; - ret = ngli_texture_upload(s->texture, canvas.buf, 0); + ret = ngli_texture_upload(ctx->font_atlas, canvas.buf, 0); if (ret < 0) goto end; @@ -518,7 +520,7 @@ static int fg_prepare(struct ngl_node *node, struct pipeline_subdesc *desc) .name = "tex", .type = NGLI_PGCRAFT_SHADER_TEX_TYPE_TEXTURE2D, .stage = NGLI_PROGRAM_SHADER_FRAG, - .texture = s->texture, + .texture = ctx->font_atlas, }, }; @@ -632,7 +634,6 @@ static void text_uninit(struct ngl_node *node) ngli_pgcraft_freep(&desc->fg.crafter); } ngli_darray_reset(&s->pipeline_descs); - ngli_texture_freep(&s->texture); ngli_buffer_freep(&s->vertices); ngli_buffer_freep(&s->uvcoords); ngli_buffer_freep(&s->indices); diff --git a/libnodegl/nodes.h b/libnodegl/nodes.h index 29d5f21005..0e7512c3ec 100644 --- a/libnodegl/nodes.h +++ b/libnodegl/nodes.h @@ -86,6 +86,7 @@ struct ngl_ctx { struct darray modelview_matrix_stack; struct darray projection_matrix_stack; struct darray activitycheck_nodes; + struct texture *font_atlas; #if defined(HAVE_VAAPI_X11) Display *x11_display; #endif From 546efa3cfa9d324006052295800079ddb48a96fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 31 Jul 2020 17:15:26 +0200 Subject: [PATCH 011/388] text: make text string live changeable --- libnodegl/doc/libnodegl.md | 2 +- libnodegl/node_text.c | 81 ++++++++++++++++++++++++++++++++------ 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/libnodegl/doc/libnodegl.md b/libnodegl/doc/libnodegl.md index b79bdff92d..c7f9b71f3c 100644 --- a/libnodegl/doc/libnodegl.md +++ b/libnodegl/doc/libnodegl.md @@ -569,7 +569,7 @@ Parameter | Live-chg. | Type | Description | Default Parameter | Live-chg. | Type | Description | Default --------- | :-------: | ---- | ----------- | :-----: -`text` | | [`string`](#parameter-types) | text string to rasterize | +`text` | ✓ | [`string`](#parameter-types) | text string to rasterize | `fg_color` | | [`vec4`](#parameter-types) | foreground text color | (`1`,`1`,`1`,`1`) `bg_color` | | [`vec4`](#parameter-types) | background text color | (`0`,`0`,`0`,`0.8`) `box_corner` | | [`vec3`](#parameter-types) | origin coordinates of `box_width` and `box_height` vectors | (`-1`,`-1`,`0`) diff --git a/libnodegl/node_text.c b/libnodegl/node_text.c index b6b3799c30..0d7b32435f 100644 --- a/libnodegl/node_text.c +++ b/libnodegl/node_text.c @@ -69,6 +69,7 @@ struct text_priv { int nb_bg_indices; struct darray pipeline_descs; + int live_changed; }; #define VALIGN_CENTER 0 @@ -99,9 +100,18 @@ static const struct param_choices halign_choices = { } }; +static int set_live_changed(struct ngl_node *node) +{ + struct text_priv *s = node->priv_data; + s->live_changed = 1; + return 0; +} + #define OFFSET(x) offsetof(struct text_priv, x) static const struct node_param text_params[] = { - {"text", PARAM_TYPE_STR, OFFSET(text), .flags=PARAM_FLAG_NON_NULL, + {"text", PARAM_TYPE_STR, OFFSET(text), {.str=""}, + .flags=PARAM_FLAG_ALLOW_LIVE_CHANGE | PARAM_FLAG_NON_NULL, + .update_func=set_live_changed, .desc=NGLI_DOCSTRING("text string to rasterize")}, {"fg_color", PARAM_TYPE_VEC4, OFFSET(fg_color), {.vec={1.0, 1.0, 1.0, 1.0}}, .desc=NGLI_DOCSTRING("foreground text color")}, @@ -198,6 +208,9 @@ static int update_character_geometries(struct ngl_node *node) int text_cols, text_rows, text_nbchr; get_char_box_dim(str, &text_cols, &text_rows, &text_nbchr); if (!text_nbchr) { + ngli_buffer_freep(&s->vertices); + ngli_buffer_freep(&s->uvcoords); + ngli_buffer_freep(&s->indices); s->nb_indices = 0; return 0; } @@ -311,18 +324,39 @@ static int update_character_geometries(struct ngl_node *node) px++; } - s->vertices = ngli_buffer_create(gctx); - s->uvcoords = ngli_buffer_create(gctx); - s->indices = ngli_buffer_create(gctx); - if (!s->vertices || !s->uvcoords || !s->indices) { - ret = NGL_ERROR_MEMORY; - goto end; - } + if (nb_indices != s->nb_indices) { + const int need_realloc = nb_indices > s->nb_indices; + + if (need_realloc) { + ngli_buffer_freep(&s->vertices); + ngli_buffer_freep(&s->uvcoords); + ngli_buffer_freep(&s->indices); + + s->vertices = ngli_buffer_create(gctx); + s->uvcoords = ngli_buffer_create(gctx); + s->indices = ngli_buffer_create(gctx); + if (!s->vertices || !s->uvcoords || !s->indices) { + ret = NGL_ERROR_MEMORY; + goto end; + } + + if ((ret = ngli_buffer_init(s->vertices, nb_vertices * sizeof(*vertices), NGLI_BUFFER_USAGE_DYNAMIC)) < 0 || + (ret = ngli_buffer_init(s->uvcoords, nb_uvcoords * sizeof(*uvcoords), NGLI_BUFFER_USAGE_DYNAMIC)) < 0 || + (ret = ngli_buffer_init(s->indices, nb_indices * sizeof(*indices), NGLI_BUFFER_USAGE_DYNAMIC)) < 0) + goto end; + } - if ((ret = ngli_buffer_init(s->vertices, nb_vertices * sizeof(*vertices), NGLI_BUFFER_USAGE_STATIC)) < 0 || - (ret = ngli_buffer_init(s->uvcoords, nb_uvcoords * sizeof(*uvcoords), NGLI_BUFFER_USAGE_STATIC)) < 0 || - (ret = ngli_buffer_init(s->indices, nb_indices * sizeof(*indices), NGLI_BUFFER_USAGE_STATIC)) < 0) - goto end; + struct pipeline_desc *descs = ngli_darray_data(&s->pipeline_descs); + const int nb_descs = ngli_darray_count(&s->pipeline_descs); + for (int i = 0; i < nb_descs; i++) { + struct pipeline_subdesc *desc = &descs[i].fg; + + if (need_realloc) { + ngli_pipeline_update_attribute(desc->pipeline, 0, s->vertices); + ngli_pipeline_update_attribute(desc->pipeline, 1, s->uvcoords); + } + } + } if ((ret = ngli_buffer_upload(s->vertices, vertices, nb_vertices * sizeof(*vertices))) < 0 || (ret = ngli_buffer_upload(s->uvcoords, uvcoords, nb_uvcoords * sizeof(*uvcoords))) < 0 || @@ -571,7 +605,14 @@ static int fg_prepare(struct ngl_node *node, struct pipeline_subdesc *desc) .nb_vert_out_vars = NGLI_ARRAY_NB(vert_out_vars), }; - return init_subdesc(node, desc, &pipeline_params, &crafter_params); + int ret = init_subdesc(node, desc, &pipeline_params, &crafter_params); + if (ret < 0) + return ret; + + ngli_assert(!strcmp("position", pipeline_params.attributes[0].name)); + ngli_assert(!strcmp("uvcoord", pipeline_params.attributes[1].name)); + + return 0; } static int text_prepare(struct ngl_node *node) @@ -597,6 +638,19 @@ static int text_prepare(struct ngl_node *node) return 0; } +static int text_update(struct ngl_node *node, double t) +{ + struct text_priv *s = node->priv_data; + + if (s->live_changed) { + int ret = update_character_geometries(node); + if (ret < 0) + return ret; + s->live_changed = 0; + } + return 0; +} + static void text_draw(struct ngl_node *node) { struct ngl_ctx *ctx = node->ctx; @@ -644,6 +698,7 @@ const struct node_class ngli_text_class = { .name = "Text", .init = text_init, .prepare = text_prepare, + .update = text_update, .draw = text_draw, .uninit = text_uninit, .priv_size = sizeof(struct text_priv), From 825ec4e814c4c2fdc3e29d61d63be49054e066a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 31 Jul 2020 17:18:16 +0200 Subject: [PATCH 012/388] text: make {bg,fg}_color live changeable --- libnodegl/doc/libnodegl.md | 4 ++-- libnodegl/node_text.c | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/libnodegl/doc/libnodegl.md b/libnodegl/doc/libnodegl.md index c7f9b71f3c..c037b59423 100644 --- a/libnodegl/doc/libnodegl.md +++ b/libnodegl/doc/libnodegl.md @@ -570,8 +570,8 @@ Parameter | Live-chg. | Type | Description | Default Parameter | Live-chg. | Type | Description | Default --------- | :-------: | ---- | ----------- | :-----: `text` | ✓ | [`string`](#parameter-types) | text string to rasterize | -`fg_color` | | [`vec4`](#parameter-types) | foreground text color | (`1`,`1`,`1`,`1`) -`bg_color` | | [`vec4`](#parameter-types) | background text color | (`0`,`0`,`0`,`0.8`) +`fg_color` | ✓ | [`vec4`](#parameter-types) | foreground text color | (`1`,`1`,`1`,`1`) +`bg_color` | ✓ | [`vec4`](#parameter-types) | background text color | (`0`,`0`,`0`,`0.8`) `box_corner` | | [`vec3`](#parameter-types) | origin coordinates of `box_width` and `box_height` vectors | (`-1`,`-1`,`0`) `box_width` | | [`vec3`](#parameter-types) | box width vector | (`2`,`0`,`0`) `box_height` | | [`vec3`](#parameter-types) | box height vector | (`0`,`2`,`0`) diff --git a/libnodegl/node_text.c b/libnodegl/node_text.c index 0d7b32435f..d040a09a36 100644 --- a/libnodegl/node_text.c +++ b/libnodegl/node_text.c @@ -40,6 +40,7 @@ struct pipeline_subdesc { struct pipeline *pipeline; int modelview_matrix_index; int projection_matrix_index; + int color_index; }; struct pipeline_desc { @@ -114,8 +115,10 @@ static const struct node_param text_params[] = { .update_func=set_live_changed, .desc=NGLI_DOCSTRING("text string to rasterize")}, {"fg_color", PARAM_TYPE_VEC4, OFFSET(fg_color), {.vec={1.0, 1.0, 1.0, 1.0}}, + .flags=PARAM_FLAG_ALLOW_LIVE_CHANGE, .desc=NGLI_DOCSTRING("foreground text color")}, {"bg_color", PARAM_TYPE_VEC4, OFFSET(bg_color), {.vec={0.0, 0.0, 0.0, 0.8}}, + .flags=PARAM_FLAG_ALLOW_LIVE_CHANGE, .desc=NGLI_DOCSTRING("background text color")}, {"box_corner", PARAM_TYPE_VEC3, OFFSET(box_corner), {.vec={-1.0, -1.0, 0.0}}, .desc=NGLI_DOCSTRING("origin coordinates of `box_width` and `box_height` vectors")}, @@ -492,6 +495,7 @@ static int init_subdesc(struct ngl_node *node, desc->modelview_matrix_index = ngli_pgcraft_get_uniform_index(desc->crafter, "modelview_matrix", NGLI_PROGRAM_SHADER_VERT); desc->projection_matrix_index = ngli_pgcraft_get_uniform_index(desc->crafter, "projection_matrix", NGLI_PROGRAM_SHADER_VERT); + desc->color_index = ngli_pgcraft_get_uniform_index(desc->crafter, "color", NGLI_PROGRAM_SHADER_FRAG); return 0; } @@ -665,12 +669,14 @@ static void text_draw(struct ngl_node *node) struct pipeline_subdesc *bg_desc = &desc->bg; ngli_pipeline_update_uniform(bg_desc->pipeline, bg_desc->modelview_matrix_index, modelview_matrix); ngli_pipeline_update_uniform(bg_desc->pipeline, bg_desc->projection_matrix_index, projection_matrix); + ngli_pipeline_update_uniform(bg_desc->pipeline, bg_desc->color_index, s->bg_color); ngli_pipeline_draw_indexed(bg_desc->pipeline, s->bg_indices, NGLI_FORMAT_R16_UNORM, s->nb_bg_indices, 1); if (s->nb_indices) { struct pipeline_subdesc *fg_desc = &desc->fg; ngli_pipeline_update_uniform(fg_desc->pipeline, fg_desc->modelview_matrix_index, modelview_matrix); ngli_pipeline_update_uniform(fg_desc->pipeline, fg_desc->projection_matrix_index, projection_matrix); + ngli_pipeline_update_uniform(fg_desc->pipeline, fg_desc->color_index, s->fg_color); ngli_pipeline_draw_indexed(fg_desc->pipeline, s->indices, NGLI_FORMAT_R16_UNORM, s->nb_indices, 1); } } From 074ea2d5c2be0ac18df6dbba6747762e15852096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 30 Jul 2020 12:15:38 +0200 Subject: [PATCH 013/388] tests/api: test text live change --- tests/api.mak | 1 + tests/api.py | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/tests/api.mak b/tests/api.mak index be6e356a63..c48c48bf4a 100644 --- a/tests/api.mak +++ b/tests/api.mak @@ -28,5 +28,6 @@ API_TEST_NAMES = \ ctx_ownership_subgraph \ capture_buffer_lifetime \ hud \ + text_live_change \ $(eval $(call DECLARE_SIMPLE_TESTS,api,$(API_TEST_NAMES))) diff --git a/tests/api.py b/tests/api.py index 1178a01d82..c2c592ed3b 100644 --- a/tests/api.py +++ b/tests/api.py @@ -23,6 +23,7 @@ import os import pynodegl as ngl from pynodegl_utils.misc import get_backend +from pynodegl_utils.toolbox.grid import autogrid_simple _backend_str = os.environ.get('BACKEND') @@ -141,3 +142,28 @@ def api_hud(width=234, height=123): for i in range(60 * 3): assert viewer.draw(i / 60.) == 0 del viewer + + +def api_text_live_change(width=320, height=240): + import zlib + viewer = ngl.Context() + capture_buffer = bytearray(width * height * 4) + assert viewer.configure(offscreen=1, width=width, height=height, backend=_backend, capture_buffer=capture_buffer) == 0 + + # An empty string forces the text node to deal with a pipeline with nul + # attributes, this is what we exercise here, along with a varying up and + # down number of characters + text_strings = ["foo", "", "foobar", "world", "hello\nworld", "\n\n", "last"] + + # Exercise the diamond-form/prepare mechanism + text_node = ngl.Text() + viewer.set_scene(autogrid_simple([text_node] * 4)) + + viewer.draw(0) + last_crc = zlib.crc32(capture_buffer) + for i, s in enumerate(text_strings, 1): + text_node.set_text(s) + viewer.draw(i) + crc = zlib.crc32(capture_buffer) + assert crc != last_crc + last_crc = crc From ce2f07460563b6c9abab38eb26e64e6c15205f4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Wed, 29 Jul 2020 10:40:40 +0200 Subject: [PATCH 014/388] tools/player: add seekbar text --- ngl-tools/player.c | 56 +++++++++++++++++++++++++++++++++++++++++----- ngl-tools/player.h | 4 ++++ 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/ngl-tools/player.c b/ngl-tools/player.c index f5de56dcb3..b9a5f07fdc 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -161,6 +161,29 @@ static void size_callback(SDL_Window *window, int width, int height) ngl_resize(p->ngl, width, height, p->ngl_config.viewport); } +static void update_text(void) +{ + struct player *p = g_player; + + if (!p->pgbar_text_node) + return; + + const int frame_ts = p->frame_ts / 1000000; + const int duration = p->duration / 1000000; + if (frame_ts == p->text_last_frame_ts && duration == p->text_last_duration) + return; + + char buf[128]; + const int cm = frame_ts / 60; + const int cs = frame_ts % 60; + const int dm = duration / 60; + const int ds = duration % 60; + snprintf(buf, sizeof(buf), "%02d:%02d / %02d:%02d", cm, cs, dm, ds); + ngl_node_param_set(p->pgbar_text_node, "text", buf); + p->text_last_frame_ts = frame_ts; + p->text_last_duration = duration; +} + static void update_time(int64_t seek_at) { struct player *p = g_player; @@ -183,6 +206,13 @@ static void update_time(int64_t seek_at) const int64_t t64_diff = gettime() - p->lasthover; const double opacity = clipd(1.5 - t64_diff / 1000000.0, 0, 1); ngl_node_param_set(p->pgbar_opacity_node, "value", opacity); + + const float text_bg[4] = {1.0, 1.0, 1.0, opacity}; + const float text_fg[4] = {0.0, 0.0, 0.0, opacity}; + ngl_node_param_set(p->pgbar_text_node, "bg_color", text_bg); + ngl_node_param_set(p->pgbar_text_node, "fg_color", text_fg); + + update_text(); } } @@ -190,7 +220,7 @@ static void seek_event(int x) { struct player *p = g_player; const int *vp = p->ngl_config.viewport; - const int pos = clipi(x - vp[0], 0, vp[2]); + const int pos = clipi(x - vp[0], 0, vp[2]) * 3/2; const int64_t seek_at64 = p->duration * pos / vp[2]; p->lasthover = gettime(); update_time(seek_at64); @@ -238,9 +268,16 @@ static struct ngl_node *add_progress_bar(struct ngl_node *scene) struct player *p = g_player; static const float bar_corner[3] = {-1.0, -1.0, 0.0}; - static const float bar_width[3] = { 2.0, 0.0, 0.0}; - static const float bar_height[3] = { 0.0, 2.0 * 0.03, 0.0}; // 3% of the height + static const float bar_width[3] = { 2.0 * 2/3., 0.0, 0.0}; + static const float bar_height[3] = { 0.0, 2.0 * 0.05, 0.0}; // 5% of the height + + static const float text_corner[3] = {1.0 - 2.0 * 1/3., -1.0, 0.0}; + static const float text_width[3] = {2.0 * 1/3., 0.0, 0.0}; + static const float text_height[3] = {0.0, 2.0 * 0.05, 0.0}; + static const float text_bg[4] = {1.0, 1.0, 1.0, 0.0}; + static const float text_fg[4] = {0.0, 0.0, 0.0, 0.0}; + struct ngl_node *text = ngl_node_create(NGL_NODE_TEXT); struct ngl_node *quad = ngl_node_create(NGL_NODE_QUAD); struct ngl_node *program = ngl_node_create(NGL_NODE_PROGRAM); struct ngl_node *render = ngl_node_create(NGL_NODE_RENDER); @@ -251,13 +288,13 @@ static struct ngl_node *add_progress_bar(struct ngl_node *scene) struct ngl_node *group = ngl_node_create(NGL_NODE_GROUP); struct ngl_node *gcfg = ngl_node_create(NGL_NODE_GRAPHICCONFIG); - if (!quad || !program || !render || !time || !v_duration || !v_opacity || + if (!text || !quad || !program || !render || !time || !v_duration || !v_opacity || !coord || !group || !gcfg) { ngl_node_unrefp(&gcfg); goto end; } - struct ngl_node *children[] = {scene, render}; + struct ngl_node *children[] = {scene, render, text}; ngl_node_param_set(quad, "corner", bar_corner); ngl_node_param_set(quad, "width", bar_width); @@ -285,7 +322,16 @@ static struct ngl_node *add_progress_bar(struct ngl_node *scene) ngl_node_param_set(gcfg, "blend_src_factor_a", "zero"); ngl_node_param_set(gcfg, "blend_dst_factor_a", "one"); + ngl_node_param_set(text, "box_corner", text_corner); + ngl_node_param_set(text, "box_width", text_width); + ngl_node_param_set(text, "box_height", text_height); + ngl_node_param_set(text, "bg_color", text_bg); + ngl_node_param_set(text, "fg_color", text_fg); + ngl_node_param_set(text, "aspect_ratio", p->aspect[0], p->aspect[1]); + p->pgbar_opacity_node = v_opacity; + p->pgbar_duration_node = v_duration; + p->pgbar_text_node = text; end: ngl_node_unrefp(&quad); diff --git a/ngl-tools/player.h b/ngl-tools/player.h index 85b7303ad5..2d65987378 100644 --- a/ngl-tools/player.h +++ b/ngl-tools/player.h @@ -43,7 +43,11 @@ struct player { int64_t lasthover; int mouse_down; int fullscreen; + int text_last_frame_ts; + int text_last_duration; struct ngl_node *pgbar_opacity_node; + struct ngl_node *pgbar_text_node; + struct ngl_node *pgbar_duration_node; }; int player_init(struct player *p, const char *win_title, struct ngl_node *scene, From 2882b9781b813b5d1cbc87b44f2db57817226c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 30 Jul 2020 15:38:44 +0200 Subject: [PATCH 015/388] tools/common: line break cosmetic --- ngl-tools/common.h | 1 - 1 file changed, 1 deletion(-) diff --git a/ngl-tools/common.h b/ngl-tools/common.h index 6b76c4fe0b..25d2fbe3b6 100644 --- a/ngl-tools/common.h +++ b/ngl-tools/common.h @@ -19,7 +19,6 @@ * under the License. */ - #ifndef COMMON_H #define COMMON_H From 6820dc7dda2a9a873d36d7011cd96f346ea332b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 30 Jul 2020 23:13:53 +0200 Subject: [PATCH 016/388] tools/player: fix crash on uninit if init was never called --- ngl-tools/player.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ngl-tools/player.c b/ngl-tools/player.c index b9a5f07fdc..4d2a937e6e 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -412,6 +412,8 @@ void player_uninit(void) { struct player *p = g_player; + if (!p) + return; ngl_freep(&p->ngl); SDL_DestroyWindow(p->window); SDL_Quit(); From 15a10c4119174cec59bee94a394ac5fd53ec68f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 27 Aug 2020 19:33:00 +0200 Subject: [PATCH 017/388] nodes: fix parameter overread in check_params_sanity() The parameter must be read as a pointer if and only it's a pointer (which is implied by PARAM_FLAG_NON_NULL). --- libnodegl/nodes.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libnodegl/nodes.c b/libnodegl/nodes.c index da2f3a1319..0a3a0f4dbc 100644 --- a/libnodegl/nodes.c +++ b/libnodegl/nodes.c @@ -261,8 +261,8 @@ static int check_params_sanity(struct ngl_node *node) return 0; while (par->key) { - const void *p = *(uint8_t **)(base_ptr + par->offset); - if ((par->flags & PARAM_FLAG_NON_NULL) && !p) { + const void *p = base_ptr + par->offset; + if ((par->flags & PARAM_FLAG_NON_NULL) && !*(uint8_t **)p) { LOG(ERROR, "%s: %s parameter can not be null", node->label, par->key); return NGL_ERROR_INVALID_ARG; } From b1f4a96e64888fa6be1661202d34c5a6aafd67a9 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 26 Aug 2020 11:16:13 +0200 Subject: [PATCH 018/388] pgcache: use gctx instead of ngl_ctx --- libnodegl/gctx_gl.c | 2 +- libnodegl/pgcache.c | 9 ++++----- libnodegl/pgcache.h | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index ed37165651..4fc5859f37 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -381,7 +381,7 @@ static int gl_init(struct gctx *s) ngli_glstate_probe(gl, &s_priv->glstate); - ret = ngli_pgcache_init(&s->pgcache, s->ctx); + ret = ngli_pgcache_init(&s->pgcache, s); if (ret < 0) return ret; diff --git a/libnodegl/pgcache.c b/libnodegl/pgcache.c index 8774f28791..de96075bbc 100644 --- a/libnodegl/pgcache.c +++ b/libnodegl/pgcache.c @@ -38,9 +38,9 @@ static void reset_cached_frag_map(void *user_arg, void *data) ngli_hmap_freep(&p); } -int ngli_pgcache_init(struct pgcache *s, struct ngl_ctx *ctx) +int ngli_pgcache_init(struct pgcache *s, struct gctx *gctx) { - s->ctx = ctx; + s->gctx = gctx; s->graphics_cache = ngli_hmap_create(); s->compute_cache = ngli_hmap_create(); if (!s->graphics_cache || !s->compute_cache) @@ -54,8 +54,7 @@ static int query_cache(struct pgcache *s, struct program **dstp, struct hmap *cache, const char *cache_key, const char *vert, const char *frag, const char *comp) { - struct ngl_ctx *ctx = s->ctx; - struct gctx *gctx = ctx->gctx; + struct gctx *gctx = s->gctx; struct program *cached_program = ngli_hmap_get(cache, cache_key); if (cached_program) { @@ -118,7 +117,7 @@ int ngli_pgcache_get_compute_program(struct pgcache *s, struct program **dstp, c void ngli_pgcache_reset(struct pgcache *s) { - if (!s->ctx) + if (!s->gctx) return; ngli_hmap_freep(&s->compute_cache); ngli_hmap_freep(&s->graphics_cache); diff --git a/libnodegl/pgcache.h b/libnodegl/pgcache.h index 55da02c753..d3fe743cbe 100644 --- a/libnodegl/pgcache.h +++ b/libnodegl/pgcache.h @@ -26,12 +26,12 @@ #include "program.h" struct pgcache { - struct ngl_ctx *ctx; + struct gctx *gctx; struct hmap *graphics_cache; struct hmap *compute_cache; }; -int ngli_pgcache_init(struct pgcache *s, struct ngl_ctx *ctx); +int ngli_pgcache_init(struct pgcache *s, struct gctx *ctx); int ngli_pgcache_get_graphics_program(struct pgcache *s, struct program **dstp, const char *vert, const char *frag); int ngli_pgcache_get_compute_program(struct pgcache *s, struct program **dstp, const char *comp); void ngli_pgcache_reset(struct pgcache *s); From ad658a89c91da6cfd0c707f42a5a1887477ba985 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 26 Aug 2020 11:52:45 +0200 Subject: [PATCH 019/388] graphicstate: replace ngli_graphicstate_init() by NGLI_GRAPHICSTATE_DEFAULTS --- libnodegl/Makefile | 1 - libnodegl/gctx_gl.c | 3 +-- libnodegl/graphicstate.c | 54 ---------------------------------------- libnodegl/graphicstate.h | 27 +++++++++++++++++++- 4 files changed, 27 insertions(+), 58 deletions(-) delete mode 100644 libnodegl/graphicstate.c diff --git a/libnodegl/Makefile b/libnodegl/Makefile index ce16b4d0dc..2f88396056 100644 --- a/libnodegl/Makefile +++ b/libnodegl/Makefile @@ -73,7 +73,6 @@ LIB_OBJS = animation.o \ drawutils.o \ format.o \ gctx.o \ - graphicstate.o \ gtimer.o \ hmap.o \ hwconv.o \ diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 4fc5859f37..6fb02c96e6 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -398,8 +398,7 @@ static int gl_init(struct gctx *s) ngli_gctx_set_clear_color(s, config->clear_color); - struct graphicstate *graphicstate = &ctx->graphicstate; - ngli_graphicstate_init(graphicstate); + ctx->graphicstate = NGLI_GRAPHICSTATE_DEFAULTS; #if defined(HAVE_VAAPI) ret = ngli_vaapi_init(s->ctx); diff --git a/libnodegl/graphicstate.c b/libnodegl/graphicstate.c deleted file mode 100644 index 7bb9f03dd8..0000000000 --- a/libnodegl/graphicstate.c +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2019 GoPro Inc. - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#include "graphicstate.h" - -static const struct graphicstate default_graphicstate = { - .blend = 0, - .blend_src_factor = NGLI_BLEND_FACTOR_ONE, - .blend_dst_factor = NGLI_BLEND_FACTOR_ZERO, - .blend_src_factor_a = NGLI_BLEND_FACTOR_ONE, - .blend_dst_factor_a = NGLI_BLEND_FACTOR_ZERO, - .blend_op = NGLI_BLEND_OP_ADD, - .blend_op_a = NGLI_BLEND_OP_ADD, - .color_write_mask = NGLI_COLOR_COMPONENT_R_BIT - | NGLI_COLOR_COMPONENT_G_BIT - | NGLI_COLOR_COMPONENT_B_BIT - | NGLI_COLOR_COMPONENT_A_BIT, - .depth_test = 0, - .depth_write_mask = 1, - .depth_func = NGLI_COMPARE_OP_LESS, - .stencil_test = 0, - .stencil_write_mask = 1, - .stencil_func = NGLI_COMPARE_OP_ALWAYS, - .stencil_ref = 0, - .stencil_read_mask = 1, - .stencil_fail = NGLI_STENCIL_OP_KEEP, - .stencil_depth_fail = NGLI_STENCIL_OP_KEEP, - .stencil_depth_pass = NGLI_STENCIL_OP_KEEP, - .cull_face = 0, - .cull_face_mode = NGLI_CULL_MODE_BACK_BIT, -}; - -void ngli_graphicstate_init(struct graphicstate *s) -{ - *s = default_graphicstate; -} diff --git a/libnodegl/graphicstate.h b/libnodegl/graphicstate.h index 2d1285b6a9..5597075c5c 100644 --- a/libnodegl/graphicstate.h +++ b/libnodegl/graphicstate.h @@ -118,6 +118,31 @@ struct graphicstate { int scissor_test; }; -void ngli_graphicstate_init(struct graphicstate *s); +#define NGLI_GRAPHICSTATE_DEFAULTS (struct graphicstate) { \ + .blend = 0, \ + .blend_src_factor = NGLI_BLEND_FACTOR_ONE, \ + .blend_dst_factor = NGLI_BLEND_FACTOR_ZERO, \ + .blend_src_factor_a = NGLI_BLEND_FACTOR_ONE, \ + .blend_dst_factor_a = NGLI_BLEND_FACTOR_ZERO, \ + .blend_op = NGLI_BLEND_OP_ADD, \ + .blend_op_a = NGLI_BLEND_OP_ADD, \ + .color_write_mask = NGLI_COLOR_COMPONENT_R_BIT \ + | NGLI_COLOR_COMPONENT_G_BIT \ + | NGLI_COLOR_COMPONENT_B_BIT \ + | NGLI_COLOR_COMPONENT_A_BIT, \ + .depth_test = 0, \ + .depth_write_mask = 1, \ + .depth_func = NGLI_COMPARE_OP_LESS, \ + .stencil_test = 0, \ + .stencil_write_mask = 1, \ + .stencil_func = NGLI_COMPARE_OP_ALWAYS, \ + .stencil_ref = 0, \ + .stencil_read_mask = 1, \ + .stencil_fail = NGLI_STENCIL_OP_KEEP, \ + .stencil_depth_fail = NGLI_STENCIL_OP_KEEP, \ + .stencil_depth_pass = NGLI_STENCIL_OP_KEEP, \ + .cull_face = 0, \ + .cull_face_mode = NGLI_CULL_MODE_BACK_BIT, \ +} \ #endif From f3d7b6e335717937e26a8f72f88f51c2a94db8fc Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 26 Aug 2020 11:57:12 +0200 Subject: [PATCH 020/388] gctx_gl: do not rely on ngl_ctx graphicstate to reset the OpenGL state --- libnodegl/gctx_gl.c | 3 ++- libnodegl/gctx_gl.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 6fb02c96e6..49e41beb9b 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -380,6 +380,7 @@ static int gl_init(struct gctx *s) ctx->rendertarget_desc = &s_priv->default_rendertarget_desc; ngli_glstate_probe(gl, &s_priv->glstate); + s_priv->default_graphicstate = NGLI_GRAPHICSTATE_DEFAULTS; ret = ngli_pgcache_init(&s->pgcache, s); if (ret < 0) @@ -448,7 +449,7 @@ static int gl_post_draw(struct gctx *s, double t) struct glcontext *gl = s_priv->glcontext; struct ngl_config *config = &ctx->config; - ngli_glstate_update(s, &ctx->graphicstate); + ngli_glstate_update(s, &s_priv->default_graphicstate); if (s_priv->capture_func) s_priv->capture_func(s); diff --git a/libnodegl/gctx_gl.h b/libnodegl/gctx_gl.h index e833f6e179..82ae35447c 100644 --- a/libnodegl/gctx_gl.h +++ b/libnodegl/gctx_gl.h @@ -45,6 +45,7 @@ struct gctx_gl { struct glcontext *glcontext; struct glstate glstate; struct rendertarget *rendertarget; + struct graphicstate default_graphicstate; struct rendertarget_desc default_rendertarget_desc; int viewport[4]; int scissor[4]; From d8ebe680767b56fc97f75528a32f641c059d37cf Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 26 Aug 2020 12:35:30 +0200 Subject: [PATCH 021/388] internal: move graphicstate initialization to cmd_configure() --- libnodegl/api.c | 2 ++ libnodegl/gctx_gl.c | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libnodegl/api.c b/libnodegl/api.c index dcc1324aee..ac34623690 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -31,6 +31,7 @@ #include "darray.h" #include "gctx.h" +#include "graphicstate.h" #include "log.h" #include "math_utils.h" #include "memory.h" @@ -96,6 +97,7 @@ static int cmd_configure(struct ngl_ctx *s, void *arg) } s->config = *config; + s->graphicstate = NGLI_GRAPHICSTATE_DEFAULTS; s->gctx = ngli_gctx_create(s); if (!s->gctx) diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 49e41beb9b..23da58c48a 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -399,8 +399,6 @@ static int gl_init(struct gctx *s) ngli_gctx_set_clear_color(s, config->clear_color); - ctx->graphicstate = NGLI_GRAPHICSTATE_DEFAULTS; - #if defined(HAVE_VAAPI) ret = ngli_vaapi_init(s->ctx); if (ret < 0) From 4fc85e71a14533d041aa413b9ad53264175b0204 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 26 Aug 2020 13:52:09 +0200 Subject: [PATCH 022/388] internal: move vaapi initialization to cmd_configure()/cmd_stop() --- libnodegl/api.c | 13 +++++++++++++ libnodegl/gctx_gl.c | 13 ------------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/libnodegl/api.c b/libnodegl/api.c index ac34623690..1f53d19ee2 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -39,6 +39,10 @@ #include "nodes.h" #include "rnode.h" +#if defined(HAVE_VAAPI) +#include "vaapi.h" +#endif + #if defined(TARGET_DARWIN) || defined(TARGET_IPHONE) #if defined(BACKEND_GL) #include "gctx_gl.h" @@ -70,6 +74,9 @@ static int get_default_platform(void) static int cmd_stop(struct ngl_ctx *s, void *arg) { +#if defined(HAVE_VAAPI) + ngli_vaapi_reset(s); +#endif ngli_texture_freep(&s->font_atlas); // allocated by the first node text ngli_gctx_freep(&s->gctx); @@ -110,6 +117,12 @@ static int cmd_configure(struct ngl_ctx *s, void *arg) return ret; } +#if defined(HAVE_VAAPI) + ret = ngli_vaapi_init(s); + if (ret < 0) + LOG(WARNING, "could not initialize vaapi"); +#endif + if (s->scene) { ret = ngli_node_attach_ctx(s->scene, s); if (ret < 0) { diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 23da58c48a..9478d35b12 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -38,10 +38,6 @@ #include "rendertarget_gl.h" #include "texture_gl.h" -#if defined(HAVE_VAAPI) -#include "vaapi.h" -#endif - static int offscreen_rendertarget_init(struct gctx *s) { struct ngl_ctx *ctx = s->ctx; @@ -399,12 +395,6 @@ static int gl_init(struct gctx *s) ngli_gctx_set_clear_color(s, config->clear_color); -#if defined(HAVE_VAAPI) - ret = ngli_vaapi_init(s->ctx); - if (ret < 0) - LOG(WARNING, "could not initialize vaapi"); -#endif - return 0; } @@ -470,9 +460,6 @@ static void gl_destroy(struct gctx *s) ngli_pgcache_reset(&s->pgcache); capture_reset(s); offscreen_rendertarget_reset(s); -#if defined(HAVE_VAAPI) - ngli_vaapi_reset(s->ctx); -#endif ngli_glcontext_freep(&s_priv->glcontext); } From d54fbc9854a8696adcc8a300afd4903f034bc276 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 26 Aug 2020 14:18:18 +0200 Subject: [PATCH 023/388] gctx: pass scene argument to ngli_gctx_draw() --- libnodegl/api.c | 3 ++- libnodegl/gctx.c | 9 ++++----- libnodegl/gctx.h | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libnodegl/api.c b/libnodegl/api.c index 1f53d19ee2..da9b68375c 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -205,7 +205,8 @@ static int cmd_draw(struct ngl_ctx *s, void *arg) if (ret < 0) return ret; - return ngli_gctx_draw(s->gctx, t); + struct ngl_node *scene = s->scene; + return ngli_gctx_draw(s->gctx, scene, t); } static int dispatch_cmd(struct ngl_ctx *s, cmd_func_type cmd_func, void *arg) diff --git a/libnodegl/gctx.c b/libnodegl/gctx.c index cbc0128429..36d5b11570 100644 --- a/libnodegl/gctx.c +++ b/libnodegl/gctx.c @@ -67,18 +67,17 @@ int ngli_gctx_resize(struct gctx *s, int width, int height, const int *viewport) return class->resize(s, width, height, viewport); } -int ngli_gctx_draw(struct gctx *s, double t) +int ngli_gctx_draw(struct gctx *s, struct ngl_node *scene, double t) { - struct ngl_ctx *ctx = s->ctx; const struct gctx_class *class = s->class; int ret = class->pre_draw(s, t); if (ret < 0) goto end; - if (ctx->scene) { - LOG(DEBUG, "draw scene %s @ t=%f", ctx->scene->label, t); - ngli_node_draw(ctx->scene); + if (scene) { + LOG(DEBUG, "draw scene %s @ t=%f", scene->label, t); + ngli_node_draw(scene); } end:; diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index 7f704d45f9..7e0bca3544 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -110,7 +110,7 @@ struct gctx { struct gctx *ngli_gctx_create(struct ngl_ctx *ctx); int ngli_gctx_init(struct gctx *s); int ngli_gctx_resize(struct gctx *s, int width, int height, const int *viewport); -int ngli_gctx_draw(struct gctx *s, double t); +int ngli_gctx_draw(struct gctx *s, struct ngl_node *scene, double t); void ngli_gctx_freep(struct gctx **sp); void ngli_gctx_set_rendertarget(struct gctx *s, struct rendertarget *rt); From 96055676457308e137c2e95a1119466867c06d0d Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 26 Aug 2020 14:30:32 +0200 Subject: [PATCH 024/388] gctx: add ngli_gctx_get_default_rendertarget_desc() --- libnodegl/gctx.c | 5 +++++ libnodegl/gctx.h | 2 ++ libnodegl/gctx_gl.c | 8 ++++++++ 3 files changed, 15 insertions(+) diff --git a/libnodegl/gctx.c b/libnodegl/gctx.c index 36d5b11570..5366a7942c 100644 --- a/libnodegl/gctx.c +++ b/libnodegl/gctx.c @@ -111,6 +111,11 @@ struct rendertarget *ngli_gctx_get_rendertarget(struct gctx *s) return s->class->get_rendertarget(s); } +struct rendertarget_desc *ngli_gctx_get_default_rendertarget_desc(struct gctx *s) +{ + return s->class->get_default_rendertarget_desc(s); +} + void ngli_gctx_set_viewport(struct gctx *s, const int *viewport) { s->class->set_viewport(s, viewport); diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index 7e0bca3544..8c0a39193c 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -44,6 +44,7 @@ struct gctx_class { void (*set_rendertarget)(struct gctx *s, struct rendertarget *rt); struct rendertarget *(*get_rendertarget)(struct gctx *s); + struct rendertarget_desc *(*get_default_rendertarget_desc)(struct gctx *s); void (*set_viewport)(struct gctx *s, const int *viewport); void (*get_viewport)(struct gctx *s, int *viewport); void (*set_scissor)(struct gctx *s, const int *scissor); @@ -115,6 +116,7 @@ void ngli_gctx_freep(struct gctx **sp); void ngli_gctx_set_rendertarget(struct gctx *s, struct rendertarget *rt); struct rendertarget *ngli_gctx_get_rendertarget(struct gctx *s); +struct rendertarget_desc *ngli_gctx_get_default_rendertarget_desc(struct gctx *s); void ngli_gctx_set_viewport(struct gctx *s, const int *viewport); void ngli_gctx_get_viewport(struct gctx *s, int *viewport); diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 9478d35b12..c65dbfdc3e 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -484,6 +484,12 @@ static struct rendertarget *gl_get_rendertarget(struct gctx *s) return s_priv->rendertarget; } +static struct rendertarget_desc *gl_get_default_rendertarget_desc(struct gctx *s) +{ + struct gctx_gl *s_priv = (struct gctx_gl *)s; + return &s_priv->default_rendertarget_desc; +} + static void gl_set_viewport(struct gctx *s, const int *viewport) { struct gctx_gl *s_priv = (struct gctx_gl *)s; @@ -589,6 +595,7 @@ const struct gctx_class ngli_gctx_gl = { .set_rendertarget = gl_set_rendertarget, .get_rendertarget = gl_get_rendertarget, + .get_default_rendertarget_desc = gl_get_default_rendertarget_desc, .set_viewport = gl_set_viewport, .get_viewport = gl_get_viewport, .set_scissor = gl_set_scissor, @@ -654,6 +661,7 @@ const struct gctx_class ngli_gctx_gles = { .set_rendertarget = gl_set_rendertarget, .get_rendertarget = gl_get_rendertarget, + .get_default_rendertarget_desc = gl_get_default_rendertarget_desc, .set_viewport = gl_set_viewport, .get_viewport = gl_get_viewport, .set_scissor = gl_set_scissor, From 3d4f5608723da18a0a290a4acd4456fd419debe5 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 26 Aug 2020 14:32:25 +0200 Subject: [PATCH 025/388] internal: move set of default rendertarget description to cmd_configure() --- libnodegl/api.c | 2 ++ libnodegl/gctx_gl.c | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/libnodegl/api.c b/libnodegl/api.c index da9b68375c..e71052cc4f 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -117,6 +117,8 @@ static int cmd_configure(struct ngl_ctx *s, void *arg) return ret; } + s->rendertarget_desc = ngli_gctx_get_default_rendertarget_desc(s->gctx); + #if defined(HAVE_VAAPI) ret = ngli_vaapi_init(s); if (ret < 0) diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index c65dbfdc3e..06743e21ba 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -373,7 +373,6 @@ static int gl_init(struct gctx *s) s_priv->default_rendertarget_desc.depth_stencil.format = NGLI_FORMAT_D24_UNORM_S8_UINT; s_priv->default_rendertarget_desc.depth_stencil.samples = gl->samples; s_priv->default_rendertarget_desc.depth_stencil.resolve = gl->samples > 1; - ctx->rendertarget_desc = &s_priv->default_rendertarget_desc; ngli_glstate_probe(gl, &s_priv->glstate); s_priv->default_graphicstate = NGLI_GRAPHICSTATE_DEFAULTS; From 1a50810d49018aebecada35c644fd82a1bf5f213 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 26 Aug 2020 14:33:46 +0200 Subject: [PATCH 026/388] gctx: remove ngl_ctx dependency --- libnodegl/api.c | 2 +- libnodegl/gctx.c | 8 +++----- libnodegl/gctx.h | 6 +++--- libnodegl/gctx_gl.c | 23 ++++++++--------------- 4 files changed, 15 insertions(+), 24 deletions(-) diff --git a/libnodegl/api.c b/libnodegl/api.c index e71052cc4f..8c81803cd5 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -106,7 +106,7 @@ static int cmd_configure(struct ngl_ctx *s, void *arg) s->config = *config; s->graphicstate = NGLI_GRAPHICSTATE_DEFAULTS; - s->gctx = ngli_gctx_create(s); + s->gctx = ngli_gctx_create(config); if (!s->gctx) return NGL_ERROR_MEMORY; diff --git a/libnodegl/gctx.c b/libnodegl/gctx.c index 5366a7942c..2f22025fb0 100644 --- a/libnodegl/gctx.c +++ b/libnodegl/gctx.c @@ -37,10 +37,8 @@ static const struct gctx_class *backend_map[] = { #endif }; -struct gctx *ngli_gctx_create(struct ngl_ctx *ctx) +struct gctx *ngli_gctx_create(const struct ngl_config *config) { - struct ngl_config *config = &ctx->config; - if (config->backend < 0 || config->backend >= NGLI_ARRAY_NB(backend_map) || !backend_map[config->backend]) { @@ -48,10 +46,10 @@ struct gctx *ngli_gctx_create(struct ngl_ctx *ctx) return NULL; } const struct gctx_class *class = backend_map[config->backend]; - struct gctx *s = class->create(ctx); + struct gctx *s = class->create(config); if (!s) return NULL; - s->ctx = ctx; + s->config = *config; s->class = class; return s; } diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index 8c0a39193c..abfe6048a4 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -35,7 +35,7 @@ struct gctx_class { const char *name; - struct gctx *(*create)(struct ngl_ctx *ctx); + struct gctx *(*create)(const struct ngl_config *config); int (*init)(struct gctx *s); int (*resize)(struct gctx *s, int width, int height, const int *viewport); int (*pre_draw)(struct gctx *s, double t); @@ -100,7 +100,7 @@ struct gctx_class { }; struct gctx { - struct ngl_ctx *ctx; + struct ngl_config config; const struct gctx_class *class; int version; int features; @@ -108,7 +108,7 @@ struct gctx { struct pgcache pgcache; }; -struct gctx *ngli_gctx_create(struct ngl_ctx *ctx); +struct gctx *ngli_gctx_create(const struct ngl_config *config); int ngli_gctx_init(struct gctx *s); int ngli_gctx_resize(struct gctx *s, int width, int height, const int *viewport); int ngli_gctx_draw(struct gctx *s, struct ngl_node *scene, double t); diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 06743e21ba..3cbd461b07 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -40,10 +40,9 @@ static int offscreen_rendertarget_init(struct gctx *s) { - struct ngl_ctx *ctx = s->ctx; struct gctx_gl *s_priv = (struct gctx_gl *)s; struct glcontext *gl = s_priv->glcontext; - struct ngl_config *config = &ctx->config; + struct ngl_config *config = &s->config; if (!(gl->features & NGLI_FEATURE_FRAMEBUFFER_OBJECT) && config->samples > 0) { LOG(WARNING, "context does not support the framebuffer object feature, " @@ -107,9 +106,8 @@ static void offscreen_rendertarget_reset(struct gctx *s) static void capture_default(struct gctx *s) { - struct ngl_ctx *ctx = s->ctx; struct gctx_gl *s_priv = (struct gctx_gl *)s; - struct ngl_config *config = &ctx->config; + struct ngl_config *config = &s->config; struct rendertarget *rt = s_priv->rt; struct rendertarget *capture_rt = s_priv->capture_rt; @@ -130,9 +128,8 @@ static void capture_ios(struct gctx *s) static void capture_gles_msaa(struct gctx *s) { - struct ngl_ctx *ctx = s->ctx; struct gctx_gl *s_priv = (struct gctx_gl *)s; - struct ngl_config *config = &ctx->config; + struct ngl_config *config = &s->config; struct rendertarget *rt = s_priv->rt; struct rendertarget *capture_rt = s_priv->capture_rt; struct rendertarget *oes_resolve_rt = s_priv->oes_resolve_rt; @@ -157,9 +154,8 @@ static void capture_ios_msaa(struct gctx *s) static void capture_cpu_fallback(struct gctx *s) { - struct ngl_ctx *ctx = s->ctx; struct gctx_gl *s_priv = (struct gctx_gl *)s; - struct ngl_config *config = &ctx->config; + struct ngl_config *config = &s->config; struct rendertarget *rt = s_priv->rt; ngli_rendertarget_read_pixels(rt, s_priv->capture_buffer); @@ -175,10 +171,9 @@ static void capture_cpu_fallback(struct gctx *s) static int capture_init(struct gctx *s) { - struct ngl_ctx *ctx = s->ctx; struct gctx_gl *s_priv = (struct gctx_gl *)s; struct glcontext *gl = s_priv->glcontext; - struct ngl_config *config = &ctx->config; + struct ngl_config *config = &s->config; const int ios_capture = gl->platform == NGL_PLATFORM_IOS && config->window; if (!config->capture_buffer && !ios_capture) @@ -331,7 +326,7 @@ static void capture_reset(struct gctx *s) s_priv->capture_func = NULL; } -static struct gctx *gl_create(struct ngl_ctx *ctx) +static struct gctx *gl_create(const struct ngl_config *config) { struct gctx_gl *s = ngli_calloc(1, sizeof(*s)); if (!s) @@ -342,8 +337,7 @@ static struct gctx *gl_create(struct ngl_ctx *ctx) static int gl_init(struct gctx *s) { int ret; - struct ngl_ctx *ctx = s->ctx; - const struct ngl_config *config = &ctx->config; + const struct ngl_config *config = &s->config; struct gctx_gl *s_priv = (struct gctx_gl *)s; s_priv->glcontext = ngli_glcontext_new(config); @@ -431,10 +425,9 @@ static int gl_pre_draw(struct gctx *s, double t) static int gl_post_draw(struct gctx *s, double t) { - struct ngl_ctx *ctx = s->ctx; struct gctx_gl *s_priv = (struct gctx_gl *)s; struct glcontext *gl = s_priv->glcontext; - struct ngl_config *config = &ctx->config; + struct ngl_config *config = &s->config; ngli_glstate_update(s, &s_priv->default_graphicstate); From 2f1df362aa9f60eb3e156e730a2a2a6ab5a8a945 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 26 Aug 2020 15:36:31 +0200 Subject: [PATCH 027/388] internal: move pgcache from gctx_gl to ngl_ctx --- libnodegl/api.c | 6 ++++++ libnodegl/gctx.h | 2 -- libnodegl/gctx_gl.c | 5 ----- libnodegl/nodes.h | 1 + libnodegl/pgcraft.c | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libnodegl/api.c b/libnodegl/api.c index 8c81803cd5..3c9d56ac23 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -37,6 +37,7 @@ #include "memory.h" #include "nodegl.h" #include "nodes.h" +#include "pgcache.h" #include "rnode.h" #if defined(HAVE_VAAPI) @@ -78,6 +79,7 @@ static int cmd_stop(struct ngl_ctx *s, void *arg) ngli_vaapi_reset(s); #endif ngli_texture_freep(&s->font_atlas); // allocated by the first node text + ngli_pgcache_reset(&s->pgcache); ngli_gctx_freep(&s->gctx); return 0; @@ -119,6 +121,10 @@ static int cmd_configure(struct ngl_ctx *s, void *arg) s->rendertarget_desc = ngli_gctx_get_default_rendertarget_desc(s->gctx); + ret = ngli_pgcache_init(&s->pgcache, s->gctx); + if (ret < 0) + return ret; + #if defined(HAVE_VAAPI) ret = ngli_vaapi_init(s); if (ret < 0) diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index abfe6048a4..02e18a702f 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -27,7 +27,6 @@ #include "gtimer.h" #include "limits.h" #include "nodegl.h" -#include "pgcache.h" #include "pipeline.h" #include "rendertarget.h" #include "texture.h" @@ -105,7 +104,6 @@ struct gctx { int version; int features; struct limits limits; - struct pgcache pgcache; }; struct gctx *ngli_gctx_create(const struct ngl_config *config); diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 3cbd461b07..de620e254b 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -371,10 +371,6 @@ static int gl_init(struct gctx *s) ngli_glstate_probe(gl, &s_priv->glstate); s_priv->default_graphicstate = NGLI_GRAPHICSTATE_DEFAULTS; - ret = ngli_pgcache_init(&s->pgcache, s); - if (ret < 0) - return ret; - const int *viewport = config->viewport; if (viewport[2] > 0 && viewport[3] > 0) { ngli_gctx_set_viewport(s, viewport); @@ -449,7 +445,6 @@ static int gl_post_draw(struct gctx *s, double t) static void gl_destroy(struct gctx *s) { struct gctx_gl *s_priv = (struct gctx_gl *)s; - ngli_pgcache_reset(&s->pgcache); capture_reset(s); offscreen_rendertarget_reset(s); ngli_glcontext_freep(&s_priv->glcontext); diff --git a/libnodegl/nodes.h b/libnodegl/nodes.h index 0e7512c3ec..af4630e039 100644 --- a/libnodegl/nodes.h +++ b/libnodegl/nodes.h @@ -87,6 +87,7 @@ struct ngl_ctx { struct darray projection_matrix_stack; struct darray activitycheck_nodes; struct texture *font_atlas; + struct pgcache pgcache; #if defined(HAVE_VAAPI_X11) Display *x11_display; #endif diff --git a/libnodegl/pgcraft.c b/libnodegl/pgcraft.c index 066cc7429c..2a49ac1678 100644 --- a/libnodegl/pgcraft.c +++ b/libnodegl/pgcraft.c @@ -1038,7 +1038,7 @@ static int get_program_compute(struct pgcraft *s, const struct pgcraft_params *p return ret; const char *comp = ngli_bstr_strptr(s->shaders[NGLI_PROGRAM_SHADER_COMP]); - ret = ngli_pgcache_get_compute_program(&s->ctx->gctx->pgcache, &s->program, comp); + ret = ngli_pgcache_get_compute_program(&s->ctx->pgcache, &s->program, comp); ngli_bstr_freep(&s->shaders[NGLI_PROGRAM_SHADER_COMP]); return ret; } @@ -1063,7 +1063,7 @@ static int get_program_graphics(struct pgcraft *s, const struct pgcraft_params * const char *vert = ngli_bstr_strptr(s->shaders[NGLI_PROGRAM_SHADER_VERT]); const char *frag = ngli_bstr_strptr(s->shaders[NGLI_PROGRAM_SHADER_FRAG]); - ret = ngli_pgcache_get_graphics_program(&s->ctx->gctx->pgcache, &s->program, vert, frag); + ret = ngli_pgcache_get_graphics_program(&s->ctx->pgcache, &s->program, vert, frag); ngli_bstr_freep(&s->shaders[NGLI_PROGRAM_SHADER_VERT]); ngli_bstr_freep(&s->shaders[NGLI_PROGRAM_SHADER_FRAG]); return ret; From 33993cf3edc7abe9f154f38d7db8a25859426b71 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 26 Aug 2020 16:27:45 +0200 Subject: [PATCH 028/388] format: add ngli_format_has_{depth,stencil}() --- libnodegl/format.c | 27 +++++++++++++++++++++++++++ libnodegl/format.h | 4 ++++ 2 files changed, 31 insertions(+) diff --git a/libnodegl/format.c b/libnodegl/format.c index 2851f348a0..bff6099d7a 100644 --- a/libnodegl/format.c +++ b/libnodegl/format.c @@ -97,3 +97,30 @@ int ngli_format_get_nb_comp(int format) { return format_comp_sizes[format].nb_comp; } + +int ngli_format_has_depth(int format) +{ + switch (format) { + case NGLI_FORMAT_D16_UNORM: + case NGLI_FORMAT_X8_D24_UNORM_PACK32: + case NGLI_FORMAT_D32_SFLOAT: + case NGLI_FORMAT_D24_UNORM_S8_UINT: + case NGLI_FORMAT_D32_SFLOAT_S8_UINT: + return 1; + default: + return 0; + } +} + +int ngli_format_has_stencil(int format) +{ + switch (format) { + case NGLI_FORMAT_X8_D24_UNORM_PACK32: + case NGLI_FORMAT_D24_UNORM_S8_UINT: + case NGLI_FORMAT_D32_SFLOAT_S8_UINT: + case NGLI_FORMAT_S8_UINT: + return 1; + default: + return 0; + } +} diff --git a/libnodegl/format.h b/libnodegl/format.h index a6cef4b78e..129c9e64aa 100644 --- a/libnodegl/format.h +++ b/libnodegl/format.h @@ -92,4 +92,8 @@ int ngli_format_get_bytes_per_pixel(int format); int ngli_format_get_nb_comp(int format); +int ngli_format_has_depth(int format); + +int ngli_format_has_stencil(int format); + #endif From d2f82be016cf8f479af2a100d5c9cce6b444b218 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 26 Aug 2020 16:32:06 +0200 Subject: [PATCH 029/388] pass: check that rendertarget supports depth/stencil operations --- libnodegl/pass.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/libnodegl/pass.c b/libnodegl/pass.c index c6720cbfd2..9a8974cf02 100644 --- a/libnodegl/pass.c +++ b/libnodegl/pass.c @@ -437,6 +437,17 @@ int ngli_pass_prepare(struct pass *s) struct ngl_ctx *ctx = s->ctx; struct gctx *gctx = ctx->gctx; + const int format = ctx->rendertarget_desc->depth_stencil.format; + if (ctx->graphicstate.depth_test && !ngli_format_has_depth(format)) { + LOG(ERROR, "depth testing is not support on rendertargets with no depth attachment"); + return NGL_ERROR_INVALID_USAGE; + } + + if (ctx->graphicstate.stencil_test && !ngli_format_has_stencil(format)) { + LOG(ERROR, "stencil operations are not support on rendertargets with no stencil attachment"); + return NGL_ERROR_INVALID_USAGE; + } + struct pipeline_graphics pipeline_graphics = s->pipeline_graphics; pipeline_graphics.state = ctx->graphicstate; pipeline_graphics.rt_desc = *ctx->rendertarget_desc; From 14dc2e345137fecf1ff08690eb9ff1f757fc5e7f Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 26 Aug 2020 23:50:54 +0200 Subject: [PATCH 030/388] hwconv: use NGLI_GRAPHICSTATE_DEFAULTS where appropriate Texture conversions that happen in hwconv need the default graphics state in order to work properly. This commit avoids relying on ngl_ctx graphicstate, even if its value is set to NGLI_GRAPHICSTATE_DEFAULTS during the update phase (when hwconv is executed). --- libnodegl/hwconv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libnodegl/hwconv.c b/libnodegl/hwconv.c index 9ecbcf8d34..96f184b555 100644 --- a/libnodegl/hwconv.c +++ b/libnodegl/hwconv.c @@ -129,7 +129,7 @@ int ngli_hwconv_init(struct hwconv *hwconv, struct ngl_ctx *ctx, .type = NGLI_PIPELINE_TYPE_GRAPHICS, .graphics = { .topology = NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, - .state = ctx->graphicstate, + .state = NGLI_GRAPHICSTATE_DEFAULTS, .rt_desc = rt_desc, }, }; From 745c6141325a72cdcf8b69c09d67c170a4e7123e Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 26 Aug 2020 23:54:51 +0200 Subject: [PATCH 031/388] graphicconfig: forward ngli_node_prepare() return --- libnodegl/node_graphicconfig.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libnodegl/node_graphicconfig.c b/libnodegl/node_graphicconfig.c index d55d47877d..491b09fe6d 100644 --- a/libnodegl/node_graphicconfig.c +++ b/libnodegl/node_graphicconfig.c @@ -296,10 +296,10 @@ static int graphicconfig_prepare(struct ngl_node *node) struct ngl_node *child = s->child; honor_config(node, 0); - ngli_node_prepare(child); + int ret = ngli_node_prepare(child); honor_config(node, 1); - return 0; + return ret; } static void graphicconfig_draw(struct ngl_node *node) From 58cfdd6765df691f03b86bdb56766b4c337bf2c0 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 26 Aug 2020 23:51:56 +0200 Subject: [PATCH 032/388] internal: use the rnode structure to store the rendering state This commit moves the rendering state management from the ngl_ctx structure to the rnode structure. Previously, the rendering state (graphics state + rendertarget description) was honored in each relevant nodes (graphicconfig, rtt) by altering the relevant fields in ngl_ctx before calling ngli_node_prepare() on the node's child. Then the state was restored to its previous value at the end of the node's prepare function. With this commit, the rendering state is now honored by directly setting the state to the current rnode (which corresponds to the current branch in the node.gl graph). When a leaf node is reached by the prepare function, the current rnode structure will hold the final rendering state that can be used by a graphics pipeline. Using this method: - removes the need to restore the previous state at the end of the node's prepare function - avoids altering two fields in ngl_ctx while ngli_node_prepare() traverses the graph - allows the final rendering state to be accessed by rendering nodes later on (update, draw). Moreover, the graphicconfig node still keeps a local copy of the parent state which might be useful to support live-changes later on. --- libnodegl/api.c | 7 +++- libnodegl/node_graphicconfig.c | 74 ++++++++++++++++------------------ libnodegl/node_hud.c | 5 ++- libnodegl/node_rtt.c | 11 ++--- libnodegl/node_text.c | 10 +++-- libnodegl/nodes.h | 2 - libnodegl/pass.c | 11 ++--- libnodegl/rnode.c | 2 + libnodegl/rnode.h | 4 ++ 9 files changed, 63 insertions(+), 63 deletions(-) diff --git a/libnodegl/api.c b/libnodegl/api.c index 3c9d56ac23..8cbe33b67c 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -106,7 +106,6 @@ static int cmd_configure(struct ngl_ctx *s, void *arg) } s->config = *config; - s->graphicstate = NGLI_GRAPHICSTATE_DEFAULTS; s->gctx = ngli_gctx_create(config); if (!s->gctx) @@ -119,7 +118,8 @@ static int cmd_configure(struct ngl_ctx *s, void *arg) return ret; } - s->rendertarget_desc = ngli_gctx_get_default_rendertarget_desc(s->gctx); + s->rnode_pos->graphicstate = NGLI_GRAPHICSTATE_DEFAULTS; + s->rnode_pos->rendertarget_desc = *ngli_gctx_get_default_rendertarget_desc(s->gctx); ret = ngli_pgcache_init(&s->pgcache, s->gctx); if (ret < 0) @@ -164,6 +164,9 @@ static int cmd_set_scene(struct ngl_ctx *s, void *arg) } ngli_rnode_clear(&s->rnode); + s->rnode_pos->graphicstate = NGLI_GRAPHICSTATE_DEFAULTS; + s->rnode_pos->rendertarget_desc = *ngli_gctx_get_default_rendertarget_desc(s->gctx); + struct ngl_node *scene = arg; if (!scene) return 0; diff --git a/libnodegl/node_graphicconfig.c b/libnodegl/node_graphicconfig.c index 491b09fe6d..75a2bd2fef 100644 --- a/libnodegl/node_graphicconfig.c +++ b/libnodegl/node_graphicconfig.c @@ -249,45 +249,42 @@ static int graphicconfig_update(struct ngl_node *node, double t) } \ } while (0) \ -static void honor_config(struct ngl_node *node, int restore) +static void honor_config(struct ngl_node *node) { struct ngl_ctx *ctx = node->ctx; + struct rnode *rnode = ctx->rnode_pos; struct graphicconfig_priv *s = node->priv_data; + struct graphicstate *pending = &rnode->graphicstate; - if (restore) { - ctx->graphicstate = s->graphicstate; - } else { - struct graphicstate *pending = &ctx->graphicstate; - s->graphicstate = *pending; - - COPY_PARAM(blend); - COPY_PARAM(blend_dst_factor); - COPY_PARAM(blend_src_factor); - COPY_PARAM(blend_dst_factor_a); - COPY_PARAM(blend_src_factor_a); - COPY_PARAM(blend_op); - COPY_PARAM(blend_op_a); - - COPY_PARAM(color_write_mask); - - COPY_PARAM(depth_test); - COPY_PARAM(depth_write_mask); - COPY_PARAM(depth_func); - - COPY_PARAM(stencil_test); - COPY_PARAM(stencil_write_mask); - COPY_PARAM(stencil_func); - COPY_PARAM(stencil_ref); - COPY_PARAM(stencil_read_mask); - COPY_PARAM(stencil_fail); - COPY_PARAM(stencil_depth_fail); - COPY_PARAM(stencil_depth_pass); - - COPY_PARAM(cull_face); - COPY_PARAM(cull_face_mode); - - COPY_PARAM(scissor_test); - } + s->graphicstate = *pending; + + COPY_PARAM(blend); + COPY_PARAM(blend_dst_factor); + COPY_PARAM(blend_src_factor); + COPY_PARAM(blend_dst_factor_a); + COPY_PARAM(blend_src_factor_a); + COPY_PARAM(blend_op); + COPY_PARAM(blend_op_a); + + COPY_PARAM(color_write_mask); + + COPY_PARAM(depth_test); + COPY_PARAM(depth_write_mask); + COPY_PARAM(depth_func); + + COPY_PARAM(stencil_test); + COPY_PARAM(stencil_write_mask); + COPY_PARAM(stencil_func); + COPY_PARAM(stencil_ref); + COPY_PARAM(stencil_read_mask); + COPY_PARAM(stencil_fail); + COPY_PARAM(stencil_depth_fail); + COPY_PARAM(stencil_depth_pass); + + COPY_PARAM(cull_face); + COPY_PARAM(cull_face_mode); + + COPY_PARAM(scissor_test); } static int graphicconfig_prepare(struct ngl_node *node) @@ -295,11 +292,8 @@ static int graphicconfig_prepare(struct ngl_node *node) struct graphicconfig_priv *s = node->priv_data; struct ngl_node *child = s->child; - honor_config(node, 0); - int ret = ngli_node_prepare(child); - honor_config(node, 1); - - return ret; + honor_config(node); + return ngli_node_prepare(child); } static void graphicconfig_draw(struct ngl_node *node) diff --git a/libnodegl/node_hud.c b/libnodegl/node_hud.c index 63361efca5..6e8abce00a 100644 --- a/libnodegl/node_hud.c +++ b/libnodegl/node_hud.c @@ -1321,7 +1321,8 @@ static int hud_init(struct ngl_node *node) }, }; - struct graphicstate graphicstate = ctx->graphicstate; + struct rnode *rnode = ctx->rnode_pos; + struct graphicstate graphicstate = rnode->graphicstate; graphicstate.blend = 1; graphicstate.blend_src_factor = NGLI_BLEND_FACTOR_SRC_ALPHA; graphicstate.blend_dst_factor = NGLI_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; @@ -1333,7 +1334,7 @@ static int hud_init(struct ngl_node *node) .graphics = { .topology = NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, .state = graphicstate, - .rt_desc = *ctx->rendertarget_desc, + .rt_desc = rnode->rendertarget_desc, } }; diff --git a/libnodegl/node_rtt.c b/libnodegl/node_rtt.c index 597b895e7f..f22c0c89b5 100644 --- a/libnodegl/node_rtt.c +++ b/libnodegl/node_rtt.c @@ -123,6 +123,7 @@ static int rtt_prepare(struct ngl_node *node) { struct ngl_ctx *ctx = node->ctx; struct gctx *gctx = ctx->gctx; + struct rnode *rnode = ctx->rnode_pos; struct rtt_priv *s = node->priv_data; struct rendertarget_desc desc = {0}; @@ -152,15 +153,9 @@ static int rtt_prepare(struct ngl_node *node) desc.depth_stencil.format = depth_format; desc.depth_stencil.samples = s->samples; } + rnode->rendertarget_desc = desc; - struct rendertarget_desc *prev_desc = ctx->rendertarget_desc; - ctx->rendertarget_desc = &desc; - - int ret = ngli_node_prepare(s->child); - - ctx->rendertarget_desc = prev_desc; - - return ret; + return ngli_node_prepare(s->child); } static int rtt_prefetch(struct ngl_node *node) diff --git a/libnodegl/node_text.c b/libnodegl/node_text.c index d040a09a36..4d117bad1b 100644 --- a/libnodegl/node_text.c +++ b/libnodegl/node_text.c @@ -503,6 +503,7 @@ static int init_subdesc(struct ngl_node *node, static int bg_prepare(struct ngl_node *node, struct pipeline_subdesc *desc) { struct ngl_ctx *ctx = node->ctx; + struct rnode *rnode = ctx->rnode_pos; struct text_priv *s = node->priv_data; const struct pgcraft_uniform uniforms[] = { @@ -525,8 +526,8 @@ static int bg_prepare(struct ngl_node *node, struct pipeline_subdesc *desc) .type = NGLI_PIPELINE_TYPE_GRAPHICS, .graphics = { .topology = NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, - .state = ctx->graphicstate, - .rt_desc = *ctx->rendertarget_desc, + .state = rnode->graphicstate, + .rt_desc = rnode->rendertarget_desc, } }; @@ -545,6 +546,7 @@ static int bg_prepare(struct ngl_node *node, struct pipeline_subdesc *desc) static int fg_prepare(struct ngl_node *node, struct pipeline_subdesc *desc) { struct ngl_ctx *ctx = node->ctx; + struct rnode *rnode = ctx->rnode_pos; struct text_priv *s = node->priv_data; const struct pgcraft_uniform uniforms[] = { @@ -580,7 +582,7 @@ static int fg_prepare(struct ngl_node *node, struct pipeline_subdesc *desc) }; /* This controls how the characters blend onto the background */ - struct graphicstate state = ctx->graphicstate; + struct graphicstate state = rnode->graphicstate; state.blend = 1; state.blend_src_factor = NGLI_BLEND_FACTOR_SRC_ALPHA; state.blend_dst_factor = NGLI_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; @@ -592,7 +594,7 @@ static int fg_prepare(struct ngl_node *node, struct pipeline_subdesc *desc) .graphics = { .topology = NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, .state = state, - .rt_desc = *ctx->rendertarget_desc, + .rt_desc = rnode->rendertarget_desc, } }; diff --git a/libnodegl/nodes.h b/libnodegl/nodes.h index af4630e039..1122cc42e8 100644 --- a/libnodegl/nodes.h +++ b/libnodegl/nodes.h @@ -79,8 +79,6 @@ struct ngl_ctx { struct gctx *gctx; struct rnode rnode; struct rnode *rnode_pos; - struct graphicstate graphicstate; - struct rendertarget_desc *rendertarget_desc; struct ngl_node *scene; struct ngl_config config; struct darray modelview_matrix_stack; diff --git a/libnodegl/pass.c b/libnodegl/pass.c index 9a8974cf02..7fc4276756 100644 --- a/libnodegl/pass.c +++ b/libnodegl/pass.c @@ -436,21 +436,22 @@ int ngli_pass_prepare(struct pass *s) { struct ngl_ctx *ctx = s->ctx; struct gctx *gctx = ctx->gctx; + struct rnode *rnode = ctx->rnode_pos; - const int format = ctx->rendertarget_desc->depth_stencil.format; - if (ctx->graphicstate.depth_test && !ngli_format_has_depth(format)) { + const int format = rnode->rendertarget_desc.depth_stencil.format; + if (rnode->graphicstate.depth_test && !ngli_format_has_depth(format)) { LOG(ERROR, "depth testing is not support on rendertargets with no depth attachment"); return NGL_ERROR_INVALID_USAGE; } - if (ctx->graphicstate.stencil_test && !ngli_format_has_stencil(format)) { + if (rnode->graphicstate.stencil_test && !ngli_format_has_stencil(format)) { LOG(ERROR, "stencil operations are not support on rendertargets with no stencil attachment"); return NGL_ERROR_INVALID_USAGE; } struct pipeline_graphics pipeline_graphics = s->pipeline_graphics; - pipeline_graphics.state = ctx->graphicstate; - pipeline_graphics.rt_desc = *ctx->rendertarget_desc; + pipeline_graphics.state = rnode->graphicstate; + pipeline_graphics.rt_desc = rnode->rendertarget_desc; struct pipeline_params pipeline_params = { .type = s->pipeline_type, diff --git a/libnodegl/rnode.c b/libnodegl/rnode.c index 3181b69ebf..f6e281ac71 100644 --- a/libnodegl/rnode.c +++ b/libnodegl/rnode.c @@ -49,5 +49,7 @@ struct rnode *ngli_rnode_add_child(struct rnode *s) { struct rnode *child = ngli_darray_push(&s->children, NULL); ngli_rnode_init(child); + child->graphicstate = s->graphicstate; + child->rendertarget_desc = s->rendertarget_desc; return child; } diff --git a/libnodegl/rnode.h b/libnodegl/rnode.h index d9f6ba71f9..df075f3246 100644 --- a/libnodegl/rnode.h +++ b/libnodegl/rnode.h @@ -23,9 +23,13 @@ #define RNODE_H #include "darray.h" +#include "graphicstate.h" +#include "rendertarget.h" struct rnode { int id; + struct graphicstate graphicstate; + struct rendertarget_desc rendertarget_desc; struct darray children; }; From 29ee3a30b0a2cbddf76adf3d11d47b94b2c4366f Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 27 Aug 2020 14:53:44 +0200 Subject: [PATCH 033/388] gctx: constify return of ngli_gctx_get_default_rendertarget_desc() --- libnodegl/gctx.c | 2 +- libnodegl/gctx.h | 4 ++-- libnodegl/gctx_gl.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libnodegl/gctx.c b/libnodegl/gctx.c index 2f22025fb0..84e86ce281 100644 --- a/libnodegl/gctx.c +++ b/libnodegl/gctx.c @@ -109,7 +109,7 @@ struct rendertarget *ngli_gctx_get_rendertarget(struct gctx *s) return s->class->get_rendertarget(s); } -struct rendertarget_desc *ngli_gctx_get_default_rendertarget_desc(struct gctx *s) +const struct rendertarget_desc *ngli_gctx_get_default_rendertarget_desc(struct gctx *s) { return s->class->get_default_rendertarget_desc(s); } diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index 02e18a702f..46d491fd2b 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -43,7 +43,7 @@ struct gctx_class { void (*set_rendertarget)(struct gctx *s, struct rendertarget *rt); struct rendertarget *(*get_rendertarget)(struct gctx *s); - struct rendertarget_desc *(*get_default_rendertarget_desc)(struct gctx *s); + const struct rendertarget_desc *(*get_default_rendertarget_desc)(struct gctx *s); void (*set_viewport)(struct gctx *s, const int *viewport); void (*get_viewport)(struct gctx *s, int *viewport); void (*set_scissor)(struct gctx *s, const int *scissor); @@ -114,7 +114,7 @@ void ngli_gctx_freep(struct gctx **sp); void ngli_gctx_set_rendertarget(struct gctx *s, struct rendertarget *rt); struct rendertarget *ngli_gctx_get_rendertarget(struct gctx *s); -struct rendertarget_desc *ngli_gctx_get_default_rendertarget_desc(struct gctx *s); +const struct rendertarget_desc *ngli_gctx_get_default_rendertarget_desc(struct gctx *s); void ngli_gctx_set_viewport(struct gctx *s, const int *viewport); void ngli_gctx_get_viewport(struct gctx *s, int *viewport); diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index de620e254b..e86f45f786 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -471,7 +471,7 @@ static struct rendertarget *gl_get_rendertarget(struct gctx *s) return s_priv->rendertarget; } -static struct rendertarget_desc *gl_get_default_rendertarget_desc(struct gctx *s) +static const struct rendertarget_desc *gl_get_default_rendertarget_desc(struct gctx *s) { struct gctx_gl *s_priv = (struct gctx_gl *)s; return &s_priv->default_rendertarget_desc; From 2890e715378fafcbfe7fd2be782f415eac45427c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 24 Aug 2020 18:28:49 +0200 Subject: [PATCH 034/388] utils/hooks: warn when hooks are not available --- pynodegl-utils/pynodegl_utils/hooks.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pynodegl-utils/pynodegl_utils/hooks.py b/pynodegl-utils/pynodegl_utils/hooks.py index 67e344781c..4c1422f8ae 100644 --- a/pynodegl-utils/pynodegl_utils/hooks.py +++ b/pynodegl-utils/pynodegl_utils/hooks.py @@ -37,6 +37,8 @@ class HooksCaller: def __init__(self, hooksdir): self._hooksdir = hooksdir self.hooks_available = all(self._get_hook(h) is not None for h in self._HOOKS) + if not self.hooks_available: + print(f'hooks not available in {hooksdir}') def _get_hook(self, name): if not self._hooksdir: From 688ae40775210465f34e415e21a329c58b338c3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 25 Aug 2020 10:38:07 +0200 Subject: [PATCH 035/388] utils/hooks: make serialized scene file unique (and temporary) --- pynodegl-utils/pynodegl_utils/hooks.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/pynodegl-utils/pynodegl_utils/hooks.py b/pynodegl-utils/pynodegl_utils/hooks.py index 4c1422f8ae..793c3c6cc7 100644 --- a/pynodegl-utils/pynodegl_utils/hooks.py +++ b/pynodegl-utils/pynodegl_utils/hooks.py @@ -177,16 +177,18 @@ def run(self): # The serialized scene is then stored in a file which is then # communicated with additional parameters to the user - local_scene = op.join(tempfile.gettempdir(), 'ngl_scene.ngl') - with open(local_scene, 'w') as f: + # FIXME: this won't work on Windows, see + # https://docs.python.org/3/library/tempfile.html#tempfile.NamedTemporaryFile + with tempfile.NamedTemporaryFile('w', prefix='ngl_scene_', suffix='.ngl', delete=True) as f: f.write(serialized_scene) - self.sendingScene.emit(session_id, self._scene_id) - try: - self._hooks_caller.scene_change(session_id, local_scene, cfg) - except subprocess.CalledProcessError as e: - self.error.emit(session_id, 'Error (%d) while sending scene' % e.returncode) - return - self.done.emit(session_id, self._scene_id, time.time() - start_time) + f.flush() + self.sendingScene.emit(session_id, self._scene_id) + try: + self._hooks_caller.scene_change(session_id, f.name, cfg) + except subprocess.CalledProcessError as e: + self.error.emit(session_id, 'Error (%d) while sending scene' % e.returncode) + return + self.done.emit(session_id, self._scene_id, time.time() - start_time) except Exception as e: self.error.emit(session_id, 'Error: %s' % str(e)) From 3e3b4404d58f5685e18c91d8623dd6a85e1fc06b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 24 Aug 2020 18:28:29 +0200 Subject: [PATCH 036/388] utils/hooks: add support for multiple sessions --- pynodegl-utils/pynodegl_utils/hooks.py | 43 ++++++++++++++++++++++++- pynodegl-utils/pynodegl_utils/viewer.py | 2 ++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/pynodegl-utils/pynodegl_utils/hooks.py b/pynodegl-utils/pynodegl_utils/hooks.py index 793c3c6cc7..0ba77b7316 100644 --- a/pynodegl-utils/pynodegl_utils/hooks.py +++ b/pynodegl-utils/pynodegl_utils/hooks.py @@ -30,7 +30,7 @@ from PySide2 import QtCore -class HooksCaller: +class _HooksCaller: _HOOKS = ('get_session_info', 'get_sessions', 'scene_change', 'sync_file') @@ -115,6 +115,47 @@ def sync_file(self, session_id, localfile): return self._get_hook_output('sync_file', session_id, localfile, self._hash_filename(localfile)) +class HooksCaller: + + def __init__(self, hooksdirs): + self._callers = [_HooksCaller(hooksdir) for hooksdir in hooksdirs] + self.hooks_available = any(c.hooks_available for c in self._callers) + + def _get_caller_session_id(self, unique_session_id): + istr, session_id = unique_session_id.split(':', maxsplit=1) + return self._callers[int(istr)], session_id + + def get_sessions(self): + ''' + Session IDs may be identical accross hook systems. A pathological case + is with several instances of the same hook system, but it could + happen in other situations as well since hook systems don't know each + others and may pick common identifiers), so we need to make them + unique. + + This methods makes these session IDs unique by adding the index of + the caller as prefix. + ''' + sessions = [] + for caller_id, caller in enumerate(self._callers): + caller_sessions = caller.get_sessions() + for (session_id, desc, backend, system) in caller_sessions: + sessions.append((f'{caller_id}:{session_id}', desc, backend, system)) + return sessions + + def get_session_info(self, session_id): + caller, session_id = self._get_caller_session_id(session_id) + return caller.get_session_info(session_id) + + def scene_change(self, session_id, local_scene, cfg): + caller, session_id = self._get_caller_session_id(session_id) + return caller.scene_change(session_id, local_scene, cfg) + + def sync_file(self, session_id, localfile): + caller, session_id = self._get_caller_session_id(session_id) + return caller.sync_file(session_id, localfile) + + class _HooksThread(QtCore.QThread): uploadingFile = QtCore.Signal(str, int, int, str) diff --git a/pynodegl-utils/pynodegl_utils/viewer.py b/pynodegl-utils/pynodegl_utils/viewer.py index 716e193de3..ea17db55f6 100644 --- a/pynodegl-utils/pynodegl_utils/viewer.py +++ b/pynodegl-utils/pynodegl_utils/viewer.py @@ -34,6 +34,8 @@ def run(): parser.add_argument('-m', dest='module', default='pynodegl_utils.examples', help='set the module name containing the scene functions') parser.add_argument('--hooks-dir', dest='hooksdir', + default=[], + action='append', help='set the directory path containing event hooks') pargs = parser.parse_args(sys.argv[1:]) From dfe888887101b78c43c0374328360b216f6fd9da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 27 Aug 2020 14:09:12 +0200 Subject: [PATCH 037/388] utils/hooks: rename hooksdir to hooksdirs This is a better name after previous commit. --- pynodegl-utils/pynodegl_utils/ui/main_window.py | 4 ++-- pynodegl-utils/pynodegl_utils/viewer.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pynodegl-utils/pynodegl_utils/ui/main_window.py b/pynodegl-utils/pynodegl_utils/ui/main_window.py index 7a9ac623d7..5fbbf733d4 100644 --- a/pynodegl-utils/pynodegl_utils/ui/main_window.py +++ b/pynodegl-utils/pynodegl_utils/ui/main_window.py @@ -42,14 +42,14 @@ class MainWindow(QtWidgets.QSplitter): error = QtCore.Signal(str) - def __init__(self, module_pkgname, hooksdir): + def __init__(self, module_pkgname, hooksdirs): super().__init__(QtCore.Qt.Horizontal) self._win_title_base = 'Node.gl viewer' self.setWindowTitle(self._win_title_base) self._module_pkgname = module_pkgname self._scripts_mgr = ScriptsManager(module_pkgname) - self._hooks_caller = HooksCaller(hooksdir) + self._hooks_caller = HooksCaller(hooksdirs) get_scene_func = self._get_scene diff --git a/pynodegl-utils/pynodegl_utils/viewer.py b/pynodegl-utils/pynodegl_utils/viewer.py index ea17db55f6..9cc0dfa336 100644 --- a/pynodegl-utils/pynodegl_utils/viewer.py +++ b/pynodegl-utils/pynodegl_utils/viewer.py @@ -33,13 +33,13 @@ def run(): parser = argparse.ArgumentParser() parser.add_argument('-m', dest='module', default='pynodegl_utils.examples', help='set the module name containing the scene functions') - parser.add_argument('--hooks-dir', dest='hooksdir', + parser.add_argument('--hooks-dir', dest='hooksdirs', default=[], action='append', help='set the directory path containing event hooks') pargs = parser.parse_args(sys.argv[1:]) app = QtWidgets.QApplication(sys.argv) - window = MainWindow(pargs.module, pargs.hooksdir) + window = MainWindow(pargs.module, pargs.hooksdirs) window.show() app.exec_() From 4fcda38c8bf4d02bf53ece80ef5f9aa865e7c7bf Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 2 Jul 2020 20:55:20 +0200 Subject: [PATCH 038/388] darray: add ngli_darray_clear() --- libnodegl/darray.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libnodegl/darray.h b/libnodegl/darray.h index f6391bc6ac..3e4e5342f4 100644 --- a/libnodegl/darray.h +++ b/libnodegl/darray.h @@ -50,4 +50,9 @@ static inline void *ngli_darray_data(const struct darray *darray) return (void *)darray->data; } +static inline void ngli_darray_clear(struct darray *darray) +{ + darray->count = 0; +} + #endif From 3ffec0aafbbbe12420563405592f6a2093475ae4 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 28 Aug 2020 17:29:52 +0200 Subject: [PATCH 039/388] api: use ngli_darray_clear() where appropriate --- libnodegl/api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libnodegl/api.c b/libnodegl/api.c index 8cbe33b67c..2657e76e3c 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -192,7 +192,7 @@ static int cmd_prepare_draw(struct ngl_ctx *s, void *arg) LOG(DEBUG, "prepare scene %s @ t=%f", scene->label, t); - s->activitycheck_nodes.count = 0; + ngli_darray_clear(&s->activitycheck_nodes); int ret = ngli_node_visit(scene, 1, t); if (ret < 0) return ret; From 1c580b5fd7a9d03307be804703b07c979b35da64 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 31 Aug 2020 11:04:09 +0200 Subject: [PATCH 040/388] gctx_gl: honor scissor in ngli_pipeline_exec() Honors scissor in ngli_pipeline_exec() instead of ngli_gctx_set_scissor(). This will allow us to perform the proper scissor transformations depending on the backend (scissor has different origin in OpenGL, Vulkan, DirectX and Metal). --- libnodegl/gctx_gl.c | 2 -- libnodegl/glstate.c | 12 ++++++++++++ libnodegl/glstate.h | 4 ++++ libnodegl/pipeline_gl.c | 2 ++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index e86f45f786..cb5514935f 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -494,8 +494,6 @@ static void gl_get_viewport(struct gctx *s, int *viewport) static void gl_set_scissor(struct gctx *s, const int *scissor) { struct gctx_gl *s_priv = (struct gctx_gl *)s; - struct glcontext *gl = s_priv->glcontext; - ngli_glScissor(gl, scissor[0], scissor[1], scissor[2], scissor[3]); memcpy(&s_priv->scissor, scissor, sizeof(s_priv->scissor)); } diff --git a/libnodegl/glstate.c b/libnodegl/glstate.c index c92d67039a..56fe8d8b0c 100644 --- a/libnodegl/glstate.c +++ b/libnodegl/glstate.c @@ -307,3 +307,15 @@ void ngli_glstate_use_program(struct gctx *gctx, GLuint program_id) glstate->program_id = program_id; } } + +void ngli_glstate_update_scissor(struct gctx *gctx, const int *scissor) +{ + struct gctx_gl *gctx_gl = (struct gctx_gl *)gctx; + struct glcontext *gl = gctx_gl->glcontext; + struct glstate *glstate = &gctx_gl->glstate; + + if (!memcmp(glstate->scissor, scissor, sizeof(glstate->scissor))) + return; + memcpy(glstate->scissor, scissor, sizeof(glstate->scissor)); + ngli_glScissor(gl, scissor[0], scissor[1], scissor[2], scissor[3]); +} diff --git a/libnodegl/glstate.h b/libnodegl/glstate.h index 02e5e8661c..7d6c7e147a 100644 --- a/libnodegl/glstate.h +++ b/libnodegl/glstate.h @@ -56,6 +56,7 @@ struct glstate { GLenum cull_face_mode; GLboolean scissor_test; + int scissor[4]; GLuint program_id; }; @@ -69,4 +70,7 @@ void ngli_glstate_update(struct gctx *gctx, void ngli_glstate_use_program(struct gctx *gctx, GLuint program_id); +void ngli_glstate_update_scissor(struct gctx *gctx, + const int *scissor); + #endif diff --git a/libnodegl/pipeline_gl.c b/libnodegl/pipeline_gl.c index c6a8e13a3b..fb4e544a2d 100644 --- a/libnodegl/pipeline_gl.c +++ b/libnodegl/pipeline_gl.c @@ -563,6 +563,7 @@ void ngli_pipeline_gl_draw(struct pipeline *s, int nb_vertices, int nb_instances struct program_gl *program_gl = (struct program_gl *)s->program; ngli_glstate_update(gctx, &graphics->state); + ngli_glstate_update_scissor(gctx, gctx_gl->scissor); ngli_glstate_use_program(gctx, program_gl->id); set_uniforms(s, gl); set_buffers(s, gl); @@ -597,6 +598,7 @@ void ngli_pipeline_gl_draw_indexed(struct pipeline *s, struct buffer *indices, i struct program_gl *program_gl = (struct program_gl *)s->program; ngli_glstate_update(gctx, &graphics->state); + ngli_glstate_update_scissor(gctx, gctx_gl->scissor); ngli_glstate_use_program(gctx, program_gl->id); set_uniforms(s, gl); set_buffers(s, gl); From d8c972b4a1360342fb44b39784bd56da29c6df7d Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 28 Aug 2020 15:05:10 +0200 Subject: [PATCH 041/388] gctx: add ngli_gctx_{get,transform}_*() Adds helpers to convert vectors and matrices from one coordinate system to another. --- libnodegl/gctx.c | 10 ++++++++++ libnodegl/gctx.h | 6 ++++++ libnodegl/gctx_gl.c | 21 +++++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/libnodegl/gctx.c b/libnodegl/gctx.c index 84e86ce281..dd71f54244 100644 --- a/libnodegl/gctx.c +++ b/libnodegl/gctx.c @@ -99,6 +99,16 @@ void ngli_gctx_freep(struct gctx **sp) ngli_freep(sp); } +void ngli_gctx_transform_projection_matrix(struct gctx *s, float *dst) +{ + s->class->transform_projection_matrix(s, dst); +} + +void ngli_gctx_get_rendertarget_uvcoord_matrix(struct gctx *s, float *dst) +{ + s->class->get_rendertarget_uvcoord_matrix(s, dst); +} + void ngli_gctx_set_rendertarget(struct gctx *s, struct rendertarget *rt) { s->class->set_rendertarget(s, rt); diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index 46d491fd2b..0c596c8f6f 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -41,6 +41,9 @@ struct gctx_class { int (*post_draw)(struct gctx *s, double t); void (*destroy)(struct gctx *s); + void (*transform_projection_matrix)(struct gctx *s, float *dst); + void (*get_rendertarget_uvcoord_matrix)(struct gctx *s, float *dst); + void (*set_rendertarget)(struct gctx *s, struct rendertarget *rt); struct rendertarget *(*get_rendertarget)(struct gctx *s); const struct rendertarget_desc *(*get_default_rendertarget_desc)(struct gctx *s); @@ -112,6 +115,9 @@ int ngli_gctx_resize(struct gctx *s, int width, int height, const int *viewport) int ngli_gctx_draw(struct gctx *s, struct ngl_node *scene, double t); void ngli_gctx_freep(struct gctx **sp); +void ngli_gctx_transform_projection_matrix(struct gctx *s, float *dst); +void ngli_gctx_get_rendertarget_uvcoord_matrix(struct gctx *s, float *dst); + void ngli_gctx_set_rendertarget(struct gctx *s, struct rendertarget *rt); struct rendertarget *ngli_gctx_get_rendertarget(struct gctx *s); const struct rendertarget_desc *ngli_gctx_get_default_rendertarget_desc(struct gctx *s); diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index cb5514935f..c466e498f0 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -450,6 +450,21 @@ static void gl_destroy(struct gctx *s) ngli_glcontext_freep(&s_priv->glcontext); } +static void gl_transform_projection_matrix(struct gctx *s, float *dst) +{ +} + +static void gl_get_rendertarget_uvcoord_matrix(struct gctx *s, float *dst) +{ + static const NGLI_ALIGNED_MAT(matrix) = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f,-1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 1.0f, + }; + memcpy(dst, matrix, 4 * 4 * sizeof(float)); +} + static void gl_set_rendertarget(struct gctx *s, struct rendertarget *rt) { struct gctx_gl *s_priv = (struct gctx_gl *)s; @@ -578,6 +593,9 @@ const struct gctx_class ngli_gctx_gl = { .post_draw = gl_post_draw, .destroy = gl_destroy, + .transform_projection_matrix = gl_transform_projection_matrix, + .get_rendertarget_uvcoord_matrix = gl_get_rendertarget_uvcoord_matrix, + .set_rendertarget = gl_set_rendertarget, .get_rendertarget = gl_get_rendertarget, .get_default_rendertarget_desc = gl_get_default_rendertarget_desc, @@ -644,6 +662,9 @@ const struct gctx_class ngli_gctx_gles = { .post_draw = gl_post_draw, .destroy = gl_destroy, + .transform_projection_matrix = gl_transform_projection_matrix, + .get_rendertarget_uvcoord_matrix = gl_get_rendertarget_uvcoord_matrix, + .set_rendertarget = gl_set_rendertarget, .get_rendertarget = gl_get_rendertarget, .get_default_rendertarget_desc = gl_get_default_rendertarget_desc, From edb58b8074fbca2efff15e3c2f02cbee80f9dc9f Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 28 Aug 2020 15:08:48 +0200 Subject: [PATCH 042/388] internal: use ngli_gctx_{get,transform}_*() --- libnodegl/api.c | 6 ++++++ libnodegl/node_camera.c | 4 ++++ libnodegl/node_rtt.c | 6 ++---- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/libnodegl/api.c b/libnodegl/api.c index 2657e76e3c..ed59f9cd8b 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -131,6 +131,12 @@ static int cmd_configure(struct ngl_ctx *s, void *arg) LOG(WARNING, "could not initialize vaapi"); #endif + NGLI_ALIGNED_MAT(matrix) = NGLI_MAT4_IDENTITY; + ngli_gctx_transform_projection_matrix(s->gctx, matrix); + ngli_darray_clear(&s->projection_matrix_stack); + if (!ngli_darray_push(&s->projection_matrix_stack, matrix)) + return NGL_ERROR_MEMORY; + if (s->scene) { ret = ngli_node_attach_ctx(s->scene, s); if (ret < 0) { diff --git a/libnodegl/node_camera.c b/libnodegl/node_camera.c index f3ad79fe1d..25e8842318 100644 --- a/libnodegl/node_camera.c +++ b/libnodegl/node_camera.c @@ -25,6 +25,7 @@ #include #include +#include "gctx.h" #include "log.h" #include "nodegl.h" #include "nodes.h" @@ -196,6 +197,9 @@ static int camera_update(struct ngl_node *node, double t) ngli_mat4_identity(s->projection_matrix); } + struct gctx *gctx = ctx->gctx; + ngli_gctx_transform_projection_matrix(gctx, s->projection_matrix); + return ngli_node_update(child, t); } diff --git a/libnodegl/node_rtt.c b/libnodegl/node_rtt.c index f22c0c89b5..474992c7f7 100644 --- a/libnodegl/node_rtt.c +++ b/libnodegl/node_rtt.c @@ -308,15 +308,13 @@ static int rtt_prefetch(struct ngl_node *node) for (int i = 0; i < s->nb_color_textures; i++) { struct texture_priv *texture_priv = s->color_textures[i]->priv_data; struct image *image = &texture_priv->image; - image->coordinates_matrix[5] = -1.0f; - image->coordinates_matrix[13] = 1.0f; + ngli_gctx_get_rendertarget_uvcoord_matrix(gctx, image->coordinates_matrix); } if (s->depth_texture) { struct texture_priv *depth_texture_priv = s->depth_texture->priv_data; struct image *depth_image = &depth_texture_priv->image; - depth_image->coordinates_matrix[5] = -1.0f; - depth_image->coordinates_matrix[13] = 1.0f; + ngli_gctx_get_rendertarget_uvcoord_matrix(gctx, depth_image->coordinates_matrix); } } From df18098f01ff9795018b23dfc844225f325db527 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 28 Aug 2020 15:07:57 +0200 Subject: [PATCH 043/388] gctx_gl: y-flip rendering when offscreen Y-flip rendering when offscreen to avoid performing a y-flip during blit and copy operations. This will help simplify the offscreen capture code in a later commit. --- libnodegl/gctx_gl.c | 22 ++++++++++++++++++---- libnodegl/glstate.c | 15 ++++++++++++--- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index c466e498f0..42c10e517c 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -31,6 +31,7 @@ #include "glcontext.h" #include "gtimer_gl.h" #include "log.h" +#include "math_utils.h" #include "memory.h" #include "nodes.h" #include "pipeline_gl.h" @@ -111,7 +112,7 @@ static void capture_default(struct gctx *s) struct rendertarget *rt = s_priv->rt; struct rendertarget *capture_rt = s_priv->capture_rt; - ngli_rendertarget_blit(rt, capture_rt, 1); + ngli_rendertarget_blit(rt, capture_rt, 0); ngli_rendertarget_read_pixels(capture_rt, config->capture_buffer); } @@ -122,7 +123,7 @@ static void capture_ios(struct gctx *s) struct rendertarget *rt = s_priv->rt; struct rendertarget *capture_rt = s_priv->capture_rt; - ngli_rendertarget_blit(rt, capture_rt, 1); + ngli_rendertarget_blit(rt, capture_rt, 0); ngli_glFinish(gl); } @@ -135,7 +136,7 @@ static void capture_gles_msaa(struct gctx *s) struct rendertarget *oes_resolve_rt = s_priv->oes_resolve_rt; ngli_rendertarget_blit(rt, oes_resolve_rt, 0); - ngli_rendertarget_blit(oes_resolve_rt, capture_rt, 1); + ngli_rendertarget_blit(oes_resolve_rt, capture_rt, 0); ngli_rendertarget_read_pixels(capture_rt, config->capture_buffer); } @@ -148,7 +149,7 @@ static void capture_ios_msaa(struct gctx *s) struct rendertarget *oes_resolve_rt = s_priv->oes_resolve_rt; ngli_rendertarget_blit(rt, oes_resolve_rt, 0); - ngli_rendertarget_blit(oes_resolve_rt, capture_rt, 1); + ngli_rendertarget_blit(oes_resolve_rt, capture_rt, 0); ngli_glFinish(gl); } @@ -452,10 +453,23 @@ static void gl_destroy(struct gctx *s) static void gl_transform_projection_matrix(struct gctx *s, float *dst) { + const struct ngl_config *config = &s->config; + if (!config->offscreen) + return; + static const NGLI_ALIGNED_MAT(matrix) = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f,-1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f, + }; + ngli_mat4_mul(dst, matrix, dst); } static void gl_get_rendertarget_uvcoord_matrix(struct gctx *s, float *dst) { + const struct ngl_config *config = &s->config; + if (config->offscreen) + return; static const NGLI_ALIGNED_MAT(matrix) = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,-1.0f, 0.0f, 0.0f, diff --git a/libnodegl/glstate.c b/libnodegl/glstate.c index 56fe8d8b0c..a389781408 100644 --- a/libnodegl/glstate.c +++ b/libnodegl/glstate.c @@ -314,8 +314,17 @@ void ngli_glstate_update_scissor(struct gctx *gctx, const int *scissor) struct glcontext *gl = gctx_gl->glcontext; struct glstate *glstate = &gctx_gl->glstate; - if (!memcmp(glstate->scissor, scissor, sizeof(glstate->scissor))) + int tmp[4]; + memcpy(tmp, scissor, sizeof(tmp)); + + struct ngl_config *config = &gctx->config; + if (config->offscreen) { + const int height = gctx_gl->rendertarget ? gctx_gl->rendertarget->height : gl->height; + tmp[1] = NGLI_MAX(height - tmp[1] - tmp[3], 0); + } + + if (!memcmp(glstate->scissor, tmp, sizeof(glstate->scissor))) return; - memcpy(glstate->scissor, scissor, sizeof(glstate->scissor)); - ngli_glScissor(gl, scissor[0], scissor[1], scissor[2], scissor[3]); + memcpy(glstate->scissor, tmp, sizeof(glstate->scissor)); + ngli_glScissor(gl, tmp[0], tmp[1], tmp[2], tmp[3]); } From 0be6c0b929dfeade2bca163f36eabe963726b1f4 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 13 Aug 2020 11:43:05 +0200 Subject: [PATCH 044/388] gctx_gl: simplify offscreen rendering and capture code Since we do not need to perform a y-flip before capture anymore, we can now remove the extra rendertarget dedicated to this purpose. --- libnodegl/gctx_gl.c | 335 ++++++++++++++------------------------------ libnodegl/gctx_gl.h | 6 +- 2 files changed, 108 insertions(+), 233 deletions(-) diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 42c10e517c..32e766f603 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -39,6 +39,30 @@ #include "rendertarget_gl.h" #include "texture_gl.h" +#if defined(HAVE_VAAPI) +#include "vaapi.h" +#endif + +static void capture_default(struct gctx *s) +{ + struct gctx_gl *s_priv = (struct gctx_gl *)s; + struct ngl_config *config = &s->config; + struct rendertarget *rt = s_priv->rt; + + ngli_rendertarget_resolve(rt); + ngli_rendertarget_read_pixels(rt, config->capture_buffer); +} + +static void capture_ios(struct gctx *s) +{ + struct gctx_gl *s_priv = (struct gctx_gl *)s; + struct glcontext *gl = s_priv->glcontext; + struct rendertarget *rt = s_priv->rt; + + ngli_rendertarget_resolve(rt); + ngli_glFinish(gl); +} + static int offscreen_rendertarget_init(struct gctx *s) { struct gctx_gl *s_priv = (struct gctx_gl *)s; @@ -51,24 +75,90 @@ static int offscreen_rendertarget_init(struct gctx *s) config->samples = 0; } + const int ios_capture = gl->platform == NGL_PLATFORM_IOS && config->window; + if (ios_capture) { +#if defined(TARGET_IPHONE) + CVPixelBufferRef capture_cvbuffer = (CVPixelBufferRef)config->window; + s_priv->capture_cvbuffer = (CVPixelBufferRef)CFRetain(capture_cvbuffer); + if (!s_priv->capture_cvbuffer) + return NGL_ERROR_MEMORY; + + CVOpenGLESTextureCacheRef *cache = ngli_glcontext_get_texture_cache(gl); + int width = CVPixelBufferGetWidth(s_priv->capture_cvbuffer); + int height = CVPixelBufferGetHeight(s_priv->capture_cvbuffer); + CVReturn err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, + *cache, + s_priv->capture_cvbuffer, + NULL, + GL_TEXTURE_2D, + GL_RGBA, + width, + height, + GL_BGRA, + GL_UNSIGNED_BYTE, + 0, + &s_priv->capture_cvtexture); + if (err != noErr) { + LOG(ERROR, "could not create CoreVideo texture from CVPixelBuffer: 0x%x", err); + return NGL_ERROR_EXTERNAL; + } + + GLuint id = CVOpenGLESTextureGetName(s_priv->capture_cvtexture); + ngli_glBindTexture(gl, GL_TEXTURE_2D, id); + ngli_glTexParameteri(gl, GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + ngli_glTexParameteri(gl, GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + ngli_glBindTexture(gl, GL_TEXTURE_2D, 0); + + struct texture_params attachment_params = NGLI_TEXTURE_PARAM_DEFAULTS; + attachment_params.format = NGLI_FORMAT_B8G8R8A8_UNORM; + attachment_params.width = width; + attachment_params.height = height; + s_priv->rt_color = ngli_texture_create(s); + if (!s_priv->rt_color) + return NGL_ERROR_MEMORY; + int ret = ngli_texture_gl_wrap(s_priv->rt_color, &attachment_params, id); + if (ret < 0) + return ret; +#endif + } else { + struct texture_params params = NGLI_TEXTURE_PARAM_DEFAULTS; + params.format = NGLI_FORMAT_R8G8B8A8_UNORM; + params.width = config->width; + params.height = config->height; + params.usage = NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY; + s_priv->rt_color = ngli_texture_create(s); + if (!s_priv->rt_color) + return NGL_ERROR_MEMORY; + int ret = ngli_texture_init(s_priv->rt_color, ¶ms); + if (ret < 0) + return ret; + } + + if (config->samples) { + struct texture_params params = NGLI_TEXTURE_PARAM_DEFAULTS; + params.format = NGLI_FORMAT_R8G8B8A8_UNORM; + params.width = config->width; + params.height = config->height; + params.usage = NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY; + params.samples = config->samples; + s_priv->rt_ms_color = ngli_texture_create(s); + if (!s_priv->rt_ms_color) + return NGL_ERROR_MEMORY; + int ret = ngli_texture_init(s_priv->rt_ms_color, ¶ms); + if (ret < 0) + return ret; + } + struct texture_params attachment_params = NGLI_TEXTURE_PARAM_DEFAULTS; - attachment_params.format = NGLI_FORMAT_R8G8B8A8_UNORM; + attachment_params.format = NGLI_FORMAT_D24_UNORM_S8_UINT; attachment_params.width = config->width; attachment_params.height = config->height; attachment_params.samples = config->samples; attachment_params.usage = NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY; - s_priv->rt_color = ngli_texture_create(s); - if (!s_priv->rt_color) - return NGL_ERROR_MEMORY; - int ret = ngli_texture_init(s_priv->rt_color, &attachment_params); - if (ret < 0) - return ret; - - attachment_params.format = NGLI_FORMAT_D24_UNORM_S8_UINT; s_priv->rt_depth = ngli_texture_create(s); if (!s_priv->rt_depth) return NGL_ERROR_MEMORY; - ret = ngli_texture_init(s_priv->rt_depth, &attachment_params); + int ret = ngli_texture_init(s_priv->rt_depth, &attachment_params); if (ret < 0) return ret; @@ -77,12 +167,14 @@ static int offscreen_rendertarget_init(struct gctx *s) .height = config->height, .nb_colors = 1, .colors[0] = { - .attachment = s_priv->rt_color, + .attachment = config->samples ? s_priv->rt_ms_color : s_priv->rt_color, + .resolve_target = config->samples ? s_priv->rt_color : NULL, }, .depth_stencil = { - .attachment = s_priv->rt_depth + .attachment = s_priv->rt_depth, }, }; + s_priv->rt = ngli_rendertarget_create(s); if (!s_priv->rt) return NGL_ERROR_MEMORY; @@ -90,6 +182,8 @@ static int offscreen_rendertarget_init(struct gctx *s) if (ret < 0) return ret; + s_priv->capture_func = ios_capture ? capture_ios : capture_default; + ngli_gctx_set_rendertarget(s, s_priv->rt); const int vp[4] = {0, 0, config->width, config->height}; ngli_gctx_set_viewport(s, vp); @@ -102,218 +196,8 @@ static void offscreen_rendertarget_reset(struct gctx *s) struct gctx_gl *s_priv = (struct gctx_gl *)s; ngli_rendertarget_freep(&s_priv->rt); ngli_texture_freep(&s_priv->rt_color); + ngli_texture_freep(&s_priv->rt_ms_color); ngli_texture_freep(&s_priv->rt_depth); -} - -static void capture_default(struct gctx *s) -{ - struct gctx_gl *s_priv = (struct gctx_gl *)s; - struct ngl_config *config = &s->config; - struct rendertarget *rt = s_priv->rt; - struct rendertarget *capture_rt = s_priv->capture_rt; - - ngli_rendertarget_blit(rt, capture_rt, 0); - ngli_rendertarget_read_pixels(capture_rt, config->capture_buffer); -} - -static void capture_ios(struct gctx *s) -{ - struct gctx_gl *s_priv = (struct gctx_gl *)s; - struct glcontext *gl = s_priv->glcontext; - struct rendertarget *rt = s_priv->rt; - struct rendertarget *capture_rt = s_priv->capture_rt; - - ngli_rendertarget_blit(rt, capture_rt, 0); - ngli_glFinish(gl); -} - -static void capture_gles_msaa(struct gctx *s) -{ - struct gctx_gl *s_priv = (struct gctx_gl *)s; - struct ngl_config *config = &s->config; - struct rendertarget *rt = s_priv->rt; - struct rendertarget *capture_rt = s_priv->capture_rt; - struct rendertarget *oes_resolve_rt = s_priv->oes_resolve_rt; - - ngli_rendertarget_blit(rt, oes_resolve_rt, 0); - ngli_rendertarget_blit(oes_resolve_rt, capture_rt, 0); - ngli_rendertarget_read_pixels(capture_rt, config->capture_buffer); -} - -static void capture_ios_msaa(struct gctx *s) -{ - struct gctx_gl *s_priv = (struct gctx_gl *)s; - struct glcontext *gl = s_priv->glcontext; - struct rendertarget *rt = s_priv->rt; - struct rendertarget *capture_rt = s_priv->capture_rt; - struct rendertarget *oes_resolve_rt = s_priv->oes_resolve_rt; - - ngli_rendertarget_blit(rt, oes_resolve_rt, 0); - ngli_rendertarget_blit(oes_resolve_rt, capture_rt, 0); - ngli_glFinish(gl); -} - -static void capture_cpu_fallback(struct gctx *s) -{ - struct gctx_gl *s_priv = (struct gctx_gl *)s; - struct ngl_config *config = &s->config; - struct rendertarget *rt = s_priv->rt; - - ngli_rendertarget_read_pixels(rt, s_priv->capture_buffer); - const int step = config->width * 4; - const uint8_t *src = s_priv->capture_buffer + (config->height - 1) * step; - uint8_t *dst = config->capture_buffer; - for (int i = 0; i < config->height; i++) { - memcpy(dst, src, step); - dst += step; - src -= step; - } -} - -static int capture_init(struct gctx *s) -{ - struct gctx_gl *s_priv = (struct gctx_gl *)s; - struct glcontext *gl = s_priv->glcontext; - struct ngl_config *config = &s->config; - const int ios_capture = gl->platform == NGL_PLATFORM_IOS && config->window; - - if (!config->capture_buffer && !ios_capture) - return 0; - - if (gl->features & NGLI_FEATURE_FRAMEBUFFER_OBJECT) { - if (ios_capture) { -#if defined(TARGET_IPHONE) - CVPixelBufferRef capture_cvbuffer = (CVPixelBufferRef)config->window; - s_priv->capture_cvbuffer = (CVPixelBufferRef)CFRetain(capture_cvbuffer); - if (!s_priv->capture_cvbuffer) - return NGL_ERROR_MEMORY; - - CVOpenGLESTextureCacheRef *cache = ngli_glcontext_get_texture_cache(gl); - int width = CVPixelBufferGetWidth(s_priv->capture_cvbuffer); - int height = CVPixelBufferGetHeight(s_priv->capture_cvbuffer); - CVReturn err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, - *cache, - s_priv->capture_cvbuffer, - NULL, - GL_TEXTURE_2D, - GL_RGBA, - width, - height, - GL_BGRA, - GL_UNSIGNED_BYTE, - 0, - &s_priv->capture_cvtexture); - if (err != noErr) { - LOG(ERROR, "could not create CoreVideo texture from CVPixelBuffer: 0x%x", err); - return NGL_ERROR_EXTERNAL; - } - - GLuint id = CVOpenGLESTextureGetName(s_priv->capture_cvtexture); - ngli_glBindTexture(gl, GL_TEXTURE_2D, id); - ngli_glTexParameteri(gl, GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - ngli_glTexParameteri(gl, GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - ngli_glBindTexture(gl, GL_TEXTURE_2D, 0); - - struct texture_params attachment_params = NGLI_TEXTURE_PARAM_DEFAULTS; - attachment_params.format = NGLI_FORMAT_B8G8R8A8_UNORM; - attachment_params.width = width; - attachment_params.height = height; - s_priv->capture_rt_color = ngli_texture_create(s); - if (!s_priv->capture_rt_color) - return NGL_ERROR_MEMORY; - int ret = ngli_texture_gl_wrap(s_priv->capture_rt_color, &attachment_params, id); - if (ret < 0) - return ret; -#endif - } else { - struct texture_params attachment_params = NGLI_TEXTURE_PARAM_DEFAULTS; - attachment_params.format = NGLI_FORMAT_R8G8B8A8_UNORM; - attachment_params.width = config->width; - attachment_params.height = config->height; - attachment_params.usage = NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY; - s_priv->capture_rt_color = ngli_texture_create(s); - if (!s_priv->capture_rt_color) - return NGL_ERROR_MEMORY; - int ret = ngli_texture_init(s_priv->capture_rt_color, &attachment_params); - if (ret < 0) - return ret; - } - - struct rendertarget_params rt_params = { - .width = config->width, - .height = config->height, - .nb_colors = 1, - .colors[0] = { - .attachment = s_priv->capture_rt_color, - }, - }; - s_priv->capture_rt = ngli_rendertarget_create(s); - if (!s_priv->capture_rt) - return NGL_ERROR_MEMORY; - int ret = ngli_rendertarget_init(s_priv->capture_rt, &rt_params); - if (ret < 0) - return ret; - - if (gl->backend == NGL_BACKEND_OPENGLES && config->samples > 0) { - struct texture_params attachment_params = NGLI_TEXTURE_PARAM_DEFAULTS; - attachment_params.format = NGLI_FORMAT_R8G8B8A8_UNORM; - attachment_params.width = config->width; - attachment_params.height = config->height; - attachment_params.samples = 0; - attachment_params.usage = NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY; - s_priv->oes_resolve_rt_color = ngli_texture_create(s); - if (!s_priv->oes_resolve_rt_color) - return NGL_ERROR_MEMORY; - int ret = ngli_texture_init(s_priv->oes_resolve_rt_color, &attachment_params); - if (ret < 0) - return ret; - - struct rendertarget_params rt_params = { - .width = config->width, - .height = config->height, - .nb_colors = 1, - .colors[0] = { - .attachment = s_priv->oes_resolve_rt_color, - } - }; - s_priv->oes_resolve_rt = ngli_rendertarget_create(s); - if (!s_priv->oes_resolve_rt) - return NGL_ERROR_MEMORY; - ret = ngli_rendertarget_init(s_priv->oes_resolve_rt, &rt_params); - if (ret < 0) - return ret; - - s_priv->capture_func = config->capture_buffer ? capture_gles_msaa : capture_ios_msaa; - } else { - s_priv->capture_func = config->capture_buffer ? capture_default : capture_ios; - } - - } else { - if (ios_capture) { - LOG(WARNING, "context does not support the framebuffer object feature, " - "capturing to a CVPixelBuffer is not supported"); - return NGL_ERROR_UNSUPPORTED; - } - s_priv->capture_buffer = ngli_calloc(config->width * config->height, 4 /* RGBA */); - if (!s_priv->capture_buffer) - return NGL_ERROR_MEMORY; - - s_priv->capture_func = capture_cpu_fallback; - } - - ngli_assert(s_priv->capture_func); - - return 0; -} - -static void capture_reset(struct gctx *s) -{ - struct gctx_gl *s_priv = (struct gctx_gl *)s; - ngli_rendertarget_freep(&s_priv->capture_rt); - ngli_texture_freep(&s_priv->capture_rt_color); - ngli_rendertarget_freep(&s_priv->oes_resolve_rt); - ngli_texture_freep(&s_priv->oes_resolve_rt_color); - ngli_freep(&s_priv->capture_buffer); #if defined(TARGET_IPHONE) if (s_priv->capture_cvbuffer) { CFRelease(s_priv->capture_cvbuffer); @@ -352,10 +236,6 @@ static int gl_init(struct gctx *s) ret = offscreen_rendertarget_init(s); if (ret < 0) return ret; - - ret = capture_init(s); - if (ret < 0) - return ret; } s->version = gl->version; @@ -446,7 +326,6 @@ static int gl_post_draw(struct gctx *s, double t) static void gl_destroy(struct gctx *s) { struct gctx_gl *s_priv = (struct gctx_gl *)s; - capture_reset(s); offscreen_rendertarget_reset(s); ngli_glcontext_freep(&s_priv->glcontext); } diff --git a/libnodegl/gctx_gl.h b/libnodegl/gctx_gl.h index 82ae35447c..9a7463c759 100644 --- a/libnodegl/gctx_gl.h +++ b/libnodegl/gctx_gl.h @@ -54,14 +54,10 @@ struct gctx_gl { /* Offscreen render target */ struct rendertarget *rt; struct texture *rt_color; + struct texture *rt_ms_color; struct texture *rt_depth; /* Capture offscreen render target */ capture_func_type capture_func; - struct rendertarget *oes_resolve_rt; - struct texture *oes_resolve_rt_color; - struct rendertarget *capture_rt; - struct texture *capture_rt_color; - uint8_t *capture_buffer; #if defined(TARGET_IPHONE) CVPixelBufferRef capture_cvbuffer; CVOpenGLESTextureRef capture_cvtexture; From 2d06aa4e7737c86d87c5119966151e65f6acc18f Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 17 Aug 2020 09:36:32 +0200 Subject: [PATCH 045/388] gctx_gl: remove rt_ prefix from color, ms_color and depth fields --- libnodegl/gctx_gl.c | 36 ++++++++++++++++++------------------ libnodegl/gctx_gl.h | 6 +++--- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 32e766f603..178ba36fb8 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -113,10 +113,10 @@ static int offscreen_rendertarget_init(struct gctx *s) attachment_params.format = NGLI_FORMAT_B8G8R8A8_UNORM; attachment_params.width = width; attachment_params.height = height; - s_priv->rt_color = ngli_texture_create(s); - if (!s_priv->rt_color) + s_priv->color = ngli_texture_create(s); + if (!s_priv->color) return NGL_ERROR_MEMORY; - int ret = ngli_texture_gl_wrap(s_priv->rt_color, &attachment_params, id); + int ret = ngli_texture_gl_wrap(s_priv->color, &attachment_params, id); if (ret < 0) return ret; #endif @@ -126,10 +126,10 @@ static int offscreen_rendertarget_init(struct gctx *s) params.width = config->width; params.height = config->height; params.usage = NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY; - s_priv->rt_color = ngli_texture_create(s); - if (!s_priv->rt_color) + s_priv->color = ngli_texture_create(s); + if (!s_priv->color) return NGL_ERROR_MEMORY; - int ret = ngli_texture_init(s_priv->rt_color, ¶ms); + int ret = ngli_texture_init(s_priv->color, ¶ms); if (ret < 0) return ret; } @@ -141,10 +141,10 @@ static int offscreen_rendertarget_init(struct gctx *s) params.height = config->height; params.usage = NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY; params.samples = config->samples; - s_priv->rt_ms_color = ngli_texture_create(s); - if (!s_priv->rt_ms_color) + s_priv->ms_color = ngli_texture_create(s); + if (!s_priv->ms_color) return NGL_ERROR_MEMORY; - int ret = ngli_texture_init(s_priv->rt_ms_color, ¶ms); + int ret = ngli_texture_init(s_priv->ms_color, ¶ms); if (ret < 0) return ret; } @@ -155,10 +155,10 @@ static int offscreen_rendertarget_init(struct gctx *s) attachment_params.height = config->height; attachment_params.samples = config->samples; attachment_params.usage = NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY; - s_priv->rt_depth = ngli_texture_create(s); - if (!s_priv->rt_depth) + s_priv->depth = ngli_texture_create(s); + if (!s_priv->depth) return NGL_ERROR_MEMORY; - int ret = ngli_texture_init(s_priv->rt_depth, &attachment_params); + int ret = ngli_texture_init(s_priv->depth, &attachment_params); if (ret < 0) return ret; @@ -167,11 +167,11 @@ static int offscreen_rendertarget_init(struct gctx *s) .height = config->height, .nb_colors = 1, .colors[0] = { - .attachment = config->samples ? s_priv->rt_ms_color : s_priv->rt_color, - .resolve_target = config->samples ? s_priv->rt_color : NULL, + .attachment = config->samples ? s_priv->ms_color : s_priv->color, + .resolve_target = config->samples ? s_priv->color : NULL, }, .depth_stencil = { - .attachment = s_priv->rt_depth, + .attachment = s_priv->depth, }, }; @@ -195,9 +195,9 @@ static void offscreen_rendertarget_reset(struct gctx *s) { struct gctx_gl *s_priv = (struct gctx_gl *)s; ngli_rendertarget_freep(&s_priv->rt); - ngli_texture_freep(&s_priv->rt_color); - ngli_texture_freep(&s_priv->rt_ms_color); - ngli_texture_freep(&s_priv->rt_depth); + ngli_texture_freep(&s_priv->color); + ngli_texture_freep(&s_priv->ms_color); + ngli_texture_freep(&s_priv->depth); #if defined(TARGET_IPHONE) if (s_priv->capture_cvbuffer) { CFRelease(s_priv->capture_cvbuffer); diff --git a/libnodegl/gctx_gl.h b/libnodegl/gctx_gl.h index 9a7463c759..a34945878c 100644 --- a/libnodegl/gctx_gl.h +++ b/libnodegl/gctx_gl.h @@ -53,9 +53,9 @@ struct gctx_gl { int timer_active; /* Offscreen render target */ struct rendertarget *rt; - struct texture *rt_color; - struct texture *rt_ms_color; - struct texture *rt_depth; + struct texture *color; + struct texture *ms_color; + struct texture *depth; /* Capture offscreen render target */ capture_func_type capture_func; #if defined(TARGET_IPHONE) From 252b6351c058c780c28479a95101ba142c40aef4 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 24 Aug 2020 18:28:15 +0200 Subject: [PATCH 046/388] rendertarget: remove ngli_rendertarget_blit() --- libnodegl/gctx.h | 1 - libnodegl/gctx_gl.c | 2 -- libnodegl/rendertarget.c | 5 ---- libnodegl/rendertarget.h | 1 - libnodegl/rendertarget_gl.c | 52 ------------------------------------- libnodegl/rendertarget_gl.h | 2 -- 6 files changed, 63 deletions(-) diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index 0c596c8f6f..07d6ee25c2 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -87,7 +87,6 @@ struct gctx_class { struct rendertarget *(*rendertarget_create)(struct gctx *ctx); int (*rendertarget_init)(struct rendertarget *s, const struct rendertarget_params *params); - void (*rendertarget_blit)(struct rendertarget *s, struct rendertarget *dst, int vflip); void (*rendertarget_resolve)(struct rendertarget *s); void (*rendertarget_read_pixels)(struct rendertarget *s, uint8_t *data); void (*rendertarget_freep)(struct rendertarget **sp); diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 178ba36fb8..57e03ff2ed 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -532,7 +532,6 @@ const struct gctx_class ngli_gctx_gl = { .rendertarget_create = ngli_rendertarget_gl_create, .rendertarget_init = ngli_rendertarget_gl_init, - .rendertarget_blit = ngli_rendertarget_gl_blit, .rendertarget_resolve = ngli_rendertarget_gl_resolve, .rendertarget_read_pixels = ngli_rendertarget_gl_read_pixels, .rendertarget_freep = ngli_rendertarget_gl_freep, @@ -601,7 +600,6 @@ const struct gctx_class ngli_gctx_gles = { .rendertarget_create = ngli_rendertarget_gl_create, .rendertarget_init = ngli_rendertarget_gl_init, - .rendertarget_blit = ngli_rendertarget_gl_blit, .rendertarget_resolve = ngli_rendertarget_gl_resolve, .rendertarget_read_pixels = ngli_rendertarget_gl_read_pixels, .rendertarget_freep = ngli_rendertarget_gl_freep, diff --git a/libnodegl/rendertarget.c b/libnodegl/rendertarget.c index 34b2a22a89..d86a9c49eb 100644 --- a/libnodegl/rendertarget.c +++ b/libnodegl/rendertarget.c @@ -32,11 +32,6 @@ int ngli_rendertarget_init(struct rendertarget *s, const struct rendertarget_par return s->gctx->class->rendertarget_init(s, params); } -void ngli_rendertarget_blit(struct rendertarget *s, struct rendertarget *dst, int vflip) -{ - return s->gctx->class->rendertarget_blit(s, dst, vflip); -} - void ngli_rendertarget_resolve(struct rendertarget *s) { s->gctx->class->rendertarget_resolve(s); diff --git a/libnodegl/rendertarget.h b/libnodegl/rendertarget.h index dde8b29c58..da9cbe947b 100644 --- a/libnodegl/rendertarget.h +++ b/libnodegl/rendertarget.h @@ -65,7 +65,6 @@ struct rendertarget { struct rendertarget *ngli_rendertarget_create(struct gctx *gctx); int ngli_rendertarget_init(struct rendertarget *s, const struct rendertarget_params *params); -void ngli_rendertarget_blit(struct rendertarget *s, struct rendertarget *dst, int vflip); void ngli_rendertarget_resolve(struct rendertarget *s); void ngli_rendertarget_read_pixels(struct rendertarget *s, uint8_t *data); void ngli_rendertarget_freep(struct rendertarget **sp); diff --git a/libnodegl/rendertarget_gl.c b/libnodegl/rendertarget_gl.c index 9ae4fed111..85ad60989d 100644 --- a/libnodegl/rendertarget_gl.c +++ b/libnodegl/rendertarget_gl.c @@ -63,36 +63,6 @@ static void blit(struct rendertarget *s, int width, int height, int vflip, int f ngli_glBlitFramebuffer(gl, 0, 0, s->width, s->height, 0, 0, width, height, flags, GL_NEAREST); } -static void blit_no_draw_buffers(struct rendertarget *s, int nb_color_attachments, int width, int height, int vflip) -{ - blit(s, width, height, vflip, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); -} - -static void blit_draw_buffers(struct rendertarget *s, int nb_color_attachments, int width, int height, int vflip) -{ - struct rendertarget_gl *s_priv = (struct rendertarget_gl *)s; - struct gctx_gl *gctx_gl = (struct gctx_gl *)s->gctx; - struct glcontext *gl = gctx_gl->glcontext; - - const GLenum *draw_buffers = s_priv->blit_draw_buffers; - const int n = NGLI_MIN(s->nb_color_attachments, nb_color_attachments); - for (int i = 0; i < n; i++) { - GLbitfield flags = GL_COLOR_BUFFER_BIT; - if (i == 0) - flags |= GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; - ngli_glReadBuffer(gl, GL_COLOR_ATTACHMENT0 + i); -#if defined(TARGET_DARWIN) - ngli_glDrawBuffer(gl, GL_COLOR_ATTACHMENT0 + i); -#else - ngli_glDrawBuffers(gl, i + 1, draw_buffers); -#endif - draw_buffers += i + 1; - blit(s, width, height, vflip, flags); - } - ngli_glReadBuffer(gl, GL_COLOR_ATTACHMENT0); - ngli_glDrawBuffers(gl, s->nb_color_attachments, s_priv->draw_buffers); -} - static void resolve_no_draw_buffers(struct rendertarget *s) { blit(s, s->width, s->height, 0, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); @@ -262,7 +232,6 @@ int ngli_rendertarget_gl_init(struct rendertarget *s, const struct rendertarget_ if (ret < 0) goto done; - s_priv->blit = blit_no_draw_buffers; s_priv->resolve = resolve_no_draw_buffers; if (gl->features & NGLI_FEATURE_DRAW_BUFFERS) { if (s->nb_color_attachments > limits->max_draw_buffers) { @@ -282,7 +251,6 @@ int ngli_rendertarget_gl_init(struct rendertarget *s, const struct rendertarget_ draw_buffers[-1] = GL_COLOR_ATTACHMENT0 + i; } - s_priv->blit = blit_draw_buffers; s_priv->resolve = resolve_draw_buffers; } } @@ -296,26 +264,6 @@ done:; return ret; } -void ngli_rendertarget_gl_blit(struct rendertarget *s, struct rendertarget *dst, int vflip) -{ - struct gctx_gl *gctx_gl = (struct gctx_gl *)s->gctx; - struct glcontext *gl = gctx_gl->glcontext; - - if (!(gl->features & NGLI_FEATURE_FRAMEBUFFER_OBJECT)) - return; - - struct rendertarget_gl *s_priv = (struct rendertarget_gl *)s; - struct rendertarget_gl *dst_gl = (struct rendertarget_gl *)dst; - ngli_glBindFramebuffer(gl, GL_READ_FRAMEBUFFER, s_priv->id); - ngli_glBindFramebuffer(gl, GL_DRAW_FRAMEBUFFER, dst_gl->id); - s_priv->blit(s, dst->nb_color_attachments, dst->width, dst->height, vflip); - - struct rendertarget *rt = gctx_gl->rendertarget; - struct rendertarget_gl *rt_gl = (struct rendertarget_gl *)rt; - const GLuint fbo_id = rt_gl ? rt_gl->id : ngli_glcontext_get_default_framebuffer(gl); - ngli_glBindFramebuffer(gl, GL_FRAMEBUFFER, fbo_id); -} - void ngli_rendertarget_gl_resolve(struct rendertarget *s) { const struct rendertarget_gl *s_priv = (const struct rendertarget_gl *)s; diff --git a/libnodegl/rendertarget_gl.h b/libnodegl/rendertarget_gl.h index ed41ad1afb..3799a6e586 100644 --- a/libnodegl/rendertarget_gl.h +++ b/libnodegl/rendertarget_gl.h @@ -32,13 +32,11 @@ struct rendertarget_gl { GLuint prev_id; GLenum draw_buffers[NGLI_MAX_COLOR_ATTACHMENTS]; GLenum blit_draw_buffers[NGLI_MAX_COLOR_ATTACHMENTS*(NGLI_MAX_COLOR_ATTACHMENTS+1)/2]; - void (*blit)(struct rendertarget *s, int nb_color_attachments, int width, int height, int vflip); void (*resolve)(struct rendertarget *s); }; struct rendertarget *ngli_rendertarget_gl_create(struct gctx *gctx); int ngli_rendertarget_gl_init(struct rendertarget *s, const struct rendertarget_params *params); -void ngli_rendertarget_gl_blit(struct rendertarget *s, struct rendertarget *dst, int vflip); void ngli_rendertarget_gl_resolve(struct rendertarget *s); void ngli_rendertarget_gl_read_pixels(struct rendertarget *s, uint8_t *data); void ngli_rendertarget_gl_freep(struct rendertarget **sp); From f9a79c69585080a506b5b3429b8f65d8dad3bf6b Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 28 Aug 2020 17:43:28 +0200 Subject: [PATCH 047/388] rendertarget_gl: inline blit() --- libnodegl/rendertarget_gl.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/libnodegl/rendertarget_gl.c b/libnodegl/rendertarget_gl.c index 85ad60989d..292193d83e 100644 --- a/libnodegl/rendertarget_gl.c +++ b/libnodegl/rendertarget_gl.c @@ -52,20 +52,13 @@ static GLenum get_gl_attachment_index(GLenum format) } } -static void blit(struct rendertarget *s, int width, int height, int vflip, int flags) +static void resolve_no_draw_buffers(struct rendertarget *s) { struct gctx_gl *gctx_gl = (struct gctx_gl *)s->gctx; struct glcontext *gl = gctx_gl->glcontext; - if (vflip) - ngli_glBlitFramebuffer(gl, 0, 0, s->width, s->height, 0, height, width, 0, flags, GL_NEAREST); - else - ngli_glBlitFramebuffer(gl, 0, 0, s->width, s->height, 0, 0, width, height, flags, GL_NEAREST); -} - -static void resolve_no_draw_buffers(struct rendertarget *s) -{ - blit(s, s->width, s->height, 0, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + const int flags = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; + ngli_glBlitFramebuffer(gl, 0, 0, s->width, s->height, 0, 0, s->width, s->height, flags, GL_NEAREST); } static void resolve_draw_buffers(struct rendertarget *s) @@ -91,7 +84,7 @@ static void resolve_draw_buffers(struct rendertarget *s) ngli_glDrawBuffers(gl, i + 1, draw_buffers); #endif draw_buffers += i + 1; - blit(s, s->width, s->height, 0, flags); + ngli_glBlitFramebuffer(gl, 0, 0, s->width, s->height, 0, 0, s->width, s->height, flags, GL_NEAREST); } ngli_glReadBuffer(gl, GL_COLOR_ATTACHMENT0); ngli_glDrawBuffers(gl, s->nb_color_attachments, s_priv->draw_buffers); From 3d1554dca54d204c7ea208dccef89f1d9c29b845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 3 Sep 2020 11:17:27 +0200 Subject: [PATCH 048/388] hwconv: remove use of triangle fan topology --- libnodegl/hwconv.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libnodegl/hwconv.c b/libnodegl/hwconv.c index 96f184b555..0489dada16 100644 --- a/libnodegl/hwconv.c +++ b/libnodegl/hwconv.c @@ -97,8 +97,8 @@ int ngli_hwconv_init(struct hwconv *hwconv, struct ngl_ctx *ctx, static const float vertices[] = { -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, -1.0f, 1.0f, 0.0f, - 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, }; hwconv->vertices = ngli_buffer_create(gctx); if (!hwconv->vertices) @@ -128,7 +128,7 @@ int ngli_hwconv_init(struct hwconv *hwconv, struct ngl_ctx *ctx, struct pipeline_params pipeline_params = { .type = NGLI_PIPELINE_TYPE_GRAPHICS, .graphics = { - .topology = NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, + .topology = NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, .state = NGLI_GRAPHICSTATE_DEFAULTS, .rt_desc = rt_desc, }, From c4a5d49062b83e4f05fd9204803235dbb01469ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 3 Sep 2020 14:38:37 +0200 Subject: [PATCH 049/388] hud: remove use of triangle fan topology --- libnodegl/node_hud.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libnodegl/node_hud.c b/libnodegl/node_hud.c index 6e8abce00a..228d1665b7 100644 --- a/libnodegl/node_hud.c +++ b/libnodegl/node_hud.c @@ -1267,8 +1267,8 @@ static int hud_init(struct ngl_node *node) static const float coords[] = { -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 0.0f, }; s->coords = ngli_buffer_create(gctx); @@ -1332,7 +1332,7 @@ static int hud_init(struct ngl_node *node) struct pipeline_params pipeline_params = { .type = NGLI_PIPELINE_TYPE_GRAPHICS, .graphics = { - .topology = NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, + .topology = NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, .state = graphicstate, .rt_desc = rnode->rendertarget_desc, } From f877c8ad007bb196fd8f71ba422d201ef27e02da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 3 Sep 2020 11:02:59 +0200 Subject: [PATCH 050/388] quad: remove use of triangle fan topology --- libnodegl/node_quad.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libnodegl/node_quad.c b/libnodegl/node_quad.c index 223b6cf587..f08d0eae7a 100644 --- a/libnodegl/node_quad.c +++ b/libnodegl/node_quad.c @@ -63,15 +63,15 @@ static int quad_init(struct ngl_node *node) const float vertices[] = { C(0), C(1), C(2), C(0) + W(0), C(1) + W(1), C(2) + W(2), - C(0) + H(0) + W(0), C(1) + H(1) + W(1), C(2) + H(2) + W(2), C(0) + H(0), C(1) + H(1), C(2) + H(2), + C(0) + H(0) + W(0), C(1) + H(1) + W(1), C(2) + H(2) + W(2), }; const float uvs[] = { UV_C(0), 1.0f - UV_C(1), UV_C(0) + UV_W(0), 1.0f - UV_C(1) - UV_W(1), - UV_C(0) + UV_H(0) + UV_W(0), 1.0f - UV_C(1) - UV_H(1) - UV_W(1), UV_C(0) + UV_H(0), 1.0f - UV_C(1) - UV_H(1), + UV_C(0) + UV_H(0) + UV_W(0), 1.0f - UV_C(1) - UV_H(1) - UV_W(1), }; s->vertices_buffer = ngli_node_geometry_generate_buffer(node->ctx, @@ -104,7 +104,7 @@ static int quad_init(struct ngl_node *node) if (!s->normals_buffer) return NGL_ERROR_MEMORY; - s->topology = NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN; + s->topology = NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; return 0; } From 471142833e17d174aec1a5c9c00e35edba2a26d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 3 Sep 2020 12:09:46 +0200 Subject: [PATCH 051/388] circle: remove use of triangle fan topology --- libnodegl/node_circle.c | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/libnodegl/node_circle.c b/libnodegl/node_circle.c index 59a346e5a9..1ba2300fe4 100644 --- a/libnodegl/node_circle.c +++ b/libnodegl/node_circle.c @@ -48,22 +48,24 @@ static int circle_init(struct ngl_node *node) LOG(ERROR, "invalid number of points (%d < 3)", s->npoints); return NGL_ERROR_INVALID_ARG; } - const int nb_vertices = s->npoints + 2; + const int nb_vertices = s->npoints + 1; + const int nb_indices = s->npoints * 3; float *vertices = ngli_calloc(nb_vertices, sizeof(*vertices) * 3); float *uvcoords = ngli_calloc(nb_vertices, sizeof(*uvcoords) * 2); float *normals = ngli_calloc(nb_vertices, sizeof(*normals) * 3); + uint16_t *indices = ngli_calloc(nb_indices, sizeof(*indices)); - if (!vertices || !uvcoords || !normals) + if (!vertices || !uvcoords || !normals || !indices) goto end; - - int i; const double step = 2.0 * M_PI / s->npoints; + vertices[0] = 0.0; + vertices[1] = 0.0; uvcoords[0] = 0.5; uvcoords[1] = 0.5; - for (i = 1; i < (nb_vertices - 1); i++) { + for (int i = 1; i < nb_vertices; i++) { const double angle = (i - 1) * step; const double x = sin(angle) * s->radius; const double y = cos(angle) * s->radius; @@ -71,11 +73,13 @@ static int circle_init(struct ngl_node *node) vertices[i*3 + 1] = y; uvcoords[i*2 + 0] = (x + 1.0) / 2.0; uvcoords[i*2 + 1] = (1.0 - y) / 2.0; + indices[(i - 1) * 3 + 0] = 0; // point to center coordinate + indices[(i - 1) * 3 + 1] = i; + indices[(i - 1) * 3 + 2] = i + 1; } - vertices[i*3 + 0] = vertices[3 + 0]; - vertices[i*3 + 1] = vertices[3 + 1]; - uvcoords[i*2 + 0] = uvcoords[2 + 0]; - uvcoords[i*2 + 1] = uvcoords[2 + 1]; + /* Fix overflowing vertex reference back to the start for sealing the + * circle */ + indices[nb_indices - 1] = 1; static const float center[3] = {0}; ngli_vec3_normalvec(normals, (float *)center, vertices, vertices + 3); @@ -100,10 +104,16 @@ static int circle_init(struct ngl_node *node) nb_vertices * sizeof(*normals) * 3, normals); - if (!s->vertices_buffer || !s->uvcoords_buffer || !s->normals_buffer) + s->indices_buffer = ngli_node_geometry_generate_buffer(node->ctx, + NGL_NODE_BUFFERUSHORT, + nb_indices, + nb_indices * sizeof(*indices), + (void *)indices); + + if (!s->vertices_buffer || !s->uvcoords_buffer || !s->normals_buffer || !s->indices_buffer) goto end; - s->topology = NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN; + s->topology = NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; ret = 0; @@ -111,6 +121,7 @@ static int circle_init(struct ngl_node *node) ngli_free(vertices); ngli_free(uvcoords); ngli_free(normals); + ngli_free(indices); return ret; } @@ -128,6 +139,7 @@ static void circle_uninit(struct ngl_node *node) NODE_UNREFP(s->vertices_buffer); NODE_UNREFP(s->uvcoords_buffer); NODE_UNREFP(s->normals_buffer); + NODE_UNREFP(s->indices_buffer); } const struct node_class ngli_circle_class = { From 5cf92f1ce24deda2b04c3e280c40ab361c28196c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 3 Sep 2020 12:29:10 +0200 Subject: [PATCH 052/388] circle: fix normals The normal (duplicated for all points) was previously computed by using the center with the first vertex. Unfortunately, the 1st vertex was also always the center (even before previous commit), leading to the normal being always (0,0,0). Now we compare the first vertex (center) with the 2nd one instead, which gives us the expected (0,0,-1) normal with the default parameters. --- libnodegl/node_circle.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libnodegl/node_circle.c b/libnodegl/node_circle.c index 1ba2300fe4..e4e643f557 100644 --- a/libnodegl/node_circle.c +++ b/libnodegl/node_circle.c @@ -81,8 +81,7 @@ static int circle_init(struct ngl_node *node) * circle */ indices[nb_indices - 1] = 1; - static const float center[3] = {0}; - ngli_vec3_normalvec(normals, (float *)center, vertices, vertices + 3); + ngli_vec3_normalvec(normals, vertices, vertices + 3, vertices + 6); for (int i = 1; i < nb_vertices; i++) memcpy(normals + (i * 3), normals, 3 * sizeof(*normals)); From ab979e9cec79ce6493bb1a801cd783001424ac51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 3 Sep 2020 12:36:15 +0200 Subject: [PATCH 053/388] circle: forward better error return values --- libnodegl/node_circle.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/libnodegl/node_circle.c b/libnodegl/node_circle.c index e4e643f557..c9ae95ccd2 100644 --- a/libnodegl/node_circle.c +++ b/libnodegl/node_circle.c @@ -41,7 +41,7 @@ static const struct node_param circle_params[] = { static int circle_init(struct ngl_node *node) { - int ret = -1; + int ret = 0; struct geometry_priv *s = node->priv_data; if (s->npoints < 3) { @@ -56,8 +56,10 @@ static int circle_init(struct ngl_node *node) float *normals = ngli_calloc(nb_vertices, sizeof(*normals) * 3); uint16_t *indices = ngli_calloc(nb_indices, sizeof(*indices)); - if (!vertices || !uvcoords || !normals || !indices) + if (!vertices || !uvcoords || !normals || !indices) { + ret = NGL_ERROR_MEMORY; goto end; + } const double step = 2.0 * M_PI / s->npoints; @@ -109,13 +111,13 @@ static int circle_init(struct ngl_node *node) nb_indices * sizeof(*indices), (void *)indices); - if (!s->vertices_buffer || !s->uvcoords_buffer || !s->normals_buffer || !s->indices_buffer) + if (!s->vertices_buffer || !s->uvcoords_buffer || !s->normals_buffer || !s->indices_buffer) { + ret = NGL_ERROR_MEMORY; goto end; + } s->topology = NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - ret = 0; - end: ngli_free(vertices); ngli_free(uvcoords); From 2595a0ba33ecf830c74bc46ed03ccde861865f10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 3 Sep 2020 15:03:58 +0200 Subject: [PATCH 054/388] utils/examples: remove use of triangle fan topology in square2circle --- pynodegl-utils/pynodegl_utils/examples/morphing.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pynodegl-utils/pynodegl_utils/examples/morphing.py b/pynodegl-utils/pynodegl_utils/examples/morphing.py index 9e1f57f52e..8e7eb8e3bf 100644 --- a/pynodegl-utils/pynodegl_utils/examples/morphing.py +++ b/pynodegl-utils/pynodegl_utils/examples/morphing.py @@ -28,13 +28,14 @@ def sqyf(t): # square y coordinates clockwise starting top-left s = 1.25 # shapes scale interp = 'exp_in_out' - square_vertices = array.array('f') + center_vertex = [0, 0, 0] + square_vertices = array.array('f', center_vertex) for i in range(n): x = (sqxf(i / float(n)) - .5) * s y = (sqyf(i / float(n)) - .5) * s square_vertices.extend([x, y, 0]) - circle_vertices = array.array('f') + circle_vertices = array.array('f', center_vertex) step = 2 * math.pi / float(n) for i in range(n): angle = i * step - math.pi/4. @@ -42,6 +43,11 @@ def sqyf(t): # square y coordinates clockwise starting top-left y = math.cos(angle) * .5 * s circle_vertices.extend([x, y, 0]) + indices = array.array('H') + for i in range(1, n + 1): + indices.extend([0, i, i + 1]) + indices[-1] = 1 + vertices_animkf = [ ngl.AnimKeyFrameBuffer(0, square_vertices), ngl.AnimKeyFrameBuffer(cfg.duration/2., circle_vertices, interp), @@ -56,8 +62,7 @@ def sqyf(t): # square y coordinates clockwise starting top-left ] ucolor = ngl.AnimatedVec4(color_animkf) - geom = ngl.Geometry(vertices) - geom.set_topology('triangle_fan') + geom = ngl.Geometry(vertices, indices=ngl.BufferUShort(data=indices)) p = ngl.Program(vertex=cfg.get_vert('color'), fragment=cfg.get_frag('color')) render = ngl.Render(geom, p) render.update_frag_resources(color=ucolor) From 9e4c85659922ac7e2f9b51f7e9455f898986d552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 3 Sep 2020 15:15:19 +0200 Subject: [PATCH 055/388] tests/compute: remove use of triangle fan topology --- tests/compute.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/compute.py b/tests/compute.py index cb4ad2d229..399e89266d 100644 --- a/tests/compute.py +++ b/tests/compute.py @@ -272,8 +272,8 @@ def compute_animation(cfg): vertices_data = array.array('f', [ -0.5, -0.5, 0.0, 0.5, -0.5, 0.0, - 0.5, 0.5, 0.0, -0.5, 0.5, 0.0, + 0.5, 0.5, 0.0, ]) nb_vertices = 4 @@ -292,7 +292,7 @@ def compute_animation(cfg): compute.update_resources(transform=transform, src=input_block, dst=output_block) quad_buffer = ngl.BufferVec3(block=output_block, block_field=0) - geometry = ngl.Geometry(quad_buffer, topology='triangle_fan') + geometry = ngl.Geometry(quad_buffer, topology='triangle_strip') program = ngl.Program(vertex=cfg.get_vert('color'), fragment=cfg.get_frag('color')) render = ngl.Render(geometry, program) render.update_frag_resources(color=ngl.UniformVec4(value=COLORS['sgreen'])) From c2d4e0a5c53712ec2fccc883e93f8ac459db8be8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 3 Sep 2020 19:48:05 +0200 Subject: [PATCH 056/388] tests/shape: fix morphing rendering This makes the shape a proper quad instead of having a glitchy diagonal crossing shape. --- tests/refs/shape_morphing.ref | 16 ++++++++-------- tests/shape.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/refs/shape_morphing.ref b/tests/refs/shape_morphing.ref index ba894b2870..a17314c1d1 100644 --- a/tests/refs/shape_morphing.ref +++ b/tests/refs/shape_morphing.ref @@ -1,8 +1,8 @@ -00000000000000000000000000000000 3155CF55EF003D50B803A0001554A000 3155CF55EF003D50B803A0001554A000 00000000000000000000000000000000 -00000000000000000000000000000000 285557D06F03285C2810280001500A00 285557D06F03285C2810280001500A00 00000000000000000000000000000000 -00000000000000000000000000000000 281415D1FF41E80C387138108E002150 281415D1FF41E80C387138108E002150 00000000000000000000000000000000 -00000000000000000000000000000000 200005556D7528C028006859295C2806 200005556D7528C028006859295C2806 00000000000000000000000000000000 -00000000000000000000000000000000 0A05157DEFF06C032C583D5C3807AA80 0A05157DEFF06C032C583D5C3807AA80 00000000000000000000000000000000 -00000000000000000000000000000000 0A3C15F08BC3CF186C5C28072AA00558 0A3C15F08BC3CF186C5C28072AA00558 00000000000000000000000000000000 -00000000000000000000000000000000 157CEFF02803385C8E178AA001780A85 157CEFF02803385C8E178AA001780A85 00000000000000000000000000000000 -00000000000000000000000000000000 455D6FF028002A552A050AA001540A80 455D6FF028002A552A050AA001540A80 00000000000000000000000000000000 +00000000000000000000000000000000 3155CF55EF003DF0B800A003155CA000 3155CF55EF003DF0B800A003155CA000 00000000000000000000000000000000 +00000000000000000000000000000000 285557F46FC228522802280201500A00 285557F46FC228522802280201500A00 00000000000000000000000000000000 +00000000000000000000000000000000 281415D6FFD2E80238523A028E002150 281415D6FFD2E80238523A028E002150 00000000000000000000000000000000 +00000000000000000000000000000000 200005556D5528542880688029D42800 200005556D5528542880688029D42800 00000000000000000000000000000000 +00000000000000000000000000000000 0A05157DEFF46C002CF43DF43800AAA0 0A05157DEFF46C002CF43DF43800AAA0 00000000000000000000000000000000 +00000000000000000000000000000000 0E1F31FC8BC0CF0C6DF428002AA00558 0E1F31FC8BC0CF0C6DF428002AA00558 00000000000000000000000000000000 +00000000000000000000000000000000 157DEFFC28013A7C8E008AA801580A85 157DEFFC28013A7C8E008AA801580A85 00000000000000000000000000000000 +00000000000000000000000000000000 45576FFD28002A742A000AA001550A80 45576FFD28002A742A000AA001550A80 00000000000000000000000000000000 diff --git a/tests/shape.py b/tests/shape.py index 3153fd78ed..c14b3daa1b 100644 --- a/tests/shape.py +++ b/tests/shape.py @@ -230,7 +230,7 @@ def shape_morphing(cfg, n=6): vertices_br = _get_morphing_coordinates(n, 0,-1) vertices_animkf = [] - for i, coords in enumerate(zip(vertices_tl, vertices_tr, vertices_bl, vertices_br)): + for i, coords in enumerate(zip(vertices_tl, vertices_tr, vertices_br, vertices_bl)): flat_coords = list(itertools.chain(*coords)) coords_array = array.array('f', flat_coords) vertices_animkf.append(ngl.AnimKeyFrameBuffer(i * cfg.duration / (n - 1), coords_array)) From 2fa54e50913cc6d3e117d89e20b743e66e3b9859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 3 Sep 2020 15:33:52 +0200 Subject: [PATCH 057/388] tests/shape: remove use of triangle fan topology --- tests/shape.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/shape.py b/tests/shape.py index c14b3daa1b..d34e21289d 100644 --- a/tests/shape.py +++ b/tests/shape.py @@ -230,14 +230,14 @@ def shape_morphing(cfg, n=6): vertices_br = _get_morphing_coordinates(n, 0,-1) vertices_animkf = [] - for i, coords in enumerate(zip(vertices_tl, vertices_tr, vertices_br, vertices_bl)): + for i, coords in enumerate(zip(vertices_tl, vertices_tr, vertices_bl, vertices_br)): flat_coords = list(itertools.chain(*coords)) coords_array = array.array('f', flat_coords) vertices_animkf.append(ngl.AnimKeyFrameBuffer(i * cfg.duration / (n - 1), coords_array)) vertices = ngl.AnimatedBufferVec3(vertices_animkf) geom = ngl.Geometry(vertices) - geom.set_topology('triangle_fan') + geom.set_topology('triangle_strip') p = ngl.Program(vertex=cfg.get_vert('color'), fragment=cfg.get_frag('color')) render = ngl.Render(geom, p) render.update_frag_resources(color=ngl.UniformVec4(COLORS['cyan'])) @@ -273,7 +273,7 @@ def cropboard(cfg, dim_clr=3, dim_cut=9): translate_b_buffer = array.array('f') if set_indices: - indices = array.array('H', [0, 2, 1, 3]) + indices = array.array('H', [0, 2, 1, 1, 3, 0]) indices_buffer = ngl.BufferUShort(data=indices) vertices = array.array('f', [ @@ -293,8 +293,7 @@ def cropboard(cfg, dim_clr=3, dim_cut=9): vertices_buffer = ngl.BufferVec3(data=vertices) uvcoords_buffer = ngl.BufferVec2(data=uvcoords) - q = ngl.Geometry(topology='triangle_fan', - vertices=vertices_buffer, + q = ngl.Geometry(vertices=vertices_buffer, uvcoords=uvcoords_buffer, indices=indices_buffer) else: From 1445e7303aa6ff66048023cdd93640c9e8b394a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 3 Sep 2020 15:35:39 +0200 Subject: [PATCH 058/388] geometry: remove triangle_fan topology It is unsupported by Metal and Direct3D. --- libnodegl/doc/libnodegl.md | 1 - libnodegl/node_geometry.c | 1 - libnodegl/topology.h | 1 - libnodegl/topology_gl.c | 1 - 4 files changed, 4 deletions(-) diff --git a/libnodegl/doc/libnodegl.md b/libnodegl/doc/libnodegl.md index c037b59423..220d8a6a31 100644 --- a/libnodegl/doc/libnodegl.md +++ b/libnodegl/doc/libnodegl.md @@ -1329,7 +1329,6 @@ Constant | Description `line_strip` | line strip `line_list` | line list `triangle_strip` | triangle strip -`triangle_fan` | triangle fan `triangle_list` | triangle list ## blend_factor choices diff --git a/libnodegl/node_geometry.c b/libnodegl/node_geometry.c index fca1a9eb25..5321d89576 100644 --- a/libnodegl/node_geometry.c +++ b/libnodegl/node_geometry.c @@ -63,7 +63,6 @@ static const struct param_choices topology_choices = { {"line_strip", NGLI_PRIMITIVE_TOPOLOGY_LINE_STRIP, .desc=NGLI_DOCSTRING("line strip")}, {"line_list", NGLI_PRIMITIVE_TOPOLOGY_LINE_LIST, .desc=NGLI_DOCSTRING("line list")}, {"triangle_strip", NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, .desc=NGLI_DOCSTRING("triangle strip")}, - {"triangle_fan", NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, .desc=NGLI_DOCSTRING("triangle fan")}, {"triangle_list", NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, .desc=NGLI_DOCSTRING("triangle list")}, {NULL} } diff --git a/libnodegl/topology.h b/libnodegl/topology.h index 0f95e91d49..00c85600ec 100644 --- a/libnodegl/topology.h +++ b/libnodegl/topology.h @@ -28,7 +28,6 @@ enum { NGLI_PRIMITIVE_TOPOLOGY_LINE_STRIP, NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, - NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, NGLI_PRIMITIVE_TOPOLOGY_NB }; diff --git a/libnodegl/topology_gl.c b/libnodegl/topology_gl.c index 14ffc9e405..743c50be38 100644 --- a/libnodegl/topology_gl.c +++ b/libnodegl/topology_gl.c @@ -28,7 +28,6 @@ static const GLenum gl_primitive_topology_map[NGLI_PRIMITIVE_TOPOLOGY_NB] = { [NGLI_PRIMITIVE_TOPOLOGY_LINE_STRIP] = GL_LINE_STRIP, [NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST] = GL_TRIANGLES, [NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP] = GL_TRIANGLE_STRIP, - [NGLI_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN] = GL_TRIANGLE_FAN, }; GLenum ngli_topology_get_gl_topology(int topology) From 97ed78c1907f29850fd5c7b041aef50fc137c9a2 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 3 Sep 2020 21:58:49 +0200 Subject: [PATCH 059/388] graphicconfig: remove back+front face culling Back+front face culling is not supported by DirectX 12 and Metal. --- libnodegl/doc/libnodegl.md | 2 ++ libnodegl/glstate.c | 1 - libnodegl/graphicstate.h | 3 --- libnodegl/node_graphicconfig.c | 4 +++- libnodegl/nodes.specs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libnodegl/doc/libnodegl.md b/libnodegl/doc/libnodegl.md index 220d8a6a31..d0f8bd54cb 100644 --- a/libnodegl/doc/libnodegl.md +++ b/libnodegl/doc/libnodegl.md @@ -1399,6 +1399,8 @@ Constant | Description Constant | Description -------- | ----------- +`unset` | unset +`none` | no facets are discarded `front` | cull front-facing facets `back` | cull back-facing facets diff --git a/libnodegl/glstate.c b/libnodegl/glstate.c index a389781408..569886ef3a 100644 --- a/libnodegl/glstate.c +++ b/libnodegl/glstate.c @@ -94,7 +94,6 @@ static GLenum get_gl_stencil_op(int stencil_op) static const GLenum gl_cull_mode_map[NGLI_CULL_MODE_NB] = { [NGLI_CULL_MODE_FRONT_BIT] = GL_FRONT, [NGLI_CULL_MODE_BACK_BIT] = GL_BACK, - [NGLI_CULL_MODE_FRONT_AND_BACK] = GL_FRONT_AND_BACK, }; static GLenum get_gl_cull_mode(int cull_mode) diff --git a/libnodegl/graphicstate.h b/libnodegl/graphicstate.h index 5597075c5c..af39d26c24 100644 --- a/libnodegl/graphicstate.h +++ b/libnodegl/graphicstate.h @@ -75,12 +75,9 @@ enum { NGLI_CULL_MODE_NONE, NGLI_CULL_MODE_FRONT_BIT, NGLI_CULL_MODE_BACK_BIT, - NGLI_CULL_MODE_FRONT_AND_BACK, NGLI_CULL_MODE_NB }; -NGLI_STATIC_ASSERT(cull_mode, (NGLI_CULL_MODE_FRONT_BIT | NGLI_CULL_MODE_BACK_BIT) == NGLI_CULL_MODE_FRONT_AND_BACK); - enum { NGLI_COLOR_COMPONENT_R_BIT = 1 << 0, NGLI_COLOR_COMPONENT_G_BIT = 1 << 1, diff --git a/libnodegl/node_graphicconfig.c b/libnodegl/node_graphicconfig.c index 75a2bd2fef..e88b7e8727 100644 --- a/libnodegl/node_graphicconfig.c +++ b/libnodegl/node_graphicconfig.c @@ -145,6 +145,8 @@ static const struct param_choices stencil_op_choices = { static const struct param_choices cull_face_choices = { .name = "cull_face", .consts = { + {"unset", -1, .desc=NGLI_DOCSTRING("unset")}, + {"none", NGLI_CULL_MODE_NONE, .desc=NGLI_DOCSTRING("no facets are discarded")}, {"front", NGLI_CULL_MODE_FRONT_BIT, .desc=NGLI_DOCSTRING("cull front-facing facets")}, {"back", NGLI_CULL_MODE_BACK_BIT, .desc=NGLI_DOCSTRING("cull back-facing facets")}, {NULL} @@ -207,7 +209,7 @@ static const struct node_param graphicconfig_params[] = { .desc=NGLI_DOCSTRING("operation to execute if stencil and depth test pass")}, {"cull_face", PARAM_TYPE_BOOL, OFFSET(cull_face), {.i64=-1}, .desc=NGLI_DOCSTRING("enable face culling")}, - {"cull_face_mode", PARAM_TYPE_FLAGS, OFFSET(cull_face_mode), {.i64=-1}, + {"cull_face_mode", PARAM_TYPE_SELECT, OFFSET(cull_face_mode), {.i64=-1}, .choices=&cull_face_choices, .desc=NGLI_DOCSTRING("face culling mode")}, {"scissor_test", PARAM_TYPE_BOOL, OFFSET(scissor_test), {.i64=-1}, diff --git a/libnodegl/nodes.specs b/libnodegl/nodes.specs index 0b441b85b7..9b14d5b8d5 100644 --- a/libnodegl/nodes.specs +++ b/libnodegl/nodes.specs @@ -211,7 +211,7 @@ - [stencil_depth_fail, select] - [stencil_depth_pass, select] - [cull_face, bool] - - [cull_face_mode, flags] + - [cull_face_mode, select] - [scissor_test, bool] - [scissor, vec4] From 5d4bdb89a73bbc079ae462ae6a2534e9c61c119b Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 27 May 2020 12:24:07 +0200 Subject: [PATCH 060/388] circle: fix winding order node.gl (and OpenGL) expects the front-facing triangles to be in the counter-clockwise winding order. --- libnodegl/node_circle.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libnodegl/node_circle.c b/libnodegl/node_circle.c index c9ae95ccd2..d694f65294 100644 --- a/libnodegl/node_circle.c +++ b/libnodegl/node_circle.c @@ -68,7 +68,7 @@ static int circle_init(struct ngl_node *node) uvcoords[0] = 0.5; uvcoords[1] = 0.5; for (int i = 1; i < nb_vertices; i++) { - const double angle = (i - 1) * step; + const double angle = (i - 1) * -step; const double x = sin(angle) * s->radius; const double y = cos(angle) * s->radius; vertices[i*3 + 0] = x; From 0dc6f9d0509d5ba449b05f50bf295c2e899afa7c Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 3 Sep 2020 21:47:38 +0200 Subject: [PATCH 061/388] tests/shape: add culling tests --- tests/refs/shape_circle_cull_back.ref | 1 + tests/refs/shape_circle_cull_front.ref | 1 + tests/refs/shape_quad_cull_back.ref | 1 + tests/refs/shape_quad_cull_front.ref | 1 + tests/refs/shape_triangle_cull_back.ref | 1 + tests/refs/shape_triangle_cull_front.ref | 1 + tests/shape.mak | 6 ++++++ tests/shape.py | 27 ++++++++++++++++++++++++ 8 files changed, 39 insertions(+) create mode 100644 tests/refs/shape_circle_cull_back.ref create mode 100644 tests/refs/shape_circle_cull_front.ref create mode 100644 tests/refs/shape_quad_cull_back.ref create mode 100644 tests/refs/shape_quad_cull_front.ref create mode 100644 tests/refs/shape_triangle_cull_back.ref create mode 100644 tests/refs/shape_triangle_cull_front.ref diff --git a/tests/refs/shape_circle_cull_back.ref b/tests/refs/shape_circle_cull_back.ref new file mode 100644 index 0000000000..e955afdcb2 --- /dev/null +++ b/tests/refs/shape_circle_cull_back.ref @@ -0,0 +1 @@ +00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 diff --git a/tests/refs/shape_circle_cull_front.ref b/tests/refs/shape_circle_cull_front.ref new file mode 100644 index 0000000000..a75868b74d --- /dev/null +++ b/tests/refs/shape_circle_cull_front.ref @@ -0,0 +1 @@ +00000000000000000000000000000000 FF55F001CF1CDC05800098018F1CA000 7F55F0A1CF1CCC0C900198098F1CA0A0 00000000000000000000000000000000 diff --git a/tests/refs/shape_quad_cull_back.ref b/tests/refs/shape_quad_cull_back.ref new file mode 100644 index 0000000000..e955afdcb2 --- /dev/null +++ b/tests/refs/shape_quad_cull_back.ref @@ -0,0 +1 @@ +00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 diff --git a/tests/refs/shape_quad_cull_front.ref b/tests/refs/shape_quad_cull_front.ref new file mode 100644 index 0000000000..8f733f2619 --- /dev/null +++ b/tests/refs/shape_quad_cull_front.ref @@ -0,0 +1 @@ +00000000000000000000000000000000 200045546DD628022DD6280228820554 200045542DD628822DD6288228820554 00000000000000000000000000000000 diff --git a/tests/refs/shape_triangle_cull_back.ref b/tests/refs/shape_triangle_cull_back.ref new file mode 100644 index 0000000000..e955afdcb2 --- /dev/null +++ b/tests/refs/shape_triangle_cull_back.ref @@ -0,0 +1 @@ +00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 diff --git a/tests/refs/shape_triangle_cull_front.ref b/tests/refs/shape_triangle_cull_front.ref new file mode 100644 index 0000000000..135d5a0fde --- /dev/null +++ b/tests/refs/shape_triangle_cull_front.ref @@ -0,0 +1 @@ +00000000000000000000000000000000 1B518B58CF1C2C863CC7B221F331C808 1B518B58CF1C2D963CC7B625F331D809 00000000000000000000000000000000 diff --git a/tests/shape.mak b/tests/shape.mak index aa0c1d0758..e21855f04b 100644 --- a/tests/shape.mak +++ b/tests/shape.mak @@ -21,9 +21,15 @@ SHAPE_TEST_NAMES = \ triangle \ + triangle_cull_back \ + triangle_cull_front \ triangles_mat4_attribute \ quad \ + quad_cull_back \ + quad_cull_front \ circle \ + circle_cull_back \ + circle_cull_front \ diamond_colormask \ geometry \ geometry_normals \ diff --git a/tests/shape.py b/tests/shape.py index d34e21289d..25bf856ff6 100644 --- a/tests/shape.py +++ b/tests/shape.py @@ -366,3 +366,30 @@ def shape_triangles_mat4_attribute(cfg): render.update_instance_attributes(matrix=matrices) render.update_frag_resources(color=ngl.UniformVec4(value=COLORS['orange'])) return render + + +def _get_shape_scene(cfg, shape, cull_face_mode): + cfg.aspect_ratio = (1, 1) + + geometry_cls = dict( + triangle=ngl.Triangle, + quad=ngl.Quad, + circle=ngl.Circle, + ) + geometry = geometry_cls[shape]() + + node = _render_shape(cfg, geometry, COLORS['sgreen']) + return ngl.GraphicConfig(node, cull_face=True, cull_face_mode=cull_face_mode) + + +def _get_shape_function(shape, cull_face_mode): + @test_fingerprint() + @scene() + def shape_function(cfg): + return _get_shape_scene(cfg, shape, cull_face_mode) + return shape_function + + +for shape in ('triangle', 'quad', 'circle'): + for cull_face_mode in ('front', 'back'): + globals()['shape_' + shape + '_cull_' + cull_face_mode] = _get_shape_function(shape, cull_face_mode) From ac30f369364bf29fc9235f2ce8104f6b4779db54 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 31 Aug 2020 16:45:28 +0200 Subject: [PATCH 062/388] gctx: add ngli_transform_cull_mode() --- libnodegl/gctx.c | 5 +++++ libnodegl/gctx.h | 2 ++ libnodegl/gctx_gl.c | 15 +++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/libnodegl/gctx.c b/libnodegl/gctx.c index dd71f54244..1743a4a1f5 100644 --- a/libnodegl/gctx.c +++ b/libnodegl/gctx.c @@ -99,6 +99,11 @@ void ngli_gctx_freep(struct gctx **sp) ngli_freep(sp); } +int ngli_gctx_transform_cull_mode(struct gctx *s, int cull_mode) +{ + return s->class->transform_cull_mode(s, cull_mode); +} + void ngli_gctx_transform_projection_matrix(struct gctx *s, float *dst) { s->class->transform_projection_matrix(s, dst); diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index 07d6ee25c2..92c82563d5 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -41,6 +41,7 @@ struct gctx_class { int (*post_draw)(struct gctx *s, double t); void (*destroy)(struct gctx *s); + int (*transform_cull_mode)(struct gctx *s, int cull_mode); void (*transform_projection_matrix)(struct gctx *s, float *dst); void (*get_rendertarget_uvcoord_matrix)(struct gctx *s, float *dst); @@ -114,6 +115,7 @@ int ngli_gctx_resize(struct gctx *s, int width, int height, const int *viewport) int ngli_gctx_draw(struct gctx *s, struct ngl_node *scene, double t); void ngli_gctx_freep(struct gctx **sp); +int ngli_gctx_transform_cull_mode(struct gctx *s, int cull_mode); void ngli_gctx_transform_projection_matrix(struct gctx *s, float *dst); void ngli_gctx_get_rendertarget_uvcoord_matrix(struct gctx *s, float *dst); diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 57e03ff2ed..881e833cc1 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -330,6 +330,19 @@ static void gl_destroy(struct gctx *s) ngli_glcontext_freep(&s_priv->glcontext); } +static int gl_transform_cull_mode(struct gctx *s, int cull_mode) +{ + const struct ngl_config *config = &s->config; + if (!config->offscreen) + return cull_mode; + static const int cull_mode_map[NGLI_CULL_MODE_NB] = { + [NGLI_CULL_MODE_NONE] = NGLI_CULL_MODE_NONE, + [NGLI_CULL_MODE_FRONT_BIT] = NGLI_CULL_MODE_BACK_BIT, + [NGLI_CULL_MODE_BACK_BIT] = NGLI_CULL_MODE_FRONT_BIT, + }; + return cull_mode_map[cull_mode]; +} + static void gl_transform_projection_matrix(struct gctx *s, float *dst) { const struct ngl_config *config = &s->config; @@ -486,6 +499,7 @@ const struct gctx_class ngli_gctx_gl = { .post_draw = gl_post_draw, .destroy = gl_destroy, + .transform_cull_mode = gl_transform_cull_mode, .transform_projection_matrix = gl_transform_projection_matrix, .get_rendertarget_uvcoord_matrix = gl_get_rendertarget_uvcoord_matrix, @@ -554,6 +568,7 @@ const struct gctx_class ngli_gctx_gles = { .post_draw = gl_post_draw, .destroy = gl_destroy, + .transform_cull_mode = gl_transform_cull_mode, .transform_projection_matrix = gl_transform_projection_matrix, .get_rendertarget_uvcoord_matrix = gl_get_rendertarget_uvcoord_matrix, From 31386c923721b300a9fb58c7281bddf3d8e12d37 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 31 Aug 2020 16:56:24 +0200 Subject: [PATCH 063/388] internal: use ngli_gctx_transform_cull_mode() The cull mode specified by the user needs to be adjusted depending on the backend coordinate system and rendering. For example, a y-flip is performed on the geometry with the Vulkan and OpenGL offscreen backends. In this case, the cull_mode needs to be inverted. Fixes an OpenGL offscreen rendering regression when culling is enabled introduced by df18098f01ff9795018b23dfc844225f325db527. --- libnodegl/node_graphicconfig.c | 4 +++- tests/refs/shape_circle_cull_back.ref | 2 +- tests/refs/shape_circle_cull_front.ref | 2 +- tests/refs/shape_quad_cull_back.ref | 2 +- tests/refs/shape_quad_cull_front.ref | 2 +- tests/refs/shape_triangle_cull_back.ref | 2 +- tests/refs/shape_triangle_cull_front.ref | 2 +- 7 files changed, 9 insertions(+), 7 deletions(-) diff --git a/libnodegl/node_graphicconfig.c b/libnodegl/node_graphicconfig.c index e88b7e8727..f8d0fb30de 100644 --- a/libnodegl/node_graphicconfig.c +++ b/libnodegl/node_graphicconfig.c @@ -254,6 +254,7 @@ static int graphicconfig_update(struct ngl_node *node, double t) static void honor_config(struct ngl_node *node) { struct ngl_ctx *ctx = node->ctx; + struct gctx *gctx = ctx->gctx; struct rnode *rnode = ctx->rnode_pos; struct graphicconfig_priv *s = node->priv_data; struct graphicstate *pending = &rnode->graphicstate; @@ -284,7 +285,8 @@ static void honor_config(struct ngl_node *node) COPY_PARAM(stencil_depth_pass); COPY_PARAM(cull_face); - COPY_PARAM(cull_face_mode); + if (s->cull_face_mode != -1) + pending->cull_face_mode = ngli_gctx_transform_cull_mode(gctx, s->cull_face_mode); COPY_PARAM(scissor_test); } diff --git a/tests/refs/shape_circle_cull_back.ref b/tests/refs/shape_circle_cull_back.ref index e955afdcb2..a75868b74d 100644 --- a/tests/refs/shape_circle_cull_back.ref +++ b/tests/refs/shape_circle_cull_back.ref @@ -1 +1 @@ -00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 +00000000000000000000000000000000 FF55F001CF1CDC05800098018F1CA000 7F55F0A1CF1CCC0C900198098F1CA0A0 00000000000000000000000000000000 diff --git a/tests/refs/shape_circle_cull_front.ref b/tests/refs/shape_circle_cull_front.ref index a75868b74d..e955afdcb2 100644 --- a/tests/refs/shape_circle_cull_front.ref +++ b/tests/refs/shape_circle_cull_front.ref @@ -1 +1 @@ -00000000000000000000000000000000 FF55F001CF1CDC05800098018F1CA000 7F55F0A1CF1CCC0C900198098F1CA0A0 00000000000000000000000000000000 +00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 diff --git a/tests/refs/shape_quad_cull_back.ref b/tests/refs/shape_quad_cull_back.ref index e955afdcb2..8f733f2619 100644 --- a/tests/refs/shape_quad_cull_back.ref +++ b/tests/refs/shape_quad_cull_back.ref @@ -1 +1 @@ -00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 +00000000000000000000000000000000 200045546DD628022DD6280228820554 200045542DD628822DD6288228820554 00000000000000000000000000000000 diff --git a/tests/refs/shape_quad_cull_front.ref b/tests/refs/shape_quad_cull_front.ref index 8f733f2619..e955afdcb2 100644 --- a/tests/refs/shape_quad_cull_front.ref +++ b/tests/refs/shape_quad_cull_front.ref @@ -1 +1 @@ -00000000000000000000000000000000 200045546DD628022DD6280228820554 200045542DD628822DD6288228820554 00000000000000000000000000000000 +00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 diff --git a/tests/refs/shape_triangle_cull_back.ref b/tests/refs/shape_triangle_cull_back.ref index e955afdcb2..135d5a0fde 100644 --- a/tests/refs/shape_triangle_cull_back.ref +++ b/tests/refs/shape_triangle_cull_back.ref @@ -1 +1 @@ -00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 +00000000000000000000000000000000 1B518B58CF1C2C863CC7B221F331C808 1B518B58CF1C2D963CC7B625F331D809 00000000000000000000000000000000 diff --git a/tests/refs/shape_triangle_cull_front.ref b/tests/refs/shape_triangle_cull_front.ref index 135d5a0fde..e955afdcb2 100644 --- a/tests/refs/shape_triangle_cull_front.ref +++ b/tests/refs/shape_triangle_cull_front.ref @@ -1 +1 @@ -00000000000000000000000000000000 1B518B58CF1C2C863CC7B221F331C808 1B518B58CF1C2D963CC7B625F331D809 00000000000000000000000000000000 +00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 From 06c772950d1f62cdc2bef20cb035db9fb5822805 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 4 Sep 2020 14:48:02 +0200 Subject: [PATCH 064/388] graphicconfig: merge cull_face and cull_face_mode params Culling can either be unset, none (disabled), front (enabled) or back (enabled). --- libnodegl/doc/libnodegl.md | 5 ++--- libnodegl/glstate.c | 5 +++-- libnodegl/graphicstate.h | 6 ++---- libnodegl/node_graphicconfig.c | 23 +++++++---------------- libnodegl/nodes.specs | 3 +-- tests/shape.py | 12 ++++++------ 6 files changed, 21 insertions(+), 33 deletions(-) diff --git a/libnodegl/doc/libnodegl.md b/libnodegl/doc/libnodegl.md index d0f8bd54cb..54f456e818 100644 --- a/libnodegl/doc/libnodegl.md +++ b/libnodegl/doc/libnodegl.md @@ -319,8 +319,7 @@ Parameter | Live-chg. | Type | Description | Default `stencil_fail` | | [`stencil_operation`](#stencil_operation-choices) | operation to execute if stencil test fails | `unset` `stencil_depth_fail` | | [`stencil_operation`](#stencil_operation-choices) | operation to execute if depth test fails | `unset` `stencil_depth_pass` | | [`stencil_operation`](#stencil_operation-choices) | operation to execute if stencil and depth test pass | `unset` -`cull_face` | | [`bool`](#parameter-types) | enable face culling | `unset` -`cull_face_mode` | | [`cull_face`](#cull_face-choices) | face culling mode | `unset` +`cull_mode` | | [`cull_mode`](#cull_mode-choices) | face culling mode | `unset` `scissor_test` | | [`bool`](#parameter-types) | enable scissor testing | `unset` `scissor` | | [`vec4`](#parameter-types) | define an area where all pixels outside are discarded | (`-1`,`-1`,`-1`,`-1`) @@ -1395,7 +1394,7 @@ Constant | Description `decr_wrap` | decrements the current stencil buffer value and wraps it `decr_invert` | bitwise inverts the current stencil buffer value -## cull_face choices +## cull_mode choices Constant | Description -------- | ----------- diff --git a/libnodegl/glstate.c b/libnodegl/glstate.c index 569886ef3a..acb6bdd142 100644 --- a/libnodegl/glstate.c +++ b/libnodegl/glstate.c @@ -92,6 +92,7 @@ static GLenum get_gl_stencil_op(int stencil_op) } static const GLenum gl_cull_mode_map[NGLI_CULL_MODE_NB] = { + [NGLI_CULL_MODE_NONE] = GL_BACK, [NGLI_CULL_MODE_FRONT_BIT] = GL_FRONT, [NGLI_CULL_MODE_BACK_BIT] = GL_BACK, }; @@ -166,8 +167,8 @@ static void init_state(struct glstate *s, const struct graphicstate *gc) s->stencil_depth_fail = get_gl_stencil_op(gc->stencil_depth_fail); s->stencil_depth_pass = get_gl_stencil_op(gc->stencil_depth_pass); - s->cull_face = gc->cull_face; - s->cull_face_mode = get_gl_cull_mode(gc->cull_face_mode); + s->cull_face = gc->cull_mode != NGLI_CULL_MODE_NONE; + s->cull_face_mode = get_gl_cull_mode(gc->cull_mode); s->scissor_test = gc->scissor_test; } diff --git a/libnodegl/graphicstate.h b/libnodegl/graphicstate.h index af39d26c24..c24e6b149b 100644 --- a/libnodegl/graphicstate.h +++ b/libnodegl/graphicstate.h @@ -109,8 +109,7 @@ struct graphicstate { int stencil_depth_fail; int stencil_depth_pass; - int cull_face; - int cull_face_mode; + int cull_mode; int scissor_test; }; @@ -138,8 +137,7 @@ struct graphicstate { .stencil_fail = NGLI_STENCIL_OP_KEEP, \ .stencil_depth_fail = NGLI_STENCIL_OP_KEEP, \ .stencil_depth_pass = NGLI_STENCIL_OP_KEEP, \ - .cull_face = 0, \ - .cull_face_mode = NGLI_CULL_MODE_BACK_BIT, \ + .cull_mode = NGLI_CULL_MODE_NONE, \ } \ #endif diff --git a/libnodegl/node_graphicconfig.c b/libnodegl/node_graphicconfig.c index f8d0fb30de..90fce5a31a 100644 --- a/libnodegl/node_graphicconfig.c +++ b/libnodegl/node_graphicconfig.c @@ -55,8 +55,7 @@ struct graphicconfig_priv { int stencil_depth_fail; int stencil_depth_pass; - int cull_face; - int cull_face_mode; + int cull_mode; int scissor_test; float scissor_f[4]; @@ -142,8 +141,8 @@ static const struct param_choices stencil_op_choices = { } }; -static const struct param_choices cull_face_choices = { - .name = "cull_face", +static const struct param_choices cull_mode_choices = { + .name = "cull_mode", .consts = { {"unset", -1, .desc=NGLI_DOCSTRING("unset")}, {"none", NGLI_CULL_MODE_NONE, .desc=NGLI_DOCSTRING("no facets are discarded")}, @@ -207,10 +206,8 @@ static const struct node_param graphicconfig_params[] = { {"stencil_depth_pass", PARAM_TYPE_SELECT, OFFSET(stencil_depth_pass), {.i64=-1}, .choices=&stencil_op_choices, .desc=NGLI_DOCSTRING("operation to execute if stencil and depth test pass")}, - {"cull_face", PARAM_TYPE_BOOL, OFFSET(cull_face), {.i64=-1}, - .desc=NGLI_DOCSTRING("enable face culling")}, - {"cull_face_mode", PARAM_TYPE_SELECT, OFFSET(cull_face_mode), {.i64=-1}, - .choices=&cull_face_choices, + {"cull_mode", PARAM_TYPE_SELECT, OFFSET(cull_mode), {.i64=-1}, + .choices=&cull_mode_choices, .desc=NGLI_DOCSTRING("face culling mode")}, {"scissor_test", PARAM_TYPE_BOOL, OFFSET(scissor_test), {.i64=-1}, .desc=NGLI_DOCSTRING("enable scissor testing")}, @@ -229,11 +226,6 @@ static int graphicconfig_init(struct ngl_node *node) const int scissor[4] = {sf[0], sf[1], sf[2], sf[3]}; memcpy(s->scissor, scissor, sizeof(s->scissor)); - if (!s->cull_face_mode) { - LOG(ERROR, "cull face mode cannot be null"); - return NGL_ERROR_INVALID_ARG; - } - return 0; } @@ -284,9 +276,8 @@ static void honor_config(struct ngl_node *node) COPY_PARAM(stencil_depth_fail); COPY_PARAM(stencil_depth_pass); - COPY_PARAM(cull_face); - if (s->cull_face_mode != -1) - pending->cull_face_mode = ngli_gctx_transform_cull_mode(gctx, s->cull_face_mode); + if (s->cull_mode != -1) + pending->cull_mode = ngli_gctx_transform_cull_mode(gctx, s->cull_mode); COPY_PARAM(scissor_test); } diff --git a/libnodegl/nodes.specs b/libnodegl/nodes.specs index 9b14d5b8d5..370e7deb98 100644 --- a/libnodegl/nodes.specs +++ b/libnodegl/nodes.specs @@ -210,8 +210,7 @@ - [stencil_fail, select] - [stencil_depth_fail, select] - [stencil_depth_pass, select] - - [cull_face, bool] - - [cull_face_mode, select] + - [cull_mode, select] - [scissor_test, bool] - [scissor, vec4] diff --git a/tests/shape.py b/tests/shape.py index 25bf856ff6..0ef7bd86d9 100644 --- a/tests/shape.py +++ b/tests/shape.py @@ -368,7 +368,7 @@ def shape_triangles_mat4_attribute(cfg): return render -def _get_shape_scene(cfg, shape, cull_face_mode): +def _get_shape_scene(cfg, shape, cull_mode): cfg.aspect_ratio = (1, 1) geometry_cls = dict( @@ -379,17 +379,17 @@ def _get_shape_scene(cfg, shape, cull_face_mode): geometry = geometry_cls[shape]() node = _render_shape(cfg, geometry, COLORS['sgreen']) - return ngl.GraphicConfig(node, cull_face=True, cull_face_mode=cull_face_mode) + return ngl.GraphicConfig(node, cull_mode=cull_mode) -def _get_shape_function(shape, cull_face_mode): +def _get_shape_function(shape, cull_mode): @test_fingerprint() @scene() def shape_function(cfg): - return _get_shape_scene(cfg, shape, cull_face_mode) + return _get_shape_scene(cfg, shape, cull_mode) return shape_function for shape in ('triangle', 'quad', 'circle'): - for cull_face_mode in ('front', 'back'): - globals()['shape_' + shape + '_cull_' + cull_face_mode] = _get_shape_function(shape, cull_face_mode) + for cull_mode in ('front', 'back'): + globals()['shape_' + shape + '_cull_' + cull_mode] = _get_shape_function(shape, cull_mode) From 24d9a0c6a294cb850dd24f6261de3e3e85088db7 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 10 Sep 2019 10:17:20 +0200 Subject: [PATCH 065/388] glcontext_egl: add EGL_ANDROID_get_native_client_buffer extension support --- libnodegl/egl.h | 3 +++ libnodegl/features.h | 1 + libnodegl/glcontext_egl.c | 17 +++++++++++++++++ 3 files changed, 21 insertions(+) diff --git a/libnodegl/egl.h b/libnodegl/egl.h index 135be31a12..677522a876 100644 --- a/libnodegl/egl.h +++ b/libnodegl/egl.h @@ -36,4 +36,7 @@ EGLImageKHR ngli_eglCreateImageKHR(struct glcontext *gl, EGLBoolean ngli_eglDestroyImageKHR(struct glcontext *gl, EGLImageKHR image); +EGLClientBuffer ngli_eglGetNativeClientBufferANDROID(struct glcontext *gl, + const struct AHardwareBuffer *buffer); + #endif diff --git a/libnodegl/features.h b/libnodegl/features.h index b538c8b5d6..e6efdbb1a4 100644 --- a/libnodegl/features.h +++ b/libnodegl/features.h @@ -52,6 +52,7 @@ #define NGLI_FEATURE_ROW_LENGTH (1 << 27) #define NGLI_FEATURE_SOFTWARE (1 << 28) #define NGLI_FEATURE_UINT_UNIFORMS (1 << 29) +#define NGLI_FEATURE_EGL_ANDROID_GET_IMAGE_NATIVE_CLIENT_BUFFER (1 << 30) #define NGLI_FEATURE_COMPUTE_SHADER_ALL (NGLI_FEATURE_COMPUTE_SHADER | \ NGLI_FEATURE_PROGRAM_INTERFACE_QUERY | \ diff --git a/libnodegl/glcontext_egl.c b/libnodegl/glcontext_egl.c index bdaa6e1a36..1333c84fca 100644 --- a/libnodegl/glcontext_egl.c +++ b/libnodegl/glcontext_egl.c @@ -34,6 +34,7 @@ #if defined(TARGET_ANDROID) #include +#include #endif #include "egl.h" @@ -60,6 +61,7 @@ struct egl_priv { EGLDisplay (*GetPlatformDisplay)(EGLenum platform, void *native_display, const EGLint *attrib_list); EGLAPIENTRY EGLImageKHR (*CreateImageKHR)(EGLDisplay, EGLContext, EGLenum, EGLClientBuffer, const EGLint *); EGLAPIENTRY EGLBoolean (*DestroyImageKHR)(EGLDisplay, EGLImageKHR); + EGLAPIENTRY EGLClientBuffer (*GetNativeClientBufferANDROID)(const struct AHardwareBuffer *); int has_platform_x11_ext; int has_platform_mesa_surfaceless_ext; int has_platform_wayland_ext; @@ -81,6 +83,12 @@ EGLBoolean ngli_eglDestroyImageKHR(struct glcontext *gl, EGLImageKHR image) return egl->DestroyImageKHR(egl->display, image); } +EGLClientBuffer ngli_eglGetNativeClientBufferANDROID(struct glcontext *gl, const struct AHardwareBuffer *buffer) +{ + struct egl_priv *egl = gl->priv_data; + return egl->GetNativeClientBufferANDROID(buffer); +} + static int egl_probe_extensions(struct glcontext *ctx) { struct egl_priv *egl = ctx->priv_data; @@ -93,6 +101,15 @@ static int egl_probe_extensions(struct glcontext *ctx) return -1; } } + + if (ngli_glcontext_check_extension("EGL_ANDROID_get_native_client_buffer", egl->extensions)) { + egl->GetNativeClientBufferANDROID = (void *)eglGetProcAddress("eglGetNativeClientBufferANDROID"); + if (!egl->GetNativeClientBufferANDROID) { + LOG(ERROR, "could not retrieve eglGetNativeClientBufferANDROID()"); + return -1; + } + ctx->features |= NGLI_FEATURE_EGL_ANDROID_GET_IMAGE_NATIVE_CLIENT_BUFFER; + } #endif if (ngli_glcontext_check_extension("EGL_KHR_image_base", egl->extensions)) { From 968bde308b375bbb8d6dc53563a6083f436af61a Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 5 Aug 2020 15:32:16 +0200 Subject: [PATCH 066/388] internal: add android_ctx API --- libnodegl/Makefile | 2 +- libnodegl/android_ctx.c | 114 ++++++++++++++++++++++++++++++++++++++++ libnodegl/android_ctx.h | 54 +++++++++++++++++++ libnodegl/api.c | 10 ++++ libnodegl/nodes.h | 4 ++ 5 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 libnodegl/android_ctx.c create mode 100644 libnodegl/android_ctx.h diff --git a/libnodegl/Makefile b/libnodegl/Makefile index 2f88396056..29d8b9a8d1 100644 --- a/libnodegl/Makefile +++ b/libnodegl/Makefile @@ -141,7 +141,7 @@ LIB_LDLIBS = -lm -lpthread LIB_EXTRA_OBJS_Linux = LIB_EXTRA_OBJS_Darwin = -LIB_EXTRA_OBJS_Android = jni_utils.o android_utils.o android_looper.o android_surface.o android_handler.o android_handlerthread.o +LIB_EXTRA_OBJS_Android = jni_utils.o android_ctx.o android_utils.o android_looper.o android_surface.o android_handler.o android_handlerthread.o LIB_EXTRA_OBJS_iPhone = LIB_EXTRA_OBJS_MinGW-w64 = diff --git a/libnodegl/android_ctx.c b/libnodegl/android_ctx.c new file mode 100644 index 0000000000..e68db359e3 --- /dev/null +++ b/libnodegl/android_ctx.c @@ -0,0 +1,114 @@ +/* + * Copyright 2020 GoPro Inc. + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include + +#include "android_ctx.h" +#include "gctx.h" +#include "log.h" +#include "nodes.h" + +#define NDK_LOAD_FUNC(handle, name) do { \ + s->name = dlsym(handle, #name); \ + if (!s->name) { \ + LOG(INFO, "missing %s symbol", #name); \ + goto done; \ + } \ +} while (0) + +static int load_media_api(struct android_ctx *s) +{ + s->libmediandk_handle = dlopen("libmediandk.so", RTLD_NOW); + if (!s->libmediandk_handle) { + LOG(ERROR, "could not open libmediandk.so"); + return NGL_ERROR_UNSUPPORTED; + } + + NDK_LOAD_FUNC(s->libmediandk_handle, AImage_delete); + NDK_LOAD_FUNC(s->libmediandk_handle, AImage_getHardwareBuffer); + NDK_LOAD_FUNC(s->libmediandk_handle, AImageReader_new); + NDK_LOAD_FUNC(s->libmediandk_handle, AImageReader_getWindow); + NDK_LOAD_FUNC(s->libmediandk_handle, AImageReader_acquireNextImage); + NDK_LOAD_FUNC(s->libmediandk_handle, AImageReader_setImageListener); + NDK_LOAD_FUNC(s->libmediandk_handle, AImageReader_delete); + + return 0; + +done: + dlclose(s->libmediandk_handle); + s->libmediandk_handle = NULL; + return NGL_ERROR_UNSUPPORTED; +} + +static int load_window_api(struct android_ctx *s) +{ + s->libandroid_handle = dlopen("libandroid.so", RTLD_NOW); + if (!s->libandroid_handle) { + LOG(ERROR, "could not open libandroid.so"); + return NGL_ERROR_UNSUPPORTED; + } + + NDK_LOAD_FUNC(s->libandroid_handle, ANativeWindow_toSurface); + + return 0; + +done: + dlclose(s->libandroid_handle); + s->libandroid_handle = NULL; + return NGL_ERROR_UNSUPPORTED; +} + +int ngli_android_ctx_init(struct gctx *gctx, struct android_ctx *s) +{ + memset(s, 0, sizeof(*s)); + + int ret = load_media_api(s); + if (ret < 0) { + LOG(INFO, "could not load native media API"); + return ret; + } + + ret = load_window_api(s); + if (ret < 0) { + LOG(INFO, "could not load native window API"); + return ret; + } + + const struct ngl_config *config = &gctx->config; + const int features = NGLI_FEATURE_OES_EGL_EXTERNAL_IMAGE | + NGLI_FEATURE_EGL_ANDROID_GET_IMAGE_NATIVE_CLIENT_BUFFER; + if (config->backend == NGL_BACKEND_OPENGLES && (gctx->features & features) == features) + s->has_native_imagereader_api = 1; + + return 0; +} + +void ngli_android_ctx_reset(struct android_ctx *s) +{ + if (s->libmediandk_handle) + dlclose(s->libmediandk_handle); + if (s->libandroid_handle) + dlclose(s->libandroid_handle); + memset(s, 0, sizeof(*s)); +} diff --git a/libnodegl/android_ctx.h b/libnodegl/android_ctx.h new file mode 100644 index 0000000000..d292a649b8 --- /dev/null +++ b/libnodegl/android_ctx.h @@ -0,0 +1,54 @@ +/* + * Copyright 2020 GoPro Inc. + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef ANDROID_CTX_H +#define ANDROID_CTX_H + +#include +#include + +struct gctx; + +struct android_ctx { + void *libandroid_handle; + void *libmediandk_handle; + + /* AImage */ + void (*AImage_delete)(AImage *image); + media_status_t (*AImage_getHardwareBuffer)(const AImage *image, AHardwareBuffer **buffer); + + /* AImageReader */ + media_status_t (*AImageReader_new)(int32_t width, int32_t height, int32_t format, int32_t maxImages, AImageReader **reader); + media_status_t (*AImageReader_setImageListener)(AImageReader *reader, AImageReader_ImageListener *listener); + media_status_t (*AImageReader_getWindow)(AImageReader *reader, ANativeWindow **window); + media_status_t (*AImageReader_acquireNextImage)(AImageReader *reader, AImage **image); + void (*AImageReader_delete)(AImageReader *reader); + + /* ANativeWindow */ + jobject (*ANativeWindow_toSurface)(JNIEnv* env, ANativeWindow* window); + + int has_native_imagereader_api; +}; + +int ngli_android_ctx_init(struct gctx *gctx, struct android_ctx *s); +void ngli_android_ctx_reset(struct android_ctx *s); + +#endif diff --git a/libnodegl/api.c b/libnodegl/api.c index ed59f9cd8b..70514acf96 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -77,6 +77,9 @@ static int cmd_stop(struct ngl_ctx *s, void *arg) { #if defined(HAVE_VAAPI) ngli_vaapi_reset(s); +#endif +#if defined(TARGET_ANDROID) + ngli_android_ctx_reset(&s->android_ctx); #endif ngli_texture_freep(&s->font_atlas); // allocated by the first node text ngli_pgcache_reset(&s->pgcache); @@ -131,6 +134,13 @@ static int cmd_configure(struct ngl_ctx *s, void *arg) LOG(WARNING, "could not initialize vaapi"); #endif +#if defined(TARGET_ANDROID) + struct android_ctx *android_ctx = &s->android_ctx; + ret = ngli_android_ctx_init(s->gctx, android_ctx); + if (ret < 0) + LOG(WARNING, "could not initialize Android context"); +#endif + NGLI_ALIGNED_MAT(matrix) = NGLI_MAT4_IDENTITY; ngli_gctx_transform_projection_matrix(s->gctx, matrix); ngli_darray_clear(&s->projection_matrix_stack); diff --git a/libnodegl/nodes.h b/libnodegl/nodes.h index 1122cc42e8..563b1bd093 100644 --- a/libnodegl/nodes.h +++ b/libnodegl/nodes.h @@ -39,6 +39,7 @@ #endif #if defined(TARGET_ANDROID) +#include "android_ctx.h" #include "android_handlerthread.h" #include "android_surface.h" #endif @@ -96,6 +97,9 @@ struct ngl_ctx { VADisplay va_display; int va_version; #endif +#if defined(TARGET_ANDROID) + struct android_ctx android_ctx; +#endif /* Shared fields */ pthread_mutex_t lock; From 27a93481858f729da54209819192a73d91ab9ecb Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 10 Sep 2019 10:18:47 +0200 Subject: [PATCH 067/388] android: use AImageReader API if possible for MediaCodec interop --- libnodegl/Makefile | 2 +- libnodegl/android_imagereader.c | 209 +++++++++++++++++++++++++++++ libnodegl/android_imagereader.h | 62 +++++++++ libnodegl/hwupload_mediacodec_gl.c | 98 +++++++++++++- libnodegl/node_media.c | 56 +++++--- libnodegl/nodes.h | 2 + 6 files changed, 408 insertions(+), 21 deletions(-) create mode 100644 libnodegl/android_imagereader.c create mode 100644 libnodegl/android_imagereader.h diff --git a/libnodegl/Makefile b/libnodegl/Makefile index 29d8b9a8d1..94239fdbec 100644 --- a/libnodegl/Makefile +++ b/libnodegl/Makefile @@ -141,7 +141,7 @@ LIB_LDLIBS = -lm -lpthread LIB_EXTRA_OBJS_Linux = LIB_EXTRA_OBJS_Darwin = -LIB_EXTRA_OBJS_Android = jni_utils.o android_ctx.o android_utils.o android_looper.o android_surface.o android_handler.o android_handlerthread.o +LIB_EXTRA_OBJS_Android = jni_utils.o android_ctx.o android_utils.o android_looper.o android_surface.o android_handler.o android_handlerthread.o android_imagereader.o LIB_EXTRA_OBJS_iPhone = LIB_EXTRA_OBJS_MinGW-w64 = diff --git a/libnodegl/android_imagereader.c b/libnodegl/android_imagereader.c new file mode 100644 index 0000000000..25e6a94230 --- /dev/null +++ b/libnodegl/android_imagereader.c @@ -0,0 +1,209 @@ +/* + * Copyright 2020 GoPro Inc. + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include +#include + +#include "android_imagereader.h" +#include "jni_utils.h" +#include "log.h" +#include "memory.h" + +struct android_image { + struct android_ctx *android_ctx; + AImage *image; +}; + +struct android_imagereader { + struct android_ctx *android_ctx; + AImageReader *reader; + jobject window; + + pthread_mutex_t lock; + pthread_cond_t cond; + int buffer_available; +}; + +AHardwareBuffer *ngli_android_image_get_hardware_buffer(struct android_image *s) +{ + struct android_ctx *android_ctx = s->android_ctx; + + AHardwareBuffer *hardware_buffer; + media_status_t status = android_ctx->AImage_getHardwareBuffer(s->image, &hardware_buffer); + if (status != AMEDIA_OK) + return NULL; + return hardware_buffer; +} + +void ngli_android_image_freep(struct android_image **sp) +{ + if (!*sp) + return; + + struct android_image *s = *sp; + struct android_ctx *android_ctx = s->android_ctx; + android_ctx->AImage_delete(s->image); + + ngli_freep(sp); +} + +static void on_buffer_available(void *context, AImageReader *reader) +{ + struct android_imagereader *s = context; + pthread_mutex_lock(&s->lock); + s->buffer_available = 1; + pthread_cond_signal(&s->cond); + pthread_mutex_unlock(&s->lock); +} + +struct android_imagereader *ngli_android_imagereader_create(struct android_ctx *android_ctx, int width, int height, int format, int max_images) +{ + if (!android_ctx->has_native_imagereader_api) + return NULL; + + struct android_imagereader *s = ngli_calloc(1, sizeof(*s)); + if (!s) + return NULL; + + s->android_ctx = android_ctx; + if (pthread_mutex_init(&s->lock, NULL)) { + ngli_freep(&s); + return NULL; + } + + if (pthread_cond_init(&s->cond, NULL)) { + pthread_mutex_destroy(&s->lock); + ngli_freep(&s); + return NULL; + } + + media_status_t status = android_ctx->AImageReader_new(width, height, format, max_images, &s->reader); + if (status != AMEDIA_OK) { + LOG(ERROR, "failed to allocate AImageReader"); + goto fail; + } + + AImageReader_ImageListener listener = { + .context = s, + .onImageAvailable = on_buffer_available, + }; + + status = android_ctx->AImageReader_setImageListener(s->reader, &listener); + if (status != AMEDIA_OK) { + LOG(ERROR, "failed to set image listener"); + goto fail; + } + + return s; +fail: + + pthread_mutex_destroy(&s->lock); + pthread_cond_destroy(&s->cond); + android_ctx->AImageReader_delete(s->reader); + ngli_freep(&s); + + return NULL; +} + +int ngli_android_imagereader_get_window(struct android_imagereader *s, void **window) +{ + struct android_ctx *android_ctx = s->android_ctx; + + if (s->window) { + *window = s->window; + return 0; + } + + ANativeWindow *native_window; + media_status_t status = android_ctx->AImageReader_getWindow(s->reader, &native_window); + if (status != AMEDIA_OK) + return NGL_ERROR_EXTERNAL; + + JNIEnv *env = ngli_jni_get_env(); + if (!env) + return NGL_ERROR_EXTERNAL; + + jobject object = android_ctx->ANativeWindow_toSurface(env, native_window); + s->window = (*env)->NewGlobalRef(env, object); + (*env)->DeleteLocalRef(env, object); + + *window = s->window; + return *window ? 0 : NGL_ERROR_EXTERNAL; +} + +int ngli_android_imagereader_acquire_next_image(struct android_imagereader *s, struct android_image **imagep) +{ + struct android_ctx *android_ctx = s->android_ctx; + + AImage *android_image; + media_status_t status; + int nb_retries = 0; + + pthread_mutex_lock(&s->lock); + for (;;) { + status = android_ctx->AImageReader_acquireNextImage(s->reader, &android_image); + if (!nb_retries++ && status == AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += 1; + pthread_cond_timedwait(&s->cond, &s->lock, &ts); + } else { + break; + } + } + pthread_mutex_unlock(&s->lock); + + if (status != AMEDIA_OK) + return NGL_ERROR_EXTERNAL; + + struct android_image *image = ngli_calloc(1, sizeof(*image)); + if (!image) { + android_ctx->AImage_delete(android_image); + return NGL_ERROR_MEMORY; + } + image->android_ctx = android_ctx; + image->image = android_image; + *imagep = image; + + return 0; +} + +void ngli_android_imagereader_freep(struct android_imagereader **sp) +{ + if (!*sp) + return; + + struct android_imagereader *s = *sp; + struct android_ctx *android_ctx = s->android_ctx; + android_ctx->AImageReader_delete(s->reader); + + pthread_mutex_destroy(&s->lock); + pthread_cond_destroy(&s->cond); + + JNIEnv *env = ngli_jni_get_env(); + if (env) + (*env)->DeleteGlobalRef(env, s->window); + + ngli_freep(sp); +} diff --git a/libnodegl/android_imagereader.h b/libnodegl/android_imagereader.h new file mode 100644 index 0000000000..0fe9b15a75 --- /dev/null +++ b/libnodegl/android_imagereader.h @@ -0,0 +1,62 @@ +/* + * Copyright 2019 GoPro Inc. + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef ANDROID_IMAGEREADER_H +#define ANDROID_IMAGEREADER_H + +#include +#include + +#include "android_ctx.h" + +enum { + NGLI_ANDROID_IMAGE_FORMAT_RGBA_8888 = 0x1, + NGLI_ANDROID_IMAGE_FORMAT_RGBX_8888 = 0x2, + NGLI_ANDROID_IMAGE_FORMAT_RGB_888 = 0x3, + NGLI_ANDROID_IMAGE_FORMAT_RGB_565 = 0x4, + NGLI_ANDROID_IMAGE_FORMAT_RGBA_FP16 = 0x16, + NGLI_ANDROID_IMAGE_FORMAT_YUV_420_888 = 0x23, + NGLI_ANDROID_IMAGE_FORMAT_JPEG = 0x100, + NGLI_ANDROID_IMAGE_FORMAT_RAW16 = 0x20, + NGLI_ANDROID_IMAGE_FORMAT_RAW_PRIVATE = 0x24, + NGLI_ANDROID_IMAGE_FORMAT_RAW10 = 0x25, + NGLI_ANDROID_IMAGE_FORMAT_RAW12 = 0x26, + NGLI_ANDROID_IMAGE_FORMAT_DEPTH16 = 0x44363159, + NGLI_ANDROID_IMAGE_FORMAT_DEPTH_POINT_CLOUD = 0x101, + NGLI_ANDROID_IMAGE_FORMAT_PRIVATE = 0x22, + NGLI_ANDROID_IMAGE_FORMAT_Y8 = 0x20203859, + NGLI_ANDROID_IMAGE_FORMAT_HEIC = 0x48454946, + NGLI_ANDROID_IMAGE_FORMAT_DEPTH_JPEG = 0x69656963 +}; + +struct android_image; + +AHardwareBuffer *ngli_android_image_get_hardware_buffer(struct android_image *s); +void ngli_android_image_freep(struct android_image **sp); + +struct android_imagereader; + +struct android_imagereader *ngli_android_imagereader_create(struct android_ctx *api, int width, int height, int format, int max_images); +int ngli_android_imagereader_get_window(struct android_imagereader *s, void **window); +int ngli_android_imagereader_acquire_next_image(struct android_imagereader *s, struct android_image **imagep); +void ngli_android_imagereader_freep(struct android_imagereader **sp); + +#endif /* ANDROID_IMAGEREADER_H */ diff --git a/libnodegl/hwupload_mediacodec_gl.c b/libnodegl/hwupload_mediacodec_gl.c index f35d405fd7..d8c6e1904e 100644 --- a/libnodegl/hwupload_mediacodec_gl.c +++ b/libnodegl/hwupload_mediacodec_gl.c @@ -23,10 +23,12 @@ #include #include #include - #include +#include +#include "android_imagereader.h" #include "android_surface.h" +#include "egl.h" #include "format.h" #include "gctx_gl.h" #include "glincludes.h" @@ -38,6 +40,11 @@ #include "nodes.h" #include "texture_gl.h" +struct hwupload_mc { + struct android_image *android_image; + EGLImageKHR egl_image; +}; + static int support_direct_rendering(struct ngl_node *node) { struct texture_priv *s = node->priv_data; @@ -94,7 +101,7 @@ static int mc_init(struct ngl_node *node, struct sxplayer_frame *frame) return 0; } -static int mc_map_frame(struct ngl_node *node, struct sxplayer_frame *frame) +static int mc_map_frame_surfacetexture(struct ngl_node *node, struct sxplayer_frame *frame) { struct texture_priv *s = node->priv_data; struct hwupload *hwupload = &s->hwupload; @@ -117,8 +124,95 @@ static int mc_map_frame(struct ngl_node *node, struct sxplayer_frame *frame) return 0; } +static int mc_map_frame_imagereader(struct ngl_node *node, struct sxplayer_frame *frame) +{ + struct texture_priv *s = node->priv_data; + struct hwupload *hwupload = &s->hwupload; + struct hwupload_mc *mc = hwupload->hwmap_priv_data; + + struct ngl_ctx *ctx = node->ctx; + struct gctx_gl *gctx_gl = (struct gctx_gl *)ctx->gctx; + struct glcontext *gl = gctx_gl->glcontext; + struct media_priv *media = s->data_src->priv_data; + + AVMediaCodecBuffer *buffer = (AVMediaCodecBuffer *)frame->data; + int ret = av_mediacodec_release_buffer(buffer, 1); + if (ret < 0) + return ret; + + struct android_image *android_image; + ret = ngli_android_imagereader_acquire_next_image(media->android_imagereader, &android_image); + if (ret < 0) + return ret; + + ngli_eglDestroyImageKHR(gl, mc->egl_image); + mc->egl_image = NULL; + ngli_android_image_freep(&mc->android_image); + + mc->android_image = android_image; + + AHardwareBuffer *hardware_buffer = ngli_android_image_get_hardware_buffer(mc->android_image); + if (!hardware_buffer) + return NGL_ERROR_EXTERNAL; + + EGLClientBuffer egl_buffer = ngli_eglGetNativeClientBufferANDROID(gl, hardware_buffer); + if (!egl_buffer) + return NGL_ERROR_EXTERNAL; + + static const EGLint attrs[] = { + EGL_IMAGE_PRESERVED_KHR, + EGL_TRUE, + EGL_NONE, + }; + + const struct texture_gl *texture_gl = (struct texture_gl *)media->android_texture; + const GLuint id = texture_gl->id; + + mc->egl_image = ngli_eglCreateImageKHR(gl, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, egl_buffer, attrs); + if (!mc->egl_image) { + LOG(ERROR, "failed to create egl image"); + return NGL_ERROR_EXTERNAL; + } + + ngli_glBindTexture(gl, GL_TEXTURE_EXTERNAL_OES, id); + ngli_glEGLImageTargetTexture2DOES(gl, GL_TEXTURE_EXTERNAL_OES, mc->egl_image); + + ngli_texture_gl_set_dimensions(media->android_texture, frame->width, frame->height, 0); + + return 0; +} + +static int mc_map_frame(struct ngl_node *node, struct sxplayer_frame *frame) +{ + struct ngl_ctx *ctx = node->ctx; + struct android_ctx *android_ctx = &ctx->android_ctx; + + if (android_ctx->has_native_imagereader_api) + return mc_map_frame_imagereader(node, frame); + + return mc_map_frame_surfacetexture(node, frame); +} + +static void mc_uninit(struct ngl_node *node) +{ + struct ngl_ctx *ctx = node->ctx; + struct android_ctx *android_ctx = &ctx->android_ctx; + struct gctx_gl *gctx_gl = (struct gctx_gl *)ctx->gctx; + struct glcontext *gl = gctx_gl->glcontext; + struct texture_priv *s = node->priv_data; + struct hwupload *hwupload = &s->hwupload; + struct hwupload_mc *mc = hwupload->hwmap_priv_data; + + if (android_ctx->has_native_imagereader_api) { + ngli_eglDestroyImageKHR(gl, mc->egl_image); + ngli_android_image_freep(&mc->android_image); + } +} + const struct hwmap_class ngli_hwmap_mc_gl_class = { .name = "mediacodec (oes zero-copy)", + .priv_size = sizeof(struct hwupload_mc), .init = mc_init, .map_frame = mc_map_frame, + .uninit = mc_uninit, }; diff --git a/libnodegl/node_media.c b/libnodegl/node_media.c index 351f9e8b1f..90fffa2241 100644 --- a/libnodegl/node_media.c +++ b/libnodegl/node_media.c @@ -26,6 +26,7 @@ #if defined(TARGET_ANDROID) #include +#include "android_imagereader.h" #endif #include "log.h" @@ -148,6 +149,7 @@ static int media_init(struct ngl_node *node) #if defined(TARGET_ANDROID) struct ngl_ctx *ctx = node->ctx; + struct android_ctx *android_ctx = &ctx->android_ctx; const struct ngl_config *config = &ctx->config; struct gctx *gctx = ctx->gctx; @@ -172,22 +174,34 @@ static int media_init(struct ngl_node *node) if (ret < 0) return ret; - s->android_handlerthread = ngli_android_handlerthread_new(); - if (!s->android_handlerthread) - return NGL_ERROR_MEMORY; - - void *handler = ngli_android_handlerthread_get_native_handler(s->android_handlerthread); - if (!handler) - return NGL_ERROR_EXTERNAL; - - struct texture_gl *texture_gl = (struct texture_gl *)s->android_texture; - s->android_surface = ngli_android_surface_new(texture_gl->id, handler); - if (!s->android_surface) - return NGL_ERROR_MEMORY; - - void *android_surface = ngli_android_surface_get_surface(s->android_surface); - if (!android_surface) - return NGL_ERROR_EXTERNAL; + void *android_surface = NULL; + if (android_ctx->has_native_imagereader_api) { + s->android_imagereader = ngli_android_imagereader_create(android_ctx, 1, 1, + NGLI_ANDROID_IMAGE_FORMAT_YUV_420_888, 2); + if (!s->android_imagereader) + return NGL_ERROR_MEMORY; + + ret = ngli_android_imagereader_get_window(s->android_imagereader, &android_surface); + if (ret < 0) + return ret; + } else { + s->android_handlerthread = ngli_android_handlerthread_new(); + if (!s->android_handlerthread) + return NGL_ERROR_MEMORY; + + void *handler = ngli_android_handlerthread_get_native_handler(s->android_handlerthread); + if (!handler) + return NGL_ERROR_EXTERNAL; + + struct texture_gl *texture_gl = (struct texture_gl *)s->android_texture; + s->android_surface = ngli_android_surface_new(texture_gl->id, handler); + if (!s->android_surface) + return NGL_ERROR_MEMORY; + + void *android_surface = ngli_android_surface_get_surface(s->android_surface); + if (!android_surface) + return NGL_ERROR_EXTERNAL; + } sxplayer_set_option(s->player, "opaque", &android_surface); } @@ -280,8 +294,14 @@ static void media_uninit(struct ngl_node *node) sxplayer_free(&s->player); #if defined(TARGET_ANDROID) - ngli_android_surface_free(&s->android_surface); - ngli_android_handlerthread_free(&s->android_handlerthread); + struct ngl_ctx *ctx = node->ctx; + struct android_ctx *android_ctx = &ctx->android_ctx; + if (android_ctx->has_native_imagereader_api) { + ngli_android_imagereader_freep(&s->android_imagereader); + } else { + ngli_android_surface_free(&s->android_surface); + ngli_android_handlerthread_free(&s->android_handlerthread); + } ngli_texture_freep(&s->android_texture); #endif } diff --git a/libnodegl/nodes.h b/libnodegl/nodes.h index 563b1bd093..0e9f8d42a2 100644 --- a/libnodegl/nodes.h +++ b/libnodegl/nodes.h @@ -42,6 +42,7 @@ #include "android_ctx.h" #include "android_handlerthread.h" #include "android_surface.h" +#include "android_imagereader.h" #endif #if defined(TARGET_IPHONE) @@ -315,6 +316,7 @@ struct media_priv { struct texture *android_texture; struct android_surface *android_surface; struct android_handlerthread *android_handlerthread; + struct android_imagereader *android_imagereader; #endif }; From 4b185af0637f5d709562b03113afc11ce7710a24 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 9 Sep 2020 09:59:10 +0200 Subject: [PATCH 068/388] text: fix background vertices and indices leak --- libnodegl/node_text.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libnodegl/node_text.c b/libnodegl/node_text.c index 4d117bad1b..b9a4f275b4 100644 --- a/libnodegl/node_text.c +++ b/libnodegl/node_text.c @@ -696,6 +696,8 @@ static void text_uninit(struct ngl_node *node) ngli_pgcraft_freep(&desc->fg.crafter); } ngli_darray_reset(&s->pipeline_descs); + ngli_buffer_freep(&s->bg_vertices); + ngli_buffer_freep(&s->bg_indices); ngli_buffer_freep(&s->vertices); ngli_buffer_freep(&s->uvcoords); ngli_buffer_freep(&s->indices); From 82974cb9da79abd7fa2fbedd06af121b110e3fa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 28 Aug 2020 09:20:50 +0200 Subject: [PATCH 069/388] utils/examples: prevent initial_seek from being larger than the media duration --- pynodegl-utils/pynodegl_utils/examples/medias.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pynodegl-utils/pynodegl_utils/examples/medias.py b/pynodegl-utils/pynodegl_utils/examples/medias.py index 5b313ebe18..4a22a00afc 100644 --- a/pynodegl-utils/pynodegl_utils/examples/medias.py +++ b/pynodegl-utils/pynodegl_utils/examples/medias.py @@ -34,7 +34,7 @@ def playback_speed(cfg, speed=1.0): '''Adjust media playback speed using animation keyframes''' m0 = cfg.medias[0] media_duration = m0.duration - initial_seek = 5 + initial_seek = min(media_duration, 5) rush_duration = media_duration - initial_seek cfg.duration = rush_duration / speed cfg.aspect_ratio = (m0.width, m0.height) From a08f88d0c8770881e20a8245da9856555927a896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 28 Aug 2020 09:18:36 +0200 Subject: [PATCH 070/388] tools/player: move set scene code in dedicated function --- ngl-tools/player.c | 32 +++++++++++++++++++------------- ngl-tools/player.h | 1 + 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/ngl-tools/player.c b/ngl-tools/player.c index 4d2a937e6e..ba25c18974 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -346,6 +346,23 @@ static struct ngl_node *add_progress_bar(struct ngl_node *scene) return gcfg; } +static int set_scene(struct ngl_node *scene) +{ + int ret; + struct player *p = g_player; + + if (p->enable_ui) { + scene = add_progress_bar(scene); + if (!scene) + return NGL_ERROR_MEMORY; + ret = ngl_set_scene(p->ngl, scene); + ngl_node_unrefp(&scene); + } else { + ret = ngl_set_scene(p->ngl, scene); + } + return ret; +} + int player_init(struct player *p, const char *win_title, struct ngl_node *scene, const struct ngl_config *cfg, double duration, int enable_ui) { @@ -366,6 +383,7 @@ int player_init(struct player *p, const char *win_title, struct ngl_node *scene, p->lasthover = -1; p->duration_f = duration; p->duration = duration * 1000000; + p->enable_ui = enable_ui; p->ngl_config = *cfg; @@ -393,19 +411,7 @@ int player_init(struct player *p, const char *win_title, struct ngl_node *scene, if (ret < 0) return ret; - if (enable_ui) { - scene = add_progress_bar(scene); - if (!scene) - return NGL_ERROR_MEMORY; - ret = ngl_set_scene(p->ngl, scene); - ngl_node_unrefp(&scene); - } else { - ret = ngl_set_scene(p->ngl, scene); - } - if (ret < 0) - return ret; - - return 0; + return set_scene(scene); } void player_uninit(void) diff --git a/ngl-tools/player.h b/ngl-tools/player.h index 2d65987378..cc6b2d238d 100644 --- a/ngl-tools/player.h +++ b/ngl-tools/player.h @@ -33,6 +33,7 @@ struct player { double duration_f; int64_t duration; + int enable_ui; int aspect[2]; struct ngl_ctx *ngl; From ed4f0a813b81b6a6e64cec183356d402c2c99788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 28 Aug 2020 09:19:19 +0200 Subject: [PATCH 071/388] tools/player: reset node references on set scene error --- ngl-tools/player.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ngl-tools/player.c b/ngl-tools/player.c index ba25c18974..abb8e54278 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -360,6 +360,11 @@ static int set_scene(struct ngl_node *scene) } else { ret = ngl_set_scene(p->ngl, scene); } + if (ret < 0) { + p->pgbar_opacity_node = NULL; + p->pgbar_duration_node = NULL; + p->pgbar_text_node = NULL; + } return ret; } From fcb22b8d701aacb6479cb23ffaa94db5fda1565c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 28 Aug 2020 09:17:13 +0200 Subject: [PATCH 072/388] tools/player: add scene kill switch --- doc/ref/ngl-tools.md | 1 + ngl-tools/player.c | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/doc/ref/ngl-tools.md b/doc/ref/ngl-tools.md index a9f98be658..55cb418f8f 100644 --- a/doc/ref/ngl-tools.md +++ b/doc/ref/ngl-tools.md @@ -90,3 +90,4 @@ Key | Action `ESC` or `q` | quit the application `SPACE` | toggle the pause/playback `f` | toggle windowed/fullscreen +`k` | kill the current scene diff --git a/ngl-tools/player.c b/ngl-tools/player.c index abb8e54278..53a06a0b06 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -124,6 +124,16 @@ static int screenshot(void) return ret; } +static void kill_scene() +{ + struct player *p = g_player; + + ngl_set_scene(p->ngl, NULL); + p->pgbar_opacity_node = NULL; + p->pgbar_duration_node = NULL; + p->pgbar_text_node = NULL; +} + static int key_callback(SDL_Window *window, SDL_KeyboardEvent *event) { struct player *p = g_player; @@ -144,6 +154,9 @@ static int key_callback(SDL_Window *window, SDL_KeyboardEvent *event) case SDLK_s: screenshot(); break; + case SDLK_k: + kill_scene(); + break; default: break; } From 1f10b832e434fa59304c18aa8512cbb025d6fb63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 28 Aug 2020 09:48:39 +0200 Subject: [PATCH 073/388] tools/player: add seeking with key bindings --- doc/ref/ngl-tools.md | 2 ++ ngl-tools/common.c | 7 +++++++ ngl-tools/common.h | 1 + ngl-tools/player.c | 8 ++++++++ 4 files changed, 18 insertions(+) diff --git a/doc/ref/ngl-tools.md b/doc/ref/ngl-tools.md index 55cb418f8f..72bff0ecea 100644 --- a/doc/ref/ngl-tools.md +++ b/doc/ref/ngl-tools.md @@ -89,5 +89,7 @@ Key | Action ------------- | ------ `ESC` or `q` | quit the application `SPACE` | toggle the pause/playback +`LEFT` | seek by -10 seconds +`RIGHT` | seek by +10 seconds `f` | toggle windowed/fullscreen `k` | kill the current scene diff --git a/ngl-tools/common.c b/ngl-tools/common.c index 7d4a828097..7d324657e7 100644 --- a/ngl-tools/common.c +++ b/ngl-tools/common.c @@ -46,6 +46,13 @@ int clipi(int v, int min, int max) return v; } +int64_t clipi64(int64_t v, int64_t min, int64_t max) +{ + if (v < min) return min; + if (v > max) return max; + return v; +} + void get_viewport(int width, int height, const int *aspect_ratio, int *vp) { vp[2] = width; diff --git a/ngl-tools/common.h b/ngl-tools/common.h index 25d2fbe3b6..618e8c5b1b 100644 --- a/ngl-tools/common.h +++ b/ngl-tools/common.h @@ -32,6 +32,7 @@ int64_t gettime(void); double clipd(double v, double min, double max); int clipi(int v, int min, int max); +int64_t clipi64(int64_t v, int64_t min, int64_t max); void get_viewport(int width, int height, const int *aspect_ratio, int *vp); #endif diff --git a/ngl-tools/player.c b/ngl-tools/player.c index 53a06a0b06..a980e09dfa 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -134,6 +134,8 @@ static void kill_scene() p->pgbar_text_node = NULL; } +static void update_time(int64_t seek_at); + static int key_callback(SDL_Window *window, SDL_KeyboardEvent *event) { struct player *p = g_player; @@ -157,6 +159,12 @@ static int key_callback(SDL_Window *window, SDL_KeyboardEvent *event) case SDLK_k: kill_scene(); break; + case SDLK_LEFT: + update_time(clipi64(p->frame_ts - 10 * 1000000, 0, p->duration)); + break; + case SDLK_RIGHT: + update_time(clipi64(p->frame_ts + 10 * 1000000, 0, p->duration)); + break; default: break; } From 1e6799f2b086da6fe2dffc9ed3fad14e9744495d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 28 Aug 2020 11:09:04 +0200 Subject: [PATCH 074/388] tools/player: remove update_time() forward declaration --- ngl-tools/player.c | 94 +++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/ngl-tools/player.c b/ngl-tools/player.c index a980e09dfa..c9d6ead3f8 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -134,54 +134,6 @@ static void kill_scene() p->pgbar_text_node = NULL; } -static void update_time(int64_t seek_at); - -static int key_callback(SDL_Window *window, SDL_KeyboardEvent *event) -{ - struct player *p = g_player; - - const SDL_Keycode key = event->keysym.sym; - switch (key) { - case SDLK_ESCAPE: - case SDLK_q: - return 1; - case SDLK_SPACE: - p->paused ^= 1; - p->clock_off = gettime() - p->frame_ts; - break; - case SDLK_f: - p->fullscreen ^= 1; - SDL_SetWindowFullscreen(window, p->fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); - break; - case SDLK_s: - screenshot(); - break; - case SDLK_k: - kill_scene(); - break; - case SDLK_LEFT: - update_time(clipi64(p->frame_ts - 10 * 1000000, 0, p->duration)); - break; - case SDLK_RIGHT: - update_time(clipi64(p->frame_ts + 10 * 1000000, 0, p->duration)); - break; - default: - break; - } - - return 0; -} - -static void size_callback(SDL_Window *window, int width, int height) -{ - struct player *p = g_player; - - get_viewport(width, height, p->aspect, p->ngl_config.viewport); - p->ngl_config.width = width; - p->ngl_config.height = height; - ngl_resize(p->ngl, width, height, p->ngl_config.viewport); -} - static void update_text(void) { struct player *p = g_player; @@ -237,6 +189,52 @@ static void update_time(int64_t seek_at) } } +static int key_callback(SDL_Window *window, SDL_KeyboardEvent *event) +{ + struct player *p = g_player; + + const SDL_Keycode key = event->keysym.sym; + switch (key) { + case SDLK_ESCAPE: + case SDLK_q: + return 1; + case SDLK_SPACE: + p->paused ^= 1; + p->clock_off = gettime() - p->frame_ts; + break; + case SDLK_f: + p->fullscreen ^= 1; + SDL_SetWindowFullscreen(window, p->fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); + break; + case SDLK_s: + screenshot(); + break; + case SDLK_k: + kill_scene(); + break; + case SDLK_LEFT: + update_time(clipi64(p->frame_ts - 10 * 1000000, 0, p->duration)); + break; + case SDLK_RIGHT: + update_time(clipi64(p->frame_ts + 10 * 1000000, 0, p->duration)); + break; + default: + break; + } + + return 0; +} + +static void size_callback(SDL_Window *window, int width, int height) +{ + struct player *p = g_player; + + get_viewport(width, height, p->aspect, p->ngl_config.viewport); + p->ngl_config.width = width; + p->ngl_config.height = height; + ngl_resize(p->ngl, width, height, p->ngl_config.viewport); +} + static void seek_event(int x) { struct player *p = g_player; From ed94ac15e275dec663b3c010f82df7624924ee51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 28 Aug 2020 09:35:39 +0200 Subject: [PATCH 075/388] doc/tools: document screenshot binding --- doc/ref/ngl-tools.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/ref/ngl-tools.md b/doc/ref/ngl-tools.md index 72bff0ecea..ad066dccd0 100644 --- a/doc/ref/ngl-tools.md +++ b/doc/ref/ngl-tools.md @@ -92,4 +92,5 @@ Key | Action `LEFT` | seek by -10 seconds `RIGHT` | seek by +10 seconds `f` | toggle windowed/fullscreen +`s` | take a screenshot `k` | kill the current scene From 6df0cbce94b1fbdf00393c0294cd9474d47a2d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 30 Jul 2020 15:36:41 +0200 Subject: [PATCH 076/388] text: make aspect ratio live changeable --- libnodegl/doc/libnodegl.md | 2 +- libnodegl/node_text.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/libnodegl/doc/libnodegl.md b/libnodegl/doc/libnodegl.md index 54f456e818..7124144f0f 100644 --- a/libnodegl/doc/libnodegl.md +++ b/libnodegl/doc/libnodegl.md @@ -578,7 +578,7 @@ Parameter | Live-chg. | Type | Description | Default `font_scale` | | [`double`](#parameter-types) | scaling of the font | `1` `valign` | | [`valign`](#valign-choices) | vertical alignment of the text in the box | `center` `halign` | | [`halign`](#halign-choices) | horizontal alignment of the text in the box | `center` -`aspect_ratio` | | [`rational`](#parameter-types) | box aspect ratio | +`aspect_ratio` | ✓ | [`rational`](#parameter-types) | box aspect ratio | **Source**: [node_text.c](/libnodegl/node_text.c) diff --git a/libnodegl/node_text.c b/libnodegl/node_text.c index b9a4f275b4..8157a52533 100644 --- a/libnodegl/node_text.c +++ b/libnodegl/node_text.c @@ -137,6 +137,8 @@ static const struct node_param text_params[] = { .choices=&halign_choices, .desc=NGLI_DOCSTRING("horizontal alignment of the text in the box")}, {"aspect_ratio", PARAM_TYPE_RATIONAL, OFFSET(aspect_ratio), + .flags=PARAM_FLAG_ALLOW_LIVE_CHANGE, + .update_func=set_live_changed, .desc=NGLI_DOCSTRING("box aspect ratio")}, {NULL} }; From 525387708db1688651bf43a2a7a05d1bff53f2f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 30 Jul 2020 15:54:37 +0200 Subject: [PATCH 077/388] tools/render: move text file reading to common code --- ngl-tools/common.c | 45 ++++++++++++++++++++++++++++++++++++++++++ ngl-tools/common.h | 1 + ngl-tools/ngl-render.c | 37 ++++------------------------------ 3 files changed, 50 insertions(+), 33 deletions(-) diff --git a/ngl-tools/common.c b/ngl-tools/common.c index 7d324657e7..bc9dad4e87 100644 --- a/ngl-tools/common.c +++ b/ngl-tools/common.c @@ -19,8 +19,12 @@ * under the License. */ +#include #include #include +#include +#include +#include #include "common.h" @@ -64,3 +68,44 @@ void get_viewport(int width, int height, const int *aspect_ratio, int *vp) vp[0] = (width - vp[2]) / 2.0; vp[1] = (height - vp[3]) / 2.0; } + +#define BUF_SIZE 1024 + +char *get_text_file_content(const char *filename) +{ + char *buf = NULL; + + int fd = filename ? open(filename, O_RDONLY) : STDIN_FILENO; + if (fd == -1) { + fprintf(stderr, "unable to open %s\n", filename); + goto end; + } + + ssize_t pos = 0; + for (;;) { + const ssize_t needed = pos + BUF_SIZE + 1; + void *new_buf = realloc(buf, needed); + if (!new_buf) { + free(buf); + buf = NULL; + goto end; + } + buf = new_buf; + const ssize_t n = read(fd, buf + pos, BUF_SIZE); + if (n < 0) { + free(buf); + buf = NULL; + goto end; + } + if (n == 0) { + buf[pos] = 0; + break; + } + pos += n; + } + +end: + if (fd != -1 && fd != STDIN_FILENO) + close(fd); + return buf; +} diff --git a/ngl-tools/common.h b/ngl-tools/common.h index 618e8c5b1b..b951e44056 100644 --- a/ngl-tools/common.h +++ b/ngl-tools/common.h @@ -34,5 +34,6 @@ double clipd(double v, double min, double max); int clipi(int v, int min, int max); int64_t clipi64(int64_t v, int64_t min, int64_t max); void get_viewport(int width, int height, const int *aspect_ratio, int *vp); +char *get_text_file_content(const char *filename); #endif diff --git a/ngl-tools/ngl-render.c b/ngl-tools/ngl-render.c index 5c33ef23a2..e1d05cdcc9 100644 --- a/ngl-tools/ngl-render.c +++ b/ngl-tools/ngl-render.c @@ -33,45 +33,16 @@ #include "opts.h" #include "wsi.h" -#define BUF_SIZE 1024 - #ifndef O_BINARY #define O_BINARY 0 #endif static struct ngl_node *get_scene(const char *filename) { - struct ngl_node *scene = NULL; - char *buf = NULL; - - int fd = filename ? open(filename, O_RDONLY) : STDIN_FILENO; - if (fd == -1) { - fprintf(stderr, "unable to open %s\n", filename); - goto end; - } - - ssize_t pos = 0; - for (;;) { - const ssize_t needed = pos + BUF_SIZE + 1; - void *new_buf = realloc(buf, needed); - if (!new_buf) - goto end; - buf = new_buf; - const ssize_t n = read(fd, buf + pos, BUF_SIZE); - if (n < 0) - goto end; - if (n == 0) { - buf[pos] = 0; - break; - } - pos += n; - } - - scene = ngl_node_deserialize(buf); - -end: - if (fd != -1 && fd != STDIN_FILENO) - close(fd); + char *buf = get_text_file_content(filename); + if (!buf) + return NULL; + struct ngl_node *scene = ngl_node_deserialize(buf); free(buf); return scene; } From dd6af0e150357252fff9ed44fc14b66b232c4daf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 30 Jul 2020 16:00:13 +0200 Subject: [PATCH 078/388] tools: add ngl-desktop and ngl-ipc --- doc/ref/ngl-tools.md | 21 ++ ngl-tools/.gitignore | 2 + ngl-tools/Makefile | 10 +- ngl-tools/ipc.c | 238 ++++++++++++++++ ngl-tools/ipc.h | 74 +++++ ngl-tools/ngl-desktop.c | 614 ++++++++++++++++++++++++++++++++++++++++ ngl-tools/ngl-ipc.c | 347 +++++++++++++++++++++++ ngl-tools/player.c | 84 ++++++ ngl-tools/player.h | 15 + 9 files changed, 1404 insertions(+), 1 deletion(-) create mode 100644 ngl-tools/ipc.c create mode 100644 ngl-tools/ipc.h create mode 100644 ngl-tools/ngl-desktop.c create mode 100644 ngl-tools/ngl-ipc.c diff --git a/doc/ref/ngl-tools.md b/doc/ref/ngl-tools.md index ad066dccd0..20a022fa0f 100644 --- a/doc/ref/ngl-tools.md +++ b/doc/ref/ngl-tools.md @@ -80,6 +80,27 @@ at build time. **Source**: [ngl-tools/ngl-serialize.c](/ngl-tools/ngl-serialize.c) +## ngl-desktop + +`ngl-desktop` is a player waiting for user commands from a socket connection. +It is meant to be used with `ngl-ipc` for communicating commands. + +By default, `ngl-desktop` listen for connections on `localhost` on port `1234`. + +The detail of available options can be obtained with `ngl-desktop -h`. + +**Example**: `ngl-desktop -x 0.0.0.0 -p 2000 --backend opengles -c 223344FF` + + +## ngl-ipc + +`ngl-ipc` is the tool used to make queries to `ngl-desktop` instances. + +The detail of available options can be obtained with `ngl-ipc -h`. + +**Example**: `ngl-serialize pynodegl_utils.examples.misc fibo - | ngl-ipc -p 2000 -f - -t 5` + + ## Player keyboard controls `ngl-player` and `ngl-python` are both scene players supporting the following diff --git a/ngl-tools/.gitignore b/ngl-tools/.gitignore index 0b6fdf724e..80660c0ad4 100644 --- a/ngl-tools/.gitignore +++ b/ngl-tools/.gitignore @@ -1,3 +1,5 @@ +/ngl-desktop +/ngl-ipc /ngl-player /ngl-python /ngl-render diff --git a/ngl-tools/Makefile b/ngl-tools/Makefile index e5d380be3e..dd6bec62c3 100644 --- a/ngl-tools/Makefile +++ b/ngl-tools/Makefile @@ -38,7 +38,7 @@ endif # "pkg-config --exists python" never will, so an explicit version is needed. HAS_PYTHON := $(if $(shell pkg-config --exists python$(PYTHON_MAJOR) && echo 1),yes,no) -TOOLS = player render +TOOLS = desktop ipc player render ifeq ($(HAS_PYTHON),yes) PYTHON_CFLAGS := $(shell python$(PYTHON_MAJOR)-config --cflags) # @@ -78,6 +78,14 @@ WSI_OBJS_Darwin = wsi_cocoa.o WSI_OBJS_MinGW-w64 = wsi_windows.o WSI_OBJS = wsi.o $(WSI_OBJS_$(TARGET_OS)) +ngl-desktop$(EXESUF): CFLAGS = $(PROJECT_CFLAGS) $(SDL_CFLAGS) $(TOOLS_CFLAGS) +ngl-desktop$(EXESUF): LDLIBS = $(PROJECT_LDLIBS) $(SDL_LDLIBS) $(TOOLS_LDLIBS) -lpthread +ngl-desktop$(EXESUF): ngl-desktop.o ipc.o player.o opts.o $(WSI_OBJS) + +ngl-ipc$(EXESUF): CFLAGS = $(PROJECT_CFLAGS) $(TOOLS_CFLAGS) +ngl-ipc$(EXESUF): LDLIBS = $(PROJECT_LDLIBS) $(TOOLS_LDLIBS) +ngl-ipc$(EXESUF): ngl-ipc.o ipc.o opts.o + ngl-serialize$(EXESUF): CFLAGS = $(PROJECT_CFLAGS) $(TOOLS_CFLAGS) $(PYTHON_CFLAGS) ngl-serialize$(EXESUF): LDLIBS = $(PROJECT_LDLIBS) $(TOOLS_LDLIBS) $(PYTHON_LDLIBS) ngl-serialize$(EXESUF): ngl-serialize.o python_utils.o diff --git a/ngl-tools/ipc.c b/ngl-tools/ipc.c new file mode 100644 index 0000000000..231d68c400 --- /dev/null +++ b/ngl-tools/ipc.c @@ -0,0 +1,238 @@ +/* + * Copyright 2020 GoPro Inc. + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include +#include + +#include + +#include "ipc.h" + +static void u32_write(uint8_t *buf, uint32_t v) +{ + buf[0] = v >> 24; + buf[1] = v >> 16 & 0xff; + buf[2] = v >> 8 & 0xff; + buf[3] = v & 0xff; +} + +static void pkt_update_header(struct ipc_pkt *pkt) +{ + memcpy(pkt->data, "nglp", 4); // 'p' stands for packet + u32_write(pkt->data + 4, pkt->size - 8); +} + +struct ipc_pkt *ipc_pkt_create(void) +{ + struct ipc_pkt *pkt = malloc(sizeof(*pkt)); + if (!pkt) + return NULL; + pkt->size = 8; + pkt->data = malloc(pkt->size); + if (!pkt->data) { + free(pkt); + return NULL; + } + pkt_update_header(pkt); + return pkt; +} + +static int pack(struct ipc_pkt *pkt, uint32_t tag, const void *data, int datalen) +{ + uint8_t *dst = realloc(pkt->data, pkt->size + 8 + datalen); + if (!dst) + return NGL_ERROR_MEMORY; + pkt->data = dst; + u32_write(dst + pkt->size, tag); + u32_write(dst + pkt->size + 4, datalen); + if (data) + memcpy(dst + pkt->size + 8, data, datalen); + pkt->size += 8 + datalen; + pkt_update_header(pkt); + return 0; +} + +int ipc_pkt_add_qtag_scene(struct ipc_pkt *pkt, const char *scene) +{ + return pack(pkt, IPC_SCENE, scene, strlen(scene) + 1); +} + +int ipc_pkt_add_qtag_file(struct ipc_pkt *pkt, const char *filename) +{ + return pack(pkt, IPC_FILE, filename, strlen(filename) + 1); +} + +int ipc_pkt_add_qtag_filepart(struct ipc_pkt *pkt, const uint8_t *chunk, int chunk_size) +{ + return pack(pkt, IPC_FILEPART, chunk, chunk_size); +} + +int ipc_pkt_add_qtag_duration(struct ipc_pkt *pkt, double duration) +{ + return pack(pkt, IPC_DURATION, &duration, sizeof(duration)); +} + +int ipc_pkt_add_qtag_aspect(struct ipc_pkt *pkt, const int *aspect) +{ + int ret = pack(pkt, IPC_ASPECT_RATIO, NULL, 2 * sizeof(*aspect)); + if (ret < 0) + return ret; + uint8_t *dst = pkt->data + pkt->size - 8; + u32_write(dst, aspect[0]); + u32_write(dst + 4, aspect[1]); + return 0; +} + +int ipc_pkt_add_qtag_framerate(struct ipc_pkt *pkt, const int *framerate) +{ + int ret = pack(pkt, IPC_FRAMERATE, NULL, 2 * sizeof(*framerate)); + if (ret < 0) + return ret; + uint8_t *dst = pkt->data + pkt->size - 8; + u32_write(dst, framerate[0]); + u32_write(dst + 4, framerate[1]); + return 0; +} + +int ipc_pkt_add_qtag_clearcolor(struct ipc_pkt *pkt, const float *clearcolor) +{ + return pack(pkt, IPC_CLEARCOLOR, clearcolor, 4 * sizeof(*clearcolor)); +} + +int ipc_pkt_add_qtag_samples(struct ipc_pkt *pkt, int samples) +{ + int ret = pack(pkt, IPC_SAMPLES, NULL, 1); + if (ret < 0) + return ret; + uint8_t *dst = pkt->data + pkt->size - 1; + *dst = samples; + return 0; +} + +int ipc_pkt_add_qtag_info(struct ipc_pkt *pkt) +{ + return pack(pkt, IPC_INFO, NULL, 0); +} + +int ipc_pkt_add_qtag_reconfigure(struct ipc_pkt *pkt) +{ + return pack(pkt, IPC_RECONFIGURE, NULL, 0); +} + +int ipc_pkt_add_rtag_info(struct ipc_pkt *pkt, const char *info) +{ + return pack(pkt, IPC_INFO, info, strlen(info) + 1); +} + +int ipc_pkt_add_rtag_filepart(struct ipc_pkt *pkt, int written) +{ + int ret = pack(pkt, IPC_FILEPART, NULL, 4); + if (ret < 0) + return ret; + uint8_t *dst = pkt->data + pkt->size - 4; + u32_write(dst, written); + return 0; +} + +int ipc_pkt_add_rtag_fileend(struct ipc_pkt *pkt, const char *dest_filename) +{ + return pack(pkt, IPC_FILEEND, dest_filename, strlen(dest_filename) + 1); +} + +void ipc_pkt_freep(struct ipc_pkt **pktp) +{ + struct ipc_pkt *pkt = *pktp; + if (!pkt) + return; + free(pkt->data); + free(pkt); + *pktp = NULL; +} + +int ipc_send(int fd, const struct ipc_pkt *pkt) +{ + const ssize_t n = write(fd, pkt->data, pkt->size); + if (n < 0) { + perror("write"); + return NGL_ERROR_IO; + } + // XXX: should we loop instead? + if (n != pkt->size) { + fprintf(stderr, "unable write packet (%zd/%d sent)\n", n, pkt->size); + return NGL_ERROR_IO; + } + return 0; +} + +static int readbuf(int fd, uint8_t *buf, ssize_t size) +{ + ssize_t nr = 0; + while (nr != size) { + const ssize_t n = read(fd, buf + nr, size - nr); + if (n == 0) + return 0; + if (n < 0) { + perror("read"); + return NGL_ERROR_IO; + } + nr += n; + } + return 1; +} + +void ipc_pkt_reset(struct ipc_pkt *pkt) +{ + pkt->size = 8; + pkt_update_header(pkt); +} + +int ipc_recv(int fd, struct ipc_pkt *pkt) +{ + int ret = readbuf(fd, pkt->data, 8); + if (ret <= 0) + return ret; + + pkt->size = 8; + + if (memcmp(pkt->data, "nglp", 4)) + return NGL_ERROR_INVALID_DATA; + + const int size = IPC_U32_READ(pkt->data + 4); + if (size == 0) // valid but empty packet + return 0; + + if (size < 0) + return NGL_ERROR_INVALID_DATA; + + uint8_t *dst = realloc(pkt->data, pkt->size + 8 + size); + if (!dst) + return NGL_ERROR_MEMORY; + pkt->data = dst; + + ret = readbuf(fd, pkt->data + 8, size); + if (ret <= 0) + return ret; + + pkt->size += size; + return ret; +} diff --git a/ngl-tools/ipc.h b/ngl-tools/ipc.h new file mode 100644 index 0000000000..a28fc8540e --- /dev/null +++ b/ngl-tools/ipc.h @@ -0,0 +1,74 @@ +/* + * Copyright 2020 GoPro Inc. + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef IPC_H +#define IPC_H + +#include + +#define IPC_U32(a,b,c,d) (((uint32_t)(a))<<24 | (b)<<16 | (c)<<8 | (d)) +#define IPC_U32_READ(buf) IPC_U32((buf)[0], (buf)[1], (buf)[2], (buf)[3]) +#define IPC_U32_FMT(tag) (tag)>>24, (tag)>>16&0xff, (tag)>>8&0xff, (tag)&0xff + +enum ipc_tag { + IPC_SCENE = IPC_U32('s','c','n','e'), + IPC_FILE = IPC_U32('f','i','l','e'), + IPC_FILEPART = IPC_U32('f','p','r','t'), + IPC_FILEEND = IPC_U32('f','e','n','d'), + IPC_DURATION = IPC_U32('d','u','r','t'), + IPC_ASPECT_RATIO = IPC_U32('r','t','i','o'), + IPC_FRAMERATE = IPC_U32('r','a','t','e'), + IPC_CLEARCOLOR = IPC_U32('c','c','l','r'), + IPC_SAMPLES = IPC_U32('m','s','a','a'), + IPC_INFO = IPC_U32('i','n','f','o'), + IPC_RECONFIGURE = IPC_U32('r','c','f','g'), +}; + +struct ipc_pkt { + uint8_t *data; + int size; +}; + +struct ipc_pkt *ipc_pkt_create(void); +void ipc_pkt_reset(struct ipc_pkt *pkt); +void ipc_pkt_freep(struct ipc_pkt **pktp); + +/* Query tags */ +int ipc_pkt_add_qtag_scene(struct ipc_pkt *pkt, const char *scene); +int ipc_pkt_add_qtag_file(struct ipc_pkt *pkt, const char *filename); +int ipc_pkt_add_qtag_filepart(struct ipc_pkt *pkt, const uint8_t *chunk, int chunk_size); +int ipc_pkt_add_qtag_duration(struct ipc_pkt *pkt, double duration); +int ipc_pkt_add_qtag_aspect(struct ipc_pkt *pkt, const int *aspect); +int ipc_pkt_add_qtag_framerate(struct ipc_pkt *pkt, const int *framerate); +int ipc_pkt_add_qtag_clearcolor(struct ipc_pkt *pkt, const float *clearcolor); +int ipc_pkt_add_qtag_samples(struct ipc_pkt *pkt, int samples); +int ipc_pkt_add_qtag_info(struct ipc_pkt *pkt); +int ipc_pkt_add_qtag_reconfigure(struct ipc_pkt *pkt); + +/* Response tags */ +int ipc_pkt_add_rtag_info(struct ipc_pkt *pkt, const char *info); +int ipc_pkt_add_rtag_filepart(struct ipc_pkt *pkt, int written); +int ipc_pkt_add_rtag_fileend(struct ipc_pkt *pkt, const char *dest_filename); + +int ipc_send(int fd, const struct ipc_pkt *pkt); +int ipc_recv(int fd, struct ipc_pkt *pkt); + +#endif diff --git a/ngl-tools/ngl-desktop.c b/ngl-tools/ngl-desktop.c new file mode 100644 index 0000000000..d206460535 --- /dev/null +++ b/ngl-tools/ngl-desktop.c @@ -0,0 +1,614 @@ +/* + * Copyright 2020 GoPro Inc. + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#define _POSIX_C_SOURCE 200112L // for struct addrinfo with glibc + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common.h" +#include "ipc.h" +#include "opts.h" +#include "player.h" + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +struct ctx { + /* options */ + const char *host; + const char *port; + int log_level; + struct ngl_config cfg; + int aspect[2]; + int player_ui; + + int sock_fd; + struct addrinfo *addr_info; + struct addrinfo *addr; + + char root_dir[1024]; + char session_file[1024]; + char files_dir[1024]; + struct player p; + int thread_started; + pthread_mutex_t lock; + pthread_t thread; + int stop_order; + int own_session_file; + struct ipc_pkt *send_pkt; + struct ipc_pkt *recv_pkt; + int upload_fd; + char upload_path[1024]; +}; + +#define OFFSET(x) offsetof(struct ctx, x) +static const struct opt options[] = { + {"-x", "--host", OPT_TYPE_STR, .offset=OFFSET(host)}, + {"-p", "--port", OPT_TYPE_STR, .offset=OFFSET(port)}, + {"-l", "--loglevel", OPT_TYPE_LOGLEVEL, .offset=OFFSET(log_level)}, + {"-b", "--backend", OPT_TYPE_BACKEND, .offset=OFFSET(cfg.backend)}, + {"-s", "--size", OPT_TYPE_RATIONAL, .offset=OFFSET(cfg.width)}, + {"-a", "--aspect", OPT_TYPE_RATIONAL, .offset=OFFSET(aspect)}, + {"-z", "--swap_interval", OPT_TYPE_INT, .offset=OFFSET(cfg.swap_interval)}, + {"-c", "--clear_color", OPT_TYPE_COLOR, .offset=OFFSET(cfg.clear_color)}, + {"-m", "--samples", OPT_TYPE_INT, .offset=OFFSET(cfg.samples)}, + {"-u", "--disable-ui", OPT_TYPE_TOGGLE, .offset=OFFSET(player_ui)}, +}; + +static int create_session_file(struct ctx *s) +{ + int fd = open(s->session_file, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd < 0) { + if (errno == EEXIST) + fprintf(stderr, "ngl-desktop is already running on %s:%s, " + "delete %s if this is not the case\n", s->host, s->port, s->session_file); + else + perror("open"); + return NGL_ERROR_IO; + } + close(fd); + s->own_session_file = 1; + return 0; +} + +static int remove_session_file(struct ctx *s) +{ + if (!s->own_session_file) + return 0; + int ret = unlink(s->session_file); + if (ret < 0 && errno != EEXIST) { + perror("unlink"); + return NGL_ERROR_IO; + } + return 0; +} + +static int send_player_signal(enum player_signal sig, const void *data, int data_size) +{ + void *p = NULL; + if (data_size) { + p = malloc(data_size); + memcpy(p, data, data_size); + } + + SDL_Event event = { + .user = { + .type = SDL_USEREVENT, + .code = sig, + .data1 = p, + }, + }; + if (SDL_PushEvent(&event) != 1) + free(p); + return 0; +} + +static int handle_tag_scene(const uint8_t *data, int size) +{ + if (size < 1 || data[size - 1] != 0) // check if string is nul-terminated + return NGL_ERROR_INVALID_DATA; + const char *scene = (const char *)data; + return send_player_signal(PLAYER_SIGNAL_SCENE, scene, size); +} + +static int handle_tag_file(struct ctx *s, const uint8_t *data, int size) +{ + if (size < 1 || data[size - 1] != 0) // check if string is nul-terminated + return NGL_ERROR_INVALID_DATA; + + if (s->upload_fd) { + fprintf(stderr, "a file is already uploading"); + return NGL_ERROR_INVALID_USAGE; + } + + /* + * Basic (and probably too strict) check to make sure the file is not going + * to be uploaded outside the files directory. + * + * TODO: we should use chdir()+chroot(), but it requires a switch to a + * process model instead of threads (because the session file should not be + * mixed with the uploaded files). + */ + const char *filename = (const char *)data; + if (strstr(filename, "..") || strchr(filename, '/')) { + fprintf(stderr, "Only a filename is allowed\n"); + return NGL_ERROR_INVALID_ARG; + } + + int ret = snprintf(s->upload_path, sizeof(s->upload_path), "%s%s", s->files_dir, filename); + if (ret < 0 || ret >= sizeof(s->upload_path)) + return NGL_ERROR_MEMORY; + + s->upload_fd = open(s->upload_path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0600); + if (s->upload_fd == -1) { + perror(s->upload_path); + return NGL_ERROR_IO; + } + + return 0; +} + +static void close_upload_file(struct ctx *s) +{ + if (s->upload_fd > 0) + close(s->upload_fd); + s->upload_fd = 0; +} + +static int handle_tag_filepart(struct ctx *s, const uint8_t *data, int size) +{ + if (s->upload_fd <= 0) { + fprintf(stderr, "file is not opened\n"); + return NGL_ERROR_INVALID_USAGE; + } + + if (!size) { + close_upload_file(s); + return ipc_pkt_add_rtag_fileend(s->send_pkt, s->upload_path); + } + + const ssize_t n = write(s->upload_fd, data, size); + if (n < 0) { + perror("write"); + close_upload_file(s); + return NGL_ERROR_IO; + } + // XXX: should we loop instead? + if (n != size) { + fprintf(stderr, "unable to write file part: %zd/%d written\n", n, size); + close_upload_file(s); + return NGL_ERROR_IO; + } + + return ipc_pkt_add_rtag_filepart(s->send_pkt, size); +} + +static int handle_tag_duration(const uint8_t *data, int size) +{ + if (size != 8) + return NGL_ERROR_INVALID_DATA; + return send_player_signal(PLAYER_SIGNAL_DURATION, data, size); +} + +static int handle_tag_clearcolor(const uint8_t *data, int size) +{ + if (size != 16) + return NGL_ERROR_INVALID_DATA; + return send_player_signal(PLAYER_SIGNAL_CLEARCOLOR, data, 16); +} + +static int handle_tag_samples(const uint8_t *data, int size) +{ + if (size != 1) + return NGL_ERROR_INVALID_DATA; + int samples = data[0]; + return send_player_signal(PLAYER_SIGNAL_SAMPLES, &samples, sizeof(samples)); +} + +static int handle_tag_aspect_ratio(const uint8_t *data, int size) +{ + if (size != 8) + return NGL_ERROR_INVALID_DATA; + const int aspect[2] = {IPC_U32_READ(data), IPC_U32_READ(data + 4)}; + return send_player_signal(PLAYER_SIGNAL_ASPECT_RATIO, aspect, sizeof(aspect)); +} + +static int handle_tag_framerate(const uint8_t *data, int size) +{ + if (size != 8) + return NGL_ERROR_INVALID_DATA; + const int rate[2] = {IPC_U32_READ(data), IPC_U32_READ(data + 4)}; + return send_player_signal(PLAYER_SIGNAL_FRAMERATE, rate, sizeof(rate)); +} + +static int handle_tag_reconfigure(const uint8_t *data, int size) +{ + if (size != 0) + return NGL_ERROR_INVALID_DATA; + return 0; +} + +static int handle_tag_info(const uint8_t *data, int size, int fd, struct ctx *s) +{ + if (size != 0) + return NGL_ERROR_INVALID_DATA; + static const char * const backend_map[] = { + [NGL_BACKEND_OPENGL] = "gl", + [NGL_BACKEND_OPENGLES] = "gles", + }; + /* Note: we use the player ngl_config and not the local config because the + * former contains the backend in use after the configure call */ + const struct ngl_config *cfg = &s->p.ngl_config; + if (cfg->backend < 0 || cfg->backend >= ARRAY_NB(backend_map)) + return NGL_ERROR_BUG; + const char *backend_str = backend_map[cfg->backend]; + if (!backend_str) + return NGL_ERROR_BUG; + + struct utsname name; + int ret = uname(&name); + if (ret < 0) + return NGL_ERROR_GENERIC; + + char info[256]; + snprintf(info, sizeof(info), "backend=%s\nsystem=%s\n", backend_str, name.sysname); + return ipc_pkt_add_rtag_info(s->send_pkt, info); +} + +static int handle_commands(struct ctx *s, int fd) +{ + for (;;) { + ipc_pkt_reset(s->send_pkt); + + int ret = ipc_recv(fd, s->recv_pkt); + if (ret <= 0) + return ret; + + int need_reconfigure = 0; + const uint8_t *data = s->recv_pkt->data + 8; + int data_size = s->recv_pkt->size - 8; + while (data_size) { + if (data_size < 8) + return NGL_ERROR_INVALID_DATA; + + const enum ipc_tag tag = IPC_U32_READ(data); + const int size = IPC_U32_READ(data + 4); + + data += 8; + data_size -= 8; + + if (size < 0 || size > data_size) + return NGL_ERROR_INVALID_DATA; + + need_reconfigure |= tag == IPC_CLEARCOLOR || tag == IPC_SAMPLES || tag == IPC_RECONFIGURE; + + int ret; + switch (tag) { + case IPC_SCENE: ret = handle_tag_scene(data, size); break; + case IPC_FILE: ret = handle_tag_file(s, data, size); break; + case IPC_FILEPART: ret = handle_tag_filepart(s, data, size); break; + case IPC_DURATION: ret = handle_tag_duration(data, size); break; + case IPC_ASPECT_RATIO: ret = handle_tag_aspect_ratio(data, size); break; + case IPC_FRAMERATE: ret = handle_tag_framerate(data, size); break; + case IPC_CLEARCOLOR: ret = handle_tag_clearcolor(data, size); break; + case IPC_SAMPLES: ret = handle_tag_samples(data, size); break; + case IPC_RECONFIGURE: ret = handle_tag_reconfigure(data, size); break; + case IPC_INFO: ret = handle_tag_info(data, size, fd, s); break; + default: + fprintf(stderr, "unrecognized query tag %c%c%c%c\n", IPC_U32_FMT(tag)); + return NGL_ERROR_INVALID_DATA; + } + if (ret < 0) { + fprintf(stderr, "failed to handle query tag %c%c%c%c of size %d\n", IPC_U32_FMT(tag), size); + return ret; + } + data += size; + data_size -= size; + } + + if (need_reconfigure) { + ret = send_player_signal(PLAYER_SIGNAL_RECONFIGURE, NULL, 0); + if (ret < 0) + return ret; + } + + ret = ipc_send(fd, s->send_pkt); + if (ret < 0) + return ret; + } +} + +static void close_conn(struct ctx *s, int conn_fd) +{ + close(conn_fd); + fprintf(stderr, "<< client %d disconnected\n", conn_fd); + + /* close uploading file when the connection ends */ + close_upload_file(s); +} + +static void *server_start(void *arg) +{ + struct ctx *s = arg; + + for (;;) { + const int conn_fd = accept(s->sock_fd, s->addr->ai_addr, &s->addr->ai_addrlen); + if (conn_fd < 0) { + perror("accept"); + break; + } + fprintf(stderr, ">> accepted client %d\n", conn_fd); + + pthread_mutex_lock(&s->lock); + int stop = s->stop_order; + pthread_mutex_unlock(&s->lock); + if (stop) { + close_conn(s, conn_fd); + break; + } + + int ret = handle_commands(s, conn_fd); + if (ret < 0) + fprintf(stderr, "client %d: error %d\n", conn_fd, ret); + close_conn(s, conn_fd); + } + + return NULL; +} + +static void stop_server(struct ctx *s) +{ + pthread_mutex_lock(&s->lock); + s->stop_order = 1; + pthread_mutex_unlock(&s->lock); +} + +static struct ngl_node *get_default_scene(const char *host, const char *port) +{ + char subtext_buf[64]; + snprintf(subtext_buf, sizeof(subtext_buf), "Listening on %s:%s", host, port); + static const float fg_color[] = {1.0, 2/3., 0.0, 1.0}; + static const float subtext_h[] = {0.0, 0.5, 0.0}; + + struct ngl_node *group = ngl_node_create(NGL_NODE_GROUP); + struct ngl_node *texts[] = { + ngl_node_create(NGL_NODE_TEXT), + ngl_node_create(NGL_NODE_TEXT), + }; + + if (!group || !texts[0] || !texts[1]) { + ngl_node_unrefp(&group); + goto end; + } + ngl_node_param_set(texts[0], "text", "No scene"); + ngl_node_param_set(texts[0], "fg_color", fg_color); + ngl_node_param_set(texts[1], "text", subtext_buf); + ngl_node_param_set(texts[1], "box_height", subtext_h); + ngl_node_param_add(group, "children", 2, texts); + +end: + ngl_node_unrefp(&texts[0]); + ngl_node_unrefp(&texts[1]); + return group; +} + +static int setup_network(struct ctx *s) +{ + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_flags = AI_PASSIVE, + }; + + int ret = getaddrinfo(s->host, s->port, &hints, &s->addr_info); + if (ret) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret)); + return NGL_ERROR_IO; + } + + struct addrinfo *rp; + for (rp = s->addr_info; rp; rp = rp->ai_next) { + s->sock_fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (s->sock_fd < 0) + continue; + + int yes = 1; + ret = setsockopt(s->sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + if (ret == -1) { + perror("setsockopt"); + break; + } + + ret = bind(s->sock_fd, rp->ai_addr, rp->ai_addrlen); + if (ret != -1) + break; + + close(s->sock_fd); + s->sock_fd = -1; + } + + if (!rp) { + fprintf(stderr, "unable to bind\n"); + return NGL_ERROR_IO; + } + + s->addr = rp; + + if (listen(s->sock_fd, 0) < 0) { + perror("listen"); + return NGL_ERROR_IO; + } + + return 0; +} + +static int makedirs(const char *path, int mode) +{ + char cur_path[1024]; + + int n = snprintf(cur_path, sizeof(cur_path), "%s", path); + if (n != strlen(path)) { + fprintf(stderr, "%s: path too long (%d > %zd)\n", path, n, strlen(path)); + return NGL_ERROR_MEMORY; + } + + const char *p = cur_path; + while (*p) { + char *next = strchr(p, '/'); + if (!next) // we do not create the last element if not ending with a '/' + break; + if (p == next) { // ignore potential first '/' + p = next + 1; + continue; + } + *next = 0; + const int r = mkdir(cur_path, mode); + *next = '/'; + if (r < 0 && errno != EEXIST) { + perror(cur_path); + return NGL_ERROR_GENERIC; + } + p = next + 1; + } + + return 0; +} + +static int setup_paths(struct ctx *s) +{ + int ret = snprintf(s->root_dir, sizeof(s->root_dir), "/tmp/ngl-desktop/%s:%s/", s->host, s->port); + if (ret < 0 || ret >= sizeof(s->root_dir)) + return ret; + ret = snprintf(s->files_dir, sizeof(s->files_dir), "%sfiles/", s->root_dir); + if (ret < 0 || ret >= sizeof(s->session_file)) + return ret; + ret = makedirs(s->files_dir, 0700); + if (ret < 0) + return ret; + ret = snprintf(s->session_file, sizeof(s->session_file), "%ssession", s->root_dir); + if (ret < 0 || ret >= sizeof(s->session_file)) + return ret; + return 0; +} + +int main(int argc, char *argv[]) +{ + struct ctx s = { + .host = "localhost", + .port = "1234", + .cfg.width = DEFAULT_WIDTH, + .cfg.height = DEFAULT_HEIGHT, + .log_level = NGL_LOG_INFO, + .aspect[0] = 1, + .aspect[1] = 1, + .cfg.swap_interval = -1, + .cfg.clear_color[3] = 1.f, + .lock = PTHREAD_MUTEX_INITIALIZER, + .sock_fd = -1, + .player_ui = 1, + }; + + int ret = opts_parse(argc, argc, argv, options, ARRAY_NB(options), &s); + if (ret < 0 || ret == OPT_HELP) { + opts_print_usage(argv[0], options, ARRAY_NB(options), NULL); + return ret == OPT_HELP ? 0 : EXIT_FAILURE; + } + + s.send_pkt = ipc_pkt_create(); + s.recv_pkt = ipc_pkt_create(); + if (!s.send_pkt || !s.recv_pkt) { + ret = EXIT_FAILURE; + goto end; + } + + ngl_log_set_min_level(s.log_level); + get_viewport(s.cfg.width, s.cfg.height, s.aspect, s.cfg.viewport); + + if ((ret = setup_paths(&s)) < 0 || + (ret = setup_network(&s)) < 0 || + (ret = create_session_file(&s)) < 0 || + (ret = pthread_create(&s.thread, NULL, server_start, &s)) < 0) + goto end; + s.thread_started = 1; + + struct ngl_node *scene = get_default_scene(s.host, s.port); + ret = player_init(&s.p, "ngl-desktop", scene, &s.cfg, 0, s.player_ui); + ngl_node_unrefp(&scene); + if (ret < 0) + goto end; + + player_main_loop(); + +end: + remove_session_file(&s); + + if (s.thread_started) + stop_server(&s); + + if (s.sock_fd != -1) { + /* + * On Solaris and MacOS, close() is the only way to terminate the + * blocking accept(). The shutdown() call will fail with errno ENOTCONN + * on these systems. + * + * On Linux though, shutdown() is required to terminate the blocking + * accept(), because close() will not. The reason for this difference + * of behaviour is to prevent a possible race scenario where the same + * FD would be re-used between a close() and an accept(). + * + * See https://bugzilla.kernel.org/show_bug.cgi?id=106241 for more + * information. + */ + if (shutdown(s.sock_fd, SHUT_RDWR) < 0) + perror("shutdown"); + close(s.sock_fd); + } + + if (s.addr_info) + freeaddrinfo(s.addr_info); + + if (s.thread_started) + pthread_join(s.thread, NULL); + + pthread_mutex_destroy(&s.lock); + + ipc_pkt_freep(&s.send_pkt); + ipc_pkt_freep(&s.recv_pkt); + + player_uninit(); + + return ret; +} diff --git a/ngl-tools/ngl-ipc.c b/ngl-tools/ngl-ipc.c new file mode 100644 index 0000000000..fbdfacb4a0 --- /dev/null +++ b/ngl-tools/ngl-ipc.c @@ -0,0 +1,347 @@ +/* + * Copyright 2020 GoPro Inc. + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#define _POSIX_C_SOURCE 200112L // for struct addrinfo with glibc + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common.h" +#include "ipc.h" +#include "opts.h" + +#define UPLOAD_CHUNK_SIZE (1024 * 1024) + +struct ctx { + /* options */ + const char *host; + const char *port; + const char *scene; + int show_info; + const char *uploadfile; + double duration; + int aspect[2]; + int framerate[2]; + float clear_color[4]; + int samples; + int reconfigure; + + struct ipc_pkt *send_pkt; + struct ipc_pkt *recv_pkt; + int upload_fd; + uint8_t *upload_buffer; + struct stat upload_stat; + off_t uploaded_size; // same type as stat.st_size +}; + +#define OFFSET(x) offsetof(struct ctx, x) +static const struct opt options[] = { + {"-x", "--host", OPT_TYPE_STR, .offset=OFFSET(host)}, + {"-p", "--port", OPT_TYPE_STR, .offset=OFFSET(port)}, + {"-f", "--scene", OPT_TYPE_STR, .offset=OFFSET(scene)}, + {"-?", "--info", OPT_TYPE_TOGGLE, .offset=OFFSET(show_info)}, + {"-u", "--uploadfile", OPT_TYPE_STR, .offset=OFFSET(uploadfile)}, + {"-t", "--duration", OPT_TYPE_TIME, .offset=OFFSET(duration)}, + {"-a", "--aspect", OPT_TYPE_RATIONAL, .offset=OFFSET(aspect)}, + {"-r", "--framerate", OPT_TYPE_RATIONAL, .offset=OFFSET(framerate)}, + {"-c", "--clearcolor", OPT_TYPE_COLOR, .offset=OFFSET(clear_color)}, + {"-m", "--samples", OPT_TYPE_INT, .offset=OFFSET(samples)}, + {"-g", "--reconfigure", OPT_TYPE_TOGGLE, .offset=OFFSET(reconfigure)}, +}; + +static int craft_packet(struct ctx *s, struct ipc_pkt *pkt) +{ + if (s->scene) { + char *serial_scene = get_text_file_content(strcmp(s->scene, "-") ? s->scene : NULL); + if (!serial_scene) + return -1; + int ret = ipc_pkt_add_qtag_scene(pkt, serial_scene); + free(serial_scene); + if (ret < 0) + return ret; + } + + if (s->uploadfile) { + char name[512]; + const size_t name_len = strcspn(s->uploadfile, "="); + if (s->uploadfile[name_len] != '=') { + fprintf(stderr, "upload file does not match \"remotename=localname\" format\n"); + return NGL_ERROR_INVALID_ARG; + } + if (name_len >= sizeof(name)) { + fprintf(stderr, "remote file name too long %zd >= %zd\n", name_len, sizeof(name)); + return NGL_ERROR_MEMORY; + } + int n = snprintf(name, sizeof(name), "%.*s", (int)name_len, s->uploadfile); + if (n < 0 || n >= sizeof(name)) + return NGL_ERROR_MEMORY; + const size_t name_size = name_len + 1; + + const char *filename = s->uploadfile + name_size; + s->upload_fd = open(filename, O_RDONLY); + if (s->upload_fd == -1) { + perror("open"); + return NGL_ERROR_IO; + } + + if (fstat(s->upload_fd, &s->upload_stat) < 0) { + perror("stat"); + return NGL_ERROR_IO; + } + + s->upload_buffer = malloc(UPLOAD_CHUNK_SIZE); + if (!s->upload_buffer) + return NGL_ERROR_MEMORY; + + int ret = ipc_pkt_add_qtag_file(pkt, name); + if (ret < 0) + return ret; + } + + if (s->duration >= 0.) { + int ret = ipc_pkt_add_qtag_duration(pkt, s->duration); + if (ret < 0) + return ret; + } + + if (s->aspect[0] > 0) { + int ret = ipc_pkt_add_qtag_aspect(pkt, s->aspect); + if (ret < 0) + return ret; + } + + if (s->framerate[0] > 0) { + int ret = ipc_pkt_add_qtag_framerate(pkt, s->framerate); + if (ret < 0) + return ret; + } + + if (s->clear_color[0] >= 0.) { + int ret = ipc_pkt_add_qtag_clearcolor(pkt, s->clear_color); + if (ret < 0) + return ret; + } + + if (s->samples >= 0) { + int ret = ipc_pkt_add_qtag_samples(pkt, s->samples); + if (ret < 0) + return ret; + } + + if (s->show_info) { + int ret = ipc_pkt_add_qtag_info(pkt); + if (ret < 0) + return ret; + } + + if (s->reconfigure) { + int ret = ipc_pkt_add_qtag_reconfigure(pkt); + if (ret < 0) + return ret; + } + + return 0; +} + +static void close_upload_file(struct ctx *s) +{ + if (s->upload_fd > 0) + close(s->upload_fd); + s->upload_fd = 0; + free(s->upload_buffer); + s->upload_buffer = NULL; +} + +static int handle_info(const uint8_t *data, int size) +{ + if (size < 1 || data[size - 1] != 0) // check if string is nul-terminated + return NGL_ERROR_INVALID_DATA; + printf("%s", data); + fflush(stdout); + return 0; +} + +static int handle_filepart(struct ctx *s, const uint8_t *data, int size) +{ + if (size != 4) + return NGL_ERROR_INVALID_DATA; + const int written = IPC_U32_READ(data); + s->uploaded_size += written; + fprintf(stderr, "\ruploading %s... %d%%", s->uploadfile, (int)(s->uploaded_size * 100LL / s->upload_stat.st_size)); + return 0; +} + +static int handle_fileend(struct ctx *s, const uint8_t *data, int size) +{ + if (size < 1 || data[size - 1] != 0) // check if string is nul-terminated + return NGL_ERROR_INVALID_DATA; + fprintf(stderr, "\ruploading %s... done\n", s->uploadfile); + close_upload_file(s); + const char *filename = (const char *)data; + printf("%s\n", filename); + return 0; +} + +static int handle_response(struct ctx *s, const struct ipc_pkt *pkt) +{ + const uint8_t *data = pkt->data + 8; + int data_size = pkt->size - 8; + while (data_size) { + if (data_size < 8) + return NGL_ERROR_INVALID_DATA; + + const enum ipc_tag tag = IPC_U32_READ(data); + const int size = IPC_U32_READ(data + 4); + + data += 8; + data_size -= 8; + + if (size < 0 || size > data_size) + return NGL_ERROR_INVALID_DATA; + + int ret; + switch (tag) { + case IPC_INFO: ret = handle_info(data, size); break; + case IPC_FILEPART: ret = handle_filepart(s, data, size); break; + case IPC_FILEEND: ret = handle_fileend(s, data, size); break; + default: + fprintf(stderr, "unrecognized response tag %c%c%c%c\n", IPC_U32_FMT(tag)); + return NGL_ERROR_INVALID_DATA; + } + if (ret < 0) { + fprintf(stderr, "failed to handle response tag %c%c%c%c of size %d\n", IPC_U32_FMT(tag), size); + return ret; + } + data += size; + data_size -= size; + } + + if (s->upload_fd > 0) { + ipc_pkt_reset(s->send_pkt); + + const ssize_t n = read(s->upload_fd, s->upload_buffer, UPLOAD_CHUNK_SIZE); + if (n < 0) + return NGL_ERROR_IO; + int ret = ipc_pkt_add_qtag_filepart(s->send_pkt, s->upload_buffer, n); + if (ret < 0) + return ret; + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + struct ctx s = { + .host = "localhost", + .port = "1234", + .duration = -1., + .aspect[0] = -1, + .framerate[0] = -1, + .clear_color[0] = -1., + .samples = -1, + }; + + int ret = opts_parse(argc, argc, argv, options, ARRAY_NB(options), &s); + if (ret < 0 || ret == OPT_HELP) { + opts_print_usage(argv[0], options, ARRAY_NB(options), NULL); + return ret == OPT_HELP ? 0 : EXIT_FAILURE; + } + + struct addrinfo *addr_info = NULL; + int fd = -1; + + s.send_pkt = ipc_pkt_create(); + s.recv_pkt = ipc_pkt_create(); + if (!s.send_pkt || !s.recv_pkt) { + ret = NGL_ERROR_MEMORY; + goto end; + } + + ret = craft_packet(&s, s.send_pkt); + if (ret < 0) + goto end; + + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + }; + + ret = getaddrinfo(s.host, s.port, &hints, &addr_info); + if (ret) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret)); + ret = EXIT_FAILURE; + goto end; + } + + struct addrinfo *rp; + for (rp = addr_info; rp; rp = rp->ai_next) { + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd < 0) + continue; + + ret = connect(fd, rp->ai_addr, rp->ai_addrlen); + if (ret != -1) + break; + + close(fd); + } + + if (!rp) { + fprintf(stderr, "unable to connect to %s\n", s.host); + ret = EXIT_FAILURE; + goto end; + } + + do { + ret = ipc_send(fd, s.send_pkt); + if (ret < 0) + goto end; + + ret = ipc_recv(fd, s.recv_pkt); + if (ret < 0) + goto end; + + ret = handle_response(&s, s.recv_pkt); + if (ret < 0) + goto end; + + } while (s.upload_fd > 0); + +end: + if (addr_info) + freeaddrinfo(addr_info); + close_upload_file(&s); + ipc_pkt_freep(&s.send_pkt); + ipc_pkt_freep(&s.recv_pkt); + if (fd != -1) + close(fd); + return ret; +} diff --git a/ngl-tools/player.c b/ngl-tools/player.c index c9d6ead3f8..8bb66fa5ed 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -444,11 +444,91 @@ void player_uninit(void) if (!p) return; + + SDL_Event event; + while (SDL_PollEvent(&event)) + if (event.type == SDL_USEREVENT) + free(event.user.data1); + ngl_freep(&p->ngl); SDL_DestroyWindow(p->window); SDL_Quit(); } +static int handle_scene(const void *data) +{ + struct ngl_node *scene = ngl_node_deserialize(data); + if (!scene) + return NGL_ERROR_INVALID_DATA; + int ret = set_scene(scene); + ngl_node_unrefp(&scene); + return ret; +} + +static int handle_duration(const void *data) +{ + struct player *p = g_player; + memcpy(&p->duration_f, data, sizeof(p->duration_f)); + p->duration = p->duration_f * 1000000; + if (p->pgbar_duration_node) + ngl_node_param_set(p->pgbar_duration_node, "value", p->duration_f); + update_text(); + return 0; +} + +static int handle_clearcolor(const void *data) +{ + struct player *p = g_player; + memcpy(p->ngl_config.clear_color, data, sizeof(p->ngl_config.clear_color)); + return 0; +} + +static int handle_samples(const void *data) +{ + struct player *p = g_player; + memcpy(&p->ngl_config.samples, data, sizeof(p->ngl_config.samples)); + return 0; +} + +static int handle_aspect_ratio(const void *data) +{ + struct player *p = g_player; + memcpy(p->aspect, data, sizeof(p->aspect)); + if (!p->aspect[0] || !p->aspect[1]) + p->aspect[0] = p->aspect[1] = 1; + int width, height; + SDL_GetWindowSize(p->window, &width, &height); + size_callback(p->window, width, height); + if (p->pgbar_text_node) + ngl_node_param_set(p->pgbar_text_node, "aspect_ratio", p->aspect[0], p->aspect[1]); + return 0; +} + +static int handle_framerate(const void *data) +{ + const int *rate = data; + fprintf(stderr, "WARNING: unhandled framerate %d/%d\n", rate[0], rate[1]); + return 0; +} + +static int handle_reconfigure(const void *data) +{ + struct player *p = g_player; + return ngl_configure(p->ngl, &p->ngl_config); +} + +typedef int (*handle_func)(const void *data); + +static const handle_func handle_map[] = { + [PLAYER_SIGNAL_SCENE] = handle_scene, + [PLAYER_SIGNAL_DURATION] = handle_duration, + [PLAYER_SIGNAL_ASPECT_RATIO] = handle_aspect_ratio, + [PLAYER_SIGNAL_FRAMERATE] = handle_framerate, + [PLAYER_SIGNAL_CLEARCOLOR] = handle_clearcolor, + [PLAYER_SIGNAL_SAMPLES] = handle_samples, + [PLAYER_SIGNAL_RECONFIGURE] = handle_reconfigure, +}; + void player_main_loop(void) { struct player *p = g_player; @@ -478,6 +558,10 @@ void player_main_loop(void) case SDL_MOUSEMOTION: mouse_pos_callback(p->window, &event.motion); break; + case SDL_USEREVENT: + run = handle_map[event.user.code](event.user.data1) == 0; + free(event.user.data1); + break; } } } diff --git a/ngl-tools/player.h b/ngl-tools/player.h index cc6b2d238d..33888bd84f 100644 --- a/ngl-tools/player.h +++ b/ngl-tools/player.h @@ -27,6 +27,21 @@ #include #include +/* + * Warning: clear color and samples will NOT trigger a reconfigure, an explicit + * reconfigure signal is required so to have them honored. The rationale is to + * have the ability to batch multiple operations before doing a reconfigure. + */ +enum player_signal { + PLAYER_SIGNAL_SCENE, + PLAYER_SIGNAL_DURATION, + PLAYER_SIGNAL_ASPECT_RATIO, + PLAYER_SIGNAL_FRAMERATE, + PLAYER_SIGNAL_CLEARCOLOR, + PLAYER_SIGNAL_SAMPLES, + PLAYER_SIGNAL_RECONFIGURE, +}; + struct player { SDL_Window *window; From eb61439e8f7c8c604c97379e10c5d5dacc3d50bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 31 Jul 2020 18:24:33 +0200 Subject: [PATCH 079/388] utils: add hooks for ngl-desktop/ngl-ipc --- .../hooks/desktop/hook.get_session_info | 10 +++++++++ .../hooks/desktop/hook.get_sessions | 21 ++++++++++++++++++ .../hooks/desktop/hook.scene_change | 22 +++++++++++++++++++ .../hooks/desktop/hook.sync_file | 19 ++++++++++++++++ pynodegl-utils/pynodegl_utils/viewer.py | 3 ++- pynodegl-utils/setup.py | 1 + 6 files changed, 75 insertions(+), 1 deletion(-) create mode 100755 pynodegl-utils/pynodegl_utils/hooks/desktop/hook.get_session_info create mode 100755 pynodegl-utils/pynodegl_utils/hooks/desktop/hook.get_sessions create mode 100755 pynodegl-utils/pynodegl_utils/hooks/desktop/hook.scene_change create mode 100755 pynodegl-utils/pynodegl_utils/hooks/desktop/hook.sync_file diff --git a/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.get_session_info b/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.get_session_info new file mode 100755 index 0000000000..997ce01a38 --- /dev/null +++ b/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.get_session_info @@ -0,0 +1,10 @@ +#!/bin/sh + +set -e + +session=$1 +host=${session%%:*} +port=${session##*:} + +set -x +ngl-ipc -x "$host" -p "$port" -? diff --git a/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.get_sessions b/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.get_sessions new file mode 100755 index 0000000000..29684b977a --- /dev/null +++ b/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.get_sessions @@ -0,0 +1,21 @@ +#!/bin/sh + +set -e + +for session_dir in /tmp/ngl-desktop/*; do + [ ! -e "$session_dir/session" ] && continue # for when no file is available + session=${session_dir##*/} + host=${session%%:*} + port=${session##*:} + if ngl-ipc -x $host -p $port >/dev/null; then + echo $session local ngl-desktop + fi +done + +for session in $NGL_DESKTOP_REMOTE_SESSIONS; do + host=${session%%:*} + port=${session##*:} + if ngl-ipc -x $host -p $port >/dev/null; then + echo $session remote ngl-desktop + fi +done diff --git a/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.scene_change b/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.scene_change new file mode 100755 index 0000000000..50c61927b6 --- /dev/null +++ b/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.scene_change @@ -0,0 +1,22 @@ +#!/bin/sh + +set -e + +session=$1 +scenefile=$2 +shift 2 +eval $@ + +host=${session%%:*} +port=${session##*:} + +set -x + +ngl-ipc -x "$host" \ + -p "$port" \ + -f "$scenefile" \ + -t "$duration" \ + -a "$aspect_ratio" \ + -r "$framerate" \ + -c "$clear_color" \ + -m "$samples" \ diff --git a/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.sync_file b/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.sync_file new file mode 100755 index 0000000000..56cefe0efe --- /dev/null +++ b/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.sync_file @@ -0,0 +1,19 @@ +#!/bin/sh + +set -e + +session=$1 +ifile=$2 +ofile=$3 + +host=${session%%:*} +port=${session##*:} + +# Upload file only if it's not local +if [ "$host" = "localhost" ]; then + echo "$ifile" + exit 0 +fi + +set -x +ngl-ipc -x "$host" -p "$port" -u "$ofile=$ifile" diff --git a/pynodegl-utils/pynodegl_utils/viewer.py b/pynodegl-utils/pynodegl_utils/viewer.py index 9cc0dfa336..507f532e9f 100644 --- a/pynodegl-utils/pynodegl_utils/viewer.py +++ b/pynodegl-utils/pynodegl_utils/viewer.py @@ -21,6 +21,7 @@ # import sys +import os.path as op from PySide2 import QtWidgets, QtCore @@ -34,7 +35,7 @@ def run(): parser.add_argument('-m', dest='module', default='pynodegl_utils.examples', help='set the module name containing the scene functions') parser.add_argument('--hooks-dir', dest='hooksdirs', - default=[], + default=[op.join(op.dirname(__file__), 'hooks', 'desktop')], action='append', help='set the directory path containing event hooks') pargs = parser.parse_args(sys.argv[1:]) diff --git a/pynodegl-utils/setup.py b/pynodegl-utils/setup.py index 43fa05d146..c200d16c4e 100644 --- a/pynodegl-utils/setup.py +++ b/pynodegl-utils/setup.py @@ -38,6 +38,7 @@ 'examples/shaders/*.frag', 'examples/shaders/*.comp', 'examples/shaders/*.vert', + 'hooks/hook.*', ], } ) From 9db92598538a6a9d0a7270d55a909d7a800a88b0 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 15 Sep 2020 14:24:34 +0200 Subject: [PATCH 080/388] tools: add gettime_relative() gettime_relative() is more appropriate to implement clocks and benchmarks as it is guaranteed to be monotonic and not prone to clock jittering. --- ngl-tools/common.c | 8 ++++++++ ngl-tools/common.h | 1 + 2 files changed, 9 insertions(+) diff --git a/ngl-tools/common.c b/ngl-tools/common.c index bc9dad4e87..ca46c41ce3 100644 --- a/ngl-tools/common.c +++ b/ngl-tools/common.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -36,6 +37,13 @@ int64_t gettime(void) return 1000000 * (int64_t)tv.tv_sec + tv.tv_usec; } +int64_t gettime_relative(void) +{ + struct timespec ts; + int ret = clock_gettime(CLOCK_MONOTONIC, &ts); + return ret != 0 ? 0 : ts.tv_sec * 1000000 + ts.tv_nsec / 1000; +} + double clipd(double v, double min, double max) { if (v < min) return min; diff --git a/ngl-tools/common.h b/ngl-tools/common.h index b951e44056..55d14a3a21 100644 --- a/ngl-tools/common.h +++ b/ngl-tools/common.h @@ -30,6 +30,7 @@ #define DEFAULT_HEIGHT 360 int64_t gettime(void); +int64_t gettime_relative(void); double clipd(double v, double min, double max); int clipi(int v, int min, int max); int64_t clipi64(int64_t v, int64_t min, int64_t max); From f02fc1cd6766b5a34264efdd6ba0fc290c91d672 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 15 Sep 2020 14:25:56 +0200 Subject: [PATCH 081/388] tools/player: use gettime_relative() where appropriate --- ngl-tools/player.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ngl-tools/player.c b/ngl-tools/player.c index 8bb66fa5ed..5f0b5a7c5a 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -118,7 +118,7 @@ static int screenshot(void) ret = ngl_configure(p->ngl, config); if (ret < 0) fprintf(stderr, "Could not configure node.gl for onscreen rendering\n"); - p->clock_off = gettime() - p->frame_ts; + p->clock_off = gettime_relative() - p->frame_ts; free(capture_buffer); return ret; @@ -162,13 +162,13 @@ static void update_time(int64_t seek_at) struct player *p = g_player; if (seek_at >= 0) { - p->clock_off = gettime() - seek_at; + p->clock_off = gettime_relative() - seek_at; p->frame_ts = seek_at; return; } if (!p->paused && !p->mouse_down) { - const int64_t now = gettime(); + const int64_t now = gettime_relative(); if (p->clock_off < 0 || now - p->clock_off > p->duration) p->clock_off = now; @@ -176,7 +176,7 @@ static void update_time(int64_t seek_at) } if (p->pgbar_opacity_node && p->lasthover >= 0) { - const int64_t t64_diff = gettime() - p->lasthover; + const int64_t t64_diff = gettime_relative() - p->lasthover; const double opacity = clipd(1.5 - t64_diff / 1000000.0, 0, 1); ngl_node_param_set(p->pgbar_opacity_node, "value", opacity); @@ -200,7 +200,7 @@ static int key_callback(SDL_Window *window, SDL_KeyboardEvent *event) return 1; case SDLK_SPACE: p->paused ^= 1; - p->clock_off = gettime() - p->frame_ts; + p->clock_off = gettime_relative() - p->frame_ts; break; case SDLK_f: p->fullscreen ^= 1; @@ -241,7 +241,7 @@ static void seek_event(int x) const int *vp = p->ngl_config.viewport; const int pos = clipi(x - vp[0], 0, vp[2]) * 3/2; const int64_t seek_at64 = p->duration * pos / vp[2]; - p->lasthover = gettime(); + p->lasthover = gettime_relative(); update_time(seek_at64); } @@ -256,13 +256,13 @@ static void mouse_buttonup_callback(SDL_Window *window, SDL_MouseButtonEvent *ev { struct player *p = g_player; p->mouse_down = 0; - p->clock_off = gettime() - p->frame_ts; + p->clock_off = gettime_relative() - p->frame_ts; } static void mouse_pos_callback(SDL_Window *window, SDL_MouseMotionEvent *event) { struct player *p = g_player; - p->lasthover = gettime(); + p->lasthover = gettime_relative(); if (p->mouse_down) seek_event(event->x); } From e0be0b370c92b003e3082478be949f2516491c1a Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 17 Sep 2020 00:36:14 +0200 Subject: [PATCH 082/388] tools/player: show progress bar on seek event --- ngl-tools/player.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ngl-tools/player.c b/ngl-tools/player.c index 5f0b5a7c5a..2f607c7f96 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -213,9 +213,11 @@ static int key_callback(SDL_Window *window, SDL_KeyboardEvent *event) kill_scene(); break; case SDLK_LEFT: + p->lasthover = gettime_relative(); update_time(clipi64(p->frame_ts - 10 * 1000000, 0, p->duration)); break; case SDLK_RIGHT: + p->lasthover = gettime_relative(); update_time(clipi64(p->frame_ts + 10 * 1000000, 0, p->duration)); break; default: From 0ff06ce54e0630e0b3857584b73d8074de954f7b Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 17 Sep 2020 09:46:16 +0200 Subject: [PATCH 083/388] tools/player: move progress bar update code to a dedicated function And only calls update_pgbar() directly from player_main_loop(). --- ngl-tools/player.c | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/ngl-tools/player.c b/ngl-tools/player.c index 2f607c7f96..88f1357702 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -157,6 +157,24 @@ static void update_text(void) p->text_last_duration = duration; } +static void update_pgbar(void) +{ + struct player *p = g_player; + + if (p->pgbar_opacity_node && p->lasthover >= 0) { + const int64_t t64_diff = gettime_relative() - p->lasthover; + const double opacity = clipd(1.5 - t64_diff / 1000000.0, 0, 1); + ngl_node_param_set(p->pgbar_opacity_node, "value", opacity); + + const float text_bg[4] = {1.0, 1.0, 1.0, opacity}; + const float text_fg[4] = {0.0, 0.0, 0.0, opacity}; + ngl_node_param_set(p->pgbar_text_node, "bg_color", text_bg); + ngl_node_param_set(p->pgbar_text_node, "fg_color", text_fg); + + update_text(); + } +} + static void update_time(int64_t seek_at) { struct player *p = g_player; @@ -174,19 +192,6 @@ static void update_time(int64_t seek_at) p->frame_ts = now - p->clock_off; } - - if (p->pgbar_opacity_node && p->lasthover >= 0) { - const int64_t t64_diff = gettime_relative() - p->lasthover; - const double opacity = clipd(1.5 - t64_diff / 1000000.0, 0, 1); - ngl_node_param_set(p->pgbar_opacity_node, "value", opacity); - - const float text_bg[4] = {1.0, 1.0, 1.0, opacity}; - const float text_fg[4] = {0.0, 0.0, 0.0, opacity}; - ngl_node_param_set(p->pgbar_text_node, "bg_color", text_bg); - ngl_node_param_set(p->pgbar_text_node, "fg_color", text_fg); - - update_text(); - } } static int key_callback(SDL_Window *window, SDL_KeyboardEvent *event) @@ -538,6 +543,7 @@ void player_main_loop(void) int run = 1; while (run) { update_time(-1); + update_pgbar(); ngl_draw(p->ngl, p->frame_ts / 1000000.0); SDL_Event event; while (SDL_PollEvent(&event)) { From 9771173e05c4bdabf01d9bbdb05ad592d1f7f009 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 17 Sep 2020 10:24:24 +0200 Subject: [PATCH 084/388] tools/player: add framerate support --- ngl-tools/Makefile | 2 +- ngl-tools/ngl-desktop.c | 6 ++++- ngl-tools/ngl-player.c | 6 ++++- ngl-tools/ngl-python.c | 6 ++++- ngl-tools/player.c | 58 ++++++++++++++++++++++++++++++++++++----- ngl-tools/player.h | 6 ++++- 6 files changed, 72 insertions(+), 12 deletions(-) diff --git a/ngl-tools/Makefile b/ngl-tools/Makefile index dd6bec62c3..1264a2b16e 100644 --- a/ngl-tools/Makefile +++ b/ngl-tools/Makefile @@ -28,7 +28,7 @@ SXPLAYER_CFLAGS := $(shell $(PKG_CONFIG) --cflags libsxplayer) SXPLAYER_LDLIBS := $(shell $(PKG_CONFIG) --libs libsxplayer) TOOLS_CFLAGS := $(shell $(PKG_CONFIG) --cflags libnodegl) -TOOLS_LDLIBS := $(shell $(PKG_CONFIG) --libs libnodegl) +TOOLS_LDLIBS := $(shell $(PKG_CONFIG) --libs libnodegl) -lm ifeq ($(TARGET_OS),Darwin) TOOLS_LDLIBS += -framework AppKit diff --git a/ngl-tools/ngl-desktop.c b/ngl-tools/ngl-desktop.c index d206460535..d311694b39 100644 --- a/ngl-tools/ngl-desktop.c +++ b/ngl-tools/ngl-desktop.c @@ -54,6 +54,7 @@ struct ctx { struct ngl_config cfg; int aspect[2]; int player_ui; + int framerate[2]; int sock_fd; struct addrinfo *addr_info; @@ -86,6 +87,7 @@ static const struct opt options[] = { {"-c", "--clear_color", OPT_TYPE_COLOR, .offset=OFFSET(cfg.clear_color)}, {"-m", "--samples", OPT_TYPE_INT, .offset=OFFSET(cfg.samples)}, {"-u", "--disable-ui", OPT_TYPE_TOGGLE, .offset=OFFSET(player_ui)}, + {"-r", "--framerate", OPT_TYPE_RATIONAL, .offset=OFFSET(framerate)}, }; static int create_session_file(struct ctx *s) @@ -539,6 +541,8 @@ int main(int argc, char *argv[]) .lock = PTHREAD_MUTEX_INITIALIZER, .sock_fd = -1, .player_ui = 1, + .framerate[0] = 60, + .framerate[1] = 1, }; int ret = opts_parse(argc, argc, argv, options, ARRAY_NB(options), &s); @@ -565,7 +569,7 @@ int main(int argc, char *argv[]) s.thread_started = 1; struct ngl_node *scene = get_default_scene(s.host, s.port); - ret = player_init(&s.p, "ngl-desktop", scene, &s.cfg, 0, s.player_ui); + ret = player_init(&s.p, "ngl-desktop", scene, &s.cfg, 0, s.framerate, s.player_ui); ngl_node_unrefp(&scene); if (ret < 0) goto end; diff --git a/ngl-tools/ngl-player.c b/ngl-tools/ngl-player.c index d7733922ff..dff1b620b4 100644 --- a/ngl-tools/ngl-player.c +++ b/ngl-tools/ngl-player.c @@ -34,6 +34,7 @@ struct ctx { struct ngl_config cfg; int direct_rendering; int player_ui; + int framerate[2]; struct sxplayer_info media_info; }; @@ -47,6 +48,7 @@ static const struct opt options[] = { {"-c", "--clear_color", OPT_TYPE_COLOR, .offset=OFFSET(cfg.clear_color)}, {"-m", "--samples", OPT_TYPE_INT, .offset=OFFSET(cfg.samples)}, {"-u", "--disable-ui", OPT_TYPE_TOGGLE, .offset=OFFSET(player_ui)}, + {"-r", "--framerate", OPT_TYPE_RATIONAL, .offset=OFFSET(framerate)}, }; static const char *media_vertex = @@ -126,6 +128,8 @@ int main(int argc, char *argv[]) .cfg.swap_interval = -1, .cfg.clear_color[3] = 1.f, .player_ui = 1, + .framerate[0] = 60, + .framerate[1] = 1, }; int ret = opts_parse(argc, argc - 1, argv, options, ARRAY_NB(options), &s); @@ -148,7 +152,7 @@ int main(int argc, char *argv[]) struct player p; s.cfg.width = s.media_info.width; s.cfg.height = s.media_info.height; - ret = player_init(&p, "ngl-player", scene, &s.cfg, s.media_info.duration, s.player_ui); + ret = player_init(&p, "ngl-player", scene, &s.cfg, s.media_info.duration, s.framerate, s.player_ui); if (ret < 0) goto end; ngl_node_unrefp(&scene); diff --git a/ngl-tools/ngl-python.c b/ngl-tools/ngl-python.c index 058fa9d2a2..c7e181c488 100644 --- a/ngl-tools/ngl-python.c +++ b/ngl-tools/ngl-python.c @@ -35,6 +35,7 @@ struct ctx { int aspect[2]; struct ngl_config cfg; int player_ui; + int framerate[2]; double duration; }; @@ -49,6 +50,7 @@ static const struct opt options[] = { {"-c", "--clear_color", OPT_TYPE_COLOR, .offset=OFFSET(cfg.clear_color)}, {"-m", "--samples", OPT_TYPE_INT, .offset=OFFSET(cfg.samples)}, {"-u", "--disable-ui", OPT_TYPE_TOGGLE, .offset=OFFSET(player_ui)}, + {"-r", "--framerate", OPT_TYPE_RATIONAL, .offset=OFFSET(framerate)}, }; int main(int argc, char *argv[]) @@ -62,6 +64,8 @@ int main(int argc, char *argv[]) .cfg.swap_interval = -1, .cfg.clear_color[3] = 1.f, .player_ui = 1, + .framerate[0] = 60, + .framerate[1] = 1, }; int ret = opts_parse(argc, argc - 2, argv, options, ARRAY_NB(options), &s); @@ -81,7 +85,7 @@ int main(int argc, char *argv[]) get_viewport(s.cfg.width, s.cfg.height, s.aspect, s.cfg.viewport); struct player p; - ret = player_init(&p, "ngl-python", scene, &s.cfg, s.duration, s.player_ui); + ret = player_init(&p, "ngl-python", scene, &s.cfg, s.duration, s.framerate, s.player_ui); if (ret < 0) goto end; ngl_node_unrefp(&scene); diff --git a/ngl-tools/player.c b/ngl-tools/player.c index 88f1357702..377c776e38 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -20,6 +20,7 @@ */ #include +#include #include #include #include @@ -103,7 +104,7 @@ static int screenshot(void) fprintf(stderr, "Could not configure node.gl for offscreen capture\n"); goto end; } - ngl_draw(p->ngl, p->frame_ts / 1000000.0); + ngl_draw(p->ngl, p->frame_time); char filename[32]; snprintf(filename, sizeof(filename), "ngl-%" PRId64 ".ppm", gettime()); @@ -141,7 +142,7 @@ static void update_text(void) if (!p->pgbar_text_node) return; - const int frame_ts = p->frame_ts / 1000000; + const int frame_ts = p->frame_time; const int duration = p->duration / 1000000; if (frame_ts == p->text_last_frame_ts && duration == p->text_last_duration) return; @@ -175,13 +176,29 @@ static void update_pgbar(void) } } +static void set_frame_ts(int64_t frame_ts) +{ + struct player *p = g_player; + p->frame_ts = frame_ts; + p->frame_index = llrint((p->frame_ts * p->framerate[0]) / (double)(p->framerate[1] * 1000000)); + p->frame_time = (p->frame_index * p->framerate[1]) / (double)p->framerate[0]; +} + +static void set_frame_index(int64_t frame_index) +{ + struct player *p = g_player; + p->frame_index = frame_index; + p->frame_time = (p->frame_index * p->framerate[1]) / (double)p->framerate[0]; + p->frame_ts = llrint(p->frame_index * p->framerate[1] * 1000000 / (double)p->framerate[0]); +} + static void update_time(int64_t seek_at) { struct player *p = g_player; if (seek_at >= 0) { p->clock_off = gettime_relative() - seek_at; - p->frame_ts = seek_at; + set_frame_ts(seek_at); return; } @@ -190,7 +207,7 @@ static void update_time(int64_t seek_at) if (p->clock_off < 0 || now - p->clock_off > p->duration) p->clock_off = now; - p->frame_ts = now - p->clock_off; + set_frame_ts(now - p->clock_off); } } @@ -225,6 +242,16 @@ static int key_callback(SDL_Window *window, SDL_KeyboardEvent *event) p->lasthover = gettime_relative(); update_time(clipi64(p->frame_ts + 10 * 1000000, 0, p->duration)); break; + case SDLK_o: + p->paused = 1; + p->lasthover = gettime_relative(); + set_frame_index(clipi64(p->frame_index - 1, 0, p->duration_i)); + break; + case SDLK_p: + p->paused = 1; + p->lasthover = gettime_relative(); + set_frame_index(clipi64(p->frame_index + 1, 0, p->duration_i)); + break; default: break; } @@ -395,7 +422,7 @@ static int set_scene(struct ngl_node *scene) } int player_init(struct player *p, const char *win_title, struct ngl_node *scene, - const struct ngl_config *cfg, double duration, int enable_ui) + const struct ngl_config *cfg, double duration, int *framerate, int enable_ui) { memset(p, 0, sizeof(*p)); @@ -415,6 +442,15 @@ int player_init(struct player *p, const char *win_title, struct ngl_node *scene, p->duration_f = duration; p->duration = duration * 1000000; p->enable_ui = enable_ui; + p->framerate[0] = 60; + p->framerate[1] = 1; + + if (!framerate[0] || !framerate[1]) { + fprintf(stderr, "Invalid framerate %d/%d\n", framerate[0], framerate[1]); + return -1; + } + memcpy(p->framerate, framerate, sizeof(p->framerate)); + p->duration_i = llrint(p->duration_f * framerate[0] / (double)framerate[1]); p->ngl_config = *cfg; @@ -477,6 +513,7 @@ static int handle_duration(const void *data) struct player *p = g_player; memcpy(&p->duration_f, data, sizeof(p->duration_f)); p->duration = p->duration_f * 1000000; + p->duration_i = llrint(p->duration_f * p->framerate[0] / (double)p->framerate[1]); if (p->pgbar_duration_node) ngl_node_param_set(p->pgbar_duration_node, "value", p->duration_f); update_text(); @@ -514,7 +551,14 @@ static int handle_aspect_ratio(const void *data) static int handle_framerate(const void *data) { const int *rate = data; - fprintf(stderr, "WARNING: unhandled framerate %d/%d\n", rate[0], rate[1]); + struct player *p = g_player; + if (!rate[0] || !rate[1]) { + fprintf(stderr, "Invalid framerate %d/%d\n", rate[0], rate[1]); + return -1; + } + memcpy(p->framerate, rate, sizeof(p->framerate)); + p->duration_i = llrint(p->duration_f * rate[0] / (double)rate[1]); + set_frame_ts(p->frame_ts); return 0; } @@ -544,7 +588,7 @@ void player_main_loop(void) while (run) { update_time(-1); update_pgbar(); - ngl_draw(p->ngl, p->frame_ts / 1000000.0); + ngl_draw(p->ngl, p->frame_time); SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { diff --git a/ngl-tools/player.h b/ngl-tools/player.h index 33888bd84f..0f7034d3f2 100644 --- a/ngl-tools/player.h +++ b/ngl-tools/player.h @@ -48,13 +48,17 @@ struct player { double duration_f; int64_t duration; + int64_t duration_i; int enable_ui; int aspect[2]; + int framerate[2]; struct ngl_ctx *ngl; struct ngl_config ngl_config; int64_t clock_off; int64_t frame_ts; + int64_t frame_index; + double frame_time; int paused; int64_t lasthover; int mouse_down; @@ -67,7 +71,7 @@ struct player { }; int player_init(struct player *p, const char *win_title, struct ngl_node *scene, - const struct ngl_config *cfg, double duration, int enable_ui); + const struct ngl_config *cfg, double duration, int *framerate, int enable_ui); void player_uninit(void); From 95a51cf39a6a614f22cc2db178260d53490a86d8 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 17 Sep 2020 10:45:34 +0200 Subject: [PATCH 085/388] tools/player: reset running time after a seek request The first call to ngl_draw() can take quite some time as it initializes and prefetch all the resources required for the graph for a given time. Calling ngl_draw() after a seek request can also take quite some time as it needs to release/prefetch all the requires resources for a given time. --- ngl-tools/player.c | 17 +++++++++++++++-- ngl-tools/player.h | 1 + 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ngl-tools/player.c b/ngl-tools/player.c index 377c776e38..e0520a2adf 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -197,6 +197,7 @@ static void update_time(int64_t seek_at) struct player *p = g_player; if (seek_at >= 0) { + p->seeking = 1; p->clock_off = gettime_relative() - seek_at; set_frame_ts(seek_at); return; @@ -204,13 +205,21 @@ static void update_time(int64_t seek_at) if (!p->paused && !p->mouse_down) { const int64_t now = gettime_relative(); - if (p->clock_off < 0 || now - p->clock_off > p->duration) + if (p->clock_off < 0 || now - p->clock_off > p->duration) { + p->seeking = 1; p->clock_off = now; + } set_frame_ts(now - p->clock_off); } } +static void reset_running_time(void) +{ + struct player *p = g_player; + p->clock_off = gettime_relative() - p->frame_ts; +} + static int key_callback(SDL_Window *window, SDL_KeyboardEvent *event) { struct player *p = g_player; @@ -222,7 +231,7 @@ static int key_callback(SDL_Window *window, SDL_KeyboardEvent *event) return 1; case SDLK_SPACE: p->paused ^= 1; - p->clock_off = gettime_relative() - p->frame_ts; + reset_running_time(); break; case SDLK_f: p->fullscreen ^= 1; @@ -589,6 +598,10 @@ void player_main_loop(void) update_time(-1); update_pgbar(); ngl_draw(p->ngl, p->frame_time); + if (p->seeking) { + reset_running_time(); + p->seeking = 0; + } SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { diff --git a/ngl-tools/player.h b/ngl-tools/player.h index 0f7034d3f2..0f4a85f815 100644 --- a/ngl-tools/player.h +++ b/ngl-tools/player.h @@ -60,6 +60,7 @@ struct player { int64_t frame_index; double frame_time; int paused; + int seeking; int64_t lasthover; int mouse_down; int fullscreen; From f6f80a236d4938afe82547d6838ac1a9627d2db5 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 17 Sep 2020 16:05:29 +0200 Subject: [PATCH 086/388] tools/player: clip seek position on mouse position event --- ngl-tools/player.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ngl-tools/player.c b/ngl-tools/player.c index e0520a2adf..f0fa367e2d 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -285,7 +285,7 @@ static void seek_event(int x) const int pos = clipi(x - vp[0], 0, vp[2]) * 3/2; const int64_t seek_at64 = p->duration * pos / vp[2]; p->lasthover = gettime_relative(); - update_time(seek_at64); + update_time(clipi64(seek_at64, 0, p->duration)); } static void mouse_buttondown_callback(SDL_Window *window, SDL_MouseButtonEvent *event) From db44091ea63ba027f35953ba71b07fb2077fbdbc Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 17 Sep 2020 15:55:29 +0200 Subject: [PATCH 087/388] tools/render: use gettime_relative() where appropriate --- ngl-tools/ngl-render.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ngl-tools/ngl-render.c b/ngl-tools/ngl-render.c index e1d05cdcc9..756472761a 100644 --- a/ngl-tools/ngl-render.c +++ b/ngl-tools/ngl-render.c @@ -212,7 +212,7 @@ int main(int argc, char *argv[]) const float t0 = r->start; const float t1 = r->start + r->duration; - const int64_t start = gettime(); + const int64_t start = gettime_relative(); for (;;) { const float t = t0 + k*1./r->freq; @@ -237,7 +237,7 @@ int main(int argc, char *argv[]) k++; } - const double tdiff = (gettime() - start) / 1000000.; + const double tdiff = (gettime_relative() - start) / 1000000.; printf("Rendered %d frames in %g (FPS=%g)\n", k, tdiff, k / tdiff); } From 1b2c53f19e0a1a413e17627af785a5eba6cf1706 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 18 Sep 2020 11:52:40 +0200 Subject: [PATCH 088/388] tools/player: update progress bar UI --- ngl-tools/player.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ngl-tools/player.c b/ngl-tools/player.c index f0fa367e2d..42da2f8ef9 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -167,8 +167,8 @@ static void update_pgbar(void) const double opacity = clipd(1.5 - t64_diff / 1000000.0, 0, 1); ngl_node_param_set(p->pgbar_opacity_node, "value", opacity); - const float text_bg[4] = {1.0, 1.0, 1.0, opacity}; - const float text_fg[4] = {0.0, 0.0, 0.0, opacity}; + const float text_bg[4] = {0.0, 0.0, 0.0, 0.8 * opacity}; + const float text_fg[4] = {1.0, 1.0, 1.0, opacity}; ngl_node_param_set(p->pgbar_text_node, "bg_color", text_bg); ngl_node_param_set(p->pgbar_text_node, "fg_color", text_fg); @@ -282,7 +282,7 @@ static void seek_event(int x) { struct player *p = g_player; const int *vp = p->ngl_config.viewport; - const int pos = clipi(x - vp[0], 0, vp[2]) * 3/2; + const int pos = clipi(x - vp[0], 0, vp[2]); const int64_t seek_at64 = p->duration * pos / vp[2]; p->lasthover = gettime_relative(); update_time(clipi64(seek_at64, 0, p->duration)); @@ -329,15 +329,15 @@ static struct ngl_node *add_progress_bar(struct ngl_node *scene) { struct player *p = g_player; - static const float bar_corner[3] = {-1.0, -1.0, 0.0}; - static const float bar_width[3] = { 2.0 * 2/3., 0.0, 0.0}; - static const float bar_height[3] = { 0.0, 2.0 * 0.05, 0.0}; // 5% of the height + static const float bar_corner[3] = {-1.0, -1.0 + 0.1, 0.0}; + static const float bar_width[3] = { 2.0, 0.0, 0.0}; + static const float bar_height[3] = { 0.0, 2.0 * 0.01, 0.0}; // 1% of the height - static const float text_corner[3] = {1.0 - 2.0 * 1/3., -1.0, 0.0}; - static const float text_width[3] = {2.0 * 1/3., 0.0, 0.0}; - static const float text_height[3] = {0.0, 2.0 * 0.05, 0.0}; - static const float text_bg[4] = {1.0, 1.0, 1.0, 0.0}; - static const float text_fg[4] = {0.0, 0.0, 0.0, 0.0}; + static const float text_corner[3] = {-1.0, -1.0, 0.0}; + static const float text_width[3] = { 2.0, 0.0, 0.0}; + static const float text_height[3] = { 0.0, 2.0 * 0.05, 0.0}; // 5% of the height + static const float text_bg[4] = { 0.0, 0.0, 0.0, 0.8}; + static const float text_fg[4] = { 1.0, 1.0, 1.0, 1.0}; struct ngl_node *text = ngl_node_create(NGL_NODE_TEXT); struct ngl_node *quad = ngl_node_create(NGL_NODE_QUAD); From 30f42877c08eb7c91ef36f7483acf3caf8a77d7e Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 18 Sep 2020 12:14:17 +0200 Subject: [PATCH 089/388] tools/player: display frame index and framerate in progress bar --- ngl-tools/player.c | 7 ++++--- ngl-tools/player.h | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ngl-tools/player.c b/ngl-tools/player.c index 42da2f8ef9..d887fdbdb3 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -144,7 +144,7 @@ static void update_text(void) const int frame_ts = p->frame_time; const int duration = p->duration / 1000000; - if (frame_ts == p->text_last_frame_ts && duration == p->text_last_duration) + if (p->frame_index == p->text_last_frame_index && duration == p->text_last_duration) return; char buf[128]; @@ -152,9 +152,10 @@ static void update_text(void) const int cs = frame_ts % 60; const int dm = duration / 60; const int ds = duration % 60; - snprintf(buf, sizeof(buf), "%02d:%02d / %02d:%02d", cm, cs, dm, ds); + snprintf(buf, sizeof(buf), "%02d:%02d / %02d:%02d (%" PRId64 " @ %d/%d)", + cm, cs, dm, ds, p->frame_index, p->framerate[0], p->framerate[1]); ngl_node_param_set(p->pgbar_text_node, "text", buf); - p->text_last_frame_ts = frame_ts; + p->text_last_frame_index = p->frame_index; p->text_last_duration = duration; } diff --git a/ngl-tools/player.h b/ngl-tools/player.h index 0f4a85f815..be17d99dbb 100644 --- a/ngl-tools/player.h +++ b/ngl-tools/player.h @@ -64,7 +64,7 @@ struct player { int64_t lasthover; int mouse_down; int fullscreen; - int text_last_frame_ts; + int text_last_frame_index; int text_last_duration; struct ngl_node *pgbar_opacity_node; struct ngl_node *pgbar_text_node; From d5341d883da5bf927e7ac89a0dc98b9c608e660f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 18 Sep 2020 13:16:31 +0200 Subject: [PATCH 090/388] tools/player: remove explicit text update at duration update This is redundant with the update_pgbar() calls (in which update_text() is called) in the main loop. --- ngl-tools/player.c | 1 - 1 file changed, 1 deletion(-) diff --git a/ngl-tools/player.c b/ngl-tools/player.c index d887fdbdb3..bd001dde05 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -526,7 +526,6 @@ static int handle_duration(const void *data) p->duration_i = llrint(p->duration_f * p->framerate[0] / (double)p->framerate[1]); if (p->pgbar_duration_node) ngl_node_param_set(p->pgbar_duration_node, "value", p->duration_f); - update_text(); return 0; } From 665e2a1e49e79322d82467f5ac6e92e0e572d129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 18 Sep 2020 13:17:35 +0200 Subject: [PATCH 091/388] tools/player: force a refresh of the info/seek bar on any received event --- ngl-tools/player.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ngl-tools/player.c b/ngl-tools/player.c index bd001dde05..e01f6e9d99 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -626,6 +626,7 @@ void player_main_loop(void) case SDL_USEREVENT: run = handle_map[event.user.code](event.user.data1) == 0; free(event.user.data1); + p->lasthover = gettime_relative(); break; } } From 4ae8e7bae07ebd059844d73983ea5c9358bcdf71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 18 Sep 2020 13:22:41 +0200 Subject: [PATCH 092/388] tools/player: fix empty text in some update scenario On scene change with the exact same scene and configuration, the original scene gets reset along with the info/seek bar. But since text_last_frame_index is identical to the previous state, the text won't be reprinted again unless forced. --- ngl-tools/player.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ngl-tools/player.c b/ngl-tools/player.c index e01f6e9d99..9f01b70b04 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -626,6 +626,7 @@ void player_main_loop(void) case SDL_USEREVENT: run = handle_map[event.user.code](event.user.data1) == 0; free(event.user.data1); + p->text_last_frame_index = -1; p->lasthover = gettime_relative(); break; } From 59087bbd675bb984319477a5d38786617328a9a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 18 Sep 2020 14:47:28 +0200 Subject: [PATCH 093/388] tools/player: add missing text_last_frame_index initialization --- ngl-tools/player.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ngl-tools/player.c b/ngl-tools/player.c index 9f01b70b04..a2812cc147 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -449,6 +449,7 @@ int player_init(struct player *p, const char *win_title, struct ngl_node *scene, p->clock_off = -1; p->lasthover = -1; + p->text_last_frame_index = -1; p->duration_f = duration; p->duration = duration * 1000000; p->enable_ui = enable_ui; From 7b04fc81065cbfa89790b76670699e1d084aea70 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 17 Sep 2020 17:28:18 +0200 Subject: [PATCH 094/388] glcontext_nsgl: error out if backend is not OpenGL OpenGLES is not supported by NSGL. --- libnodegl/glcontext_nsgl.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libnodegl/glcontext_nsgl.m b/libnodegl/glcontext_nsgl.m index f7d4123f56..5f7cd2c5b8 100644 --- a/libnodegl/glcontext_nsgl.m +++ b/libnodegl/glcontext_nsgl.m @@ -42,6 +42,11 @@ static int nsgl_init(struct glcontext *ctx, uintptr_t display, uintptr_t window, { struct nsgl_priv *nsgl = ctx->priv_data; + if (ctx->backend != NGL_BACKEND_OPENGL) { + LOG(ERROR, "unsupported backend: %d, only OpenGL is supported by NSGL", ctx->backend); + return -1; + } + CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); if (!framework) { LOG(ERROR, "could not retrieve OpenGL framework"); From 26f194fd5406ed130c74ce439fb0910c2590992a Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 17 Sep 2020 17:30:56 +0200 Subject: [PATCH 095/388] glcontext_wgl: error out if backend is not OpenGL Our WGL backend currently only supports OpenGL. --- libnodegl/glcontext_wgl.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libnodegl/glcontext_wgl.c b/libnodegl/glcontext_wgl.c index 9bf127bea8..67f0b8bbaa 100644 --- a/libnodegl/glcontext_wgl.c +++ b/libnodegl/glcontext_wgl.c @@ -49,6 +49,11 @@ static int wgl_init(struct glcontext *ctx, uintptr_t display, uintptr_t window, { struct wgl_priv *wgl = ctx->priv_data; + if (ctx->backend != NGL_BACKEND_OPENGL) { + LOG(ERROR, "unsupported backend: %d, only OpenGL is currently supported", ctx->backend); + return -1; + } + wgl->module = LoadLibrary("opengl32.dll"); if (!wgl->module) { LOG(ERROR, "could not load opengl32.dll (%lu)", GetLastError()); From 645ca7f3b35168cdcfeadbab76c7f34452bc88dc Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 17 Sep 2020 17:32:51 +0200 Subject: [PATCH 096/388] glcontext: initialize {major,minor}_version variables If the underlying context does not support querying the OpenGL version using GL_MAJOR, GL_MINOR, major_version and minor_version are left uninitialized but are still used to determine the OpenGL version. --- libnodegl/glcontext.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libnodegl/glcontext.c b/libnodegl/glcontext.c index f7d3d3a40d..a082e7779d 100644 --- a/libnodegl/glcontext.c +++ b/libnodegl/glcontext.c @@ -113,8 +113,8 @@ static int glcontext_load_functions(struct glcontext *glcontext) static int glcontext_probe_version(struct glcontext *glcontext) { - GLint major_version; - GLint minor_version; + GLint major_version = 0; + GLint minor_version = 0; if (glcontext->backend == NGL_BACKEND_OPENGL) { ngli_glGetIntegerv(glcontext, GL_MAJOR_VERSION, &major_version); From d2df3915a3fb70754d45876264e657d5a2f49cb3 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 18 Sep 2020 13:29:58 +0200 Subject: [PATCH 097/388] glcontext_wgl: add OpenGLES support --- libnodegl/glcontext_wgl.c | 41 ++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/libnodegl/glcontext_wgl.c b/libnodegl/glcontext_wgl.c index 67f0b8bbaa..402ddf5adf 100644 --- a/libnodegl/glcontext_wgl.c +++ b/libnodegl/glcontext_wgl.c @@ -42,6 +42,7 @@ struct wgl_priv { PFNWGLRELEASEPBUFFERDCARBPROC ReleasePbufferDCARB; PFNWGLDESTROYPBUFFERARBPROC DestroyPbufferARB; PFNWGLGETPBUFFERDCARBPROC GetPbufferDCARB; + PFNWGLGETEXTENSIONSSTRINGARBPROC GetExtensionsStringARB; PFNWGLSWAPINTERVALEXTPROC SwapIntervalEXT; }; @@ -49,11 +50,6 @@ static int wgl_init(struct glcontext *ctx, uintptr_t display, uintptr_t window, { struct wgl_priv *wgl = ctx->priv_data; - if (ctx->backend != NGL_BACKEND_OPENGL) { - LOG(ERROR, "unsupported backend: %d, only OpenGL is currently supported", ctx->backend); - return -1; - } - wgl->module = LoadLibrary("opengl32.dll"); if (!wgl->module) { LOG(ERROR, "could not load opengl32.dll (%lu)", GetLastError()); @@ -123,6 +119,7 @@ static int wgl_init(struct glcontext *ctx, uintptr_t display, uintptr_t window, {"wglReleasePbufferDCARB", offsetof(struct wgl_priv, ReleasePbufferDCARB)}, {"wglDestroyPbufferARB", offsetof(struct wgl_priv, DestroyPbufferARB)}, {"wglGetPbufferDCARB", offsetof(struct wgl_priv, GetPbufferDCARB)}, + {"wglGetExtensionsStringARB", offsetof(struct wgl_priv, GetExtensionsStringARB)}, }; for (int i = 0; i < NGLI_ARRAY_NB(extensions); i++) { @@ -184,15 +181,33 @@ static int wgl_init(struct glcontext *ctx, uintptr_t display, uintptr_t window, if (wglDeleteContext(wgl->rendering_context) == FALSE) LOG(WARNING, "failed to delete dummy rendering context (%lu)", GetLastError()); - const int context_attributes[] = { - WGL_CONTEXT_MAJOR_VERSION_ARB, 1, - WGL_CONTEXT_MINOR_VERSION_ARB, 0, - WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, - 0 - }; - HGLRC shared_context = (HGLRC)other; - wgl->rendering_context = wgl->CreateContextAttribsARB(wgl->device_context, shared_context, context_attributes); + + if (ctx->backend == NGL_BACKEND_OPENGL) { + static const int context_attributes[] = { + WGL_CONTEXT_MAJOR_VERSION_ARB, 1, + WGL_CONTEXT_MINOR_VERSION_ARB, 0, + WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + 0 + }; + wgl->rendering_context = wgl->CreateContextAttribsARB(wgl->device_context, shared_context, context_attributes); + } else if (ctx->backend == NGL_BACKEND_OPENGLES) { + const char *extensions = wgl->GetExtensionsStringARB(wgl->device_context); + if (!ngli_glcontext_check_extension("WGL_EXT_create_context_es2_profile", extensions) && + !ngli_glcontext_check_extension("WGL_EXT_create_context_es_profile", extensions)) { + LOG(ERROR, "OpenGLES is not supported by this device"); + return -1; + } + static const int context_attributes[] = { + WGL_CONTEXT_MAJOR_VERSION_ARB, 2, + WGL_CONTEXT_MINOR_VERSION_ARB, 0, + WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_ES2_PROFILE_BIT_EXT, + 0 + }; + wgl->rendering_context = wgl->CreateContextAttribsARB(wgl->device_context, shared_context, context_attributes); + } else { + ngli_assert(0); + } if (!wgl->rendering_context) { LOG(ERROR, "failed to create rendering context (%lu)", GetLastError()); From 3baf8655446f98f3e9c1ef8cd401b5fdd89fb0cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Sat, 19 Sep 2020 17:39:03 +0200 Subject: [PATCH 098/388] utils: remove PySide2 dependency from scene controllers We don't want the scene() decorator to pull PySide2 in the dependency tree, because this prevents using Python scenes on systems where Qt/PySide2 are not well supported (this is typically the case on Windows currently). Note that the scene() decorator and associated objects probably belongs into pynodegl as it is core to the Python API and relied on in the ngl-tools. --- .../pynodegl_utils/control_widgets.py | 11 +++ pynodegl-utils/pynodegl_utils/controls.py | 72 ------------------- pynodegl-utils/pynodegl_utils/misc.py | 35 +++++---- pynodegl-utils/pynodegl_utils/ui/toolbar.py | 6 +- 4 files changed, 35 insertions(+), 89 deletions(-) delete mode 100644 pynodegl-utils/pynodegl_utils/controls.py diff --git a/pynodegl-utils/pynodegl_utils/control_widgets.py b/pynodegl-utils/pynodegl_utils/control_widgets.py index d8fcf42b38..c2333588c5 100644 --- a/pynodegl-utils/pynodegl_utils/control_widgets.py +++ b/pynodegl-utils/pynodegl_utils/control_widgets.py @@ -188,3 +188,14 @@ def __init__(self, name, value, **kwargs): @QtCore.Slot() def _submit_text(self): self.signal_change(self._text.toPlainText()) + + +control_to_widget = dict( + Range=Slider, + Vector=VectorWidget, + Color=ColorPicker, + Bool=Checkbox, + File=FilePicker, + List=ComboBox, + Text=TextInput, +) diff --git a/pynodegl-utils/pynodegl_utils/controls.py b/pynodegl-utils/pynodegl_utils/controls.py deleted file mode 100644 index 9c28978e9a..0000000000 --- a/pynodegl-utils/pynodegl_utils/controls.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2019 GoPro Inc. -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -from pynodegl_utils import control_widgets - - -class _SceneControlWidget: - - _widget_cls = None - - def __init__(self, **kwargs): - self._widget_args = kwargs - - def set_default(self, value): - self._value = value - - def get_widget(self, name): - return self._widget_cls(name, self._value, **self._widget_args) - - -class Range(_SceneControlWidget): - - _widget_cls = control_widgets.Slider - - -class Vector(_SceneControlWidget): - - _widget_cls = control_widgets.VectorWidget - - -class Color(_SceneControlWidget): - - _widget_cls = control_widgets.ColorPicker - - -class Bool(_SceneControlWidget): - - _widget_cls = control_widgets.Checkbox - - -class File(_SceneControlWidget): - - _widget_cls = control_widgets.FilePicker - - -class List(_SceneControlWidget): - - _widget_cls = control_widgets.ComboBox - - -class Text(_SceneControlWidget): - - _widget_cls = control_widgets.TextInput diff --git a/pynodegl-utils/pynodegl_utils/misc.py b/pynodegl-utils/pynodegl_utils/misc.py index c292e98a55..4c524cc5f2 100644 --- a/pynodegl-utils/pynodegl_utils/misc.py +++ b/pynodegl-utils/pynodegl_utils/misc.py @@ -27,10 +27,10 @@ import json import subprocess import pynodegl as ngl -from pynodegl_utils import controls +from collections import namedtuple -def scene(**widgets_specs): +def scene(**controls): def real_decorator(scene_func): def func_wrapper(idict=None, **extra_args): if idict is None: @@ -45,20 +45,25 @@ def func_wrapper(idict=None, **extra_args): final_specs = [] - # Construct a arg -> default dict + # Construct widgets specs + widgets_specs = [] func_specs = inspect.getfullargspec(scene_func) if func_specs.defaults: nb_optionnals = len(func_specs.defaults) for i, key in enumerate(func_specs.args[-nb_optionnals:]): # Set controller defaults according to the function prototype - if key in widgets_specs: - widgets_specs[key].set_default(func_specs.defaults[i]) - - # Transfers the widgets controls to the UI. + control = controls.get(key) + if control is not None: + default = func_specs.defaults[i] + ctl_id = control.__class__.__name__ + ctl_data = control._asdict() + widgets_specs.append((key, default, ctl_id, ctl_data)) + + # Transfers the widget specs to the UI. # We could use the return value but it's better if the user can still # call its decorated scene function transparently inside his own code # without getting garbage along the return value. - func_wrapper.widgets_specs = list(widgets_specs.items()) + func_wrapper.widgets_specs = widgets_specs # Flag the scene as a scene function so it's registered in the UI. func_wrapper.iam_a_ngl_scene_func = True @@ -71,13 +76,13 @@ def func_wrapper(idict=None, **extra_args): return real_decorator -scene.Range = controls.Range -scene.Vector = controls.Vector -scene.Color = controls.Color -scene.Bool = controls.Bool -scene.File = controls.File -scene.List = controls.List -scene.Text = controls.Text +scene.Range = namedtuple('Range', 'range unit_base', defaults=([0, 1], 1)) +scene.Vector = namedtuple('Vector', 'n minv maxv', defaults=(None, None)) +scene.Color = namedtuple('Color', '') +scene.Bool = namedtuple('Bool', '') +scene.File = namedtuple('File', 'filter', defaults=(None,)) +scene.List = namedtuple('List', 'choices') +scene.Text = namedtuple('Text', '') class Media: diff --git a/pynodegl-utils/pynodegl_utils/ui/toolbar.py b/pynodegl-utils/pynodegl_utils/ui/toolbar.py index 64dc1b5186..29f0fe6855 100644 --- a/pynodegl-utils/pynodegl_utils/ui/toolbar.py +++ b/pynodegl-utils/pynodegl_utils/ui/toolbar.py @@ -27,6 +27,7 @@ import pynodegl as ngl from pynodegl_utils.config import Config +from pynodegl_utils.control_widgets import control_to_widget class Toolbar(QtWidgets.QWidget): @@ -182,8 +183,9 @@ def _widget_scene_reload(self, name, value): def _get_opts_widget_from_specs(self, widgets_specs): widgets = [] - for name, controller in widgets_specs: - widget = controller.get_widget(name) + for key, default, ctl_id, ctl_data in widgets_specs: + widget_cls = control_to_widget[ctl_id] + widget = widget_cls(key, default, **ctl_data) widget.needSceneReload.connect(self._widget_scene_reload) widgets.append(widget) if not widgets: From d1a4fd525559c7d0e87772c2bd3d7f2624eb3ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 21 Sep 2020 11:45:24 +0200 Subject: [PATCH 099/388] utils: rework control kwargs In practice this also fixes a regression introduced in 3baf8655446f98f3e9c1ef8cd401b5fdd89fb0cf when using Vector(n=2), typically in examples.medias:centered_medias, where the Qt widget has minv and maxv in its kwargs, but they are set to None, so kwargs.get() doesn't work as expected (no fallback on the default). --- .../pynodegl_utils/control_widgets.py | 28 ++++++++----------- pynodegl-utils/pynodegl_utils/misc.py | 2 +- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/pynodegl-utils/pynodegl_utils/control_widgets.py b/pynodegl-utils/pynodegl_utils/control_widgets.py index c2333588c5..e0d3fd9d5c 100644 --- a/pynodegl-utils/pynodegl_utils/control_widgets.py +++ b/pynodegl-utils/pynodegl_utils/control_widgets.py @@ -43,13 +43,11 @@ def get_label_text(self, value=None): class Slider(_ControlWidget): - def __init__(self, name, value, **kwargs): + def __init__(self, name, value, range, unit_base): super().__init__(name) slider = QtWidgets.QSlider(QtCore.Qt.Horizontal) - self._unit_base = kwargs.get('unit_base', 1) - if 'range' in kwargs: - srange = kwargs['range'] - slider.setRange(srange[0] * self._unit_base, srange[1] * self._unit_base) + self._unit_base = unit_base + slider.setRange(range[0] * self._unit_base, range[1] * self._unit_base) slider.setValue(value * self._unit_base) self._label = QtWidgets.QLabel(self.get_label_text(value)) slider.valueChanged.connect(self._slider_value_changed) @@ -65,13 +63,12 @@ def _slider_value_changed(self, value): class VectorWidget(_ControlWidget): - def __init__(self, name, value, **kwargs): + def __init__(self, name, value, n, minv, maxv): super().__init__(name) - n = kwargs.get("n", 3) hlayout = QtWidgets.QHBoxLayout() self._spinboxes = [] - elems_min = kwargs.get("minv", [0] * n) - elems_max = kwargs.get("maxv", [1] * n) + elems_min = minv if minv is not None else [0] * n + elems_max = maxv if maxv is not None else [1] * n assert 2 <= n <= 4 assert len(elems_min) == len(elems_max) == len(value) == n for elem_value, elem_min, elem_max in zip(value, elems_min, elems_max): @@ -94,7 +91,7 @@ def _spin_value_changed(self, value): class ColorPicker(_ControlWidget): - def __init__(self, name, value, **kwargs): + def __init__(self, name, value): super().__init__(name) self._color_btn = QtWidgets.QPushButton() color = QtGui.QColor() @@ -120,7 +117,7 @@ def _pick_color(self): class Checkbox(_ControlWidget): - def __init__(self, name, value, **kwargs): + def __init__(self, name, value): super().__init__(name) self._chkbox = QtWidgets.QCheckBox(name) self._chkbox.setChecked(value) @@ -134,9 +131,9 @@ def _checkbox_toggle(self): class FilePicker(_ControlWidget): - def __init__(self, name, value, **kwargs): + def __init__(self, name, value, filter): super().__init__(name) - self._filter = kwargs.get('filter', '') + self._filter = filter dialog_btn = QtWidgets.QPushButton('Open file') self._label = QtWidgets.QLabel(self.get_label_text(value)) dialog_btn.pressed.connect(self._choose_filename) @@ -154,11 +151,10 @@ def _choose_filename(self): class ComboBox(_ControlWidget): - def __init__(self, name, value, **kwargs): + def __init__(self, name, value, choices): super().__init__(name) combobox = QtWidgets.QComboBox() label = QtWidgets.QLabel(self.get_label_text()) - choices = kwargs['choices'] combobox.addItems(choices) combobox.setCurrentIndex(choices.index(value)) combobox.currentTextChanged.connect(self._combobox_select) @@ -172,7 +168,7 @@ def _combobox_select(self, text): class TextInput(_ControlWidget): - def __init__(self, name, value, **kwargs): + def __init__(self, name, value): super().__init__(name) self._text = QtWidgets.QPlainTextEdit() self._text.setPlainText(value) diff --git a/pynodegl-utils/pynodegl_utils/misc.py b/pynodegl-utils/pynodegl_utils/misc.py index 4c524cc5f2..c56618046a 100644 --- a/pynodegl-utils/pynodegl_utils/misc.py +++ b/pynodegl-utils/pynodegl_utils/misc.py @@ -80,7 +80,7 @@ def func_wrapper(idict=None, **extra_args): scene.Vector = namedtuple('Vector', 'n minv maxv', defaults=(None, None)) scene.Color = namedtuple('Color', '') scene.Bool = namedtuple('Bool', '') -scene.File = namedtuple('File', 'filter', defaults=(None,)) +scene.File = namedtuple('File', 'filter', defaults=('',)) scene.List = namedtuple('List', 'choices') scene.Text = namedtuple('Text', '') From 76d917ce815576d04a55cf35bf5f46c0d5a73815 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 21 Sep 2020 14:08:01 +0200 Subject: [PATCH 100/388] gctx_gl: check capture_buffer before reading pixels ngli_rendertarget_read_pixels() must not be called with data set to NULL. This commit fixes a potential crash when node.gl is configured to render offscreen and no capture_buffer is set. Fixes a regression introduced by 0be6c0b929dfeade2bca163f36eabe963726b1f4. --- libnodegl/gctx_gl.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 881e833cc1..c8b15ac844 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -50,7 +50,8 @@ static void capture_default(struct gctx *s) struct rendertarget *rt = s_priv->rt; ngli_rendertarget_resolve(rt); - ngli_rendertarget_read_pixels(rt, config->capture_buffer); + if (config->capture_buffer) + ngli_rendertarget_read_pixels(rt, config->capture_buffer); } static void capture_ios(struct gctx *s) From 313c480c9fc76294e66df6c81b5e520b9d46d5a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 21 Sep 2020 10:58:43 +0200 Subject: [PATCH 101/388] build: disable pulling utils requirements on Windows --- Makefile | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Makefile b/Makefile index db1e4cb921..2695328b51 100644 --- a/Makefile +++ b/Makefile @@ -51,13 +51,25 @@ ngl-tools-install: nodegl-install pynodegl-utils-install: pynodegl-utils-deps-install (. $(ACTIVATE) && pip -v install -e ./pynodegl-utils) +# # pynodegl-install is in dependency to prevent from trying to install pynodegl # from its requirements. Pulling pynodegl from requirements has two main issue: # it tries to get it from PyPi (and we want to install the local pynodegl # version), and it would fail anyway because pynodegl is currently not # available on PyPi. +# +# We do not pull the requirements on Windows because of various issues: +# - PySide2 can't be pulled +# - Pillow fails to find zlib +# - ngl-viewer can not currently work because of temporary files handling +# +# Still, we want the module to be installed so we can access the scene() +# decorator and other related utils. +# pynodegl-utils-deps-install: pynodegl-install +ifneq ($(TARGET_OS),MinGW-w64) (. $(ACTIVATE) && pip install -r ./pynodegl-utils/requirements.txt) +endif pynodegl-install: pynodegl-deps-install (. $(ACTIVATE) && PKG_CONFIG_PATH=$(PREFIX)/lib/pkgconfig LDFLAGS=$(RPATH_LDFLAGS) pip -v install -e ./pynodegl) From e4e6fe0d842d83163250a34094f8f2bfab53ab13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Sat, 19 Sep 2020 21:55:02 +0200 Subject: [PATCH 102/388] tools: replace read/write on net fd with recv/send calls read/write do not work with the incoming winsock2 support. The calls are equivalent on *nix, with potentially just a difference on error codes. --- ngl-tools/ipc.c | 9 +++++---- ngl-tools/ngl-desktop.c | 2 +- ngl-tools/ngl-ipc.c | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/ngl-tools/ipc.c b/ngl-tools/ipc.c index 231d68c400..dfb2b37395 100644 --- a/ngl-tools/ipc.c +++ b/ngl-tools/ipc.c @@ -23,6 +23,7 @@ #include #include #include +#include #include @@ -171,9 +172,9 @@ void ipc_pkt_freep(struct ipc_pkt **pktp) int ipc_send(int fd, const struct ipc_pkt *pkt) { - const ssize_t n = write(fd, pkt->data, pkt->size); + const ssize_t n = send(fd, pkt->data, pkt->size, 0); if (n < 0) { - perror("write"); + perror("send"); return NGL_ERROR_IO; } // XXX: should we loop instead? @@ -188,11 +189,11 @@ static int readbuf(int fd, uint8_t *buf, ssize_t size) { ssize_t nr = 0; while (nr != size) { - const ssize_t n = read(fd, buf + nr, size - nr); + const ssize_t n = recv(fd, buf + nr, size - nr, 0); if (n == 0) return 0; if (n < 0) { - perror("read"); + perror("recv"); return NGL_ERROR_IO; } nr += n; diff --git a/ngl-tools/ngl-desktop.c b/ngl-tools/ngl-desktop.c index d311694b39..eedcce5928 100644 --- a/ngl-tools/ngl-desktop.c +++ b/ngl-tools/ngl-desktop.c @@ -202,7 +202,7 @@ static int handle_tag_filepart(struct ctx *s, const uint8_t *data, int size) return ipc_pkt_add_rtag_fileend(s->send_pkt, s->upload_path); } - const ssize_t n = write(s->upload_fd, data, size); + const ssize_t n = send(s->upload_fd, data, size, 0); if (n < 0) { perror("write"); close_upload_file(s); diff --git a/ngl-tools/ngl-ipc.c b/ngl-tools/ngl-ipc.c index fbdfacb4a0..cbaea0e1d8 100644 --- a/ngl-tools/ngl-ipc.c +++ b/ngl-tools/ngl-ipc.c @@ -246,7 +246,7 @@ static int handle_response(struct ctx *s, const struct ipc_pkt *pkt) if (s->upload_fd > 0) { ipc_pkt_reset(s->send_pkt); - const ssize_t n = read(s->upload_fd, s->upload_buffer, UPLOAD_CHUNK_SIZE); + const ssize_t n = recv(s->upload_fd, s->upload_buffer, UPLOAD_CHUNK_SIZE, 0); if (n < 0) return NGL_ERROR_IO; int ret = ipc_pkt_add_qtag_filepart(s->send_pkt, s->upload_buffer, n); From e1a7449c2781f31fe348ea917146f7df2e4b4c61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 21 Sep 2020 00:09:24 +0200 Subject: [PATCH 103/388] tools,utils: replace ':' with '-' in ngl-desktop session path ':' is not supported on Windows. --- ngl-tools/ngl-desktop.c | 2 +- .../pynodegl_utils/hooks/desktop/hook.get_session_info | 4 ++-- .../pynodegl_utils/hooks/desktop/hook.get_sessions | 8 ++++---- .../pynodegl_utils/hooks/desktop/hook.scene_change | 4 ++-- .../pynodegl_utils/hooks/desktop/hook.sync_file | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ngl-tools/ngl-desktop.c b/ngl-tools/ngl-desktop.c index eedcce5928..376eb9231c 100644 --- a/ngl-tools/ngl-desktop.c +++ b/ngl-tools/ngl-desktop.c @@ -511,7 +511,7 @@ static int makedirs(const char *path, int mode) static int setup_paths(struct ctx *s) { - int ret = snprintf(s->root_dir, sizeof(s->root_dir), "/tmp/ngl-desktop/%s:%s/", s->host, s->port); + int ret = snprintf(s->root_dir, sizeof(s->root_dir), "/tmp/ngl-desktop/%s-%s/", s->host, s->port); if (ret < 0 || ret >= sizeof(s->root_dir)) return ret; ret = snprintf(s->files_dir, sizeof(s->files_dir), "%sfiles/", s->root_dir); diff --git a/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.get_session_info b/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.get_session_info index 997ce01a38..d19720dc1f 100755 --- a/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.get_session_info +++ b/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.get_session_info @@ -3,8 +3,8 @@ set -e session=$1 -host=${session%%:*} -port=${session##*:} +host=${session%%-*} +port=${session##*-} set -x ngl-ipc -x "$host" -p "$port" -? diff --git a/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.get_sessions b/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.get_sessions index 29684b977a..0d045034f9 100755 --- a/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.get_sessions +++ b/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.get_sessions @@ -5,16 +5,16 @@ set -e for session_dir in /tmp/ngl-desktop/*; do [ ! -e "$session_dir/session" ] && continue # for when no file is available session=${session_dir##*/} - host=${session%%:*} - port=${session##*:} + host=${session%%-*} + port=${session##*-} if ngl-ipc -x $host -p $port >/dev/null; then echo $session local ngl-desktop fi done for session in $NGL_DESKTOP_REMOTE_SESSIONS; do - host=${session%%:*} - port=${session##*:} + host=${session%%-*} + port=${session##*-} if ngl-ipc -x $host -p $port >/dev/null; then echo $session remote ngl-desktop fi diff --git a/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.scene_change b/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.scene_change index 50c61927b6..883c329d49 100755 --- a/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.scene_change +++ b/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.scene_change @@ -7,8 +7,8 @@ scenefile=$2 shift 2 eval $@ -host=${session%%:*} -port=${session##*:} +host=${session%%-*} +port=${session##*-} set -x diff --git a/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.sync_file b/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.sync_file index 56cefe0efe..cca702862a 100755 --- a/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.sync_file +++ b/pynodegl-utils/pynodegl_utils/hooks/desktop/hook.sync_file @@ -6,8 +6,8 @@ session=$1 ifile=$2 ofile=$3 -host=${session%%:*} -port=${session##*:} +host=${session%%-*} +port=${session##*-} # Upload file only if it's not local if [ "$host" = "localhost" ]; then From 843f80adea093e355add755b75183de8d993b7be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Sat, 19 Sep 2020 21:56:43 +0200 Subject: [PATCH 104/388] tools: add winsock2 support MinGW doesn't include a network compatibility layer so we have to use the winsock2 API directly instead. The API is mostly the same as POSIX, with a few exceptions. One thing not handled properly in this commit is the error management, which is different with winsock2. --- ngl-tools/Makefile | 15 +++++++++++---- ngl-tools/ipc.c | 4 ++++ ngl-tools/ngl-desktop.c | 25 +++++++++++++++++++++++-- ngl-tools/ngl-ipc.c | 14 ++++++++++++++ 4 files changed, 52 insertions(+), 6 deletions(-) diff --git a/ngl-tools/Makefile b/ngl-tools/Makefile index 1264a2b16e..44784858ee 100644 --- a/ngl-tools/Makefile +++ b/ngl-tools/Makefile @@ -21,6 +21,9 @@ include ../common.mak +NETWORK_CFLAGS := +NETWORK_LDLIBS := + SDL_CFLAGS := $(shell $(PKG_CONFIG) --cflags sdl2) SDL_LDLIBS := $(shell $(PKG_CONFIG) --libs sdl2) @@ -34,6 +37,10 @@ ifeq ($(TARGET_OS),Darwin) TOOLS_LDLIBS += -framework AppKit endif +ifeq ($(TARGET_OS),MinGW-w64) +NETWORK_LDLIBS += -lws2_32 +endif + # Warning: while all 3 "python{,2,3}-config" should work as expected, # "pkg-config --exists python" never will, so an explicit version is needed. HAS_PYTHON := $(if $(shell pkg-config --exists python$(PYTHON_MAJOR) && echo 1),yes,no) @@ -78,12 +85,12 @@ WSI_OBJS_Darwin = wsi_cocoa.o WSI_OBJS_MinGW-w64 = wsi_windows.o WSI_OBJS = wsi.o $(WSI_OBJS_$(TARGET_OS)) -ngl-desktop$(EXESUF): CFLAGS = $(PROJECT_CFLAGS) $(SDL_CFLAGS) $(TOOLS_CFLAGS) -ngl-desktop$(EXESUF): LDLIBS = $(PROJECT_LDLIBS) $(SDL_LDLIBS) $(TOOLS_LDLIBS) -lpthread +ngl-desktop$(EXESUF): CFLAGS = $(PROJECT_CFLAGS) $(SDL_CFLAGS) $(TOOLS_CFLAGS) $(NETWORK_CFLAGS) +ngl-desktop$(EXESUF): LDLIBS = $(PROJECT_LDLIBS) $(SDL_LDLIBS) $(TOOLS_LDLIBS) $(NETWORK_LDLIBS) -lpthread ngl-desktop$(EXESUF): ngl-desktop.o ipc.o player.o opts.o $(WSI_OBJS) -ngl-ipc$(EXESUF): CFLAGS = $(PROJECT_CFLAGS) $(TOOLS_CFLAGS) -ngl-ipc$(EXESUF): LDLIBS = $(PROJECT_LDLIBS) $(TOOLS_LDLIBS) +ngl-ipc$(EXESUF): CFLAGS = $(PROJECT_CFLAGS) $(TOOLS_CFLAGS) $(NETWORK_CFLAGS) +ngl-ipc$(EXESUF): LDLIBS = $(PROJECT_LDLIBS) $(TOOLS_LDLIBS) $(NETWORK_LDLIBS) ngl-ipc$(EXESUF): ngl-ipc.o ipc.o opts.o ngl-serialize$(EXESUF): CFLAGS = $(PROJECT_CFLAGS) $(TOOLS_CFLAGS) $(PYTHON_CFLAGS) diff --git a/ngl-tools/ipc.c b/ngl-tools/ipc.c index dfb2b37395..1a2edd85e9 100644 --- a/ngl-tools/ipc.c +++ b/ngl-tools/ipc.c @@ -23,7 +23,11 @@ #include #include #include +#ifdef _WIN32 +#include +#else #include +#endif #include diff --git a/ngl-tools/ngl-desktop.c b/ngl-tools/ngl-desktop.c index 376eb9231c..128b371e98 100644 --- a/ngl-tools/ngl-desktop.c +++ b/ngl-tools/ngl-desktop.c @@ -26,12 +26,17 @@ #include #include #include +#ifdef _WIN32 +#include +#include +#else #include -#include #include +#include +#endif +#include #include #include -#include #include #include @@ -429,6 +434,15 @@ static struct ngl_node *get_default_scene(const char *host, const char *port) static int setup_network(struct ctx *s) { +#if _WIN32 + WSADATA wsa_data; + int sret = WSAStartup(MAKEWORD(2, 2), &wsa_data); + if (sret != 0) { + fprintf(stderr, "WSAStartup: failed with %d\n", sret); + return NGL_ERROR_IO; + } +#endif + struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, @@ -593,11 +607,18 @@ int main(int argc, char *argv[]) * of behaviour is to prevent a possible race scenario where the same * FD would be re-used between a close() and an accept(). * + * On Windows, both shutdown() and close() won't work, we have to use a + * Windows specific one: closesocket(). + * * See https://bugzilla.kernel.org/show_bug.cgi?id=106241 for more * information. */ +#if _WIN32 + closesocket(s.sock_fd); +#else if (shutdown(s.sock_fd, SHUT_RDWR) < 0) perror("shutdown"); +#endif close(s.sock_fd); } diff --git a/ngl-tools/ngl-ipc.c b/ngl-tools/ngl-ipc.c index cbaea0e1d8..9d069aa300 100644 --- a/ngl-tools/ngl-ipc.c +++ b/ngl-tools/ngl-ipc.c @@ -25,8 +25,13 @@ #include #include #include +#ifdef _WIN32 +#include +#include +#else #include #include +#endif #include #include #include @@ -275,6 +280,15 @@ int main(int argc, char *argv[]) return ret == OPT_HELP ? 0 : EXIT_FAILURE; } +#if _WIN32 + WSADATA wsa_data; + int sret = WSAStartup(MAKEWORD(2, 2), &wsa_data); + if (sret != 0) { + fprintf(stderr, "WSAStartup: failed with %d\n", sret); + return NGL_ERROR_IO; + } +#endif + struct addrinfo *addr_info = NULL; int fd = -1; From 6c6e72f63411adf69249bcf6ecfad68f8e4c4e1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 21 Sep 2020 00:01:34 +0200 Subject: [PATCH 105/388] tools/desktop: fix mkdir on Windows --- ngl-tools/ngl-desktop.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ngl-tools/ngl-desktop.c b/ngl-tools/ngl-desktop.c index 128b371e98..ac6737f082 100644 --- a/ngl-tools/ngl-desktop.c +++ b/ngl-tools/ngl-desktop.c @@ -29,6 +29,7 @@ #ifdef _WIN32 #include #include +#include #else #include #include @@ -511,7 +512,11 @@ static int makedirs(const char *path, int mode) continue; } *next = 0; +#ifdef _WIN32 + const int r = _mkdir(cur_path); +#else const int r = mkdir(cur_path, mode); +#endif *next = '/'; if (r < 0 && errno != EEXIST) { perror(cur_path); From 26636e86e125bf7d6e4d02e4030ddbc402cd6eab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 21 Sep 2020 00:04:10 +0200 Subject: [PATCH 106/388] tools/desktop: remove use of uname() on Windows --- ngl-tools/ngl-desktop.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ngl-tools/ngl-desktop.c b/ngl-tools/ngl-desktop.c index ac6737f082..33eee7b387 100644 --- a/ngl-tools/ngl-desktop.c +++ b/ngl-tools/ngl-desktop.c @@ -286,13 +286,18 @@ static int handle_tag_info(const uint8_t *data, int size, int fd, struct ctx *s) if (!backend_str) return NGL_ERROR_BUG; +#ifdef _WIN32 + const char *sysname = "Windows"; +#else struct utsname name; int ret = uname(&name); if (ret < 0) return NGL_ERROR_GENERIC; + const char *sysname = name.sysname; +#endif char info[256]; - snprintf(info, sizeof(info), "backend=%s\nsystem=%s\n", backend_str, name.sysname); + snprintf(info, sizeof(info), "backend=%s\nsystem=%s\n", backend_str, sysname); return ipc_pkt_add_rtag_info(s->send_pkt, info); } From 359cadb698ad393aabe052201a0756507ebdce32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 21 Sep 2020 00:06:27 +0200 Subject: [PATCH 107/388] tools/desktop: cast option value argument in setsockopt() With winsock2, the argument is a char * (but interpreted the same as POSIX). --- ngl-tools/ngl-desktop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ngl-tools/ngl-desktop.c b/ngl-tools/ngl-desktop.c index 33eee7b387..c59622f8aa 100644 --- a/ngl-tools/ngl-desktop.c +++ b/ngl-tools/ngl-desktop.c @@ -468,7 +468,7 @@ static int setup_network(struct ctx *s) continue; int yes = 1; - ret = setsockopt(s->sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + ret = setsockopt(s->sock_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes)); if (ret == -1) { perror("setsockopt"); break; From dc5e4d70adb2adebdc8768889714c10c5cec779e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 21 Sep 2020 11:01:18 +0200 Subject: [PATCH 108/388] log: force stdout flushing after logging a line This addresses a flushing issue at least on MinGW where both stderr and stdout are fully buffered, even when using fprintf. An alternative would be to configure the flushing policy with setvbuf(), but this can have unexpected behaviour for library users. Typically, we don't want to get unbuffered behaviour in the ngl-tools where the user make use of the pipes to communicate data between tools. --- libnodegl/log.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libnodegl/log.c b/libnodegl/log.c index 4999ff054b..8b43597f29 100644 --- a/libnodegl/log.c +++ b/libnodegl/log.c @@ -58,6 +58,7 @@ static void default_callback(void *arg, int level, const char *filename, int ln, printf("%s[%s] %s:%d %s: %s%s\n", color_start, log_strs[level], filename, ln, fn, logline, color_end); + fflush(stdout); } static struct { From be79303c54aca5f8c17744e66a5de2cc355de8a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 21 Sep 2020 00:37:01 +0200 Subject: [PATCH 109/388] doc/install: document Windows quick setup --- doc/howto/installation.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/doc/howto/installation.md b/doc/howto/installation.md index 3624ec53c7..fb72e28cad 100644 --- a/doc/howto/installation.md +++ b/doc/howto/installation.md @@ -14,7 +14,7 @@ for building and running the complete **node.gl** stack: - **Graphviz** - **SDL2** -## Quick user installation +## Quick user installation on Linux and MacOS The following steps describe how to install `node.gl` and its dependencies in your home user directory, without polluting your system (aside from the system @@ -38,6 +38,22 @@ number of parallel processes. **Note**: to leave the environment, you can use `deactivate`. +## Quick user installation on Windows + +On Windows, the bootstrap is slightly more complex: + +- install [MSYS2](https://www.msys2.org/) +- run MinGW64 shell (*NOT* MSYS2, "MINGW64" should be visible in the prompt) +- run the following in the shell: +```shell +pacman -Syuu # and restart the shell +pacman -S git make +pacman -S mingw-w64-x86_64-{toolchain,ffmpeg,python} +make TARGET_OS=MinGW-w64 +``` + +Then you should be able to enter the environment and run the tools. + ## Installation of `libnodegl` (the core library) ### Build From 023e9c3ca6d0a8f7c124a9652afb052a798eb508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 22 Sep 2020 20:33:58 +0200 Subject: [PATCH 110/388] tools/build: automatically workaround a Python linking bug specific to Ubuntu/Debian MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The issue has been reported¹ months ago, and ignored. [1]: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=959945 --- .github/workflows/ci_linux.yml | 22 ---------------------- ngl-tools/Makefile | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci_linux.yml b/.github/workflows/ci_linux.yml index 3e03e7c497..bfca20fcfb 100644 --- a/.github/workflows/ci_linux.yml +++ b/.github/workflows/ci_linux.yml @@ -30,28 +30,6 @@ jobs: - name: Build run: | - # - # This -fPIE is a workaround for the following link issue: - # relocation R_X86_64_32 against `.rodata.str1.8' can not be used - # when making a PIE object; recompile with -fPIE - # - # To summarize the issue: - # - # - On Python side, `python3-config` is a script generated from a - # template using a global CFLAGS. But at the same time, the script - # uses LIBS and SYSLIBS to construct the LDFLAGS (and not LDFLAGS for - # some moronic reason). - # - On Debian/Ubuntu side, they decided to have the following injected: - # CFLAGS="-specs=/usr/share/dpkg/no-pie-compile.specs" - # LDFLAGS="-specs=/usr/share/dpkg/no-pie-link.specs" - # And unfortunately, the former can not go without the latter - # (otherwise there is a link error). - # - # As a result, `python3-config --cflags` will return the `-specs=...` - # flags, but `python3-config --ldflags` will not return the - # complementary `-specs=...`. - # - export CFLAGS=-fPIE make -j$(($(nproc)+1)) - name: Run tests with GL backend run: | diff --git a/ngl-tools/Makefile b/ngl-tools/Makefile index 44784858ee..de66c85994 100644 --- a/ngl-tools/Makefile +++ b/ngl-tools/Makefile @@ -74,6 +74,32 @@ PYTHON_LDLIBS_EMBED-yes = $(shell python$(PYTHON_MAJOR)-config --ldflags --embed PYTHON_HAS_EMBED := $(shell python$(PYTHON_MAJOR)-config --embed >/dev/null && echo yes || echo no) PYTHON_LDLIBS := $(PYTHON_LDLIBS_EMBED-$(PYTHON_HAS_EMBED)) TOOLS += python serialize + +# +# This is a workaround for the following link issue: +# relocation R_X86_64_32 against `.rodata.str1.8' can not be used +# when making a PIE object; recompile with -fPIE +# +# To summarize the issue: +# +# - On Python side, `python3-config` is a script generated from a +# template using a global CFLAGS. But at the same time, the script +# uses LIBS and SYSLIBS to construct the LDFLAGS (and not LDFLAGS for +# some moronic reason). +# - On Debian/Ubuntu side, they decided to have the following injected: +# CFLAGS="-specs=/usr/share/dpkg/no-pie-compile.specs" +# LDFLAGS="-specs=/usr/share/dpkg/no-pie-link.specs" +# And unfortunately, the former can not go without the latter +# (otherwise there is a link error). +# +# As a result, `python3-config --cflags` will return the `-specs=...` +# flags, but `python3-config --ldflags` will not return the +# complementary `-specs=...`. +# +DISTRIB_ID := $(or $(shell lsb_release -si 2>/dev/null),none) +ifeq ($(DISTRIB_ID),$(filter $(DISTRIB_ID),Ubuntu Debian)) +PYTHON_LDLIBS += -specs=/usr/share/dpkg/no-pie-link.specs +endif endif TOOLS_BINS = $(addprefix ngl-, $(addsuffix $(EXESUF), $(TOOLS))) From 1fd4164cf1f13f53338144d958c2ec53da92cbe2 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Wed, 9 Sep 2020 14:58:49 -0700 Subject: [PATCH 111/388] glcontext_egl: guard ngli_eglGetNativeClientBufferANDROID() declaration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit struct AHardwareBuffer might not be declared in EGL/eglext.h, causing a duplicated mismatching inside-parameter declaration of struct. Fixes build issue on Ubuntu 18.10: glcontext_egl.c:86:17: error: conflicting types for ‘ngli_eglGetNativeClientBufferANDROID’ --- libnodegl/egl.h | 2 ++ libnodegl/glcontext_egl.c | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/libnodegl/egl.h b/libnodegl/egl.h index 677522a876..eec5962c96 100644 --- a/libnodegl/egl.h +++ b/libnodegl/egl.h @@ -36,7 +36,9 @@ EGLImageKHR ngli_eglCreateImageKHR(struct glcontext *gl, EGLBoolean ngli_eglDestroyImageKHR(struct glcontext *gl, EGLImageKHR image); +#if defined(TARGET_ANDROID) EGLClientBuffer ngli_eglGetNativeClientBufferANDROID(struct glcontext *gl, const struct AHardwareBuffer *buffer); +#endif #endif diff --git a/libnodegl/glcontext_egl.c b/libnodegl/glcontext_egl.c index 1333c84fca..91df1d2e0a 100644 --- a/libnodegl/glcontext_egl.c +++ b/libnodegl/glcontext_egl.c @@ -61,7 +61,9 @@ struct egl_priv { EGLDisplay (*GetPlatformDisplay)(EGLenum platform, void *native_display, const EGLint *attrib_list); EGLAPIENTRY EGLImageKHR (*CreateImageKHR)(EGLDisplay, EGLContext, EGLenum, EGLClientBuffer, const EGLint *); EGLAPIENTRY EGLBoolean (*DestroyImageKHR)(EGLDisplay, EGLImageKHR); +#if defined(TARGET_ANDROID) EGLAPIENTRY EGLClientBuffer (*GetNativeClientBufferANDROID)(const struct AHardwareBuffer *); +#endif int has_platform_x11_ext; int has_platform_mesa_surfaceless_ext; int has_platform_wayland_ext; @@ -83,11 +85,13 @@ EGLBoolean ngli_eglDestroyImageKHR(struct glcontext *gl, EGLImageKHR image) return egl->DestroyImageKHR(egl->display, image); } +#if defined(TARGET_ANDROID) EGLClientBuffer ngli_eglGetNativeClientBufferANDROID(struct glcontext *gl, const struct AHardwareBuffer *buffer) { struct egl_priv *egl = gl->priv_data; return egl->GetNativeClientBufferANDROID(buffer); } +#endif static int egl_probe_extensions(struct glcontext *ctx) { From ef9860797e71ab4e81facb816a2f4cfce7d20ae4 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Mon, 21 Sep 2020 08:55:41 -0700 Subject: [PATCH 112/388] internal: change features flag from int to uin64t_t Signed-off-by: Matthieu Bouron --- libnodegl/gctx.h | 2 +- libnodegl/glcontext.h | 2 +- libnodegl/glfeatures_data.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index 92c82563d5..e6ee7929da 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -105,7 +105,7 @@ struct gctx { struct ngl_config config; const struct gctx_class *class; int version; - int features; + uint64_t features; struct limits limits; }; diff --git a/libnodegl/glcontext.h b/libnodegl/glcontext.h index 2ca04e640c..18aa4efd20 100644 --- a/libnodegl/glcontext.h +++ b/libnodegl/glcontext.h @@ -48,7 +48,7 @@ struct glcontext { int version; /* GL features */ - int features; + uint64_t features; /* GL limits */ struct limits limits; diff --git a/libnodegl/glfeatures_data.h b/libnodegl/glfeatures_data.h index f6257bd035..640df9eb46 100644 --- a/libnodegl/glfeatures_data.h +++ b/libnodegl/glfeatures_data.h @@ -26,7 +26,7 @@ #define OFFSET(x) offsetof(struct glfunctions, x) static const struct glfeature { const char *name; - int flag; + uint64_t flag; size_t offset; int version; int es_version; From 3cd54499a18e038c9d29f54cfd7fc7e9a7605021 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 23 Sep 2020 18:18:20 +0200 Subject: [PATCH 113/388] gl: move NGLI_GL_APIENTRY to glincludes.h --- libnodegl/gen-gl-wrappers.py | 6 ------ libnodegl/glfunctions.h | 6 ------ libnodegl/glincludes.h | 6 ++++++ 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/libnodegl/gen-gl-wrappers.py b/libnodegl/gen-gl-wrappers.py index 9b0c9b2bd3..f64bc29e61 100644 --- a/libnodegl/gen-gl-wrappers.py +++ b/libnodegl/gen-gl-wrappers.py @@ -277,12 +277,6 @@ def gen(gl_xml): #include "glincludes.h" -#ifdef _WIN32 -#define NGLI_GL_APIENTRY WINAPI -#else -#define NGLI_GL_APIENTRY -#endif - struct glfunctions { ''' diff --git a/libnodegl/glfunctions.h b/libnodegl/glfunctions.h index e304f55b03..601f3a9174 100644 --- a/libnodegl/glfunctions.h +++ b/libnodegl/glfunctions.h @@ -5,12 +5,6 @@ #include "glincludes.h" -#ifdef _WIN32 -#define NGLI_GL_APIENTRY WINAPI -#else -#define NGLI_GL_APIENTRY -#endif - struct glfunctions { NGLI_GL_APIENTRY void (*ActiveTexture)(GLenum texture); NGLI_GL_APIENTRY void (*AttachShader)(GLuint program, GLuint shader); diff --git a/libnodegl/glincludes.h b/libnodegl/glincludes.h index 9063a7f14b..5e2b43806a 100644 --- a/libnodegl/glincludes.h +++ b/libnodegl/glincludes.h @@ -59,6 +59,12 @@ # define NGL_OGL3_COMPAT_INCLUDES 1 #endif +#ifdef _WIN32 +#define NGLI_GL_APIENTRY WINAPI +#else +#define NGLI_GL_APIENTRY +#endif + #ifndef GL_OES_EGL_image typedef void* GLeglImageOES; #endif From e0d575b055279a4fbe187d80ed2ac169dbe5f522 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Wed, 23 Sep 2020 07:06:05 -0700 Subject: [PATCH 114/388] glfeatures: add khr_debug feature Signed-off-by: Matthieu Bouron --- libnodegl/features.h | 1 + libnodegl/gen-gl-wrappers.py | 3 +++ libnodegl/gldefinitions_data.h | 1 + libnodegl/glfeatures_data.h | 7 +++++++ libnodegl/glfunctions.h | 1 + libnodegl/glincludes.h | 30 ++++++++++++++++++++++++++++++ libnodegl/glwrappers.h | 6 ++++++ 7 files changed, 49 insertions(+) diff --git a/libnodegl/features.h b/libnodegl/features.h index e6efdbb1a4..05679caaff 100644 --- a/libnodegl/features.h +++ b/libnodegl/features.h @@ -53,6 +53,7 @@ #define NGLI_FEATURE_SOFTWARE (1 << 28) #define NGLI_FEATURE_UINT_UNIFORMS (1 << 29) #define NGLI_FEATURE_EGL_ANDROID_GET_IMAGE_NATIVE_CLIENT_BUFFER (1 << 30) +#define NGLI_FEATURE_KHR_DEBUG (1ULL << 31) #define NGLI_FEATURE_COMPUTE_SHADER_ALL (NGLI_FEATURE_COMPUTE_SHADER | \ NGLI_FEATURE_PROGRAM_INTERFACE_QUERY | \ diff --git a/libnodegl/gen-gl-wrappers.py b/libnodegl/gen-gl-wrappers.py index f64bc29e61..1ba3eda76e 100644 --- a/libnodegl/gen-gl-wrappers.py +++ b/libnodegl/gen-gl-wrappers.py @@ -73,6 +73,9 @@ 'glDeleteQueries', 'glGetQueryObjectui64v', + # Debug + 'glDebugMessageCallback', + # Query EXT 'glBeginQueryEXT', 'glEndQueryEXT', diff --git a/libnodegl/gldefinitions_data.h b/libnodegl/gldefinitions_data.h index 5fd743a4d8..b9e02b5a67 100644 --- a/libnodegl/gldefinitions_data.h +++ b/libnodegl/gldefinitions_data.h @@ -43,6 +43,7 @@ static const struct gldefinition { {"glCreateProgram", offsetof(struct glfunctions, CreateProgram), M}, {"glCreateShader", offsetof(struct glfunctions, CreateShader), M}, {"glCullFace", offsetof(struct glfunctions, CullFace), M}, + {"glDebugMessageCallback", offsetof(struct glfunctions, DebugMessageCallback), 0}, {"glDeleteBuffers", offsetof(struct glfunctions, DeleteBuffers), M}, {"glDeleteFramebuffers", offsetof(struct glfunctions, DeleteFramebuffers), M}, {"glDeleteProgram", offsetof(struct glfunctions, DeleteProgram), M}, diff --git a/libnodegl/glfeatures_data.h b/libnodegl/glfeatures_data.h index 640df9eb46..267f261fac 100644 --- a/libnodegl/glfeatures_data.h +++ b/libnodegl/glfeatures_data.h @@ -251,5 +251,12 @@ static const struct glfeature { OFFSET(Uniform3uiv), OFFSET(Uniform4uiv), -1} + }, { + .name = "khr_debug", + .flag = NGLI_FEATURE_KHR_DEBUG, + .version = 430, + .es_version = 320, + .funcs_offsets = (const size_t[]){OFFSET(DebugMessageCallback), + -1} } }; diff --git a/libnodegl/glfunctions.h b/libnodegl/glfunctions.h index 601f3a9174..acbcad7560 100644 --- a/libnodegl/glfunctions.h +++ b/libnodegl/glfunctions.h @@ -36,6 +36,7 @@ struct glfunctions { NGLI_GL_APIENTRY GLuint (*CreateProgram)(); NGLI_GL_APIENTRY GLuint (*CreateShader)(GLenum type); NGLI_GL_APIENTRY void (*CullFace)(GLenum mode); + NGLI_GL_APIENTRY void (*DebugMessageCallback)(GLDEBUGPROC callback, const void * userParam); NGLI_GL_APIENTRY void (*DeleteBuffers)(GLsizei n, const GLuint * buffers); NGLI_GL_APIENTRY void (*DeleteFramebuffers)(GLsizei n, const GLuint * framebuffers); NGLI_GL_APIENTRY void (*DeleteProgram)(GLuint program); diff --git a/libnodegl/glincludes.h b/libnodegl/glincludes.h index 5e2b43806a..6c8fca6204 100644 --- a/libnodegl/glincludes.h +++ b/libnodegl/glincludes.h @@ -35,6 +35,7 @@ # define NGL_CS_COMPAT_INCLUDES 1 # define NGL_OGL3_COMPAT_INCLUDES 1 # endif +# define NGL_KHR_DEBUG_COMPAT_INCLUDES 1 #endif #if __ANDROID__ @@ -42,6 +43,7 @@ # include # define NGL_GLES2_COMPAT_INCLUDES 1 # define NGL_CS_COMPAT_INCLUDES 1 +# define NGL_KHR_DEBUG_COMPAT_INCLUDES 1 # define GL_BGRA GL_BGRA_EXT #endif @@ -69,6 +71,34 @@ typedef void* GLeglImageOES; #endif +#if !defined(GL_VERSION_4_3) && !defined(GL_ES_VERSION_3_2) +typedef void (NGLI_GL_APIENTRY *GLDEBUGPROC)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *user_param); +#endif + +#if NGL_KHR_DEBUG_COMPAT_INCLUDES +# define GL_DEBUG_OUTPUT 0x92E0 +# define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242 +# define GL_DEBUG_SOURCE_API 0x8246 +# define GL_DEBUG_SOURCE_WINDOW_SYSTEM 0x8247 +# define GL_DEBUG_SOURCE_SHADER_COMPILER 0x8248 +# define GL_DEBUG_SOURCE_THIRD_PARTY 0x8249 +# define GL_DEBUG_SOURCE_APPLICATION 0x824A +# define GL_DEBUG_SOURCE_OTHER 0x824B +# define GL_DEBUG_TYPE_ERROR 0x824C +# define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR 0x824D +# define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR 0x824E +# define GL_DEBUG_TYPE_PORTABILITY 0x824F +# define GL_DEBUG_TYPE_PERFORMANCE 0x8250 +# define GL_DEBUG_TYPE_OTHER 0x8251 +# define GL_DEBUG_TYPE_MARKER 0x8268 +# define GL_DEBUG_TYPE_PUSH_GROUP 0x8269 +# define GL_DEBUG_TYPE_POP_GROUP 0x826A +# define GL_DEBUG_SEVERITY_HIGH 0x9146 +# define GL_DEBUG_SEVERITY_MEDIUM 0x9147 +# define GL_DEBUG_SEVERITY_LOW 0x9148 +# define GL_DEBUG_SEVERITY_NOTIFICATION 0x826B +#endif + #if NGL_OGL3_COMPAT_INCLUDES # define GL_LUMINANCE 0x1909 # define GL_LUMINANCE_ALPHA 0x190A diff --git a/libnodegl/glwrappers.h b/libnodegl/glwrappers.h index d06ec5c8dc..435ff110f3 100644 --- a/libnodegl/glwrappers.h +++ b/libnodegl/glwrappers.h @@ -192,6 +192,12 @@ static inline void ngli_glCullFace(const struct glcontext *gl, GLenum mode) check_error_code(gl, "glCullFace"); } +static inline void ngli_glDebugMessageCallback(const struct glcontext *gl, GLDEBUGPROC callback, const void * userParam) +{ + gl->funcs.DebugMessageCallback(callback, userParam); + check_error_code(gl, "glDebugMessageCallback"); +} + static inline void ngli_glDeleteBuffers(const struct glcontext *gl, GLsizei n, const GLuint * buffers) { gl->funcs.DeleteBuffers(n, buffers); From cf815ae5f8c439bd6245164fc953ebd137634cc9 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 24 Sep 2020 09:36:10 +0200 Subject: [PATCH 115/388] glcontext_wgl: create a debug context if DEBUG_GL is defined --- libnodegl/glcontext_wgl.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libnodegl/glcontext_wgl.c b/libnodegl/glcontext_wgl.c index 402ddf5adf..2c2b88f029 100644 --- a/libnodegl/glcontext_wgl.c +++ b/libnodegl/glcontext_wgl.c @@ -188,6 +188,9 @@ static int wgl_init(struct glcontext *ctx, uintptr_t display, uintptr_t window, WGL_CONTEXT_MAJOR_VERSION_ARB, 1, WGL_CONTEXT_MINOR_VERSION_ARB, 0, WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, +#if DEBUG_GL + WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_DEBUG_BIT_ARB, +#endif 0 }; wgl->rendering_context = wgl->CreateContextAttribsARB(wgl->device_context, shared_context, context_attributes); From b8371f71496d4d8ee306d669fe9c19653003d4ca Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Thu, 24 Sep 2020 09:39:56 +0200 Subject: [PATCH 116/388] glcontext_egl: create a debug context if DEBUG_GL is defined Signed-off-by: Matthieu Bouron --- libnodegl/glcontext_egl.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libnodegl/glcontext_egl.c b/libnodegl/glcontext_egl.c index 91df1d2e0a..a824af23cb 100644 --- a/libnodegl/glcontext_egl.c +++ b/libnodegl/glcontext_egl.c @@ -318,6 +318,9 @@ try_again:; EGL_CONTEXT_MAJOR_VERSION_KHR, gl_versions[i].major, EGL_CONTEXT_MINOR_VERSION_KHR, gl_versions[i].minor, EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR, +#ifdef DEBUG_GL + EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, +#endif EGL_NONE }; From 4f23f1c536d4e3d851d0c01cb438647064eafd2e Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Thu, 24 Sep 2020 09:42:11 +0200 Subject: [PATCH 117/388] gctx_gl: enable debug output if DEBUG_GL is defined Signed-off-by: Matthieu Bouron --- libnodegl/gctx_gl.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index c8b15ac844..55789d08f1 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -220,6 +220,23 @@ static struct gctx *gl_create(const struct ngl_config *config) return (struct gctx *)s; } +#ifdef DEBUG_GL +#define GL_DEBUG_LOG(log_level, ...) ngli_log_print(log_level, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) + +static void NGLI_GL_APIENTRY gl_debug_message_callback(GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar *message, + const void *user_param) +{ + const int log_level = type == GL_DEBUG_TYPE_ERROR ? NGL_LOG_ERROR : NGL_LOG_DEBUG; + const char *msg_type = type == GL_DEBUG_TYPE_ERROR ? "ERROR" : "GENERAL"; + GL_DEBUG_LOG(log_level, "%s: %s", msg_type, message); +} +#endif + static int gl_init(struct gctx *s) { int ret; @@ -233,6 +250,13 @@ static int gl_init(struct gctx *s) struct glcontext *gl = s_priv->glcontext; s->features = gl->features; +#ifdef DEBUG_GL + if ((gl->features & NGLI_FEATURE_KHR_DEBUG)) { + ngli_glEnable(gl, GL_DEBUG_OUTPUT_SYNCHRONOUS); + ngli_glDebugMessageCallback(gl, gl_debug_message_callback, NULL); + } +#endif + if (gl->offscreen) { ret = offscreen_rendertarget_init(s); if (ret < 0) From 24bb8e188986822739e6676fa21cea2dac308643 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 21 Sep 2020 20:47:45 +0200 Subject: [PATCH 118/388] glcontext_wgl: do not create a puffer for offscreen rendering node.gl offscreen rendering relies on FBOs, creating a dedicated pixel buffer context is not required and superfluous. --- libnodegl/glcontext_wgl.c | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/libnodegl/glcontext_wgl.c b/libnodegl/glcontext_wgl.c index 2c2b88f029..14010bb485 100644 --- a/libnodegl/glcontext_wgl.c +++ b/libnodegl/glcontext_wgl.c @@ -32,16 +32,11 @@ struct wgl_priv { HWND window; - HPBUFFERARB pixel_buffer; HDC device_context; HGLRC rendering_context; HMODULE module; PFNWGLCHOOSEPIXELFORMATARBPROC ChoosePixelFormatARB; PFNWGLCREATECONTEXTATTRIBSARBPROC CreateContextAttribsARB; - PFNWGLCREATEPBUFFERARBPROC CreatePbufferARB; - PFNWGLRELEASEPBUFFERDCARBPROC ReleasePbufferDCARB; - PFNWGLDESTROYPBUFFERARBPROC DestroyPbufferARB; - PFNWGLGETPBUFFERDCARBPROC GetPbufferDCARB; PFNWGLGETEXTENSIONSSTRINGARBPROC GetExtensionsStringARB; PFNWGLSWAPINTERVALEXTPROC SwapIntervalEXT; }; @@ -115,10 +110,6 @@ static int wgl_init(struct glcontext *ctx, uintptr_t display, uintptr_t window, } extensions[] = { {"wglChoosePixelFormatARB", offsetof(struct wgl_priv, ChoosePixelFormatARB)}, {"wglCreateContextAttribsARB", offsetof(struct wgl_priv, CreateContextAttribsARB)}, - {"wglCreatePbufferARB", offsetof(struct wgl_priv, CreatePbufferARB)}, - {"wglReleasePbufferDCARB", offsetof(struct wgl_priv, ReleasePbufferDCARB)}, - {"wglDestroyPbufferARB", offsetof(struct wgl_priv, DestroyPbufferARB)}, - {"wglGetPbufferDCARB", offsetof(struct wgl_priv, GetPbufferDCARB)}, {"wglGetExtensionsStringARB", offsetof(struct wgl_priv, GetExtensionsStringARB)}, }; @@ -140,7 +131,6 @@ static int wgl_init(struct glcontext *ctx, uintptr_t display, uintptr_t window, const int pixel_format_attributes[] = { WGL_DRAW_TO_WINDOW_ARB, GL_TRUE, - WGL_DRAW_TO_PBUFFER_ARB, GL_TRUE, WGL_SUPPORT_OPENGL_ARB, GL_TRUE, WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, @@ -169,15 +159,6 @@ static int wgl_init(struct glcontext *ctx, uintptr_t display, uintptr_t window, return -1; } - if (ctx->offscreen) { - wgl->pixel_buffer = wgl->CreatePbufferARB(wgl->device_context, pixel_format, ctx->width, ctx->height, NULL); - if (!wgl->pixel_buffer) { - LOG(ERROR, "could not create pixel buffer"); - return -1; - } - wgl->device_context = wgl->GetPbufferDCARB(wgl->pixel_buffer); - } - if (wglDeleteContext(wgl->rendering_context) == FALSE) LOG(WARNING, "failed to delete dummy rendering context (%lu)", GetLastError()); @@ -229,14 +210,6 @@ static void wgl_uninit(struct glcontext *ctx) { struct wgl_priv *wgl = ctx->priv_data; - if (wgl->pixel_buffer && wgl->device_context) { - if (wgl->ReleasePbufferDCARB) - wgl->ReleasePbufferDCARB(wgl->pixel_buffer, wgl->device_context); - - if (wgl->DestroyPbufferARB) - wgl->DestroyPbufferARB(wgl->pixel_buffer); - } - if (wgl->rendering_context) wglDeleteContext(wgl->rendering_context); From a90987caaf5efc06820379f0562bbd0acf001a25 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 22 Sep 2020 14:36:02 +0200 Subject: [PATCH 119/388] hud: remove bg_color parameter --- libnodegl/doc/libnodegl.md | 1 - libnodegl/node_hud.c | 6 ++---- libnodegl/nodes.specs | 1 - pynodegl-utils/pynodegl_utils/com.py | 1 - 4 files changed, 2 insertions(+), 7 deletions(-) diff --git a/libnodegl/doc/libnodegl.md b/libnodegl/doc/libnodegl.md index 7124144f0f..950741dd82 100644 --- a/libnodegl/doc/libnodegl.md +++ b/libnodegl/doc/libnodegl.md @@ -345,7 +345,6 @@ Parameter | Live-chg. | Type | Description | Default `measure_window` | | [`int`](#parameter-types) | window size for latency measures | `60` `refresh_rate` | | [`rational`](#parameter-types) | refresh data buffer every `update_rate` second | `export_filename` | | [`string`](#parameter-types) | path to export file (CSV), disable display if enabled | -`bg_color` | | [`vec4`](#parameter-types) | background buffer color | (`0`,`0`,`0`,`1`) `aspect_ratio` | | [`rational`](#parameter-types) | buffer aspect ratio | diff --git a/libnodegl/node_hud.c b/libnodegl/node_hud.c index 228d1665b7..b89694f083 100644 --- a/libnodegl/node_hud.c +++ b/libnodegl/node_hud.c @@ -48,7 +48,6 @@ struct hud_priv { int measure_window; int refresh_rate[2]; char *export_filename; - float bg_color[4]; int aspect_ratio[2]; struct darray widgets; @@ -80,8 +79,6 @@ static const struct node_param hud_params[] = { .desc=NGLI_DOCSTRING("refresh data buffer every `update_rate` second")}, {"export_filename", PARAM_TYPE_STR, OFFSET(export_filename), .desc=NGLI_DOCSTRING("path to export file (CSV), disable display if enabled")}, - {"bg_color", PARAM_TYPE_VEC4, OFFSET(bg_color), {.vec={0.0, 0.0, 0.0, 1.0}}, - .desc=NGLI_DOCSTRING("background buffer color")}, {"aspect_ratio", PARAM_TYPE_RATIONAL, OFFSET(aspect_ratio), .desc=NGLI_DOCSTRING("buffer aspect ratio")}, {NULL} @@ -1261,7 +1258,8 @@ static int hud_init(struct ngl_node *node) if (!s->canvas.buf) return NGL_ERROR_MEMORY; - s->bg_color_u32 = NGLI_COLOR_VEC4_TO_U32(s->bg_color); + static const float bg_color[] = {0.0f, 0.0f, 0.0f, 0.8f}; + s->bg_color_u32 = NGLI_COLOR_VEC4_TO_U32(bg_color); widgets_clear(s); static const float coords[] = { diff --git a/libnodegl/nodes.specs b/libnodegl/nodes.specs index 370e7deb98..5ce05bb93d 100644 --- a/libnodegl/nodes.specs +++ b/libnodegl/nodes.specs @@ -222,7 +222,6 @@ - [measure_window, int] - [refresh_rate, rational] - [export_filename, string] - - [bg_color, vec4] - [aspect_ratio, rational] - Identity: diff --git a/pynodegl-utils/pynodegl_utils/com.py b/pynodegl-utils/pynodegl_utils/com.py index 3aace240de..8206202a6a 100644 --- a/pynodegl-utils/pynodegl_utils/com.py +++ b/pynodegl-utils/pynodegl_utils/com.py @@ -127,7 +127,6 @@ def query_inplace(**idict): measure_window = fr[0] / (4 * fr[1]) # 1/4-second measurement window scene = ngl.HUD(scene, measure_window=measure_window, - bg_color=(0.0, 0.0, 0.0, 0.8), aspect_ratio=odict['aspect_ratio']) # Prepare output data From adbac2c1c21e502611310444248fcdce9da12f65 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 22 Sep 2020 15:22:47 +0200 Subject: [PATCH 120/388] hud: update layout and adjust rendering to the viewport size Also drops the aspect_ratio parameter in favor of the scale parameter since we now rely on the viewport to determine how to draw the hud. --- libnodegl/doc/libnodegl.md | 2 +- libnodegl/node_hud.c | 78 +++++++++++++++------------- libnodegl/nodes.specs | 2 +- pynodegl-utils/pynodegl_utils/com.py | 3 +- 4 files changed, 46 insertions(+), 39 deletions(-) diff --git a/libnodegl/doc/libnodegl.md b/libnodegl/doc/libnodegl.md index 950741dd82..f6066a0c75 100644 --- a/libnodegl/doc/libnodegl.md +++ b/libnodegl/doc/libnodegl.md @@ -345,7 +345,7 @@ Parameter | Live-chg. | Type | Description | Default `measure_window` | | [`int`](#parameter-types) | window size for latency measures | `60` `refresh_rate` | | [`rational`](#parameter-types) | refresh data buffer every `update_rate` second | `export_filename` | | [`string`](#parameter-types) | path to export file (CSV), disable display if enabled | -`aspect_ratio` | | [`rational`](#parameter-types) | buffer aspect ratio | +`scale` | | [`int`](#parameter-types) | scaling applied to the HUD, useful for high DPI displays | `0` **Source**: [node_hud.c](/libnodegl/node_hud.c) diff --git a/libnodegl/node_hud.c b/libnodegl/node_hud.c index b89694f083..f69eaced70 100644 --- a/libnodegl/node_hud.c +++ b/libnodegl/node_hud.c @@ -29,6 +29,7 @@ #include #include +#include "gctx.h" #include "hmap.h" #include "memory.h" #include "nodegl.h" @@ -48,7 +49,7 @@ struct hud_priv { int measure_window; int refresh_rate[2]; char *export_filename; - int aspect_ratio[2]; + int scale; struct darray widgets; uint32_t bg_color_u32; @@ -79,8 +80,8 @@ static const struct node_param hud_params[] = { .desc=NGLI_DOCSTRING("refresh data buffer every `update_rate` second")}, {"export_filename", PARAM_TYPE_STR, OFFSET(export_filename), .desc=NGLI_DOCSTRING("path to export file (CSV), disable display if enabled")}, - {"aspect_ratio", PARAM_TYPE_RATIONAL, OFFSET(aspect_ratio), - .desc=NGLI_DOCSTRING("buffer aspect ratio")}, + {"scale", PARAM_TYPE_INT, OFFSET(scale), + .desc=NGLI_DOCSTRING("scaling applied to the HUD, useful for high DPI displays")}, {NULL} }; @@ -900,7 +901,7 @@ static const struct widget_spec widget_specs[] = { [WIDGET_MEMORY] = { .text_cols = MEMORY_WIDGET_TEXT_LEN, .text_rows = NB_MEMORY, - .graph_h = 50, + .graph_w = 285, .nb_data_graph = NB_MEMORY, .priv_size = sizeof(struct widget_memory), .init = widget_memory_init, @@ -1022,31 +1023,19 @@ static int widgets_init(struct ngl_node *node) ngli_darray_init(&s->widgets, sizeof(struct widget), 0); /* Smallest dimensions possible (in pixels) */ - const int top_width = WIDGET_MARGIN * 3 - + get_widget_width(WIDGET_LATENCY) - + get_widget_width(WIDGET_MEMORY); - const int bot_width = WIDGET_MARGIN * 3 - + get_widget_width(WIDGET_ACTIVITY) * NB_ACTIVITY + WIDGET_MARGIN * (NB_ACTIVITY - 1) - + get_widget_width(WIDGET_DRAWCALL) * NB_DRAWCALL + WIDGET_MARGIN * (NB_DRAWCALL - 1); - const int left_height = WIDGET_MARGIN * 3 - + get_widget_height(WIDGET_LATENCY) - + get_widget_height(WIDGET_ACTIVITY); - const int right_height = WIDGET_MARGIN * 3 - + get_widget_height(WIDGET_MEMORY) - + get_widget_height(WIDGET_DRAWCALL); - const int min_width = NGLI_MAX(top_width, bot_width); - const int min_height = NGLI_MAX(left_height, right_height); - - /* Compute buffer dimensions according to user specified aspect ratio and - * minimal dimensions */ - static const int default_ar[] = {1, 1}; - const int *ar = s->aspect_ratio[0] && s->aspect_ratio[1] ? s->aspect_ratio : default_ar; - s->canvas.w = min_width; - s->canvas.h = min_width * ar[1] / ar[0]; - if (s->canvas.h < min_height) { - s->canvas.w = min_height * ar[0] / ar[1]; - s->canvas.h = min_height; - } + const int latency_width = get_widget_width(WIDGET_LATENCY); + const int memory_width = get_widget_width(WIDGET_MEMORY); + const int activity_width = get_widget_width(WIDGET_ACTIVITY) * NB_ACTIVITY + WIDGET_MARGIN * (NB_ACTIVITY - 1); + const int drawcall_width = get_widget_width(WIDGET_DRAWCALL) * NB_DRAWCALL + WIDGET_MARGIN * (NB_DRAWCALL - 1); + + s->canvas.w = WIDGET_MARGIN * 2 + + NGLI_MAX(NGLI_MAX(NGLI_MAX(latency_width, memory_width), activity_width), drawcall_width); + + s->canvas.h = WIDGET_MARGIN * 4 + + get_widget_height(WIDGET_LATENCY) + + get_widget_height(WIDGET_MEMORY) + + get_widget_height(WIDGET_ACTIVITY) + + get_widget_height(WIDGET_DRAWCALL); /* Latency widget in the top-left */ const int x_latency = WIDGET_MARGIN; @@ -1056,15 +1045,15 @@ static int widgets_init(struct ngl_node *node) return ret; /* Memory widget in the top-right */ - const int x_memory = -get_widget_width(WIDGET_MEMORY) - WIDGET_MARGIN; - const int y_memory = WIDGET_MARGIN; + const int x_memory = WIDGET_MARGIN; + const int y_memory = WIDGET_MARGIN + y_latency + get_widget_height(WIDGET_LATENCY); ret = create_widget(s, WIDGET_MEMORY, NULL, x_memory, y_memory); if (ret < 0) return ret; /* Activity nodes counter widgets in the bottom-left */ int x_activity = WIDGET_MARGIN; - const int y_activity = -get_widget_height(WIDGET_ACTIVITY) - WIDGET_MARGIN; + const int y_activity = WIDGET_MARGIN + y_memory + get_widget_height(WIDGET_MEMORY); const int x_activity_step = get_widget_width(WIDGET_ACTIVITY) + WIDGET_MARGIN; for (int i = 0; i < NB_ACTIVITY; i++) { ret = create_widget(s, WIDGET_ACTIVITY, &activity_specs[i], x_activity, y_activity); @@ -1074,8 +1063,8 @@ static int widgets_init(struct ngl_node *node) } /* Draw-calls widgets in the bottom-right */ - int x_drawcall = -get_widget_width(WIDGET_DRAWCALL) * NB_DRAWCALL - WIDGET_MARGIN * NB_DRAWCALL; - const int y_drawcall = -get_widget_height(WIDGET_DRAWCALL) - WIDGET_MARGIN; + int x_drawcall = WIDGET_MARGIN; + const int y_drawcall = WIDGET_MARGIN + y_activity + get_widget_height(WIDGET_ACTIVITY); const int x_drawcall_step = get_widget_width(WIDGET_DRAWCALL) + WIDGET_MARGIN; for (int i = 0; i < NB_DRAWCALL; i++) { ret = create_widget(s, WIDGET_DRAWCALL, &drawcall_specs[i], x_drawcall, y_drawcall); @@ -1387,6 +1376,7 @@ static int hud_update(struct ngl_node *node, double t) static void hud_draw(struct ngl_node *node) { struct ngl_ctx *ctx = node->ctx; + struct gctx *gctx = ctx->gctx; struct hud_priv *s = node->priv_data; widgets_make_stats(node); @@ -1402,7 +1392,25 @@ static void hud_draw(struct ngl_node *node) if (s->export_filename) return; - int ret = ngli_texture_upload(s->texture, s->canvas.buf, 0); + int viewport[4]; + ngli_gctx_get_viewport(gctx, viewport); + const int scale = s->scale > 0 ? s->scale : 1; + const float ratio_w = scale * s->canvas.w / (double)viewport[2]; + const float ratio_h = scale * s->canvas.h / (double)viewport[3]; + const float x =-1.0f + 2 * ratio_w; + const float y = 1.0f - 2 * ratio_h; + const float coords[] = { + -1.0f, y, 0.0f, 1.0f, + x, y, 1.0f, 1.0f, + -1.0f, 1.0f, 0.0f, 0.0f, + x, 1.0f, 1.0f, 0.0f, + }; + + int ret = ngli_buffer_upload(s->coords, coords, sizeof(coords)); + if (ret < 0) + return; + + ret = ngli_texture_upload(s->texture, s->canvas.buf, 0); if (ret < 0) return; diff --git a/libnodegl/nodes.specs b/libnodegl/nodes.specs index 5ce05bb93d..91fe542e51 100644 --- a/libnodegl/nodes.specs +++ b/libnodegl/nodes.specs @@ -222,7 +222,7 @@ - [measure_window, int] - [refresh_rate, rational] - [export_filename, string] - - [aspect_ratio, rational] + - [scale, int] - Identity: diff --git a/pynodegl-utils/pynodegl_utils/com.py b/pynodegl-utils/pynodegl_utils/com.py index 8206202a6a..e81f279150 100644 --- a/pynodegl-utils/pynodegl_utils/com.py +++ b/pynodegl-utils/pynodegl_utils/com.py @@ -126,8 +126,7 @@ def query_inplace(**idict): fr = odict['framerate'] measure_window = fr[0] / (4 * fr[1]) # 1/4-second measurement window scene = ngl.HUD(scene, - measure_window=measure_window, - aspect_ratio=odict['aspect_ratio']) + measure_window=measure_window) # Prepare output data odict['scene'] = scene.dot() if idict.get('fmt') == 'dot' else scene.serialize() From 6280859ac14afb4557fd73113f43a5b8d985baf3 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 25 Sep 2020 16:23:24 +0200 Subject: [PATCH 121/388] hud: ignore refresh rate if csv export is enabled --- libnodegl/node_hud.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/libnodegl/node_hud.c b/libnodegl/node_hud.c index f69eaced70..823ccd5d4c 100644 --- a/libnodegl/node_hud.c +++ b/libnodegl/node_hud.c @@ -1171,7 +1171,7 @@ static void widgets_csv_report(struct ngl_node *node) ngli_bstr_clear(s->csv_line); /* Quoting to prevent locale issues with float printing */ - ngli_bstr_printf(s->csv_line, "\"%f\"", s->last_refresh_time); + ngli_bstr_printf(s->csv_line, "\"%f\"", node->last_update_time); struct darray *widgets_array = &s->widgets; struct widget *widgets = ngli_darray_data(widgets_array); @@ -1380,17 +1380,15 @@ static void hud_draw(struct ngl_node *node) struct hud_priv *s = node->priv_data; widgets_make_stats(node); - if (s->need_refresh) { - if (s->export_filename) { - widgets_csv_report(node); - } else { - widgets_clear(s); - widgets_draw(node); - } + if (s->export_filename) { + widgets_csv_report(node); + return; } - if (s->export_filename) - return; + if (s->need_refresh) { + widgets_clear(s); + widgets_draw(node); + } int viewport[4]; ngli_gctx_get_viewport(gctx, viewport); From bf72413a90567dc53750de8e621d9e49d530e22d Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 25 Sep 2020 16:26:27 +0200 Subject: [PATCH 122/388] hud: make the refresh interval tied to the system clock The refresh interval needs to be tied to the system clock instead of the node.gl one. This will allow to have a consistent refresh rate of the HUD. --- libnodegl/node_hud.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/libnodegl/node_hud.c b/libnodegl/node_hud.c index 823ccd5d4c..5c6eda730a 100644 --- a/libnodegl/node_hud.c +++ b/libnodegl/node_hud.c @@ -58,7 +58,6 @@ struct hud_priv { struct canvas canvas; double refresh_rate_interval; double last_refresh_time; - int need_refresh; struct pgcraft *crafter; struct texture *texture; @@ -1363,11 +1362,6 @@ static int hud_init(struct ngl_node *node) static int hud_update(struct ngl_node *node, double t) { struct hud_priv *s = node->priv_data; - - s->need_refresh = fabs(t - s->last_refresh_time) >= s->refresh_rate_interval; - if (s->need_refresh) - s->last_refresh_time = t; - struct darray *widgets_array = &s->widgets; struct widget *widgets = ngli_darray_data(widgets_array); return widget_latency_update(node, &widgets[0], t); @@ -1385,7 +1379,10 @@ static void hud_draw(struct ngl_node *node) return; } - if (s->need_refresh) { + const double t = ngli_gettime_relative() / 1000000.; + const int need_refresh = fabs(t - s->last_refresh_time) >= s->refresh_rate_interval; + if (need_refresh) { + s->last_refresh_time = t; widgets_clear(s); widgets_draw(node); } From fe4aef4ad43e5a8cb43ed794178bbd0a6088ddca Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 28 Sep 2020 10:38:45 +0200 Subject: [PATCH 123/388] utils/viewer: set hud scale based on the device pixel ratio --- pynodegl-utils/pynodegl_utils/com.py | 2 ++ pynodegl-utils/pynodegl_utils/config.py | 1 + pynodegl-utils/pynodegl_utils/ui/toolbar.py | 1 + 3 files changed, 4 insertions(+) diff --git a/pynodegl-utils/pynodegl_utils/com.py b/pynodegl-utils/pynodegl_utils/com.py index e81f279150..edcf890ba7 100644 --- a/pynodegl-utils/pynodegl_utils/com.py +++ b/pynodegl-utils/pynodegl_utils/com.py @@ -123,9 +123,11 @@ def query_inplace(**idict): # Make extra adjustments to the scene according to user options if idict.get('enable_hud'): + scale = idict['hud_scale'] fr = odict['framerate'] measure_window = fr[0] / (4 * fr[1]) # 1/4-second measurement window scene = ngl.HUD(scene, + scale=scale, measure_window=measure_window) # Prepare output data diff --git a/pynodegl-utils/pynodegl_utils/config.py b/pynodegl-utils/pynodegl_utils/config.py index 3be9cf7b16..f426f0a6e3 100644 --- a/pynodegl-utils/pynodegl_utils/config.py +++ b/pynodegl-utils/pynodegl_utils/config.py @@ -69,6 +69,7 @@ def __init__(self, module_pkgname): 'log_level': 'info', 'clear_color': (0.0, 0.0, 0.0, 1.0), 'enable_hud': False, + 'hud_scale': 1, 'backend': 'gl', # Export diff --git a/pynodegl-utils/pynodegl_utils/ui/toolbar.py b/pynodegl-utils/pynodegl_utils/ui/toolbar.py index 29f0fe6855..c1d83c2b90 100644 --- a/pynodegl-utils/pynodegl_utils/ui/toolbar.py +++ b/pynodegl-utils/pynodegl_utils/ui/toolbar.py @@ -207,6 +207,7 @@ def get_cfg(self): 'samples': choices['samples'][self._samples_cbbox.currentIndex()], 'extra_args': self._scene_extra_args, 'enable_hud': self._hud_chkbox.isChecked(), + 'hud_scale': round(self.devicePixelRatioF()), 'clear_color': self._clear_color, 'backend': choices['backend'][self._backend_cbbox.currentIndex()], } From 4a5f419c270eb6ef2f7ff0839b3f7a9b138990b0 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 29 Sep 2020 14:08:38 +0200 Subject: [PATCH 124/388] hud: set vertices buffer usage to dynamic The vertices buffer is now dynamically updated at draw time depending on the viewport size. This was forgotten in adbac2c1c21e502611310444248fcdce9da12f65. --- libnodegl/node_hud.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libnodegl/node_hud.c b/libnodegl/node_hud.c index 5c6eda730a..f2a1214e5b 100644 --- a/libnodegl/node_hud.c +++ b/libnodegl/node_hud.c @@ -1261,7 +1261,7 @@ static int hud_init(struct ngl_node *node) if (!s->coords) return NGL_ERROR_MEMORY; - ret = ngli_buffer_init(s->coords, sizeof(coords), NGLI_BUFFER_USAGE_STATIC); + ret = ngli_buffer_init(s->coords, sizeof(coords), NGLI_BUFFER_USAGE_DYNAMIC); if (ret < 0) return ret; From c6d9471436586475f6074596b42b623c94165fe8 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Wed, 30 Sep 2020 18:20:52 -0700 Subject: [PATCH 125/388] gctx: cosmetics --- libnodegl/gctx.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index e6ee7929da..12dfc005ac 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -92,7 +92,7 @@ struct gctx_class { void (*rendertarget_read_pixels)(struct rendertarget *s, uint8_t *data); void (*rendertarget_freep)(struct rendertarget **sp); - struct texture *(*texture_create)(struct gctx* ctx); + struct texture *(*texture_create)(struct gctx *ctx); int (*texture_init)(struct texture *s, const struct texture_params *params); int (*texture_has_mipmap)(const struct texture *s); int (*texture_match_dimensions)(const struct texture *s, int width, int height, int depth); From f4242fc88e70519e0197bc30b51dced25d0dd37b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 31 Jul 2020 18:25:47 +0200 Subject: [PATCH 126/388] utils: remove player view The Qt widget causes too much issues with Qt/PySide2 (lags, deadlocks, design). But we can now rely on an external tool (ngl-desktop) through the hooks to show the scene in a fast and independant way. --- pynodegl-utils/pynodegl_utils/player.py | 175 ---------------- .../pynodegl_utils/ui/main_window.py | 5 +- .../pynodegl_utils/ui/player_view.py | 192 ------------------ 3 files changed, 1 insertion(+), 371 deletions(-) delete mode 100644 pynodegl-utils/pynodegl_utils/ui/player_view.py diff --git a/pynodegl-utils/pynodegl_utils/player.py b/pynodegl-utils/pynodegl_utils/player.py index e75194efa9..7fe13dd66f 100644 --- a/pynodegl-utils/pynodegl_utils/player.py +++ b/pynodegl-utils/pynodegl_utils/player.py @@ -21,10 +21,6 @@ # import time -from PySide2 import QtCore - -import pynodegl as ngl -from pynodegl_utils import misc class Clock: @@ -87,174 +83,3 @@ def step_playback_index(self, step): )) self._playback_index = min(max(self._playback_index + step, 0), max_duration_index) self.reset_running_time() - - -class Player(QtCore.QThread): - - onPlay = QtCore.Signal() - onPause = QtCore.Signal() - onSceneMetadata = QtCore.Signal(dict) - onFrame = QtCore.Signal(int, float) - - def __init__(self, window, width, height, config): - super().__init__() - - self._mutex = QtCore.QMutex() - self._cond = QtCore.QWaitCondition() - - self._window = window - self._width = width - self._height = height - - self._scene = None - self._framerate = config.get('framerate') - self._duration = 0.0 - self._clear_color = config.get('clear_color') - self._aspect_ratio = config.get('aspect_ratio') - self._samples = config.get('samples') - self._backend = config.get('backend') - - self._events = [] - self._wait_first_frame = True - self._clock = Clock(self._framerate, self._duration) - self._viewer = ngl.Context() - self._configure_viewer() - - def _configure_viewer(self): - self._viewer.configure( - platform=ngl.PLATFORM_AUTO, - backend=misc.get_backend(self._backend), - window=self._window, - width=self._width, - height=self._height, - viewport=misc.get_viewport(self._width, self._height, self._aspect_ratio), - swap_interval=1, - samples=self._samples, - clear_color=self._clear_color, - ) - - def _render(self): - frame_index, frame_time = self._clock.get_playback_time_info() - with QtCore.QMutexLocker(self._mutex): - self._viewer.draw(frame_time) - if self._wait_first_frame: - self._clock.reset_running_time() - self._wait_first_frame = False - self.onFrame.emit(frame_index, frame_time) - - def run(self): - while True: - self._render() - should_stop = self._handle_events() - if should_stop: - break - del self._viewer - self._viewer = None - - def _handle_events(self): - should_stop = False - with QtCore.QMutexLocker(self._mutex): - if not self._events and not self._clock.is_running(): - self._cond.wait(self._mutex) - while self._events: - event = self._events.pop(0) - should_stop = event() - if should_stop: - break - return should_stop - - def _push_event(self, event): - with QtCore.QMutexLocker(self._mutex): - self._events.append(event) - self._cond.wakeAll() - - def draw(self): - self._push_event(lambda: False) - - def play(self): - self._push_event(lambda: self._play()) - - def _play(self): - if not self._scene: - return False - self._clock.start() - self._wait_first_frame = True - self.onPlay.emit() - return False - - def pause(self): - self._push_event(lambda: self._pause()) - - def _pause(self): - self._clock.stop() - self.onPause.emit() - return False - - def stop(self): - self._push_event(lambda: self._stop()) - - def _stop(self): - return True - - def seek(self, time): - self._push_event(lambda: self._seek(time)) - - def _seek(self, time): - self._clock.set_playback_time(time) - self._wait_first_frame = True - return False - - def step(self, step): - self._push_event(lambda: self._step(step)) - - def _step(self, step): - self._clock.step_playback_index(step) - self._clock.stop() - self.onPause.emit() - return False - - def resize(self, width, height): - with QtCore.QMutexLocker(self._mutex): - self._width = width - self._height = height - viewport = misc.get_viewport(width, height, self._aspect_ratio) - self._viewer.resize(width, height, viewport) - - def set_scene(self, cfg): - with QtCore.QMutexLocker(self._mutex): - need_reconfigure = False - self._scene = cfg['scene'] - self._framerate = cfg['framerate'] - self._duration = cfg['duration'] - self._aspect_ratio = cfg['aspect_ratio'] - if self._clear_color != cfg['clear_color']: - self._clear_color = cfg['clear_color'] - need_reconfigure = True - if self._samples != cfg['samples']: - self._samples = cfg['samples'] - need_reconfigure = True - if self._backend != cfg['backend']: - self._backend = cfg['backend'] - need_reconfigure = True - if need_reconfigure: - self._viewer.set_scene(None) - self._configure_viewer() - else: - viewport = misc.get_viewport(self._width, self._height, self._aspect_ratio) - self._viewer.resize(self._width, self._height, viewport) - self._push_event(lambda: self._set_scene()) - - def _set_scene(self): - self._viewer.set_scene_from_string(self._scene) - self._clock.configure(self._framerate, self._duration) - self.onSceneMetadata.emit({'framerate': self._framerate, 'duration': self._duration}) - return False - - def reset_scene(self): - self._push_event(lambda: self._reset_scene()) - - def _reset_scene(self): - self._pause() - self._scene = None - self._viewer.set_scene(self._scene) - return False diff --git a/pynodegl-utils/pynodegl_utils/ui/main_window.py b/pynodegl-utils/pynodegl_utils/ui/main_window.py index 5fbbf733d4..8d0819700f 100644 --- a/pynodegl-utils/pynodegl_utils/ui/main_window.py +++ b/pynodegl-utils/pynodegl_utils/ui/main_window.py @@ -29,7 +29,6 @@ from pynodegl_utils.scriptsmgr import ScriptsManager from pynodegl_utils.hooks import HooksController, HooksCaller -from pynodegl_utils.ui.player_view import PlayerView from pynodegl_utils.ui.graph_view import GraphView from pynodegl_utils.ui.export_view import ExportView from pynodegl_utils.ui.hooks_view import HooksView @@ -61,7 +60,6 @@ def __init__(self, module_pkgname, hooksdirs): geometry = QtCore.QRect(*rect) self.setGeometry(geometry) - player_view = PlayerView(get_scene_func, self._config) graph_view = GraphView(get_scene_func, self._config) export_view = ExportView(get_scene_func, self._config) hooks_view = HooksView(self._hooks_caller) @@ -69,10 +67,9 @@ def __init__(self, module_pkgname, hooksdirs): serial_view = SerialView(get_scene_func) self._tabs = [ - ('Player view', player_view), + ('Hooks', hooks_view), ('Graph view', graph_view), ('Export', export_view), - ('Hooks', hooks_view), ('Medias', self._medias_view), ('Serialization', serial_view), ] diff --git a/pynodegl-utils/pynodegl_utils/ui/player_view.py b/pynodegl-utils/pynodegl_utils/ui/player_view.py deleted file mode 100644 index d9f021b3c5..0000000000 --- a/pynodegl-utils/pynodegl_utils/ui/player_view.py +++ /dev/null @@ -1,192 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2018 GoPro Inc. -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -from PySide2 import QtCore, QtWidgets -from PySide2.QtCore import Qt -from PySide2.QtCore import QEvent - -from .seekbar import Seekbar - -from pynodegl_utils import player -from pynodegl_utils import export - -MIN_RESIZE_INTERVAL = 100 - - -class _PlayerWidget(QtWidgets.QWidget): - - onPlayerAvailable = QtCore.Signal() - - def __init__(self, parent, config): - super().__init__(parent) - - self.setAttribute(Qt.WA_DontCreateNativeAncestors) - self.setAttribute(Qt.WA_NativeWindow) - self.setAttribute(Qt.WA_PaintOnScreen) - self.setMinimumSize(640, 360) - - self._player = None - self._last_frame_time = 0.0 - self._config = config - - self._req_width = None - self._req_height = None - - self._timer = QtCore.QTimer() - self._timer.setSingleShot(True) - self._timer.setInterval(MIN_RESIZE_INTERVAL) - self._timer.timeout.connect(self._resize) - - def paintEngine(self): - return None - - @QtCore.Slot() - def _resize(self): - assert self._req_width is not None - assert self._req_height is not None - width = int(self._req_width * self.devicePixelRatioF()) - height = int(self._req_height * self.devicePixelRatioF()) - self._player.resize(width, height) - self._player.draw() - - def resizeEvent(self, event): - if not self._player: - return - - size = event.size() - self._req_width = size.width() - self._req_height = size.height() - self._timer.start() - - super().resizeEvent(event) - - def event(self, event): - if event.type() == QEvent.Paint: - if not self._player: - self._player = player.Player( - self.winId(), - self.width() * self.devicePixelRatioF(), - self.height() * self.devicePixelRatioF(), - self._config, - ) - self._player.start() - self._player.onFrame.connect(self._set_last_frame_time) - self.onPlayerAvailable.emit() - else: - self._player.draw() - elif event.type() == QEvent.Close: - if self._player: - self._player.stop() - self._player.wait() - return super().event(event) - - @QtCore.Slot(int, float) - def _set_last_frame_time(self, frame_index, frame_time): - self._last_frame_time = frame_time - - def get_last_frame_time(self): - return self._last_frame_time - - def get_player(self): - return self._player - - -class PlayerView(QtWidgets.QWidget): - - def __init__(self, get_scene_func, config): - super().__init__() - - self._get_scene_func = get_scene_func - self._cfg = None - - self._seekbar = Seekbar(config) - self._player_widget = _PlayerWidget(self, config) - self._player_widget.onPlayerAvailable.connect(self._connect_seekbar) - - screenshot_btn = QtWidgets.QToolButton() - screenshot_btn.setText(u'📷') - - toolbar = QtWidgets.QHBoxLayout() - toolbar.addWidget(self._seekbar) - toolbar.addWidget(screenshot_btn) - - self._gl_layout = QtWidgets.QVBoxLayout(self) - self._gl_layout.addWidget(self._player_widget, stretch=1) - self._gl_layout.addLayout(toolbar) - - screenshot_btn.clicked.connect(self._screenshot) - - @QtCore.Slot() - def _connect_seekbar(self): - player = self._player_widget.get_player() - player.onPlay.connect(self._seekbar.set_play_state) - player.onPause.connect(self._seekbar.set_pause_state) - player.onSceneMetadata.connect(self._seekbar.set_scene_metadata) - player.onFrame.connect(self._seekbar.set_frame_time) - - self._seekbar.seek.connect(player.seek) - self._seekbar.play.connect(player.play) - self._seekbar.pause.connect(player.pause) - self._seekbar.step.connect(player.step) - self._seekbar.stop.connect(player.reset_scene) - - if self._cfg: - player.set_scene(self._cfg) - - @QtCore.Slot() - def _screenshot(self): - filenames = QtWidgets.QFileDialog.getSaveFileName(self, 'Save screenshot file') - if not filenames[0]: - return - exporter = export.Exporter( - self._get_scene_func, - filenames[0], - self._player_widget.width(), - self._player_widget.height(), - ['-frames:v', '1'], - self._player_widget.get_last_frame_time() - ) - exporter.start() - exporter.wait() - - def enter(self): - self._cfg = self._get_scene_func() - if not self._cfg: - return - - player = self._player_widget.get_player() - if not player: - return - player.set_scene(self._cfg) - - self._player_widget.update() - - def leave(self): - player = self._player_widget.get_player() - if not player: - return - player.pause() - - def closeEvent(self, close_event): - self._player_widget.close() - self._seekbar.close() - super().closeEvent(close_event) From 3107afce62ca9151ff35f5ff4d411b3fe261a6f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 28 Aug 2020 09:21:26 +0200 Subject: [PATCH 127/388] utils: add spawn widget The spawn widget is a convenient helper to spawn ngl-desktop instances from the UI. --- .../pynodegl_utils/ui/hooks_view.py | 83 ++++++++++++++++++- .../pynodegl_utils/ui/main_window.py | 2 +- 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/pynodegl-utils/pynodegl_utils/ui/hooks_view.py b/pynodegl-utils/pynodegl_utils/ui/hooks_view.py index d67253054f..020c4d1d41 100644 --- a/pynodegl-utils/pynodegl_utils/ui/hooks_view.py +++ b/pynodegl-utils/pynodegl_utils/ui/hooks_view.py @@ -20,14 +20,92 @@ # under the License. # +import subprocess from PySide2 import QtCore, QtGui, QtWidgets +class _SpawnView(QtWidgets.QGroupBox): + + def __init__(self, config): + + QtWidgets.QGroupBox.__init__(self, 'Local ngl-desktop') + self._config = config + + all_loglevels = config.CHOICES['log_level'] + default_loglevel = config.get('log_level') + self._loglevel_cbbox = QtWidgets.QComboBox() + for level in all_loglevels: + self._loglevel_cbbox.addItem(level.title()) + log_level_idx = all_loglevels.index(default_loglevel) + self._loglevel_cbbox.setCurrentIndex(log_level_idx) + loglevel_lbl = QtWidgets.QLabel('Min log level:') + loglevel_hbox = QtWidgets.QHBoxLayout() + loglevel_hbox.addWidget(loglevel_lbl) + loglevel_hbox.addWidget(self._loglevel_cbbox) + + backend_names = { + 'gl': 'OpenGL', + 'gles': 'OpenGL ES', + } + all_backends = config.CHOICES['backend'] + default_backend = config.get('backend') + self._backend_cbbox = QtWidgets.QComboBox() + for backend in all_backends: + self._backend_cbbox.addItem(backend_names[backend]) + backend_idx = all_backends.index(default_backend) + self._backend_cbbox.setCurrentIndex(backend_idx) + backend_lbl = QtWidgets.QLabel('Backend:') + backend_hbox = QtWidgets.QHBoxLayout() + backend_hbox.addWidget(backend_lbl) + backend_hbox.addWidget(self._backend_cbbox) + + self._listen_text = QtWidgets.QLineEdit() + self._listen_text.setText('localhost') + listen_lbl = QtWidgets.QLabel('Listening on:') + listen_hbox = QtWidgets.QHBoxLayout() + listen_hbox.addWidget(listen_lbl) + listen_hbox.addWidget(self._listen_text) + + self._port_spin = QtWidgets.QSpinBox() + self._port_spin.setMinimum(1) + self._port_spin.setMaximum(0xffff) + self._port_spin.setValue(2345) + port_lbl = QtWidgets.QLabel('Port:') + port_hbox = QtWidgets.QHBoxLayout() + port_hbox.addWidget(port_lbl) + port_hbox.addWidget(self._port_spin) + + self._spawn_btn = QtWidgets.QPushButton('Spawn ngl-desktop') + btn_hbox = QtWidgets.QHBoxLayout() + btn_hbox.addStretch() + btn_hbox.addWidget(self._spawn_btn) + + layout = QtWidgets.QFormLayout() + layout.addRow('Min log level:', self._loglevel_cbbox) + layout.addRow('Backend:', self._backend_cbbox) + layout.addRow('Listening on:', self._listen_text) + layout.addRow('Port:', self._port_spin) + layout.addRow(btn_hbox) + + self.setLayout(layout) + + self._spawn_btn.clicked.connect(self._spawn) + + @QtCore.Slot() + def _spawn(self): + loglevel = self._config.CHOICES['log_level'][self._loglevel_cbbox.currentIndex()] + backend_remap = dict(gl='opengl', gles='opengles') + backend = backend_remap[self._config.CHOICES['backend'][self._backend_cbbox.currentIndex()]] + listen = self._listen_text.text() + port = self._port_spin.value() + subprocess.Popen(['ngl-desktop', '--host', listen, '--backend', backend, '--loglevel', loglevel, '--port', str(port)]) + + class HooksView(QtWidgets.QWidget): _COLUMNS = ('Session', 'Description', 'Backend', 'System', 'Status') - def __init__(self, hooks_caller): + def __init__(self, hooks_caller, config): super().__init__() self._hooks_caller = hooks_caller @@ -46,11 +124,14 @@ def __init__(self, hooks_caller): self._refresh_btn = QtWidgets.QPushButton('Refresh') + spawn_view = _SpawnView(config) + hbox = QtWidgets.QHBoxLayout() hbox.addStretch() hbox.addWidget(self._refresh_btn) serial_layout = QtWidgets.QVBoxLayout(self) + serial_layout.addWidget(spawn_view) serial_layout.addLayout(hbox) serial_layout.addWidget(self._view) diff --git a/pynodegl-utils/pynodegl_utils/ui/main_window.py b/pynodegl-utils/pynodegl_utils/ui/main_window.py index 8d0819700f..6ccad4d2f1 100644 --- a/pynodegl-utils/pynodegl_utils/ui/main_window.py +++ b/pynodegl-utils/pynodegl_utils/ui/main_window.py @@ -62,7 +62,7 @@ def __init__(self, module_pkgname, hooksdirs): graph_view = GraphView(get_scene_func, self._config) export_view = ExportView(get_scene_func, self._config) - hooks_view = HooksView(self._hooks_caller) + hooks_view = HooksView(self._hooks_caller, self._config) self._medias_view = MediasView(self._config) serial_view = SerialView(get_scene_func) From 938b40bbf90a0956e629c2f30e77b4e5989d41bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 22 Sep 2020 17:20:32 +0200 Subject: [PATCH 128/388] utils: rename player to clock This file now only contains the Clock (which is still required for the graph view). --- pynodegl-utils/pynodegl_utils/{player.py => clock.py} | 0 pynodegl-utils/pynodegl_utils/ui/graph_view.py | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename pynodegl-utils/pynodegl_utils/{player.py => clock.py} (100%) diff --git a/pynodegl-utils/pynodegl_utils/player.py b/pynodegl-utils/pynodegl_utils/clock.py similarity index 100% rename from pynodegl-utils/pynodegl_utils/player.py rename to pynodegl-utils/pynodegl_utils/clock.py diff --git a/pynodegl-utils/pynodegl_utils/ui/graph_view.py b/pynodegl-utils/pynodegl_utils/ui/graph_view.py index 2ba1c9044b..4d4237407b 100644 --- a/pynodegl-utils/pynodegl_utils/ui/graph_view.py +++ b/pynodegl-utils/pynodegl_utils/ui/graph_view.py @@ -27,7 +27,7 @@ from .seekbar import Seekbar -from pynodegl_utils import player +from pynodegl_utils import clock from pynodegl_utils import misc import pynodegl as ngl @@ -91,7 +91,7 @@ def __init__(self, get_scene_func, config): self._timer = QtCore.QTimer() self._timer.timeout.connect(self._update) - self._clock = player.Clock(self._framerate, 0.0) + self._clock = clock.Clock(self._framerate, 0.0) @QtCore.Slot() def _play(self): From b9120bd37958f6386a99cbf7106bc551a47220f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 22 Sep 2020 17:50:42 +0200 Subject: [PATCH 129/388] utils: rename ngl-viewer to ngl-control This tool is not a viewer anymore, but a controller of external programs, through hooks. Several other names were considered: ngl-ctl, ngl-toolkit, ngl-controller, ngl-ide. --- Makefile | 2 +- doc/project/architecture.md | 6 ++-- doc/ref/pynodegl-utils.md | 14 ++++---- ...pes.png => ngl-control-3-basic-shapes.png} | Bin ...idget.png => ngl-control-color-widget.png} | Bin ...cene.png => ngl-control-reddish-scene.png} | Bin .../img/{ngl-viewer.png => ngl-control.png} | Bin doc/tuto/start.md | 32 +++++++++--------- pynodegl-utils/pynodegl_utils/config.py | 2 +- .../{viewer.py => controller.py} | 0 .../pynodegl_utils/ui/main_window.py | 2 +- pynodegl-utils/setup.py | 2 +- 12 files changed, 30 insertions(+), 30 deletions(-) rename doc/tuto/img/{ngl-viewer-3-basic-shapes.png => ngl-control-3-basic-shapes.png} (100%) rename doc/tuto/img/{ngl-viewer-color-widget.png => ngl-control-color-widget.png} (100%) rename doc/tuto/img/{ngl-viewer-reddish-scene.png => ngl-control-reddish-scene.png} (100%) rename doc/tuto/img/{ngl-viewer.png => ngl-control.png} (100%) rename pynodegl-utils/pynodegl_utils/{viewer.py => controller.py} (100%) diff --git a/Makefile b/Makefile index 2695328b51..47c26dd88b 100644 --- a/Makefile +++ b/Makefile @@ -61,7 +61,7 @@ pynodegl-utils-install: pynodegl-utils-deps-install # We do not pull the requirements on Windows because of various issues: # - PySide2 can't be pulled # - Pillow fails to find zlib -# - ngl-viewer can not currently work because of temporary files handling +# - ngl-control can not currently work because of temporary files handling # # Still, we want the module to be installed so we can access the scene() # decorator and other related utils. diff --git a/doc/project/architecture.md b/doc/project/architecture.md index 940a38aad4..d92fc9901d 100644 --- a/doc/project/architecture.md +++ b/doc/project/architecture.md @@ -9,7 +9,7 @@ The `node.gl` project is split in several parts: - `pynodegl`: a Python binding for `libnodegl` (with the help of `Cython`) to create graph scenes in the most simple way. - `pynodegl-utils`: various Python utilities and examples such as an advanced - Qt5 viewer with many features such as live editing. + Qt5 controller with many features such as live editing. ## Dependencies @@ -20,10 +20,10 @@ The `node.gl` project is split in several parts: - `ngl-tools` needs [SDL2][sdl2] and `libnodegl` installed. - `pynodegl` needs [Python][python] and [Cython][cython], and `libnodegl` installed. -- `pynodegl-utils` needs [Python][python] and `pynodegl`. The viewer depends on +- `pynodegl-utils` needs [Python][python] and `pynodegl`. The controller depends on `PySide2` (which is the main reason why this package is separated from the `pynodegl` package). It is also recommended to install [Graphviz][graphviz] - in order to render graph in the viewer. + in order to render graph in the controller. ![Dependencies](dependencies.png) diff --git a/doc/ref/pynodegl-utils.md b/doc/ref/pynodegl-utils.md index 6a48409ae0..6ed4c666c8 100644 --- a/doc/ref/pynodegl-utils.md +++ b/doc/ref/pynodegl-utils.md @@ -2,17 +2,17 @@ pynodegl-utils ============== [pynodegl-utils][pynodegl-utils] provides various utilities around `node.gl` -and its Python binding. Its core tool is the Qt viewer with all its +and its Python binding. Its core tool is the Qt controller with all its peripheral features such as the exporter or the tooling for easing the creation of `node.gl` scene graphs. [pynodegl-utils]: /pynodegl-utils -## Viewer scenes +## Controller scenes Each scene needs to be decorated with the `misc.scene` decorator to be -recognized by the `ngl-viewer`. +recognized by the `ngl-control`. **Example**: @@ -31,7 +31,7 @@ Extra optional arguments to the scene function are allowed. Every scene must return a valid `pynodegl` node object. -## Viewer widgets +## Controller widgets Widgets are specified as named object arguments to the `@misc.scene` decorator. The `@misc.scene()` arguments must match the name of the corresponding argument @@ -190,9 +190,9 @@ def demo(cfg, intro='Hello World!'): ![text widget](img/widget-text.png) -## Viewer hooks +## Controller hooks -When using the `--hooks-dir` option, `ngl-viewer` will execute various hook +When using the `--hooks-dir` option, `ngl-control` will execute various hook according to various events. These hooks are typically used for triggering a synchronization with external devices. @@ -274,7 +274,7 @@ $ ./hook.sync_file Y5fd953df /tmp/ngl-media.mp4 media-001.mp4 **Example**: -A call from `ngl-viewer` to this hook will look like this: +A call from `ngl-control` to this hook will look like this: ```shell $ ./hook.scene_change X2fca1f2c /tmp/ngl_scene.ngl duration=5 framerate=60000/1001 aspect_ratio=16/9 clear_color=4A646BFF samples=4 diff --git a/doc/tuto/img/ngl-viewer-3-basic-shapes.png b/doc/tuto/img/ngl-control-3-basic-shapes.png similarity index 100% rename from doc/tuto/img/ngl-viewer-3-basic-shapes.png rename to doc/tuto/img/ngl-control-3-basic-shapes.png diff --git a/doc/tuto/img/ngl-viewer-color-widget.png b/doc/tuto/img/ngl-control-color-widget.png similarity index 100% rename from doc/tuto/img/ngl-viewer-color-widget.png rename to doc/tuto/img/ngl-control-color-widget.png diff --git a/doc/tuto/img/ngl-viewer-reddish-scene.png b/doc/tuto/img/ngl-control-reddish-scene.png similarity index 100% rename from doc/tuto/img/ngl-viewer-reddish-scene.png rename to doc/tuto/img/ngl-control-reddish-scene.png diff --git a/doc/tuto/img/ngl-viewer.png b/doc/tuto/img/ngl-control.png similarity index 100% rename from doc/tuto/img/ngl-viewer.png rename to doc/tuto/img/ngl-control.png diff --git a/doc/tuto/start.md b/doc/tuto/start.md index c579b7f312..5fd11131f8 100644 --- a/doc/tuto/start.md +++ b/doc/tuto/start.md @@ -14,12 +14,12 @@ in that environment. [install]: /doc/howto/installation.md -## 👁️ Running the demo viewer +## 👁️ Running the controller -When running `ngl-viewer` for the first time and selecting a scene, you should +When running `ngl-control` for the first time and selecting a scene, you should see something like this: -![ngl-viewer](img/ngl-viewer.png) +![ngl-control](img/ngl-control.png) All the scenes listed on the left tree view can be found in the [pynodegl_utils.examples][demo-tree] Python module. If you are curious on how @@ -27,7 +27,7 @@ each demo scene operates, look into this place. The default video being an overly saturated mire generated with FFmpeg, it is not a very interesting asset for most demos. It is suggested to select your own -assets using the "Medias" tab, and then play around with the viewer and its +assets using the "Medias" tab, and then play around with the controller and its default demos. Some demo scenes also offer customization widgets (look at the bottom left of the UI), check them out! @@ -38,8 +38,8 @@ bottom left of the UI), check them out! ### My first demo scene -Now that you are familiar with the viewer, we are going to write our own first -demo. +Now that you are familiar with the controller, we are going to write our own +first demo. Edit a script such as `~/mydemo.py` and add the following: @@ -77,10 +77,10 @@ def test_demo(cfg): return render ``` -You should be able to preview your scene with `ngl-viewer -m ~/mydemo.py` and +You should be able to preview your scene with `ngl-control -m ~/mydemo.py` and observe a centered quadrilateral geometry with the video playing in it. But -first, let's look at the `Graph view` tab in the viewer to understand the scene -we just crafted: +first, let's look at the `Graph view` tab in the controller to understand the +scene we just crafted: ![my-demo](img/graph-simple-render.png) @@ -112,7 +112,7 @@ void main() ``` -![my reddish demo](img/ngl-viewer-reddish-scene.png) +![my reddish demo](img/ngl-control-reddish-scene.png) [book-of-shaders]: http://thebookofshaders.com/ [expl-shaders]: /doc/expl/shaders.md @@ -146,7 +146,7 @@ like this: ### Scene widgets One way to adjust the red color is to edit the code and observe the result -in the `ngl-viewer` immediately. Another way is to integrate a widget directly +in the `ngl-control` immediately. Another way is to integrate a widget directly in the UI. For that, we can adjust the `@scene()` decorator and the `test_demo()` prototype like the following: @@ -158,12 +158,12 @@ def test_demo(cfg, color=(1,0,0,1)): ... ``` -![color widget](img/ngl-viewer-color-widget.png) +![color widget](img/ngl-control-color-widget.png) -All the other widgets are documented in the [Viewer widgets -documentation][viewer-widgets]. +All the other widgets are documented in the [Controller widgets +documentation][controller-widgets]. -[viewer-widgets]: /doc/ref/pynodegl-utils.md#viewer-widgets +[controller-widgets]: /doc/ref/pynodegl-utils.md#controller-widgets ### Animations @@ -365,7 +365,7 @@ with `get_frag('color')`, grabbing the content of [color.frag][color-frag]. [color-frag]: /pynodegl-utils/pynodegl_utils/examples/shaders/color.frag -![3 basic shapes](img/ngl-viewer-3-basic-shapes.png) +![3 basic shapes](img/ngl-control-3-basic-shapes.png) Now back on the original topic: how are we going to *make each shape appear and disappear according to time?* diff --git a/pynodegl-utils/pynodegl_utils/config.py b/pynodegl-utils/pynodegl_utils/config.py index f426f0a6e3..876cfbc512 100644 --- a/pynodegl-utils/pynodegl_utils/config.py +++ b/pynodegl-utils/pynodegl_utils/config.py @@ -101,7 +101,7 @@ def __init__(self, module_pkgname): def _get_config_filepath(self): config_basedir = os.environ.get('XDG_DATA_HOME', op.expanduser('~/.local/share')) config_dir = op.join(config_basedir, 'node.gl') - return op.join(config_dir, 'viewer.json') + return op.join(config_dir, 'controller.json') @QtCore.Slot() def _check_config(self): diff --git a/pynodegl-utils/pynodegl_utils/viewer.py b/pynodegl-utils/pynodegl_utils/controller.py similarity index 100% rename from pynodegl-utils/pynodegl_utils/viewer.py rename to pynodegl-utils/pynodegl_utils/controller.py diff --git a/pynodegl-utils/pynodegl_utils/ui/main_window.py b/pynodegl-utils/pynodegl_utils/ui/main_window.py index 6ccad4d2f1..013f2f459f 100644 --- a/pynodegl-utils/pynodegl_utils/ui/main_window.py +++ b/pynodegl-utils/pynodegl_utils/ui/main_window.py @@ -43,7 +43,7 @@ class MainWindow(QtWidgets.QSplitter): def __init__(self, module_pkgname, hooksdirs): super().__init__(QtCore.Qt.Horizontal) - self._win_title_base = 'Node.gl viewer' + self._win_title_base = 'Node.gl controller' self.setWindowTitle(self._win_title_base) self._module_pkgname = module_pkgname diff --git a/pynodegl-utils/setup.py b/pynodegl-utils/setup.py index c200d16c4e..0f8249d306 100644 --- a/pynodegl-utils/setup.py +++ b/pynodegl-utils/setup.py @@ -28,7 +28,7 @@ install_requires=['pynodegl'], entry_points={ 'console_scripts': [ - 'ngl-viewer = pynodegl_utils.viewer:run', + 'ngl-control = pynodegl_utils.controller:run', 'ngl-test = pynodegl_utils.tests:run', ], }, From a33d1327ea945e19b7bc7f2c32daa2d0ea4c39ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 22 Sep 2020 18:01:44 +0200 Subject: [PATCH 130/388] utils: remove forced old PySide2 version The deadlock is not present without the graphic widget. --- pynodegl-utils/requirements.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pynodegl-utils/requirements.txt b/pynodegl-utils/requirements.txt index d1f894444f..c12833a4ab 100644 --- a/pynodegl-utils/requirements.txt +++ b/pynodegl-utils/requirements.txt @@ -1,6 +1,4 @@ pillow pynodegl -# Workaround a regression introduced by pyside2 5.14.2 which makes the node.gl -# viewer deadlock in various place. -pyside2==5.14.1 +pyside2 watchdog From 85e0025d04536d4567a1f93dc49423a38bfdccb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 22 Sep 2020 18:03:26 +0200 Subject: [PATCH 131/388] utils/export: rename ngl_viewer into ctx The usage of "viewer" for the node.gl context in the Python binding is an historic legacy which doesn't make much sense anymore, since a long time. --- pynodegl-utils/pynodegl_utils/export.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pynodegl-utils/pynodegl_utils/export.py b/pynodegl-utils/pynodegl_utils/export.py index d7a4e92fec..c82f4f692d 100644 --- a/pynodegl-utils/pynodegl_utils/export.py +++ b/pynodegl-utils/pynodegl_utils/export.py @@ -91,8 +91,8 @@ def _export(self, filename, width, height, extra_enc_args=None): capture_buffer = bytearray(width * height * 4) # node.gl context - ngl_viewer = ngl.Context() - ngl_viewer.configure( + ctx = ngl.Context() + ctx.configure( platform=ngl.PLATFORM_AUTO, backend=get_backend(cfg['backend']), offscreen=1, @@ -103,10 +103,10 @@ def _export(self, filename, width, height, extra_enc_args=None): clear_color=cfg['clear_color'], capture_buffer=capture_buffer, ) - ngl_viewer.set_scene_from_string(cfg['scene']) + ctx.set_scene_from_string(cfg['scene']) if self._time is not None: - ngl_viewer.draw(self._time) + ctx.draw(self._time) os.write(fd_w, capture_buffer) self.progressed.emit(100) else: @@ -116,7 +116,7 @@ def _export(self, filename, width, height, extra_enc_args=None): if self._cancelled: break time = i * fps[1] / float(fps[0]) - ngl_viewer.draw(time) + ctx.draw(time) os.write(fd_w, capture_buffer) self.progressed.emit(i*100 / nb_frame) self.progressed.emit(100) From 27d8e5bc49d605a6b414b063be5ea669d8437d72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 22 Sep 2020 18:04:13 +0200 Subject: [PATCH 132/388] utils/graph_view: rename _viewer into _ctx --- .../pynodegl_utils/ui/graph_view.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/pynodegl-utils/pynodegl_utils/ui/graph_view.py b/pynodegl-utils/pynodegl_utils/ui/graph_view.py index 4d4237407b..8c6348b401 100644 --- a/pynodegl-utils/pynodegl_utils/ui/graph_view.py +++ b/pynodegl-utils/pynodegl_utils/ui/graph_view.py @@ -58,7 +58,7 @@ def __init__(self, get_scene_func, config): self._framerate = config.get('framerate') self._duration = 0.0 self._samples = config.get('samples') - self._viewer = None + self._ctx = None self._save_btn = QtWidgets.QPushButton('Save image') @@ -118,10 +118,10 @@ def _step(self, step): @QtCore.Slot() def _update(self): - if not self._viewer: + if not self._ctx: return frame_index, frame_time = self._clock.get_playback_time_info() - dot_scene = self._viewer.dot(frame_time) + dot_scene = self._ctx.dot(frame_time) if dot_scene: self._update_graph(dot_scene) self._seekbar.set_frame_time(frame_index, frame_time) @@ -151,32 +151,32 @@ def enter(self): self._seekbar.set_scene_metadata(cfg) if self._seek_chkbox.isChecked(): - self._init_viewer(cfg['backend']) + self._init_ctx(cfg['backend']) self._framerate = cfg['framerate'] self._duration = cfg['duration'] - self._viewer.set_scene_from_string(cfg['scene']) + self._ctx.set_scene_from_string(cfg['scene']) self._clock.configure(self._framerate, self._duration) self._timer.setInterval(self._framerate[1] * 1000 / self._framerate[0]) # in milliseconds self._update() else: - self._reset_viewer() + self._reset_ctx() dot_scene = cfg['scene'] self._update_graph(dot_scene) def leave(self): self._pause() - def _reset_viewer(self): - if not self._viewer: + def _reset_ctx(self): + if not self._ctx: return - del self._viewer - self._viewer = None + del self._ctx + self._ctx = None - def _init_viewer(self, rendering_backend): - if self._viewer: + def _init_ctx(self, rendering_backend): + if self._ctx: return - self._viewer = ngl.Context() - self._viewer.configure( + self._ctx = ngl.Context() + self._ctx.configure( backend=misc.get_backend(rendering_backend), offscreen=1, width=16, From 9b0098049ff1cc04e67747570f1999ea21580678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 22 Sep 2020 18:02:26 +0200 Subject: [PATCH 133/388] tests: rename viewer into ctx --- pynodegl-utils/pynodegl_utils/tests/cmp.py | 18 +-- tests/api.py | 122 ++++++++++----------- 2 files changed, 70 insertions(+), 70 deletions(-) diff --git a/pynodegl-utils/pynodegl_utils/tests/cmp.py b/pynodegl-utils/pynodegl_utils/tests/cmp.py index f3c0a6644e..de0affa02b 100644 --- a/pynodegl-utils/pynodegl_utils/tests/cmp.py +++ b/pynodegl-utils/pynodegl_utils/tests/cmp.py @@ -94,12 +94,12 @@ def render_frames(self): scene = ret['scene'] capture_buffer = bytearray(width * height * 4) - viewer = ngl.Context() - assert viewer.configure(offscreen=1, width=width, height=height, - backend=get_backend(backend) if backend else ngl.BACKEND_AUTO, - samples=self._samples, - clear_color=self._clear_color, - capture_buffer=capture_buffer) == 0 + ctx = ngl.Context() + assert ctx.configure(offscreen=1, width=width, height=height, + backend=get_backend(backend) if backend else ngl.BACKEND_AUTO, + samples=self._samples, + clear_color=self._clear_color, + capture_buffer=capture_buffer) == 0 timescale = duration / float(self._nb_keyframes) if self._scene_wrap: @@ -110,14 +110,14 @@ def render_frames(self): if self._exercise_serialization: scene_str = scene.serialize() - viewer.set_scene_from_string(scene_str) + ctx.set_scene_from_string(scene_str) else: - viewer.set_scene(scene) + ctx.set_scene(scene) for t_id in range(self._nb_keyframes): if self._keyframes_callback: self._keyframes_callback(t_id) - viewer.draw(t_id * timescale) + ctx.draw(t_id * timescale) yield (width, height, capture_buffer) diff --git a/tests/api.py b/tests/api.py index c2c592ed3b..a680002496 100644 --- a/tests/api.py +++ b/tests/api.py @@ -41,114 +41,114 @@ def _get_scene(geometry=None): return scene def api_backend(): - viewer = ngl.Context() - assert viewer.configure(backend=0x1234) < 0 - del viewer + ctx = ngl.Context() + assert ctx.configure(backend=0x1234) < 0 + del ctx def api_reconfigure(): - viewer = ngl.Context() - assert viewer.configure(offscreen=1, width=16, height=16, backend=_backend) == 0 + ctx = ngl.Context() + assert ctx.configure(offscreen=1, width=16, height=16, backend=_backend) == 0 scene = _get_scene() - assert viewer.set_scene(scene) == 0 - assert viewer.draw(0) == 0 - assert viewer.configure(offscreen=1, width=16, height=16, backend=_backend) == 0 - assert viewer.draw(1) == 0 - del viewer + assert ctx.set_scene(scene) == 0 + assert ctx.draw(0) == 0 + assert ctx.configure(offscreen=1, width=16, height=16, backend=_backend) == 0 + assert ctx.draw(1) == 0 + del ctx def api_reconfigure_clearcolor(width=16, height=16): import zlib - viewer = ngl.Context() + ctx = ngl.Context() capture_buffer = bytearray(width * height * 4) - viewer = ngl.Context() - assert viewer.configure(offscreen=1, width=width, height=height, backend=_backend, capture_buffer=capture_buffer) == 0 + ctx = ngl.Context() + assert ctx.configure(offscreen=1, width=width, height=height, backend=_backend, capture_buffer=capture_buffer) == 0 scene = _get_scene() - assert viewer.set_scene(scene) == 0 - assert viewer.draw(0) == 0 + assert ctx.set_scene(scene) == 0 + assert ctx.draw(0) == 0 assert zlib.crc32(capture_buffer) == 0xb4bd32fa - assert viewer.configure(offscreen=1, width=width, height=height, backend=_backend, capture_buffer=capture_buffer, - clear_color=(0.4, 0.4, 0.4, 1.0)) == 0 - assert viewer.draw(0) == 0 + assert ctx.configure(offscreen=1, width=width, height=height, backend=_backend, capture_buffer=capture_buffer, + clear_color=(0.4, 0.4, 0.4, 1.0)) == 0 + assert ctx.draw(0) == 0 assert zlib.crc32(capture_buffer) == 0x05c44869 del capture_buffer - del viewer + del ctx def api_reconfigure_fail(): - viewer = ngl.Context() - assert viewer.configure(offscreen=1, width=16, height=16, backend=_backend) == 0 + ctx = ngl.Context() + assert ctx.configure(offscreen=1, width=16, height=16, backend=_backend) == 0 scene = _get_scene() - assert viewer.set_scene(scene) == 0 - assert viewer.draw(0) == 0 - assert viewer.configure(offscreen=0, backend=_backend) != 0 - assert viewer.draw(1) != 0 - del viewer + assert ctx.set_scene(scene) == 0 + assert ctx.draw(0) == 0 + assert ctx.configure(offscreen=0, backend=_backend) != 0 + assert ctx.draw(1) != 0 + del ctx def api_ctx_ownership(): - viewer = ngl.Context() - viewer2 = ngl.Context() - assert viewer.configure(offscreen=1, width=16, height=16, backend=_backend) == 0 - assert viewer2.configure(offscreen=1, width=16, height=16, backend=_backend) == 0 + ctx = ngl.Context() + ctx2 = ngl.Context() + assert ctx.configure(offscreen=1, width=16, height=16, backend=_backend) == 0 + assert ctx2.configure(offscreen=1, width=16, height=16, backend=_backend) == 0 scene = _get_scene() - assert viewer.set_scene(scene) == 0 - assert viewer.draw(0) == 0 - assert viewer2.set_scene(scene) != 0 - assert viewer2.draw(0) == 0 - del viewer - del viewer2 + assert ctx.set_scene(scene) == 0 + assert ctx.draw(0) == 0 + assert ctx2.set_scene(scene) != 0 + assert ctx2.draw(0) == 0 + del ctx + del ctx2 def api_ctx_ownership_subgraph(): for shared in (True, False): - viewer = ngl.Context() - viewer2 = ngl.Context() - assert viewer.configure(offscreen=1, width=16, height=16, backend=_backend) == 0 - assert viewer2.configure(offscreen=1, width=16, height=16, backend=_backend) == 0 + ctx = ngl.Context() + ctx2 = ngl.Context() + assert ctx.configure(offscreen=1, width=16, height=16, backend=_backend) == 0 + assert ctx2.configure(offscreen=1, width=16, height=16, backend=_backend) == 0 quad = ngl.Quad() render1 = _get_scene(quad) if not shared: quad = ngl.Quad() render2 = _get_scene(quad) scene = ngl.Group([render1, render2]) - assert viewer.set_scene(render2) == 0 - assert viewer.draw(0) == 0 - assert viewer2.set_scene(scene) != 0 - assert viewer2.draw(0) == 0 # XXX: drawing with no scene is allowed? - del viewer - del viewer2 + assert ctx.set_scene(render2) == 0 + assert ctx.draw(0) == 0 + assert ctx2.set_scene(scene) != 0 + assert ctx2.draw(0) == 0 # XXX: drawing with no scene is allowed? + del ctx + del ctx2 def api_capture_buffer_lifetime(width=1024, height=1024): capture_buffer = bytearray(width * height * 4) - viewer = ngl.Context() - assert viewer.configure(offscreen=1, width=width, height=height, backend=_backend, capture_buffer=capture_buffer) == 0 + ctx = ngl.Context() + assert ctx.configure(offscreen=1, width=width, height=height, backend=_backend, capture_buffer=capture_buffer) == 0 del capture_buffer scene = _get_scene() - assert viewer.set_scene(scene) == 0 - assert viewer.draw(0) == 0 - del viewer + assert ctx.set_scene(scene) == 0 + assert ctx.draw(0) == 0 + del ctx # Exercise the HUD rasterization. We can't really check the output, so this is # just for blind coverage and similar code instrumentalization. def api_hud(width=234, height=123): - viewer = ngl.Context() - assert viewer.configure(offscreen=1, width=width, height=height, backend=_backend) == 0 + ctx = ngl.Context() + assert ctx.configure(offscreen=1, width=width, height=height, backend=_backend) == 0 render = _get_scene() scene = ngl.HUD(render) - assert viewer.set_scene(scene) == 0 + assert ctx.set_scene(scene) == 0 for i in range(60 * 3): - assert viewer.draw(i / 60.) == 0 - del viewer + assert ctx.draw(i / 60.) == 0 + del ctx def api_text_live_change(width=320, height=240): import zlib - viewer = ngl.Context() + ctx = ngl.Context() capture_buffer = bytearray(width * height * 4) - assert viewer.configure(offscreen=1, width=width, height=height, backend=_backend, capture_buffer=capture_buffer) == 0 + assert ctx.configure(offscreen=1, width=width, height=height, backend=_backend, capture_buffer=capture_buffer) == 0 # An empty string forces the text node to deal with a pipeline with nul # attributes, this is what we exercise here, along with a varying up and @@ -157,13 +157,13 @@ def api_text_live_change(width=320, height=240): # Exercise the diamond-form/prepare mechanism text_node = ngl.Text() - viewer.set_scene(autogrid_simple([text_node] * 4)) + ctx.set_scene(autogrid_simple([text_node] * 4)) - viewer.draw(0) + ctx.draw(0) last_crc = zlib.crc32(capture_buffer) for i, s in enumerate(text_strings, 1): text_node.set_text(s) - viewer.draw(i) + ctx.draw(i) crc = zlib.crc32(capture_buffer) assert crc != last_crc last_crc = crc From 76a014733bb8814d081b8e4423629be73baadf98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 22 Sep 2020 18:34:25 +0200 Subject: [PATCH 134/388] doc/tuto: rewrite starter doc relative to ngl-viewer/ngl-control --- doc/tuto/img/ngl-control-3-basic-shapes.png | Bin 28819 -> 48600 bytes doc/tuto/img/ngl-control-color-widget.png | Bin 59888 -> 83677 bytes doc/tuto/img/ngl-control-reddish-scene.png | Bin 38397 -> 49080 bytes doc/tuto/img/ngl-control.png | Bin 78788 -> 23951 bytes doc/tuto/start.md | 44 ++++++++++++++++++-- 5 files changed, 41 insertions(+), 3 deletions(-) diff --git a/doc/tuto/img/ngl-control-3-basic-shapes.png b/doc/tuto/img/ngl-control-3-basic-shapes.png index 337e3f98814809e02579fc2d3399079dd676136a..88e63356ccd10aba88ceec4830d7e884616ae9a3 100644 GIT binary patch literal 48600 zcmZsDcRZJE`~SyCOG2`;GKvzS5VEq986s4&k|ZKoDaj@>k`5)j}_)jvQZB+PI zFIP_m{zYk{a7KYd$_wAQc4-U#Ki6e7y)z_|`vDTk=RS$_4}at{Mk2Y0kVwBTl1NgI zNhBu6g!j5K_zNnF)2d3OP2!)YWf_m~CtGi-pHbdAykpCDF>d-o$~AnLq^_iJ)~#nU z@s=A~N5`g|SZ};x){i5``Fytqe(Vz$>UwM;a{SmA`nkyA*o|_5`;iYnldJQmELqt5 zR@V9KeD(9*&Ob3(?!vvR)4Ol@N52jY2{!#TFXPTT_36`h0gvf-qe=58BfFo6r~dED z$$bAUSBkU$`_hujBzyYbpF44+vXTz{_cgk|ho_8Roj$_xF+Ueso&tHhL`8+Lqx|m= z;`Q?^v*xPdcS`F|CV6@HFRDs~>vwza#aHwrO$OfWmZ1-9|9+p$>t!ZI^3v_tqJF0F zsZH+A0D3<9K(6MTuzO##=0x-R5EX2d4@gHyimXC z&+AKV?%7@IR|-w421L~8S=$a+P<4z&XflpWMy=Bo_&xJ{wa|4fyjkMf#^Row-Z$5; z_BvlLF}v!K>LaMG#7*~*ci45bdqd_~SDow6!pq4eLHKH#X2t8V<^l4%@)LC?M=IrMeJkbp8OY@$`K-8tl(_FJUJ9W)Xil5c^6fdhn%<%O)s@z<61)G(s=u+GGrOIdu8kJ55?+LEe zlw_;qoY@nq62TW|drTwh`o@Me$)5cpA9q_o`vXTw>d$tMJ;lsbcyg|F+8(F$Rlp6U z=mgj@{B>u?jpP`fj^d}My&^#yJh692k@;@pnDe>H$L`@lE4o!;tQ1p34y4^xI~LSB zy456{j>a!yjl0NnCWva_k>-G zm#nYz@|$KRv4&E`Hn)rooXRuFIeXSH|5vk1SNV#^%m*?{E6djx+v+6;hi2Vm#$2S< z?vf5X3cT+oUFhOsDmS6=>&WuM%-^hk-A``F`@D%_$h5P2Z6er^kDuq)XqlRx9`{}7 z1ob|dfa3o6@D7W*5vNOqkJ}ndSmU28w`Cu`J=C|k_;gyUstD}| z>%!CyyO-Io4)7caegDt>jmZ%_RYr5|-dIf=ZT0QFu79|HIAo-!`ziTT+~M+Rv9n%# z+*W$rMo20#?4pR8ordU;YWS~?;vdg#E=g!KHwBI=unnpP4DcAS3`b?N?OXx zmw|XlPg%|lc~Yp#smrH#cE_EkMgEpPic==bay z>&%c~3S(*U;1s+#_JM5drW8$d4BZaD7ulJE!3+C7kKeRC&KmUo78Wb8#Jc}pqpjtS zd*REqeR9A0ZqL8W$;wc@bZMdpb$0RL@H5Mx9VIDkcH_ETi4W>jUcH;!@mGpHePo2X z^e6eRJIi!s9*aM9t_VaM9H*YV@cp+jS?1M=7nzyQJ&HYLIE2;J1F;5fHh*Ni7l%rg zEywj3z49r8Yrp?i&?UZ3TuL1s`yl*zjQWXO@Ew!BW!(@eF($3I0a(?`h36QUisWcN za4@M#vORxS8_26Z`Mu14GF$Tv>k;wz=&w8Dfy+cD! z)Ma6zroQ)zox=fIY83U%%G%kEuX>-4hhw!&Id&JiEZh`s$SBPZdj5;i(pV*{4KH$= zpJGF#QsV~2WLMeNL@ib0+gn1S|jcqHs9GuOUydy7`aqv2<~rRKuwfq`GYeWTmqzm4i%1M9Amr6?Vv z3!7&?`u*2j)%Wk%x-?Zcbt5IBwQR#6$bg(-;xEg%v8m*W9xn^|T`rY0x==y=^G2$j zr82*NPXsD)^YR_gG1EVt(4yjXiemlWU+Wp4jna)zZ1#p%C%7h^Qrc{1R@R3tt=hY~ zD1B6V-+z6&T6Y=`C&Ef?dyKVImh-N>&)c_>mc~`%HzS@zhSU%7(T7?To_mq`YQKQM zT}S?q-YG*W?*Xda{?+L7Z%lINuVi$N%aiAJ)-M#ivRHj(X5W(^{W0Vw4TqGbiQS_I`6<&Cl7#H|uq{gmDN8hNEb^fUk*?-+xvOk2SP3^u_vf$JAMf80qm?_;T zDY)dVRcJ0B)H+yIT>HPODAo3t$8EJ^_WwI`zw|pRiNADLByZvNo}HBUY>(wcYz{awmuYE>Fxc<<`RKR? zZ<&+OplscbFAc>Ct~bQ@?FtR;ryMPG=OeHDwq972ZK@`{_bF3!n6YCOJ!J+QAHRR_;2W31%fhKa=6^sapQX408mHd z#Y!%^4wB9a+08olxJ*CH3a8ZjJQ3e!g&F=#K2OjKvnPGdOg;F$%Pu3+sq{_VJzK5j z0-h-Tv^K+WJ31NnBD%w3Ua}k)-jGSvyUBEqK6P454e=RW&iTsGS{KR7K0Cmu;c@8D z=YI*yp0cd0tPG7~UtCN}`of0xxzrTAS>miL;Y@L<`la-FPy|3 zO{oBV?9LcV-(xIS+jalC^GvPa%(*woE^JpB>CZoYaeSAyUYKzEL`|&rv!2Az#D;Dy zUiy@jl)ICYlV4od{eGRgefxx!m6gW8i-Lg-)im9kQtW_E#l2oVCz`^Z+k;S+0xPyWFSm6JtL8NQZY}vw9_P1`qSAOR$E%dGqFBQj*rpJ~MwY_LSSY zW@cv;6%{jXlQOfiZb-6?E&S7ZN0Tr3rM+E|mwvyj>_&D}A!FTjJ3BiY3G642@z-Ru zJN#bvPTd@DACaEsl=XZp&-L`h(8<}FJ?NN`F)?)uofot{Jw2~hvKH@Ck93q|1I+vG zAkiwjDp=g-x^MgT?NL!tGr!`m&&?-qvSoDy2{ub6e!Bjf-gf2h7wz%B5(z5byM88kOkw9 zgvDmnsgv?kp>>PBJGdhP2Dj&T+vog_p0c2|=qsoqolJ86SI&}1TktRCt*OvQYv=f@ z2YQZB4~DsQgx!~1;_)&x-JS6zu-ks))0}$m+z2-!5YO&27b?0BEIz4wXJxwB?b*z^ij^s@N zQp?G)s(!F*?UzJFrA3~Ou5M-N?fDru8O=D+Ey=C*s+?X*#SVRsMJ|!!b<50u6k}jw z!b5Um;qnFNKS@vjTyTH)xBYZ9--B@3e*xuQtMp7v^%=?DO-~hkyWTk;9I6W)ns1fM zc<~|tYbScG=NJu} z-Rk#>H7jw|)z`1aN@|;$CJb(I`~5P*Z8$=88zuSU7VFv;HYO&2!z?}lfk2Lv?vC~B zw|*|Ip730LJ(q4cE~9kt-)udnn9V0PUiHEL%?*j(;_IrByiLu`(cL+{Q_qWv7=3Dk znZA7gPW{Gp24JJwe>bO*&0(p3I{Y=w!WAF;Hx?yiS0}}Q=4t#X;#(dxT5<~rsNYT+ z92yD?53e6^bo}{&?1%frk@hcNdSy;qoG&v+Z~2AK@-kCnN2pp@aGT^X7IXu>qk1`V z41;Se#%GT2@in@9nI~LkZrh!;Av%R|CSs<$fLQZC!JbVh;@sm0AD$1&~gLW4E^4`Z2uib2)~Qk&&M# z7VcMHYHx41wYMLhD(<)5+*k+ersd?+*3+Xu@JRjLD;obHX_e1;f6~W{h@DA1s=4=y z72YFbu=0s=k;rVr!xubfmE$kh*4EBeZsmOKz3yIgt(Qt(QIWi{v5`4cMUZi4U|?XE zy|{*k2LIv1)%M~A4t-L!m*XBjB)zHQmt>=vbX~pGlTEX6$Yx*t2w6bl-hloovowpB z7r(!~VQFohe`836iiWKLHM2YzEPSrOdRJj#AxQM0!-wgnkzIyeXgGaj*Mc({MEyTE_{sRuV3xI(@4kIsKiImIU9 z_VB{mAQQMxjF6cIF1@g@fNIFh%#`$87Kw|C(@c_yOFtc(f91;o;=xXz-nmcwO-#P` zpLe6njoRK-%1mZo2Rxp)*`oXh^6EL#w3B5q6*#>9>K@Qe`q+8lPRnTWolnD!j~bhs zhoX(lFQGoq2{MML@K_hbbz~XM*D_rYvHr;X-esch{Pi-w`k_m|zrL)gt@WyJxszQa zvo=$0>b1~;R`#^RZsp%Zu5q5l7X1t@fYjN=-s?k-lGw)wX}8UMJAb|H>(`pXPV>|J z3{}%hkDffyP*&b@y?o^W;Hz$$3h}pL?9y)s9_(9Q9ttMc?9+qcVPC)IHYi2}<;`j{i< zRV3LCu+gA%io~~Ir|bz->QQRi7}$O1;+N-VLRiEeU@uvJe0;aoA|)>`;`?{g+H3aW z@hqa2+t46XGrruY+`@#ZyZj}2LrH|$a_n;oYHOx}Cw%4u#cq+?bC!Dg`W?(ZT3oL3 zVeFICR20P4*!S)6@GjkF)MVg%huTb0v8*<<+aO_I5p#it$^=6pOR2fq>uF@yW=>&mSEnimqb_-vsUwqro@esM};lpmb(hCGQE8H zQf97!=VQD?IPTH>&bo_}vvYUe<*mfayZS9V1)~o;DbXOyFz?p1`Geh@T9+>EFLPgz z`s2YuyH<)!%7!>uJRr@Ft;|}T5z$F9M#&;)KoPz`}$Hf)#6mI zs-4{-b#?VpjNfdNLXE&7y3VI*{ils%SdQ7!CVMTi03{PkijO}^NcdRLD%*^geXh`M zFU3}x;q|5dgRG&E@$sk4%-E;PS6}5Hn@3l>Slr7Z<0gXpRfw@d7Y|fPE5%&5w6Z$t zHggdj?y%9T;k0nsRA?`1yjAzM>^%4WrhrLK%#$aq-?EL<+|16N{iGr_Oh)^v;lTqc z+;h*fpMed1!^4&szvA(sTRGf(-oKYZ>7X!4s9A^i;|D%ki}};ATg(kNqGsB1FUSGz z?Tx)^XvhdS)-~n&`}c3P)2H>nQ9wfFfVu^4?D6N_){?$Yx!wzh(XZvr%{iciVW=$; zFz@kWHABM~di|$P)}OJ1P`K~Vg09%w%4c=Z2PzRml_T6g>7+;gwO(-(6B9K}O{KIp z5%<5g#PE8qmDq$LZyXAj3lnTMbsO+wH`zfeHg0>YsnU1r_?L{`!exKy^`C2ATAmsB zJJ#lFlXP3fxIQ;G_n@$F_|eOCGb^LX`ntLbx#ne$Wp0Zmd;b$K&t)nzZ$+c2NjTvw z!WwGoy)r`V+@>atCr_TJX=@)maUw1xgnDD8^$t-0F~S!;9NB8XTi(1gn?L3Y0F*Y^^TBxlG z+x3I4Z5?9=z!eP?XbO1#QC!?hOO_YDn{juUpLs0($r*Or%`O$SvA&AGXGkCSpa zL_tOU6Wu!P*|YoI1vc|oH0PCuCpdjy&BcXUaXa&*H(a$n64$qQMvul5DRaDy^YcIKVJx0*@-j{Kuj4uK=cC7zf z7)tTyu&4>3!_5udl!^lyKXT+qRBWuisi|hHkeQ=YA~96`b}~gqN4uEbUK}hjGBaxd z9%yWBwdu$-Fm>rLK!Y#1Ij%?ASgx1jR;R!9*Lx);YX-Z#x|!j%Wq;I zK0bcc(BCGfY8mh4yGy58L;lCZxusRhvqRe{$=M_weSVaAhyls}`jnXECRp>~oUySw z2KDUh?ANbfbxce&xM)92FLA~iX{ELOH=0~a+Qx5!QutvT+yGJx3MPR?}FJA0=eTl#p07aPV zwRNHUW>-9Otg2i_&2OKJp5NJM|K+guYr$s9HQD#B)72D9>(N&p9r$_kT@+bGnud?+ z=?=RJ^Ir2s{nA^{u35-!dfnk1?>dyRmF}fE9wk}Ut7l9kKweA!=#QQ~@eYe#Y*+d* zuIfhTBch_dT92cDT8PF$|4e=V{t2oh6+63OU58)k$`t`r5CHtf`t%*=PP1EdoSfn2 zx%CqZ`2MJL!5gnE?UXF+Z?+><*eYHg2kEWFFk$uY=hx1giA0#tS5 z#*Lt!l>X`Z>81Iu%i%|ze`)3y6sT)zKEpJ|%-XSI2gBuNOVNRwQZLbhq@`B_mU5my ze=s~e%ws+(4{_a^!lX=c@JV!3)K5prhEJcKXJ+oG=qI3WijLZLn(P-ZxC8{YCu-HB zy{T(xa34966H3QL9eR$PM90Y`6N)K&*1#YlCPoD{16}}N7V+>Q0emYj(mrau-#k*~ zP^_=7@7p=S$H$j`=2ztBgW&hL0a|LREB5wh@5wheHHk1&12fr+@55%luecwr%ht}W z>Y@lg%kDgd6mIKO-1D0^$6V%aFz%u8&oj;`E&j=~v7E6vGP7YewyVzB;oaz4Uy`)N zEXQHFWwq)0jXQ6yg%$PXZ5WPw_UyCFtJ1dc2|H6wFW{ahV4}KXE_iQ%-5r^{PR}D;MzA@Ej=MBI zLhXKOsf_X}lyGP>DGld?LwNVce3cDTNT_ammEIKByuyEF)AuIn)2&a2iF11fERD8fEiL{VN~3^;ahR&1Tq})26rfr&6%Bo%`J|E7kL-YhuOg+T_>` zOJmqf0(rYedwrb5xeqWa77uzV9w&wEIhsa`Kc$G|6?iof=4zYKD^Gvzv>Iexr^MGW zo;Ee%k2Rn3eEL+eJGawAujk*X@H?V(CbS&k8T!392<(Nq`Kd&`syk?gRa{)WJ?DJ! zeA70aT|PtdN=g#q;z_S3C%W%9{8iJ?_&p>4g+(kGI#Y{v+O6py8S7neS8XJuZ~ZxI zVf2Y9te0eap=;Ei>syOdWcl8b=e=Aizf? z?*MigG!@+X$U0BXo@Be46M@Nqj&z#5+`hF7V;|cyf#Nez zwLT7ePD)70gfjiM0WKQ=EP;g0-?PdXyahY3dH{k2zpt~R;yu0XpD&6$1o#2XK3`<7 z`=*8`VjD(d+brY1LB;Kk^S@dMJPES$)9D?2 z#zY@B0!hCo*gX0D-J`>=-rh@;zU8#JzBE5mz58-;@6k9-LG#kb*LsU?V73ma6Z>>|rh;AsWCvfOLZB z?UtD#L4(ERH&**MMO}WM#o%jfX&D*MNPdnUe&l@7XIv@i&c*J+~Z#l$Rt*TN~emrW) zLbAv?Q*)~(CMfjC<$YPZxMIvLn#=2+PX?|BINJ&zVDOO1G0X~hTl1~>y4>gH{A`9) z`e;h!`|kEHFLua7{5x@eTz!rGF<8sF2EN!+?Bh9fx^D7T|)s3BKj}O(- z2L-ItPEYlf#DdIe-444ymlW`A?EPJqc3u`?{%m{f zTF*?~zU!}Dr%Yh}wSU0?NmNP2#?lgBka$(*r_URo>gOwl*qfgOO$5_da@V&WT{<%_CCR zROBVuRI)ns3=Qi%rpr_f4E7RwD`dB4PoI+FTk8Bdy&C}5#($JaTNk_wwV#R$>YX|O zmC(4rTA*<6&tau+!5(W+BECRQBFNZoPV0@e73}9Iw7ml8CIlae640MnUH9Br8P!ab zGKdK9`&PL$kz4T#|5Jeq&}|iaW9;*O#v>+58X7xzR6;&XEF3CuHjY^Vo8}h~NO}I8 zNzxg;_0^@(oZ=tca&pPr_Z*l3 zs~`k_Fk%*wizLj#1xmuwak(i;MTj1#sU*-#OXKHHpZZ|!?XF%Ww29oZMOILdK?rGv znQ!@cc_HcoWqtQvoto?^48|vjD*W`R&R%@T=^ZmTGcJonz+7+t2Ek?tt~EF~xIA_J zPH_M9=)IjnA1v}7CMKRab7mL3t7GEgk%@_+t)tRY9HdK>Yk<>+rp-L6VKo;;e$KkN zjivZU9k&^9lHF%bjPJmVfavj)EZ1Ur1L2ceB@^-oK@B)PXUO2uZQ02v;`Q$)SQAJH zh+dM{qA?(?A3nJ_4;_Ra0J70?uzI(gWpB~75VjMMV7e8SmX;R_x)H*tLuvtd+VGWW zqYoKH7gXha;`SGz+z}fL`UE{S71$ubh;qyS1raL5rRoO+p?Ci~rqEyRbsI7=|Di*J zgiwnW{oyfvqxIyH$ej&OQBhHl(`iRD5281U`Oz?w`kQ!xk(BrK*^6H;=#I*~v$C=R zV3&STg`a_2Sa>(43g8xf;Ail7aDD^}Qd7D4_Ub%q*+Nc+6@|2`^S2CdlwqL^Aj{O0 z4XUYInR#uk_j~CJdFIy&O``w(&YM_AZ-A2GBi=#XN)`63)iM8BxF20aWF+|;cQ#PM z2Jm-6A_a+~-X~7&0~StI(54?9e*rtZp+UT(ELHt6&Yi4B0`1TC9*=a4?bQg89y z)aYt3v7+o8F02i^Ev9`LM+Y6jaOT1FZ%%v*m0KIw4MYhwlOxGGWvoWWt4tBF&Po>8RHc1{HP12@XGmGbN_${X>yTqc$; zs1MT*9ZV%HVr|Wn(79Y)#Q}9H`q2P9#@f?&LKHwdjCk@SsLdAFu*f^-W7v8Zu1wN9 zD3wL_-BtCRcRmVx&+fE})A;)Qy1o6gckfvIs{!URV2xsIMaIVZ_fHcV3PJcGe8y>B z``yv`?VFyC&IdF~^)qMqSVQMVDzH`hjkT>XA;80ntypyT+%F75j8;lrofI$nx6*phuZmvzh8$DsOvy+oeMYIy736>u9XGPJG+t6BEOT-L3{X zB>tERvBhxJ!Ebb5E#&1M&R?s$m0x}gG_ElqAg4n}X6xWIk2-zuRgQRFq=1;wFQHJ7 zbYem*rnOhCo^@Tbh_$QHlr@}tyKaLN3Xsr}Iz}|Y zw;pW{gRNs%nDjXo1~K7W$M!ZVZrc|4rb6HAWKU-*0Tw(k6fofg8Jkaxw#%8HrFiNa zppXSK31Jrp2Zwk?xzS8j`Gi~8N~}oUQEluGXsxZUPu{ES2c7s6dyOU9SAvUjkJjFL zD^iW&L~sigs-PiXlb=B*(Tfr@Gtqg2CBOMxSccyBnXKqQY#{~ zjrZMz=@Jg8rSJ^I&|NX*W6fMy2uug8r%uO^?$a9MFTzH1xMwNPedlkVPDUU#)>9xW zk`Zc3E(>>d*Yjj}0JQEefe(U=U#i|n- zd-k_GW2h^~bUG9}*X}jKywS2d@w0$EWfzoHCqtH7hYpSIGt7#HxADoLj-};BEy=df z1lO#W+nC+$zBn1!JTizSv;6b@9ieOu1s#5+V@D!nQ$V#xNlBmJD!p6z6~FS+cQ-R| zBJ@N3zrR{lYA!y8TVFC=9xT}W-KqHq!`{8RrludATTiM&xX{X&e-_hzG8$b8LPA4B z13oSLGed4&z_o+}Hm#T*s*!AYh9<;6H<>dkkS5MyiTrEL+4-=+2COjS%#_C7-Roy^41cLwe zx%GiFLqRu@c!6mA5DchwltN4E_=o80`atl7XyamRdr>`99-KzQvs-H?WyE(XG*AUU4u#kr zd%;^BLTNy7@YVT*9hIrJ$1<525Lalk#YlyKlTO?Acl=9Uw>>sNhlq~ui7jjbj>c{z zi!C3{hH3imB8AsiKR$YSn}0Qi8}yP7Utj9O*8dJ8odmoA>;v$Oz?$ggI%?!9(JEj} zV6i%$i+t|>zMqPmjGmr;8K5*Lp%of$aPO2gEDn6q%181%IY(`*s^<-*ymb95qyU9~ z?WcuD-cP(5YC4>22_IwaZ-!htz_;yj8VJ>xntDA&aCe~_opk^E6iOGm5iT7JlECG}YZ8m(VN}vpNnrY&c4W00N3iA?a!S`XZS`-_1R6MEZ}O^}CyIQM*Iq z^yzmC-_F0x$~u+tE0DAH|}KHn#r+V@`?_r+NXh6iEGN1F2^ zXC1|Pnh?wGJh&&qoE2s>c0E`gBvCpVe_~^!Rey{V^Z)o!Q&dds-NwHOGI;N3v|t4_ zu-{Ns{~?46oBx=QkI3z8CODY=Rna)Ymx4dV!^7iy?;bfJp2O+AJ(*X}EaxqAI#vj& zb;k|^BZ0cQVBzwHY~z9`8mjVL*=+thhKTfyB$(0oIG@OKm|F% zqz4QDiHF_^Pk%1Hf9=8M(&h%Ik(tY0sN4vVJ3EU3#Sfw8eiAVZBxd;G&zQP)TYZPw z*%S$358WlnvfTQryH7^t4Ew>k=}q+eC~}c9iTBX8E`7Gx~K%}PYEvt{Q~A0 zANZ)GVbZj9bv1;0|K$N)uLgmOll77U>iLBcHbAWWoz2yL;}X{+j}DxHte4o)PeDL? z9sc_rcInsq%L!9o^RdWXNZRzv z&YVf+n1CGv4_PmM!*k$llg!m)tW>)>KEBXRLtDkhQrSKTx2oEq80Q37Bpeb%UqCWs z_pSUeGf;gSIu?u6O@3GqN2e<`WkK*2b2|@K<>Qr&G{09&$s1^!_ zhKxoRypB(oEpb4ROF@(eL(<@Pc1B}VTa$m4n zxSSPHlwP>S_a`)NHkrgHt> zYN__Be5`^Ri|$YA3EHUn5xz*4mB(HianJbj9slY}NWDM-nd~i|PKe(HapQ16dQnJ7 z$ozqGtNx!aQE>fdH8I?>b9OEel@1CGVpL%M?7DbBN@sw3Z$PEe$Hz*A3UFdNC$2qG z<=l7w8;iu*1YeQ+_bQ(bvND~Rw~(hBf7oXGrp5h@q|x%X9QM^alP=PIqu*rZ&y->n z7>wfFrZm?T1G_g~+9tp7kBED0ZcyKljtkKzvT{LV3be@BiRt-Z?j*7$S8E>C7<1r?}rcJ~EOX z1*-`!YA2&0fm#R^tMd&)a|Gp2WaJYHi9I)@sWuR_)Ou5ROM-i~&RNZ4@hyp7nV7Bh zTx>L11)I|YZ>dQUoG0O?5nYlnNt>I~VB}#Q3stN+Ar;sC`Vs{R;s&6zu>k#FwCdQ& zNqd!Wjx!?6ytLayuK$q1JcpK`|LlUZv-3*-=K2L9%|JA5Xp&&0qmieDF0C$3VQW83 zNcet5w8Wy%4Mr>x5>bD2fDQu`YQ7faGH4A4_wT<8?)e41yUK6pyNOpNV|Z-ju0+7| z|1VMV`p%{|aS1+0&D_r9#V+!^PZvO!&CC+@+9jo>_Z4(!8&ZTQyk#^KG9nVO$|L;S zTC;T4&Ylou7bVqPv=oo;MpgyQ7K%DvUiCBeN86};337l7(L&Y$F@;P(`~h0pETlI8 z1h}@If7?_wG``)X*oq})db{+)1FF2GjSZ3gB<6A((f~yE00yxl7wyyXa?H7`XZ?XY zv=i?>d~8=l_RLi}{I0AE5Y_3n=Mz)JcyMU$`zUqIlYafw=N)x;2@aydTT_lw`-K4h4QY>n$=S7lA$9)dVTtGRWMY|Fy&-LR6a1k`qk>gk?v z4lr|#v5Ld&x1vv*GYdPs4YUd%kZ>*Gs1iE`yAtr_flAr{wEMqPy|ICN4q6m+cNN&| zMyhutJ=UBsc9Bsdd;owoAYcq#2yBE{I9)nVgBFUP0ac{&oSOj77})VRCY=n*cOm1i8@ffA*5(X1Rin6%z_JJ_Ng@3ekd zXcHpq;?YA=)fO)AOz|u?s&`2sy&C!=Rz zxaDQf^*fMKBq;e@OQ9f2qIBtXF!L=W|Q`NFhxr9=|KTg-|n3oG>0A<5f>ZYe92+ z_3jrd7=fC)`hJETm-5aLF%j5lL!6sns*Hb1=e3CV3>XJkQ99-XW#ZYpjdoctgEM&^v|AE!igt9Ts$;vb(qHp0XRW_#nXd{eRmrQ1@Tm- zwLzp1!3q&{EU2ha^S=d)ow#Uid7j*m$|U=JQwq_vdO{%s{vWCjSH)PLUS0RbzWsdY zE#aii7bLG!Bby1eN$IXdF-1=RX`N8qi!0W1xG9~uReYGg`ccn^x&}Zy0;v--xk|~1Dx@q*2Or&=ZS)2vYld9UUxxD;Ac=wYa>iBq~hsU zk`oeef=Kt7X_-0HT7g8Nd44L@V|#=n~E0~*caV^`>517@3*#K7kigAS@} zd>w6>NY6lte;5%_iI5B7=D;=&6U)-F!7&q z>I~dBk7y_QB}mXEaG7V%pNrlYR6+*s+*`-}2sRx=ihJ*2!_UwGKt*3>XAifja*n`u zsUMvmSX#ZkxxPd|^($Ad*!aobCswr7-Jv)h2@$+S*<_rZN(rH}v;VZ@jP!o$7lv1# zyYA}H_P*S)QxJvaYdI;NC-ej?dDt+xHDIY`qc&2(=GFQ>YATdEL{mq}wZzi-uA(|` zp~dyCB}5_Sylr-GsPZuEDAc&@&69Xrv{&H!0Bh(2ZebqK{z#zxlOeEKkp~p z0At@y<`bktGEs{Nw}=7v6Fdb1*iI%PoSGzhGm*t?%B5uzdFL51bX<;~Nd3(zWSFDMm;ar~T zDI`+7h%0RcX(SSENFflTh%n9~&6(rxy-ZDQXB_y~Ies(SyzB`=P=uCC2>1MZ1dq>5 zub%kB>@&T(s@BV?9x>&%*d1{>(eA!(Bw`jYbb!4^OZ`~(>Fz^-zSiQz_=Q7xLJlI# zgm{DYef;V-{z`u!8^T`ZIO#58QL~K%*l-G^aJepI0FJsPa#??WckIMTJ=`{!6^?S> z!`3)Fa@PnBMD@T$Xa@)1P?Gwo?)KP=6VYoVhp_7(K6%mv8G|s2FLo6M#cXIm0O2DM=jgsTK4*P!=ul!{!1njx+rUq7Lqaf7 z5Q3LV{F5-9tW9Ni#Aiq;k{2*dx`ZXM>gDSfes{sW&Xu_z#}|~fJd`#O&+ebLL7YWq z;j8`{PQO^-$m<_+H`_X(`7BMSkW+6Lcmfs-T!%P70RjqZgYfTApeRlpW<)RFzGcf6%rgWK zh;KW7JQ{b33?Rm2)x-jEW&v?=BGT5`31p@Q=@Fv}F?FnEc<*DnDsBWl}O|RIX7mu5*c5l$S@9Y2x=DDz{`aufC@ObcS$_{ii5)eKE5r`r~pC`;=DQi zBM~GQk`B&e;*})Q=LQEgy;E5|Yl5@QaJ5W!&3NdXB*_u+S-zwJPkA-*^Eu!2=e9r% zLz6<3cjucrb?C6zQ9TFvZz6c#^!{!CG!kL)zz_(d`h4s5X`m+jH&K0LviA;f%*JuL zv)5ct`HoYZEMVb?sTTL*(0uEZW`Ots2T3 z-m)OuOE+p}VBja%oS|vZJiS!h*dNaROnFV&e)eV8&GIh!kLSDs&n0f3-3WZU0oH^i zhqHqXjbou85O3MZh)8JXM0$C-`H9^T`{{ebx&&ELlJ|!Mb&dY>oIm#RMFM|n>ztbm zLKhtYvQ!5xNdYh1eg?2#wKu4kJ{8F;$ava6E8D9-PkT$%Nc^Kmr%X-1>-M?`x%3Qr zLa+my0Xf!B&Ft`geYIkk}8CV~;XbEN=mD)%fh! zZEraDK0ZD^4qhjwQwBi0dSsLf_L4P@Gz}iQC6)LwQ971TGA>-uh6#f6cGA8$0GeJ@ z6a#Ng+~K<#kP8$enBhr9!3WNM|MfA>x6RhFE`$a3ayjoDtRb1rwRvF6U5M!*X%6-B zT2Ot$%Nlosnx2yPVXl^6jmv|Y;zED_+w=}QO$3;9%+0k>mS}`EIEsczE6Mf?#&^y1 z60#X@r;2(Rm~Nx*;&j6x!bM=lPQaY`IB8RKyv`!wgRr-zfVaV=7EW@ z^FvqOWx|dj042QP;$z2_`8tWPoEx;l#*$`*!)w*R8aMR>)q``1>AA_KbwY z4>6VHgcC1xyURUqu9SjzAaRe#@8R9Uan_XE6haXNdF?o$__V{z6@9WOFv{Qx`|`x^ z->~O#h!lYZHj1ncXju6@x7dA$OvphILiS07A)4_RYY9&Qow%uInXoO*-3GQ0q!dYP z06=)_1hj*!+Gcx9W_7XvXInTtXB4~_vMwO=xp(PZi}euv*xD&q}lr&A@l(c+7Rb>m_ydY-W8i~YRLn(Y6KMGyJj9GuieL4i7z-@g3Azx zTyQ*K6g_=dqGDy2wCm(kY}@|r+obh@dBOS!6%ukZgmj7j?3Oz{&1A`HE~WZ~RKn!J zs2}JbFxU%?Q~#A06$zWPN3ROcxTcDETJ5z|S1>nAx#s|=$u|eTcAsKx(Q(QSsH$f|7&bz{U z9m!U>lnxc^t^`GPU-(@M=@gyyyJPh(5Ly-l6PichJn)m*Ww5qIc$Ap~>r?qE1RCQ4 zxFvzaB{Xk5p@zUuu@2U~Q)Rz1vzVn_1(C$Lmf4&5$`#}jNNm7y`K|9Q2%xsdl0;AG z2pYi6M1#C(^2#5-+GKb2l&ikEx%ZD9#V+?@$fW+g4ycH@?(mbVuLf|`1!poIA*RXj zE`m1@h!T?n;0TGaFxYbt7g{ji)Q+)^gh~Io2q7DSnY#0i|w#n&$&4^#=c(GM|4NNkW{uuO_bS>bF-#__>|7caKKTCN_DkFkO$ zjYJ4|1(8%b;e3D~cY6+Ka!E)qh1WaRJgpPk5+NE391c^b$6bn=bV&phL)^Yg9*QO| z+%&7~4nh6wJvwFadyi)!^dcoz4|gkCa#zZVa4B=%=zi-3Za0Idna~{Vwhd$Yf?!NH z08xzFriuE@aiCyLCHmMlrcD$%(Pqla zli-;kuMsI8u_l`w4zfNu?If}RIO!O8Dr|67H`?+fEP36u1{G4f27NZUn z24QDRZz3$CJgoe7qxJ8HD4c6<9o5f2S=0KG_5S@)oWJB40E)*E4i;N8bG%kq!w~d{ zh|W74Em}>v#^9BMCyt1-M#L-HEO#Y@;Q6Aa)^!-{zGL0~qTI4fo3yYQ?Q?AA8OuIlC(M(>+VO?E+g`WD-%20nE?C4(CA!E>s1LoZd>PUUtM z=ZQdEb>hHpFvv0NeJ=P~D?@tZn)KcPmg$#_>DYa`A&tm%A|OwQK1AnZK6(!x_~F)0 z5Cz~J(>v=6rE^V(;pBlz7@6MKf*?5>ACZX!QXx7oCP(RP-98WxBKrezX2%DdQ9cJ{ z2@2fh_GjucE5nb7doJ_1MI538MXiM8xz1iKN*{mLL+m>M=3=9k+_FW=>%2yr! zlr5Q~W{H!EU$o`;n5ms`zJSo_3V;};XcjI4q2V9NJet;C^S-N!f2oba>x0_ z8<^D`E+-TTX;MjYs*qIE6Y`iP`Ap&yelDCt*Y0(yj=NWDp-V2VEgs3+_W3iBKsxYf zdvYrgE-LDE7=nQnJIIba;eH$f1{cKmCh{Srl!I~f6D#0F_*GDFh8&8ClYdsbICNqt zT&H^cnn(0@%wsi>ItIv%ZC19h7`zojRuc(eQ)hcDA18|79yK&JreNX!cR)>Bc7+%0 zZa+H4O(`fAC}2UxU<;imE!KeSD!4HK+7Q;`K?LG}2?pQ0tK^n2PG?O|_U0lVPSY+> z>+`ATu3QX!+(yfPTFNEPiTcr%T;!WDpDU5JBaZEib!0JtV8Hc)Q(Xxvhy&XqoL+zF z2;(T2N!S`v0_^x`Xs`I4O5@!H2NAbeUUoy$DC+pePNBCxFdPct#}_&DHNnO5`No%U z-r=2}k@;=Efogv-$Di^<-v+xvBLplf@K|zqk0q%i(Ymi==n`l9aPp0y@l@2zA5d9M zTRA;HJkpkJBs@gS6nLgv5GaLHr5&gW%0xJf(0egMRyr#-l)-n2xGC%^*gyV6Pes0f z2naYiJ0obD$wcJ8o~6c6$ZFMm%vsN#F@-pFOKtCre-s7dYB-;GvYhMBU@tSJmaV>( z;zt=RKv#Sqo2shR+sZbgnP2iK%RTxCdJMh1u6u8FdKa=~_lRSTM=FHW&n`Kd+la==%q zJSyQ0#!j_BswB8kO;YhFt?kn!GD6tZ3T*{t>abSNdu2HRU1&4=n@F zAgKu>-EqmmRqlJS1{D9s*kZ}J^s1Ep%&mMqy8Un-qGnK^*=SVbC!DwwHv{?m6NP9v zWCFg7*9Q$~WEXMmT_k&^nn`YpGqvb*pNpIxl?iw*x;!Qf~71a8gr29%%B}A2n|=(X%s6-88(bJInEKaI3%Ke&xr{#W;Ev z6;I^7x;}77dLK_aPeg!dmRy9ZDmNX1si^(8eSbd59)Y%;MjNU^9QcO=ERod1spwBl zI=ams7!eO3j`SW7 zY!=1i;+IA|fe$i;q#xE%?EtE}Zs)S@FR8%CTAu4+%Av;?s8BqucDMBeq%F3v$B2<#> zl_VjQ9nbsdd;fl~*Yn>q{(bKM?r+6)eLm-ToX7EACs2>lYl79-I4WS|R|IxFQ_PMJ zNFdlb<8Ok6XpBhAqs+j`Ev$z45)$m<Cx6T{fLq`wf zOK;JH6H5g0T)P@ZH^4P}yG(byZM{4(SDKa_#@+ zYUtpYBSCXklifl*Ivj#6?!yPDvIC)TZZHYDY>xPb2;dUn<}jQCA5mWi#NY}4)6H?U zrPl+>$6xo{Ut}*;(|&^SJ~zUZpdonXLo-4f6sHSCrPd z2q2(MNI*Ba%LmVM{K*6?O<*Pz zYOwyiyCoS2oL^=0clF)!hX6@HV#9!r6&f2Vp%4+_0MQ1F2X>7hxGlQ5aL;1RQk(z= zX-@RHa0X&dGy$3k-zh#bdNrR{@7I-f97T8wRBQ$q`$)0^M$=FXr1Y#-|3xx_{vvam z;LfTvJ7UfWq|XnrVZdc!VFjWZ&b2w@0hDFx7_QNmCqLiUR9|e%%Rv7GmI12))7wky ztOPVb;4QGqS`qR>A{F%h(?|}8B+&|~n6Y=zK0xrmhtCh)^rp@!(s zaDcX@!TkHzupvirtL5j-P)WJ8U6@OfeK;nF%*K#Ii7ZWStE}8|&DsEyDy?L=N6$Hz zvdH_3n!AwI-gEb!)qHXIRJAfw0vJ20XX4c-H9ctY^;CJUE=pm&QiOp3QwEs3?)>A_ zX!(VQ&)op*2W0a%FkQfsg&#lUi(OpC@Zn6nW*pt9Z~qPR5+Ft>Bkge{nsIVsTDs@X%}wC%J(`Jwf>sjnLj1(3p~ynu zrO?)pq){=2Vt&Zt9w^9pf^c>K8Ql`OUwi=+03dIz z*tP5Qy0(nma)k^k`PBInMzuY1+XwW{Gj`p*E<|jL(CrX}DA;&So}7kWa1dPj6Z2{T z44q`h9*40-19?E$$?NF?* zhjdaFPtP54C46g2&*Fw(ZMQG0td$jXEs@JwcB8~wWi=;purpV^VSOqQ)EU4e6M9{3 za^_l6n$tZiWBtj3J~>`P#@co3Fo7kQ?0(jLwC9!cNKRQvNo?q|cNl^-k`s{NiC=qd zp=jQ9vJ4WC1mm1I3}WcBoSPjv$>0*YMpIiGlJ>(5_PKGKw(^kTLywA&{S7rhmVx#i zUG##7wdIsiLChPpfQ-Vi5>d+4fYd;g1Z&lAx=*Qeon6hh7_O6sVkR)}P1acx{F4Ho zPB6-#Jz6Cq(wAp161&m))-MKnk7+C&m?3Gae<}1*BzVnL_Su3>wVyxFN=*rjbzD+x zYkB4j8GI_>Hd4cghD;q|v)#wquMjziBMajxwFY&1ZXy6KFkTMg^lBd-$9!lWn-UM? z^3r=TS!5`X8tmz2m@|kV`y|KUSAxei>|eB*)e(<=_m|MPKTYIT4W+X4e&TXLPg3-G z9IONk`SMyDw7+nA*H^L*q9sB{ zzuTXg0zW810W?tunr5;%1-$&2^GyUgh*-b*%Kw@2Np#X^{UQjUDDu*dc22k*s$deF z`&u{Ou1k%)o2Me}Ys;?&v+Wc1oC^;&e)D>v z%SUE&`@iE1N!pOPtZzlVL_F8gvpW+d2DxF+IJq=TJS6ic3eee>1{R8UngS51D zI74puNt7)1ORrs1GSjC&aP_6)ve_q&9V<%=WBMnw z5n8Tw@R`9EXb0OG8QtKp1BAU6s60t!FhZbrCN(p99eA^%Q3S40m`AuSWF_cp9QUMr z6u!?NP+sqQ-pu%NeU)kPy<-RF$V>4@%o>^ei;xR@bPd1`CceF@S?4#EX6264w5VByt`HieZylR@Qmk@X1_ z08UHzoOa+4%P^}H!Uu0?XpqQMx{kxFJZc|O7@t06gLAz~<*DF&od`6E6!4*E5SkCD`c(p%oW%8#xQDFnRb+ZzGpqStp{Mtf5gG2Kw?5MIZbn&NhIYn@mkbRxAYa z3BznjNg>d197#9kX@FIL-vi+S(En*YRC!zn|Lf7whxi<7bC*f9PfWPt#Qvu{GtnBTZxuz^+mC0Gg@i=Sd(Y`+R=lhCh9|}d%f`0Vx!Y|cjDVI{X8D&Yk#Tma+ zzjoklrOK-d1?Y#ap{_!BIRj>fJ!|9i&-VUtMM=dkBM%_8errc3aTG@nt;7h}g<|YE z9No%D9d9R#4iErwnW{^FW`-*nnuW?R3}fEDQ4!|hryKj0nEFo;;2wEGVy@Fgc*Cwh{(G3227 zV#LF#ExcjF>97C+R+jKie_Pe?`f1PLlXLGpc+lfF$9>APaOlL@JF1gb0Ay2I{p;Kn z;zD1b7l9-_xu)LCYuCIaiP;85%k+g}HJn>!f10;e0b_3l=PJDsSp7*02N(|>q&vEC zvi|_@U|Gad3@IcG1;%*Q5HN+oUWDpM+Q_st^0b4oK+^~23jgOCgu4KzkYltIie@kq zl=S$&JVUh8`269XddW3|p!_o&d$kf@|HRKmcSyPqDhdEk$qkROi2-(QE@|S?sJG)K z@S>5~to_7$)ox#i#HgkKxn$*&xsIVK#@Wb9@lvPOEcsiTe+?cTwp8>MJn*|~-($xJ zmGu={@lG&EgMwG}@c8R;ck5c#C%_#;!ouuJ?-{_m%_kWD7<^KlldXZ4Du&SWRe9js1JBx)XwxV_s-rzoNhnckc z;RP{JIS*@7SZbgqP6WfYa%me13)YT5ZB$I5hrlKQ4X3n4N{d^3Ww+*zd`#iQjgW{8 z=@=$RR6zeoKmm5`c_^GNXmtvKfkTmg0y_(EMWo9Z;{=B=M)iSb4t_gkc`OhT>mZJR z0uKm#25^V=2^0q@?ZK$NMk|YzM~}AkZ@=^ZlybleVQlLznC$PQG${H2fLu;Fd{80z zDO&sc=|=o**^9h~bA~y4t`n+^nAf}$#zU_8KiVG0+fi@~5XU^Uq=d)%E7M2~bY==^ zMq2V76pBWD5@;$+Do5a!N2*a7*^H3%0`3CIN1JPA1(?4?GlQcW)3q!T>JG{e&PET8 z!x0NLyEAw@j2(|oW(5NU$v`q3=49e4kOeX4&dDrmwcJtotAo(nP;X1-KPn&eRUnE4 zj(-m19R%o6QdT^Ks3W@t{TQqh)h|>VmfYQ~=3TDe`u%R)fL3^8pTa;IM8ta|x`zAn zU`SlPMpX#1W26HBy;Xs2ucz*@Aoiazq(>wG0p#K2B!zq)n&?AYIuln$ zY9x2UbzjnD$8?SkN6e>KQ;_kZX?cR}3Gn`ZgDPqwcn2gsA`7<7=>dL;Dd7$UKW%NhHNIGUxXzgC_^peMU+1FY8>z6NF6) z30b+DQT>rAg$D7q#*`-?PIG{n#B0cba|APt+C8tIv*Z8T8qHj*9po%q9<@*4+FqB( zC@U(GE+WAJz25YZjXwaQSZ~h%u=nDS5wObq8(S- z9R?|9v?_4!;h!0Q3zT;zoZi;(?XFE>>z8W(Op9$iDxy1|ZQ=>SYHakg_Y<`;Kc2zX z$7s;5jrEv;fkqCwkfmpygWeKG4v>bI-My*lu$^cBd_J;6mHou*^fKTJc!nGPMwC0t zTJtjoYod@4h9-$l0R;D=1n6+zGg4Wj{C@vr-Q~6QdU;sB&z6NPT~dSGK=R7qCzNLb zZbD%IGH|0#+23H5ZIh?sqD6}+{IJQkjKAErAy5x)Ex<7EZ)p+HOY$a*J(1aUulF4W zS3HRp5v>)Rq%HLaWK$hRX8%y{ek8$AURePV{;RG?eGQlh0VNR>07h-?fohOdDEm+t z2G^f8gISMgFd%yn)*w|HRN*NwF*MLd^y+Pb@hl5-X3M!%#DMt=*Q%6pm!0_A&iUo||Iee!l z$d0Dzo_-?;9$<2h9tn)DSjQN~s~eA(|OuspzP!rVHSzP}A)}ham>&Hu33a zOwLhgSfPS20BDQh0BjprKidD4>4+~6{n3s4Cy|qeS4G5r4)|y?OyH$LRsfXw9emHo z4(I|>PJ(VVhPt`_Xjspj?>{wm{?+vQ6gNDuK660Rq6@=l@uMdaTjJ*p;jjci1_z%% zG@?L4$w7_+OePZ8K$yNmm@wMvrARsB&}5T01~=D0?Q&z|ad0Q7qBwBLkU%&|*!qK| z1EZTXGth?OW0r?+LgwX40Q}(2^gR9?L+LWvN^(o642eiY+8+#MfW|wDk(%`B{d3FX zm4#Rz=+p6`vR1)NKsS;M2}7ny)J3% zp(pc0NBPp53CJ$8Z1&((M#>u0k6439WZLP&0#F?MBd}9SpX@Tsfg0=!UI0l_(SL#^ zT^{z9(sMycLyT;l)3pNf1q%FyBNlI;9mN)ZO7IGrWU#F`|Mz0GK&Bp$Y=B=(Z7>Mt zIz-p~GfJaPItNuYbeoMwo8+4P+&s5lzxTOq-?cIyt%oUxkc1WE2}W5eI`u$A-FUg} zpVvn32YxPGry44xu_}TYI=F;SDOK;@9X-s9OdSnO`(1X=m)%;*Bu-&7|GeCg$f>XubNb3KE$0fESe`!3I5D?NTYSM_!3IfTJ@&@3H168Ex+8}UDPb+( zwP}8ANBwKmzMd|7KfN!|%ICbUpPrx4?(|ZhL#1-;pC1R@c{ z2i@u#yjmsiP9Ufs@*$9fs5am8dW$i_RE@iTDvC7aMzmw?nPY`A_^7SW1ScY_Fs{0; z{=oQR@ZRW_hSLNU*~$`Ub0NJOLH_1hI*IIlk{2PpI2wHE*NTNDJH;)=R_PV37S0l| zY#bQA8Ib9(=C^C@FZXZRDNN;0n|iibgjemBWZ)mjUEHvdV^h6n5-_g&4<1+~bhs9L zY(L{2-|=zD^KWt&Q^dW@Aa;Y@F{M`>75Ju~oG=nL{@sV-{!g*91Gyvqk~O`5sB9w1 z3wg(1r6i9TjS4TAp&kS;38~-W&u5;W5P>JQuc)9Mp8|PO5YOIN`Ql69`FjfmPO|RG zzOGW7mwh)~#Ywp&MZ50cPlaLyWBud#Y{`Hj32DV04A;sMw3xvH_blDZYXWi@djfLC zE6^!iLS_)n)~E}pcSwOi2uQX3Fe}18a9_%X(hKM6qt({|SjNc;XpHdLq=Ru)u*cpp z@qI4Hc~ob3;A1-%BmFiiyM~T2)n)9Sh^m5h8~1`f+O#5{#`JW*+N-jM|Li2ARa)+k zjmQe1a3^SC@+N*fB)J>Wc1UkluQLx94aO@%NL}Q zk^CP0Jf!!5r~)D3;U$>1gEfxvA8{-9cm^8C98%g*=Qwk6(UtzCAu6(U&%&rcvQdzIG1 z<&dI3v#8myd9%s*5aAzy_;6Q9tVXimb5k=j^nrA!Q%eD|gYc;5+QoylrryT(r%JqM zqk;ef0HaU}!kp^zje4)2bFKvjb|MwQ7$IorVC2n+^9dSW~Uq6by~ z+c$5Z7x3sFOGifXiSeBRjAe|Qpb-EhE#i~CsK~{NlqR&3=yfpqVUdCp4W%=KG{M+g zgP)EZL`u6dW+mySbIj@eDq}`Eu+Ig=mNg)IE)#OSo_g$tHrefF2s?6 z(ya8*`R?DRf8K6B>y&nG*M7$9*pGVNJLgH0&JN28wFgPiIuWEygf50OOz4cEhNXZB z%x1(7gX=KNs0VoqeII$7h|feb9gKVAKs-PT1%ihRD=0B&JU6QRE5JwPI3XeD$WK_5 zEflSuJ<@*CUvE|PmWYcbp2{nqay6BVt@aFLn%OtOyldWf_3PS-M`Z4Xp56Q6dWSC_ z9aHKnfpiL#B!CFj6=xY1I!1NoR4rqiInW2VMOR}?W2E*7Uc|O#IB1I?A0LJJ6RPz6 zM~{vUO;lfEI)0TMjW_JmRn}G9uY@1I<*^K(zwnUu=?wE~mV)PRW~X;?7qvg>kn~&o z`4&fG#nX4#pn$kY$ymQdOYx4!$xnB7Qs7zGl{Kn(=!x%g5%iWY@dc&0#K>Z`rn`dZ z{FsmNKhu~0nH_&Kcnm-y<*-x28X8JQYTByy*8sXurm#nMVNm@XWTK;G_7|I3y45}G z-*p9lTxVVIHk!wmDqatliEfYe?iwBI66v#4PA@qL@H(2U>WF)3&uJY0#%~RA6R}N7tI%) zGw2ZL;=TflONOh}@^O9y)*P`}a56$UZHBsmOB4!8+Lwo35_E(QK=1sFfmbd*+1=fi z9M+EpUD7^8bvwyj^Vrf_6jm9b9pvtJIY~m8;Ajj-4L5r z)jfH;H*Bye?t6~oz0k%F3acxgvMu*adHisVYI2|U#$?120_VfJ+R$^cTO#%hV;-g< z91eiwaAe{!C1SQ92_BuaByPa%H=Cq4@h!$(fB^Qf7tn{{ej`wG(D*_Fhowng04Ver zI2&=`U?n5i49QFg9H=^cq5J_(6ilodlp%6##Z~?{+Z%)fyAo;=&)tU~^~{-F5vC9# z$hkLZFE17Bc*^lyI(SW~kkMI<-#qwjn6H*+*~su|jR-~yJFGG4^p^V(5%gCb?PZSx z#(4^p7F<;|X%he8G;mW8(|n7|TKm-r`wbSX1W#Y zbL=^HxNHQCfM>>;pEP8r4R{VK+HXZi&U2Aw=nPk2% zoMD89hK)7uUP46?He!F}_?4>4M}4NZqZi#4TKqP9o6&p_AaS20Wq>h5fZnl3`GfGX zuQi@;RiV&91`BE{aJ&hPy)B*xCQVKiec}6hXv3$wDYuX~0TAy~>r$p;9jA&}vpozq z+w!GqRbVa?-n{wff=AfQ4sx-F{jol+;6NR{-~HEvfAT}sf#e!Ut$-b>-8z1@UDR+T z4g(xXPqa5%{m2j$NrTM<>SYT3Lz|84NR_s=PEP%HIXC9-bnD!EO2SZi-oRxs--bAw zpOVX~2i|)6R&OhQDSCTnzTV?vUH{&-x7Uj=Tgc+mIzKZwza=*4w9h=TOWa!rBxM=;3u-DR~Z|;N)-u}v6?eDZ8KEsP9aiHsn zNo;Z-u0%;vabIwPSrz>wgkI3qf}B{Ba@qG17i;m$4bzJCw*!K27b0^DY6?j7Omw~h zG^7oHh6BqOT3ppo>M=nRN<||$42=Thd3f-ciOM>eP6VyNWuvucOiKcmgN7qjF#H)E zRV$dYWNgIu1-SPOY?l^S;$daE?fOF)l2tVr^6O67FTxy?^x%Q2CTkGCHpK*!BM>tT z2Ec!zGsmkSUEEk2^y>s+!ElYD<-H*#5Daw~#0TK-=5E-q;m~X|Y*>62od^r0IfBe0 z;t+}&(%hg57n`*;N_vJ|NkdY*C7b2ZnEA|&50paJfNr9087@!~uAl%tIa9mQton8k zQd0RAvxC*7C|`7NI6$;mS0i1H%iTKbWXPF;vqy#7G9BJ9=IxCy=6)`pF)g+!a(nve z_?XSzx7MhR`510vJ1w~tn@i{7J`PkE;RTxWF0KClOipcypvla1(MnYllS^+m`Q2GN zQQ2*>Qq59-?S~&3*Ez0T-kfziaLAg&B|I8AhhAsF$a{7wE`^(0Z(vT z{R4=@!)X>lKY~Uh0VFaySRORZXtpXbq*EV{X6}Cx39~M|6mK5u8?b~NnQUBOnv4Ld zL-sX@c8CyY^oIY7-9@TU+M!dUbd3QQ+D*iv-T~oAf;HfAL^=acR2+YMko0BeOn%Q*Kmp~)cYH}WvV(0#ar;law^6U_G(oudjM31InODP;;I4(0EGo&wSaEO-gf zmn2ZDxOs{L@d3ze0Bbm-7({`%BMv;t0no67Y zAI%)oahCDZUD6(QZjtGlsrGr?wk$mPtMV?0EsLMMOY`iWMIrODnGM&>f7%%|c2TdX z!Q|;&hgE!+&xt8HW?Tz(73Fk1XkwWdcE;R$gTedq*}IlG@NkD|l=5C}TX;F0`2XWohg6E<<6JT*@|(=3%`<%KlEnDXj2i$u>;6_!jVh$ zgP5N@c$z7P7?%Ru+KR*U7Y`#4b@!u_^vrRESvL(BU_;2+0X?C0b~}s0^q4UXX(;%^c! zrb{?52BHdfK`F5Vg96|=GT>3rDy)I!xcr1Z8^UNn91&#?b$vN{ryzh?K&8;WP>Qxl zR@24S0mYOr5i(hjG8ESWEXP*z#SJsiNvVkqzzO&Uydt6J1o%Ns0?W)0ViZGYOeTpU zWcBppyZA)GSc3y90gV0yp=pqy7Idz0_K4Ude3 zCzBM37`;x}mMllm=MXsQbBJyp#P_4M3@!pK7XoW3&pG}4pb-L;kau&mYWP$z*++=N z!h`vDwUfb12C8%gIR-%SfGc5^m6fH}fcf<{kuM;k#1V7J_%98OAXw-?Fi4^`j#U^7 zFh7-tlzHqXvH$}+2xHb*8``C zdcCfAeczz|w`K~hb^Bs8_m7%LNTxAZQq_H%UzYz3;(ESy+~17#MAVfr%ubZ;wzL%c9VYAG7 zSGv}h5K-r!JU{%aX2TfR<3JX0<|XnkshXB-fZm}K0fC9Q0t!gRzss#E^ru6qu94aT zj|ssgz$3vbbWk4O{J;{JU_cqch#st6_4~Yq=#h<|A6fUKchO3?nKVh{A+;PljYNl63yd_m(jri%{J{X^rv860&F3M z1DQsbWR&P3DkUc;3W*Qm2uL6k=DGv7F2S9Oxsk%>UKxmCl?wjCHi|_pgFXNeXj&H~ zD0T(0a0hXP!P6K+xSt+0jFgyG)pEhDN`59ZAA}ynK_rPO5q%nDQXl77t%o zcJPkd&N{8gpv?)9LYrGpmW^y;3ctf($iG)r-g$H#w@*^7ieOiFF`u5+0pV{=VC&k@%6@z#uc0<>2+6{3G)&#s5~%2#r1+fACiLvB{G`7cQ=m(B)g# z!FpoPSJ_`IMJC1E-$Ty4x>qA6*LtBs(99r{-$!G2D=Y6?tsM!&{6ABiG`m=rF4%i; z%r(Yekug2bB>J_wu-clG4_;*Sw%wB2wI9$7nqt;n?;Gy^$BIL|SRVOH*>g7C z9e`X`0?%;9a049&vjKt?sep9p8OZvpwP(6OQ{8>?FwG(BCtIUh ztRrMry?-#L9ph$P`=q5xD_70?_T~y4)9Dz;O2S21IRH{oLJw4zSd^F-skwvaYPsY= zEbCGZc8J94R870)U`K zu7ELw8wzstLdFmvle`?TJbC5O&Hh8MXCt=bu$dGWE7Ey!qxCwTppB*P{1uAKe?XK7 zs*@Zjlru|K7KoMKkU#+x2B1q!atI(I8w7fvcu*}Map89M&v@(gc6}SInM1GEj#Y*u-MPw(TRV>ZZdLxvqHV@-kC=- zzE0^GBL?hJ2@#cD8hgH$9BKGkbSK2(m+zQ~Ge7zd)Mft_{E;IX?Kb-t|LPrdaksti ze^rJjla)s zbnaNF@Ve zEIZ=9a(~+bRn~*QhO4=o{lAMWwfg(fPc+PL)(ZV6XewYqhR{K?$qG0jgmsWutXkE6 zPx(+_r+-x5JOOsebw;bdTwa$m4|h}V;k;ne0d7ry{)C)P->1{tu$>qiEWBH@^p|}} z#!L~F57pX&ct6wmA%iHoHzc)mB0wRcLq&7Bm|Xlb(*rZ8SGW`&s6dxHuA4_eKCP>@ z?87giU;UGxXH;Bcz902@b?0zKLVow|4UVNr)BXnOUq+2qPn1}`{xbWl1ST!6A zC^?HQ4aY=LQ+GX;>x38Vv=DO$6OyB@+oJt0VX*H3||6k;}`@mVP zO#>oheu~pQUb1;!oUT6UHGlSNNR>UG4c!Ef&aTz=hgam8&i~EBovc{=sAF4!e3N>D z^d-$?L-XgnfqJ_)u$d}p@%8SHj(RV0p?rnlF%c$*r^+iiR_&;Id9_9OyCj>OPU)jC ze`$*kmL`u_7A?*QmYf^CFY|}L^qKk-Jx2RDx39>@xxKGYotJli^jm-F_`34bl5F^U z1~R)`?7d&?H4BS7{Vi;!wI#MpN3(T&+Ur=YY;*ExP`{atR7j$xCWo@oDa}~{-0a8w zvQ<76i;qT{gzB4xMn~Ct4!<#3Cv6|Aym$Q`<9*vSmvV%Aa~2iIkM);*_3U6Y-MGY zownc6@sDQhy4ae=<`KE%2M_-qkeAZC#t_|m{Nzbqn|s~-!{8Hf978L#dJF;1!5hPS zdZhBNzyXG`2g%sM7gu&3&z-bhI~_gdA^UldZ{Dj=i(9B|W$ROhUkJ=)n7LX0y|sd~ zu0NKI;rG1QYdXQMiYMY#aKJhzehaf*o*O4EPwacD9=bV9(ONh;Y3(i@kDEzP4j9X* zz2Ex4GH6KOCj5Fho5Aoa*IPx4-BNBnI1}(|%=hrdXrU>mBize_44nil%+46f9Xnt+ z{iI7Qm|0R}nSf_6L+38z@xX(-B0t(@wcp>ZYPG5Teo^t%wymMeH$G)P9SU3hP9XX~ zvD5go+^8FO-ac+od@~ZTZdQ`2qy4?YYV*eujl4`!$t66-PS$f7JC@8kRkv1DRJ7%X z+sdY;6BkE+eV1Q)NdIS{vyXtZb(8Sh3oetNT~^*HXpk1&Yu-4bxpsd`YhYj?;DlKU zGuey%*YfLq$cjsAui4old*i0S9ER^Qw+$Lxq2-PbqAsb=&EjO4HI%(&+upMUVq4~x zIa>J3yyFUAZt1t&Hv62qlxoDzWA>LetzX>vT3knF%g_6#jryw2Y-ZnMdby){>SBay zv6I{suj8o`ucb$wXnKV*tm_&NhDy_pGyggFWd{RbIS*TR>UH+} zv&2|^zU_OYu;I-1zpl$%qMbTtwjbjSSnkZ*=QDpiq&Ii=W(g^QS-OJ#>^Up)Tzf!d zCVXy}Sqv_n-_Ek7C}DW_y@$Ee#yu%%Oym*IhsKr03M*8JE#N$_`oi#koDV`7+p>wT_qN4nN&d@6=)EUw6p4Y|3Or zI9*`cBzW(^H2=;yck*5I6s}D#_GSrXUr@mw_-1n0lqWsXG^WGqxlNhagjd|;a98YP zn!t2fZ_VvDmWD`2T5hA{B7+j5$hmzR>#I;ABBYLCg~MOOh? z2lHWq37CIkmYdhWsc_pzd-m&{U|XNB$`{=_nU{4?7f7x7m6(0b$AnuB3;`@d+DXZ% zY&z%eZfgw$SapE2EIX>@V(FUxvpyf}S)AqaQMvE#1*}EiN8G}0rlnQccKdthzE~&U?BSd@ngwe;l)IWoHaSO*RMlx8 z?;g;u>-N_=GU0Ege7(BTQ5U0#tNgq}9o^*Sf&i#vX1Z;zqTe-(}Vmo!QxL^XA1#JM$nEkG?&rnce zrHyA8fEdKqy+%^megW6-tBBTBub~?kA^XJLuCI{a?1Ev&1&ZxX=GHX=39ysq?LK$? z=8DIUpVf&hY6ER-43K%Si+w*gn@u_6ao*4ZD9JRYxXzb#f$%g!gUBBoG>R1EXV0J0 zr7Xp*ClSboOBh@u%id1rKjb^taZeHcTMD+=+c)zddj%6+Ne$f%LkW4a?o$`jKHeT1 z@PD_ir>~I^Ib7NN#%rp~tJp_kpvekuLkO&$TvA;MnFm+JAyGi{R9zQH_%1m0uZj*x z-v9k^6aj?kXKGW%{(O)<_x+4uejG;1YB-R1Td(w2nI0c->p2h6F=ASWHG770180%& z736(t(eqPy2(Zm9UK+Re<@Od`tE^#VJ-t1v(=S*YpS(F1RW*6@yvV+H-ZvL(vplZ0 zKQhX~O~gNS@UV!AfUlyssN#G5o`TVDL!N*Sa88R_;141AkpnH2qH`(x&wO^Pk zK(a2%gg<3pDM@S{-M>3C@pvXcdMp>Q5`QV-0l$rQ}hjJ0xH)An^d9RZzQ9 zDi=JcG<49n#neI~DA1sJQ$rPGtA@S$u#V1K0OLxAhN~!e9>Y@5wRFZWm`X^GtzJWH z8&poe>m9QZZ+<>-2C6O^IFQG$hMpL?5Tx6KOe+Hz5b4lKL;;kY0$+i^!R#ZDRe8}_ z0EsiIlvhgtGiEuuE`f|P=OI3(`>imibnI1}_?Kk?OAuWzkKbC{&6y2w8TbLDl0#P% zr!Iia1gOwCCOHj9QU;WPc%gE*=5#!6W?T@d32y)^UC>VWs1H{ky*KDxd|A17p-N&x zqkTp|D_tT^W)o5|0*5|V%56@j3(T*oKx6kuY;MPLPsCJ-wFDd*E{9I&(-jI4-!YCi zm;=da&OMJ7x}LTq`Lq{OUMSB4`6~Q)btLg1^K1u2W1(6YWAYmumxYp!BoQ6MY6!m= z4%EjX9_HVoH=cLA^y-1GUf7A1*B7W`gI_9DQH+887uD5bl@iPeDB z{RV{d6nzML5B(tpK$6`LsV)d7eS*1sb43RTQd~A&&8aKn7Qc5jlk1TkuEPdy6#P8;v|eNCFp0BDH&otuCnW04y~fb(?!z;rO!$wSAEslXM;fcDlMRqT{o-*YlO za;#Wk2XQ4rarVNg?uA&GY}y#JXVERYR5u4a2HavCKkIzSJg|X-z<0`HfWrt(C(A^h17JV1vJ-)*sBy+;jK{q=9Fr-^yjG@A%0Af5I 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 MV!pj#t3&AD0SrjN@c;k- literal 28819 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�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 G`@aCXP0mdK literal 59888 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∾JZA;_LS=h0r4q?jtl&$=kNC$-|Dv74?|{b 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 diff --git a/doc/tuto/img/ngl-control-reddish-scene.png b/doc/tuto/img/ngl-control-reddish-scene.png index 3cd2a4a7884228172d29baff616172404b7bfad2..26aa7b5552eb85bdb4b19f9ec11dfcc11a1bca4a 100644 GIT binary patch literal 49080 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≪DZ%S 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 literal 38397 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 diff --git a/doc/tuto/img/ngl-control.png b/doc/tuto/img/ngl-control.png index c2cdfdd3d6330033a5b5f56abf7fae3b55457888..b0015c058a3278eef2be5dd7fd2657e52b928658 100644 GIT binary patch literal 23951 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

KUC+-?4oALveCM!f0j7HyZ8N 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 Date: Thu, 1 Oct 2020 16:50:13 +0200 Subject: [PATCH 135/388] params: forward declare struct ngl_node struct node_class (from nodes.h) depends on struct node_param (from params.h), and at the same time node_param needs ngl_node (from nodes.h). This forward declaration addresses the cyclical dependency and allows including params.h without nodes.h. --- libnodegl/params.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libnodegl/params.h b/libnodegl/params.h index 0b540ef5a5..4492586dad 100644 --- a/libnodegl/params.h +++ b/libnodegl/params.h @@ -71,6 +71,8 @@ struct param_choices { const struct param_const consts[]; }; +struct ngl_node; + #define PARAM_FLAG_NON_NULL (1<<0) #define PARAM_FLAG_DOT_DISPLAY_PACKED (1<<1) #define PARAM_FLAG_DOT_DISPLAY_FIELDNAME (1<<2) From db54c9f89e759f3497dc003e1882b621cab2a435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 1 Oct 2020 16:45:04 +0200 Subject: [PATCH 136/388] resourceprops: move precision enum and choices in a dedicated file They are going to be shared with the IO nodes. --- libnodegl/Makefile | 1 + libnodegl/node_resourceprops.c | 14 ++----------- libnodegl/nodes.h | 8 -------- libnodegl/pgcraft.h | 1 + libnodegl/precision.c | 34 +++++++++++++++++++++++++++++++ libnodegl/precision.h | 37 ++++++++++++++++++++++++++++++++++ 6 files changed, 75 insertions(+), 20 deletions(-) create mode 100644 libnodegl/precision.c create mode 100644 libnodegl/precision.h diff --git a/libnodegl/Makefile b/libnodegl/Makefile index 94239fdbec..0c499957a4 100644 --- a/libnodegl/Makefile +++ b/libnodegl/Makefile @@ -124,6 +124,7 @@ LIB_OBJS = animation.o \ pgcache.o \ pgcraft.o \ pipeline.o \ + precision.o \ program.o \ rendertarget.o \ rnode.o \ diff --git a/libnodegl/node_resourceprops.c b/libnodegl/node_resourceprops.c index 61c6dce8aa..590b940dcf 100644 --- a/libnodegl/node_resourceprops.c +++ b/libnodegl/node_resourceprops.c @@ -23,22 +23,12 @@ #include "nodegl.h" #include "nodes.h" - -static const struct param_choices precision_choices = { - .name = "precision", - .consts = { - {"auto", NGLI_PRECISION_AUTO, .desc=NGLI_DOCSTRING("automatic")}, - {"high", NGLI_PRECISION_HIGH, .desc=NGLI_DOCSTRING("high")}, - {"medium", NGLI_PRECISION_MEDIUM, .desc=NGLI_DOCSTRING("medium")}, - {"low", NGLI_PRECISION_LOW, .desc=NGLI_DOCSTRING("low")}, - {NULL} - } -}; +#include "precision.h" #define OFFSET(x) offsetof(struct resourceprops_priv, x) static const struct node_param resourceprops_params[] = { {"precision", PARAM_TYPE_SELECT, OFFSET(precision), {.i64=NGLI_PRECISION_AUTO}, - .choices=&precision_choices, + .choices=&ngli_precision_choices, .desc=NGLI_DOCSTRING("precision qualifier for the shader")}, {"as_image", PARAM_TYPE_BOOL, OFFSET(as_image), .desc=NGLI_DOCSTRING("flag this resource for image accessing (only applies to texture nodes)")}, diff --git a/libnodegl/nodes.h b/libnodegl/nodes.h index 0e9f8d42a2..c282638627 100644 --- a/libnodegl/nodes.h +++ b/libnodegl/nodes.h @@ -209,14 +209,6 @@ int ngli_node_buffer_ref(struct ngl_node *node); void ngli_node_buffer_unref(struct ngl_node *node); int ngli_node_buffer_upload(struct ngl_node *node); -enum { - NGLI_PRECISION_AUTO, - NGLI_PRECISION_HIGH, - NGLI_PRECISION_MEDIUM, - NGLI_PRECISION_LOW, - NGLI_PRECISION_NB -}; - struct variable_priv { union { double dbl; diff --git a/libnodegl/pgcraft.h b/libnodegl/pgcraft.h index 9d289adb00..cc78295ba6 100644 --- a/libnodegl/pgcraft.h +++ b/libnodegl/pgcraft.h @@ -27,6 +27,7 @@ #include "buffer.h" #include "image.h" #include "pipeline.h" +#include "precision.h" #include "texture.h" struct ngl_ctx; diff --git a/libnodegl/precision.c b/libnodegl/precision.c new file mode 100644 index 0000000000..218139be9c --- /dev/null +++ b/libnodegl/precision.c @@ -0,0 +1,34 @@ +/* + * Copyright 2020 GoPro Inc. + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "precision.h" +#include "utils.h" + +const struct param_choices ngli_precision_choices = { + .name = "precision", + .consts = { + {"auto", NGLI_PRECISION_AUTO, .desc=NGLI_DOCSTRING("automatic")}, + {"high", NGLI_PRECISION_HIGH, .desc=NGLI_DOCSTRING("high")}, + {"medium", NGLI_PRECISION_MEDIUM, .desc=NGLI_DOCSTRING("medium")}, + {"low", NGLI_PRECISION_LOW, .desc=NGLI_DOCSTRING("low")}, + {NULL} + } +}; diff --git a/libnodegl/precision.h b/libnodegl/precision.h new file mode 100644 index 0000000000..e1d0b5c246 --- /dev/null +++ b/libnodegl/precision.h @@ -0,0 +1,37 @@ +/* + * Copyright 2020 GoPro Inc. + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef PRECISION_H +#define PRECISION_H + +#include "params.h" + +enum { + NGLI_PRECISION_AUTO, + NGLI_PRECISION_HIGH, + NGLI_PRECISION_MEDIUM, + NGLI_PRECISION_LOW, + NGLI_PRECISION_NB +}; + +extern const struct param_choices ngli_precision_choices; + +#endif From 560253876704dbd314932ff86349a2a6ade733dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 1 Oct 2020 16:46:35 +0200 Subject: [PATCH 137/388] io: add precision_{in,out} parameters Contrary to uniform precisions, the in/out/varying precisions can mismatch between the vertex and fragment shader (even in GLSL 100), so they are exposed in two separated precision fields. The default precision (highp) matches the one we currently use for injecting the other vertex attributes and uniforms (with the exception of samplers). It might be relevant to tweak the default precision between frag and vertex (with maybe a lower precision for the fragment). --- libnodegl/doc/libnodegl.md | 111 +++++++++++-------------------------- libnodegl/node_io.c | 17 ++++++ libnodegl/node_render.c | 6 +- libnodegl/nodes.h | 2 + libnodegl/nodes.specs | 34 +++++++----- libnodegl/pgcraft.c | 6 +- libnodegl/pgcraft.h | 2 + 7 files changed, 82 insertions(+), 96 deletions(-) diff --git a/libnodegl/doc/libnodegl.md b/libnodegl/doc/libnodegl.md index f6066a0c75..9a83bc2a67 100644 --- a/libnodegl/doc/libnodegl.md +++ b/libnodegl/doc/libnodegl.md @@ -356,80 +356,33 @@ Parameter | Live-chg. | Type | Description | Default **Source**: [node_identity.c](/libnodegl/node_identity.c) -## IOInt - -**Source**: [node_io.c](/libnodegl/node_io.c) - - -## IOIVec2 - -**Source**: [node_io.c](/libnodegl/node_io.c) - - -## IOIVec3 - -**Source**: [node_io.c](/libnodegl/node_io.c) - - -## IOIVec4 - -**Source**: [node_io.c](/libnodegl/node_io.c) - - -## IOUInt - -**Source**: [node_io.c](/libnodegl/node_io.c) - - -## IOUIvec2 - -**Source**: [node_io.c](/libnodegl/node_io.c) - - -## IOUIvec3 - -**Source**: [node_io.c](/libnodegl/node_io.c) - - -## IOUIvec4 - -**Source**: [node_io.c](/libnodegl/node_io.c) - - -## IOFloat - -**Source**: [node_io.c](/libnodegl/node_io.c) - - -## IOVec2 - -**Source**: [node_io.c](/libnodegl/node_io.c) - - -## IOVec3 - -**Source**: [node_io.c](/libnodegl/node_io.c) - - -## IOVec4 - -**Source**: [node_io.c](/libnodegl/node_io.c) - - -## IOMat3 - -**Source**: [node_io.c](/libnodegl/node_io.c) - - -## IOMat4 - -**Source**: [node_io.c](/libnodegl/node_io.c) +## IOVar* +Parameter | Live-chg. | Type | Description | Default +--------- | :-------: | ---- | ----------- | :-----: +`precision_out` | | [`precision`](#precision-choices) | precision qualifier for the output side (vertex) | `auto` +`precision_in` | | [`precision`](#precision-choices) | precision qualifier for the input side (fragment) | `auto` -## IOBool **Source**: [node_io.c](/libnodegl/node_io.c) +List of `IOVar*` nodes: + +- `IOInt` +- `IOIVec2` +- `IOIVec3` +- `IOIVec4` +- `IOUInt` +- `IOUIvec2` +- `IOUIvec3` +- `IOUIvec4` +- `IOFloat` +- `IOVec2` +- `IOVec3` +- `IOVec4` +- `IOMat3` +- `IOMat4` +- `IOBool` ## Media @@ -456,7 +409,7 @@ Parameter | Live-chg. | Type | Description | Default `vertex` | | [`string`](#parameter-types) | vertex shader | `fragment` | | [`string`](#parameter-types) | fragment shader | `properties` | | [`NodeDict`](#parameter-types) ([ResourceProps](#resourceprops)) | resource properties | -`vert_out_vars` | | [`NodeDict`](#parameter-types) ([IOInt](#ioint), [IOIVec2](#ioivec2), [IOIVec3](#ioivec3), [IOIVec4](#ioivec4), [IOUInt](#iouint), [IOUIvec2](#iouivec2), [IOUIvec3](#iouivec3), [IOUIvec4](#iouivec4), [IOFloat](#iofloat), [IOVec2](#iovec2), [IOVec3](#iovec3), [IOVec4](#iovec4), [IOMat3](#iomat3), [IOMat4](#iomat4), [IOBool](#iobool)) | in/out communication variables shared between vertex and fragment stages | +`vert_out_vars` | | [`NodeDict`](#parameter-types) ([IOInt](#iovar), [IOIVec2](#iovar), [IOIVec3](#iovar), [IOIVec4](#iovar), [IOUInt](#iovar), [IOUIvec2](#iovar), [IOUIvec3](#iovar), [IOUIvec4](#iovar), [IOFloat](#iovar), [IOVec2](#iovar), [IOVec3](#iovar), [IOVec4](#iovar), [IOMat3](#iovar), [IOMat4](#iovar), [IOBool](#iovar)) | in/out communication variables shared between vertex and fragment stages | `nb_frag_output` | | [`int`](#parameter-types) | number of color outputs in the fragment shader | `0` @@ -1402,6 +1355,15 @@ Constant | Description `front` | cull front-facing facets `back` | cull back-facing facets +## precision choices + +Constant | Description +-------- | ----------- +`auto` | automatic +`high` | high +`medium` | medium +`low` | low + ## sxplayer_log_level choices Constant | Description @@ -1420,15 +1382,6 @@ Constant | Description `stencil` | add stencil buffer `no_clear` | not cleared between draws (non-deterministic) -## precision choices - -Constant | Description --------- | ----------- -`auto` | automatic -`high` | high -`medium` | medium -`low` | low - ## valign choices Constant | Description diff --git a/libnodegl/node_io.c b/libnodegl/node_io.c index 0d5c26b1e9..0ee0ce18eb 100644 --- a/libnodegl/node_io.c +++ b/libnodegl/node_io.c @@ -19,10 +19,25 @@ * under the License. */ +#include + #include "nodegl.h" #include "nodes.h" +#include "params.h" +#include "precision.h" #include "type.h" +#define OFFSET(x) offsetof(struct io_priv, x) +static const struct node_param io_params[] = { + {"precision_out", PARAM_TYPE_SELECT, OFFSET(precision_out), {.i64=NGLI_PRECISION_AUTO}, + .choices=&ngli_precision_choices, + .desc=NGLI_DOCSTRING("precision qualifier for the output side (vertex)")}, + {"precision_in", PARAM_TYPE_SELECT, OFFSET(precision_in), {.i64=NGLI_PRECISION_AUTO}, + .choices=&ngli_precision_choices, + .desc=NGLI_DOCSTRING("precision qualifier for the input side (fragment)")}, + {NULL} +}; + #define DEFINE_IO_CLASS(class_id, class_name, type_id, dtype) \ static int io##type_id##_init(struct ngl_node *node) \ { \ @@ -37,6 +52,8 @@ const struct node_class ngli_io##type_id##_class = { \ .name = class_name, \ .init = io##type_id##_init, \ .priv_size = sizeof(struct io_priv), \ + .params = io_params, \ + .params_id = "IOVar", \ .file = __FILE__, \ }; diff --git a/libnodegl/node_render.c b/libnodegl/node_render.c index 4970da6fbb..9a9400aac2 100644 --- a/libnodegl/node_render.c +++ b/libnodegl/node_render.c @@ -163,7 +163,11 @@ static int render_init(struct ngl_node *node) while ((e = ngli_hmap_next(program->vert_out_vars, e))) { const struct ngl_node *iovar_node = e->data; const struct io_priv *iovar_priv = iovar_node->priv_data; - struct pgcraft_iovar iovar = {.type = iovar_priv->type}; + struct pgcraft_iovar iovar = { + .type = iovar_priv->type, + .precision_in = iovar_priv->precision_in, + .precision_out = iovar_priv->precision_out, + }; snprintf(iovar.name, sizeof(iovar.name), "%s", e->key); if (!ngli_darray_push(&s->vert_out_vars, &iovar)) return NGL_ERROR_MEMORY; diff --git a/libnodegl/nodes.h b/libnodegl/nodes.h index c282638627..310ff080e5 100644 --- a/libnodegl/nodes.h +++ b/libnodegl/nodes.h @@ -328,6 +328,8 @@ struct identity_priv { }; struct io_priv { + int precision_out; + int precision_in; int type; }; diff --git a/libnodegl/nodes.specs b/libnodegl/nodes.specs index 91fe542e51..096ad0b1f1 100644 --- a/libnodegl/nodes.specs +++ b/libnodegl/nodes.specs @@ -226,35 +226,39 @@ - Identity: -- IOInt: +- _IOVar: + - [precision_out, select] + - [precision_in, select] -- IOIVec2: +- IOInt: _IOVar -- IOIVec3: +- IOIVec2: _IOVar -- IOIVec4: +- IOIVec3: _IOVar -- IOUInt: +- IOIVec4: _IOVar -- IOUIvec2: +- IOUInt: _IOVar -- IOUIvec3: +- IOUIvec2: _IOVar -- IOUIvec4: +- IOUIvec3: _IOVar -- IOFloat: +- IOUIvec4: _IOVar -- IOVec2: +- IOFloat: _IOVar -- IOVec3: +- IOVec2: _IOVar -- IOVec4: +- IOVec3: _IOVar -- IOMat3: +- IOVec4: _IOVar -- IOMat4: +- IOMat3: _IOVar -- IOBool: +- IOMat4: _IOVar + +- IOBool: _IOVar - Media: - [filename, string] diff --git a/libnodegl/pgcraft.c b/libnodegl/pgcraft.c index 2a49ac1678..9d09e7f57c 100644 --- a/libnodegl/pgcraft.c +++ b/libnodegl/pgcraft.c @@ -29,6 +29,7 @@ #include "memory.h" #include "nodes.h" #include "pgcraft.h" +#include "precision.h" #include "type.h" /* @@ -697,8 +698,11 @@ static int inject_iovars(struct pgcraft *s, struct bstr *b, int stage) if (s->has_in_out_layout_qualifiers) ngli_bstr_printf(b, "layout(location=%d) ", i); const struct pgcraft_iovar *iovar = &iovars[i]; + const char *precision = stage == NGLI_PROGRAM_SHADER_VERT + ? get_precision_qualifier(s, iovar->precision_out, "highp") + : get_precision_qualifier(s, iovar->precision_in, "highp"); const char *type = get_glsl_type(iovar->type); - ngli_bstr_printf(b, "%s %s %s;\n", qualifier, type, iovar->name); + ngli_bstr_printf(b, "%s %s %s %s;\n", qualifier, precision, type, iovar->name); } return 0; } diff --git a/libnodegl/pgcraft.h b/libnodegl/pgcraft.h index cc78295ba6..a241a4f37e 100644 --- a/libnodegl/pgcraft.h +++ b/libnodegl/pgcraft.h @@ -82,6 +82,8 @@ struct pgcraft_attribute { struct pgcraft_iovar { char name[MAX_ID_LEN]; + int precision_out; + int precision_in; int type; }; From 410a54589e919e1cd02d4c1f1f1f28c5f1aaf95b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 1 Oct 2020 17:12:49 +0200 Subject: [PATCH 138/388] tests/shape: add an IOVar precision test Exact result is not relevant in this test (hence the tolerance 5) but at least we exercise different codepaths. --- tests/refs/shape_precision_iovar.ref | 1 + tests/shape.mak | 1 + tests/shape.py | 25 +++++++++++++++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 tests/refs/shape_precision_iovar.ref diff --git a/tests/refs/shape_precision_iovar.ref b/tests/refs/shape_precision_iovar.ref new file mode 100644 index 0000000000..363952210d --- /dev/null +++ b/tests/refs/shape_precision_iovar.ref @@ -0,0 +1 @@ +bl:01FE00FF br:FEFE00FF c:807F00FF tl:0101FDFF tr:FE0100FF diff --git a/tests/shape.mak b/tests/shape.mak index e21855f04b..a526e5ddcb 100644 --- a/tests/shape.mak +++ b/tests/shape.mak @@ -20,6 +20,7 @@ # SHAPE_TEST_NAMES = \ + precision_iovar \ triangle \ triangle_cull_back \ triangle_cull_front \ diff --git a/tests/shape.py b/tests/shape.py index 0ef7bd86d9..0ab3e34344 100644 --- a/tests/shape.py +++ b/tests/shape.py @@ -27,11 +27,36 @@ from pynodegl_utils.misc import scene from pynodegl_utils.toolbox.colors import COLORS from pynodegl_utils.toolbox.colors import get_random_color_buffer +from pynodegl_utils.tests.cmp_cuepoints import test_cuepoints from pynodegl_utils.tests.cmp_fingerprint import test_fingerprint from pynodegl_utils.toolbox.shapes import equilateral_triangle_coords from pynodegl_utils.toolbox.grid import autogrid_simple +@test_cuepoints(points=dict(bl=(-1, -1), br=(1, -1), tr=(1, 1), tl=(-1, 1), c=(0, 0)), tolerance=5) +@scene() +def shape_precision_iovar(cfg): + cfg.aspect_ratio = (1, 1) + vert = ''' +void main() +{ + ngl_out_pos = ngl_projection_matrix * ngl_modelview_matrix * ngl_position; + color = vec4((ngl_out_pos.xy + 1.0) * .5, 1.0 - (ngl_out_pos.x + 1.0) * .5 - (ngl_out_pos.y + 1.0) * .5, 1.0); +} +''' + frag = ''' +void main() +{ + ngl_out_color = color; +} +''' + program = ngl.Program(vertex=vert, fragment=frag) + program.update_vert_out_vars(color=ngl.IOVec4(precision_out='high', precision_in='low')) + geometry = ngl.Quad(corner=(-1, -1, 0), width=(2, 0, 0), height=(0, 2, 0)) + scene = ngl.Render(geometry, program) + return scene + + def _render_shape(cfg, geometry, color): prog = ngl.Program(vertex=cfg.get_vert('color'), fragment=cfg.get_frag('color')) render = ngl.Render(geometry, prog) From 46e6d9820c7a3a9b61f7055942da5e176d4f1612 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 5 Oct 2020 11:30:39 +0200 Subject: [PATCH 139/388] tests/rtt: add rtt_load_attachment test Ensures that when a node rtt draw ends, we restore the previous rendertarget without discarding its attachments. --- tests/refs/rtt_load_attachment.ref | 1 + tests/rtt.mak | 1 + tests/rtt.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 tests/refs/rtt_load_attachment.ref diff --git a/tests/refs/rtt_load_attachment.ref b/tests/refs/rtt_load_attachment.ref new file mode 100644 index 0000000000..94d5a96ea9 --- /dev/null +++ b/tests/refs/rtt_load_attachment.ref @@ -0,0 +1 @@ +bottom-left:FFFFFFFF top-right:FF8000FF diff --git a/tests/rtt.mak b/tests/rtt.mak index 44b231bc4c..6ad89713d4 100644 --- a/tests/rtt.mak +++ b/tests/rtt.mak @@ -20,6 +20,7 @@ # RTT_TEST_NAMES = \ + load_attachment \ feature_depth \ feature_depth_stencil \ mipmap \ diff --git a/tests/rtt.py b/tests/rtt.py index e8f0de0d5f..8c3bf1740c 100644 --- a/tests/rtt.py +++ b/tests/rtt.py @@ -23,7 +23,9 @@ import array import pynodegl as ngl from pynodegl_utils.misc import scene +from pynodegl_utils.tests.cmp_cuepoints import test_cuepoints from pynodegl_utils.tests.cmp_fingerprint import test_fingerprint +from pynodegl_utils.toolbox.colors import COLORS def _get_cube(): @@ -171,3 +173,29 @@ def rtt_function(cfg): for name, params in _rtt_tests.items(): globals()['rtt_' + name] = _get_rtt_function(**params) + + +@test_cuepoints(width=32, height=32, points={'bottom-left': (-0.5, -0.5), 'top-right': (0.5, 0.5)}, tolerance=1) +@scene() +def rtt_load_attachment(cfg): + quad = ngl.Quad((-1, -1, 0), (2, 0, 0), (0, 2, 0)) + program = ngl.Program(vertex=cfg.get_vert('color'), fragment=cfg.get_frag('color')) + program.update_vert_out_vars(var_tex0_coord=ngl.IOVec2(), var_uvcoord=ngl.IOVec2()) + background = ngl.Render(quad, program) + background.update_frag_resources(color=ngl.UniformVec4(value=COLORS['white'])) + + program = ngl.Program(vertex=cfg.get_vert('color'), fragment=cfg.get_frag('color')) + program.update_vert_out_vars(var_tex0_coord=ngl.IOVec2(), var_uvcoord=ngl.IOVec2()) + render = ngl.Render(quad, program) + render.update_frag_resources(color=ngl.UniformVec4(value=COLORS['orange'])) + + texture = ngl.Texture2D(width=16, height=16) + rtt = ngl.RenderToTexture(render, [texture]) + + quad = ngl.Quad((0, 0, 0), (1, 0, 0), (0, 1, 0)) + program = ngl.Program(vertex=cfg.get_vert('texture'), fragment=cfg.get_frag('texture')) + program.update_vert_out_vars(var_tex0_coord=ngl.IOVec2(), var_uvcoord=ngl.IOVec2()) + foreground = ngl.Render(quad, program) + foreground.update_frag_resources(tex0=texture) + + return ngl.Group(children=(background, rtt, foreground)) From 38cea769492bb6bea107075b8f9adc94b72343ac Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 26 May 2020 14:53:20 +0200 Subject: [PATCH 140/388] pgcraft: define ngl_{instance,vertex}_index --- doc/expl/shaders.md | 2 ++ libnodegl/pgcraft.c | 8 +++++++- libnodegl/pgcraft.h | 2 ++ .../pynodegl_utils/examples/shaders/particules.vert | 2 +- tests/compute.py | 2 +- 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/doc/expl/shaders.md b/doc/expl/shaders.md index 0b73238ad4..42dc383e41 100644 --- a/doc/expl/shaders.md +++ b/doc/expl/shaders.md @@ -22,6 +22,8 @@ Type | Name | Description `vec4` | `ngl_position` | geometry vertices, always available `vec2` | `ngl_uvcoord` | geometry uv coordinates, if provided by the geometry `vec3` | `ngl_normal` | geometry normals, if provided by the geometry +`int` | `ngl_vertex_index` | index of the current vertex +`int` | `ngl_instance_index` | instance number of the current primitive in an instanced draw call **Note**: these are commonly referred as `attributes` or `in` in GL lexicon. diff --git a/libnodegl/pgcraft.c b/libnodegl/pgcraft.c index 9d09e7f57c..293778fd0b 100644 --- a/libnodegl/pgcraft.c +++ b/libnodegl/pgcraft.c @@ -713,7 +713,10 @@ static int craft_vert(struct pgcraft *s, const struct pgcraft_params *params) set_glsl_header(s, b); - ngli_bstr_print(b, "#define ngl_out_pos gl_Position\n"); + ngli_bstr_printf(b, "#define ngl_out_pos gl_Position\n" + "#define ngl_vertex_index %s\n" + "#define ngl_instance_index %s\n", + s->sym_vertex_index, s->sym_instance_index); int ret; if ((ret = inject_iovars(s, b, NGLI_PROGRAM_SHADER_VERT)) < 0 || @@ -932,6 +935,9 @@ static void setup_glsl_info_gl(struct pgcraft *s) const struct ngl_config *config = &ctx->config; struct gctx *gctx = ctx->gctx; + s->sym_vertex_index = "gl_VertexID"; + s->sym_instance_index = "gl_InstanceID"; + if (config->backend == NGL_BACKEND_OPENGL) { switch (gctx->version) { case 300: s->glsl_version = 130; break; diff --git a/libnodegl/pgcraft.h b/libnodegl/pgcraft.h index a241a4f37e..a286d32ba3 100644 --- a/libnodegl/pgcraft.h +++ b/libnodegl/pgcraft.h @@ -178,6 +178,8 @@ struct pgcraft { /* GLSL info */ int glsl_version; const char *glsl_version_suffix; + const char *sym_vertex_index; + const char *sym_instance_index; const char *rg; // 2-component texture picking (could be either rg or ra depending on the OpenGL version) int has_in_out_qualifiers; int has_in_out_layout_qualifiers; diff --git a/pynodegl-utils/pynodegl_utils/examples/shaders/particules.vert b/pynodegl-utils/pynodegl_utils/examples/shaders/particules.vert index 6977b090f6..894fe468d4 100644 --- a/pynodegl-utils/pynodegl_utils/examples/shaders/particules.vert +++ b/pynodegl-utils/pynodegl_utils/examples/shaders/particules.vert @@ -1,5 +1,5 @@ void main() { - vec4 position = ngl_position + vec4(positions.data[gl_InstanceID], 0.0); + vec4 position = ngl_position + vec4(positions.data[ngl_instance_index], 0.0); ngl_out_pos = ngl_projection_matrix * ngl_modelview_matrix * position; } diff --git a/tests/compute.py b/tests/compute.py index 399e89266d..c03d7e6599 100644 --- a/tests/compute.py +++ b/tests/compute.py @@ -50,7 +50,7 @@ _PARTICULES_VERT = ''' void main() { - vec4 position = ngl_position + vec4(data.positions[gl_InstanceID], 0.0); + vec4 position = ngl_position + vec4(data.positions[ngl_instance_index], 0.0); ngl_out_pos = ngl_projection_matrix * ngl_modelview_matrix * position; } ''' From 72183559e30457190f960bb038a9dfe0e6b36316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 23 Jun 2020 10:48:38 +0200 Subject: [PATCH 141/388] block: add ngli_block_field_copy() helper --- libnodegl/block.c | 11 +++++++++++ libnodegl/block.h | 4 ++++ libnodegl/node_block.c | 8 ++------ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/libnodegl/block.c b/libnodegl/block.c index fc0be9a922..1e09f52692 100644 --- a/libnodegl/block.c +++ b/libnodegl/block.c @@ -152,6 +152,17 @@ int ngli_block_add_field(struct block *s, const char *name, int type, int count) return 0; } +void ngli_block_field_copy(const struct block_field *fi, uint8_t *dst, const uint8_t *src) +{ + const int src_stride = sizes_map[fi->type]; + if (fi->count == 0 || src_stride == fi->stride) { + memcpy(dst, src, fi->size); + } else { + for (int i = 0; i < fi->count; i++) + memcpy(dst + i * fi->stride, src + i * src_stride, src_stride); + } +} + void ngli_block_reset(struct block *s) { ngli_darray_reset(&s->fields); diff --git a/libnodegl/block.h b/libnodegl/block.h index 15bda9f9ac..a8adbcac77 100644 --- a/libnodegl/block.h +++ b/libnodegl/block.h @@ -22,6 +22,8 @@ #ifndef BLOCK_H #define BLOCK_H +#include + #include "buffer.h" #include "darray.h" #include "program.h" // MAX_ID_LEN @@ -41,6 +43,8 @@ struct block_field { int stride; }; +void ngli_block_field_copy(const struct block_field *fi, uint8_t *dst, const uint8_t *src); + struct block { int type; // NGLI_TYPE_UNIFORM_BUFFER or NGLI_TYPE_STORAGE_BUFFER enum block_layout layout; diff --git a/libnodegl/node_block.c b/libnodegl/node_block.c index 199fbc1de2..ab719551ad 100644 --- a/libnodegl/node_block.c +++ b/libnodegl/node_block.c @@ -205,7 +205,7 @@ static void update_uniform_field(uint8_t *dst, const struct block_field *fi) { const struct variable_priv *uniform = node->priv_data; - memcpy(dst, uniform->data, uniform->data_size); + ngli_block_field_copy(fi, dst, uniform->data); } static void update_buffer_field(uint8_t *dst, @@ -213,11 +213,7 @@ static void update_buffer_field(uint8_t *dst, const struct block_field *fi) { const struct buffer_priv *buffer = node->priv_data; - if (buffer->data_stride == fi->stride) - memcpy(dst, buffer->data, fi->size); - else - for (int i = 0; i < buffer->count; i++) - memcpy(dst + i * fi->stride, buffer->data + i * buffer->data_stride, buffer->data_stride); + ngli_block_field_copy(fi, dst, buffer->data); } enum field_type { IS_SINGLE, IS_ARRAY }; From 3116b23db62e76be3fc97c96c0897878b01b755e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Wed, 7 Oct 2020 23:15:37 +0200 Subject: [PATCH 142/388] pgcraft: fix precision being specified for bools Precision qualifiers apply only to floating point, integer and opaque types. --- libnodegl/pgcraft.c | 70 ++++++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/libnodegl/pgcraft.c b/libnodegl/pgcraft.c index 293778fd0b..2bb136d13b 100644 --- a/libnodegl/pgcraft.c +++ b/libnodegl/pgcraft.c @@ -98,39 +98,49 @@ static const char *image_glsl_format_map[NGLI_FORMAT_NB] = { [NGLI_FORMAT_S8_UINT] = NULL, }; +enum { + TYPE_FLAG_IS_SAMPLER_OR_IMAGE = 1 << 0, + TYPE_FLAG_HAS_PRECISION = 1 << 1, +}; + static const struct { - int is_sampler_or_image; + int flags; const char *glsl_type; } type_info_map[NGLI_TYPE_NB] = { - [NGLI_TYPE_INT] = {0, "int"}, - [NGLI_TYPE_IVEC2] = {0, "ivec2"}, - [NGLI_TYPE_IVEC3] = {0, "ivec3"}, - [NGLI_TYPE_IVEC4] = {0, "ivec4"}, - [NGLI_TYPE_UINT] = {0, "uint"}, - [NGLI_TYPE_UIVEC2] = {0, "uvec2"}, - [NGLI_TYPE_UIVEC3] = {0, "uvec3"}, - [NGLI_TYPE_UIVEC4] = {0, "uvec4"}, - [NGLI_TYPE_FLOAT] = {0, "float"}, - [NGLI_TYPE_VEC2] = {0, "vec2"}, - [NGLI_TYPE_VEC3] = {0, "vec3"}, - [NGLI_TYPE_VEC4] = {0, "vec4"}, - [NGLI_TYPE_MAT3] = {0, "mat3"}, - [NGLI_TYPE_MAT4] = {0, "mat4"}, + [NGLI_TYPE_INT] = {TYPE_FLAG_HAS_PRECISION, "int"}, + [NGLI_TYPE_IVEC2] = {TYPE_FLAG_HAS_PRECISION, "ivec2"}, + [NGLI_TYPE_IVEC3] = {TYPE_FLAG_HAS_PRECISION, "ivec3"}, + [NGLI_TYPE_IVEC4] = {TYPE_FLAG_HAS_PRECISION, "ivec4"}, + [NGLI_TYPE_UINT] = {TYPE_FLAG_HAS_PRECISION, "uint"}, + [NGLI_TYPE_UIVEC2] = {TYPE_FLAG_HAS_PRECISION, "uvec2"}, + [NGLI_TYPE_UIVEC3] = {TYPE_FLAG_HAS_PRECISION, "uvec3"}, + [NGLI_TYPE_UIVEC4] = {TYPE_FLAG_HAS_PRECISION, "uvec4"}, + [NGLI_TYPE_FLOAT] = {TYPE_FLAG_HAS_PRECISION, "float"}, + [NGLI_TYPE_VEC2] = {TYPE_FLAG_HAS_PRECISION, "vec2"}, + [NGLI_TYPE_VEC3] = {TYPE_FLAG_HAS_PRECISION, "vec3"}, + [NGLI_TYPE_VEC4] = {TYPE_FLAG_HAS_PRECISION, "vec4"}, + [NGLI_TYPE_MAT3] = {TYPE_FLAG_HAS_PRECISION, "mat3"}, + [NGLI_TYPE_MAT4] = {TYPE_FLAG_HAS_PRECISION, "mat4"}, [NGLI_TYPE_BOOL] = {0, "bool"}, - [NGLI_TYPE_SAMPLER_2D] = {1, "sampler2D"}, - [NGLI_TYPE_SAMPLER_2D_RECT] = {1, "sampler2DRect"}, - [NGLI_TYPE_SAMPLER_3D] = {1, "sampler3D"}, - [NGLI_TYPE_SAMPLER_CUBE] = {1, "samplerCube"}, - [NGLI_TYPE_SAMPLER_EXTERNAL_OES] = {1, "samplerExternalOES"}, - [NGLI_TYPE_SAMPLER_EXTERNAL_2D_Y2Y_EXT] = {1, "__samplerExternal2DY2YEXT"}, - [NGLI_TYPE_IMAGE_2D] = {1, "image2D"}, + [NGLI_TYPE_SAMPLER_2D] = {TYPE_FLAG_HAS_PRECISION|TYPE_FLAG_IS_SAMPLER_OR_IMAGE, "sampler2D"}, + [NGLI_TYPE_SAMPLER_2D_RECT] = {TYPE_FLAG_HAS_PRECISION|TYPE_FLAG_IS_SAMPLER_OR_IMAGE, "sampler2DRect"}, + [NGLI_TYPE_SAMPLER_3D] = {TYPE_FLAG_HAS_PRECISION|TYPE_FLAG_IS_SAMPLER_OR_IMAGE, "sampler3D"}, + [NGLI_TYPE_SAMPLER_CUBE] = {TYPE_FLAG_HAS_PRECISION|TYPE_FLAG_IS_SAMPLER_OR_IMAGE, "samplerCube"}, + [NGLI_TYPE_SAMPLER_EXTERNAL_OES] = {TYPE_FLAG_HAS_PRECISION|TYPE_FLAG_IS_SAMPLER_OR_IMAGE, "samplerExternalOES"}, + [NGLI_TYPE_SAMPLER_EXTERNAL_2D_Y2Y_EXT] = {TYPE_FLAG_HAS_PRECISION|TYPE_FLAG_IS_SAMPLER_OR_IMAGE, "__samplerExternal2DY2YEXT"}, + [NGLI_TYPE_IMAGE_2D] = {TYPE_FLAG_HAS_PRECISION|TYPE_FLAG_IS_SAMPLER_OR_IMAGE, "image2D"}, [NGLI_TYPE_UNIFORM_BUFFER] = {0, "uniform"}, [NGLI_TYPE_STORAGE_BUFFER] = {0, "buffer"}, }; static int is_sampler_or_image(int type) { - return type_info_map[type].is_sampler_or_image; + return type_info_map[type].flags & TYPE_FLAG_IS_SAMPLER_OR_IMAGE; +} + +static int type_has_precision(int type) +{ + return type_info_map[type].flags & TYPE_FLAG_HAS_PRECISION; } static const char *get_glsl_type(int type) @@ -140,9 +150,9 @@ static const char *get_glsl_type(int type) return ret; } -static const char *get_precision_qualifier(const struct pgcraft *s, int precision, const char *defaultp) +static const char *get_precision_qualifier(const struct pgcraft *s, int type, int precision, const char *defaultp) { - if (!s->has_precision_qualifiers) + if (!s->has_precision_qualifiers || !type_has_precision(type)) return ""; static const char *precision_qualifiers[NGLI_PRECISION_NB] = { [NGLI_PRECISION_AUTO] = NULL, @@ -168,7 +178,7 @@ static int inject_uniform(struct pgcraft *s, struct bstr *b, snprintf(pl_uniform.name, sizeof(pl_uniform.name), "%s", uniform->name); const char *type = get_glsl_type(uniform->type); - const char *precision = get_precision_qualifier(s, uniform->precision, "highp"); + const char *precision = get_precision_qualifier(s, uniform->type, uniform->precision, "highp"); if (uniform->count) ngli_bstr_printf(b, "uniform %s %s %s[%d];\n", precision, type, uniform->name, uniform->count); else @@ -327,7 +337,7 @@ static int inject_texture_info(struct pgcraft *s, struct pgcraft_texture_info *i } const char *type = get_glsl_type(field->type); - const char *precision = get_precision_qualifier(s, info->precision, "lowp"); + const char *precision = get_precision_qualifier(s, field->type, info->precision, "lowp"); ngli_bstr_printf(b, "uniform %s %s %s;\n", precision, type, field->name); if (!ngli_darray_push(&s->pipeline_textures, &pl_texture)) @@ -424,7 +434,7 @@ static int inject_attribute(struct pgcraft *s, struct bstr *b, int base_location = -1; const int attribute_count = attribute->type == NGLI_TYPE_MAT4 ? 4 : 1; const char *qualifier = s->has_in_out_qualifiers ? "in" : "attribute"; - const char *precision = get_precision_qualifier(s, attribute->precision, "highp"); + const char *precision = get_precision_qualifier(s, attribute->type, attribute->precision, "highp"); ngli_bstr_printf(b, "%s %s %s %s;\n", qualifier, precision, type, attribute->name); const int attribute_offset = ngli_format_get_bytes_per_pixel(attribute->format); @@ -699,8 +709,8 @@ static int inject_iovars(struct pgcraft *s, struct bstr *b, int stage) ngli_bstr_printf(b, "layout(location=%d) ", i); const struct pgcraft_iovar *iovar = &iovars[i]; const char *precision = stage == NGLI_PROGRAM_SHADER_VERT - ? get_precision_qualifier(s, iovar->precision_out, "highp") - : get_precision_qualifier(s, iovar->precision_in, "highp"); + ? get_precision_qualifier(s, iovar->type, iovar->precision_out, "highp") + : get_precision_qualifier(s, iovar->type, iovar->precision_in, "highp"); const char *type = get_glsl_type(iovar->type); ngli_bstr_printf(b, "%s %s %s %s;\n", qualifier, precision, type, iovar->name); } From c9fb83577bced37be3fee89d9dee862ab2831653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Wed, 7 Oct 2020 22:53:01 +0200 Subject: [PATCH 143/388] nodes: add UniformBool --- libnodegl/doc/libnodegl.md | 10 ++++++++++ libnodegl/node_uniform.c | 4 ++++ libnodegl/nodegl.h | 1 + libnodegl/nodes.specs | 3 +++ libnodegl/nodes_register.h | 1 + 5 files changed, 19 insertions(+) diff --git a/libnodegl/doc/libnodegl.md b/libnodegl/doc/libnodegl.md index 9a83bc2a67..80d7a65abd 100644 --- a/libnodegl/doc/libnodegl.md +++ b/libnodegl/doc/libnodegl.md @@ -1034,6 +1034,16 @@ Parameter | Live-chg. | Type | Description | Default **Source**: [node_streamedbuffer.c](/libnodegl/node_streamedbuffer.c) +## UniformBool + +Parameter | Live-chg. | Type | Description | Default +--------- | :-------: | ---- | ----------- | :-----: +`value` | ✓ | [`bool`](#parameter-types) | value exposed to the shader | `0` + + +**Source**: [node_uniform.c](/libnodegl/node_uniform.c) + + ## UniformInt Parameter | Live-chg. | Type | Description | Default diff --git a/libnodegl/node_uniform.c b/libnodegl/node_uniform.c index 2097ee5c9b..52c3bccd51 100644 --- a/libnodegl/node_uniform.c +++ b/libnodegl/node_uniform.c @@ -93,6 +93,7 @@ static const struct node_param uniform##type##_params[] = { \ {NULL} \ } +DECLARE_PARAMS(bool, PARAM_TYPE_BOOL, opt.ivec, uniformivec_update_func); DECLARE_PARAMS(float, PARAM_TYPE_DBL, opt.dbl, uniformfloat_update_func); DECLARE_PARAMS(vec2, PARAM_TYPE_VEC2, opt.vec, uniformvec_update_func); DECLARE_PARAMS(vec3, PARAM_TYPE_VEC3, opt.vec, uniformvec_update_func); @@ -133,6 +134,7 @@ static int uniform_update(struct ngl_node *node, double t) return 0; } +#define uniformbool_update uniform_update #define uniformfloat_update uniform_update #define uniformvec2_update uniform_update #define uniformvec3_update uniform_update @@ -185,6 +187,7 @@ static int uniform##type##_init(struct ngl_node *node) \ return 0; \ } +DECLARE_INIT_FUNC(bool, NGLI_TYPE_BOOL, 1, s->ivector, s->opt.ivec) DECLARE_INIT_FUNC(int, NGLI_TYPE_INT, 1, s->ivector, s->opt.ivec) DECLARE_INIT_FUNC(ivec2, NGLI_TYPE_IVEC2, 2, s->ivector, s->opt.ivec) DECLARE_INIT_FUNC(ivec3, NGLI_TYPE_IVEC3, 3, s->ivector, s->opt.ivec) @@ -253,6 +256,7 @@ const struct node_class ngli_uniform##type##_class = { \ .file = __FILE__, \ }; +DEFINE_UNIFORM_CLASS(NGL_NODE_UNIFORMBOOL, "UniformBool", bool) DEFINE_UNIFORM_CLASS(NGL_NODE_UNIFORMFLOAT, "UniformFloat", float) DEFINE_UNIFORM_CLASS(NGL_NODE_UNIFORMVEC2, "UniformVec2", vec2) DEFINE_UNIFORM_CLASS(NGL_NODE_UNIFORMVEC3, "UniformVec3", vec3) diff --git a/libnodegl/nodegl.h b/libnodegl/nodegl.h index 45c84be3a8..cc12283b9f 100644 --- a/libnodegl/nodegl.h +++ b/libnodegl/nodegl.h @@ -207,6 +207,7 @@ struct ngl_node; #define NGL_NODE_TRANSFORM NGLI_FOURCC('T','r','f','m') #define NGL_NODE_TRANSLATE NGLI_FOURCC('T','m','o','v') #define NGL_NODE_TRIANGLE NGLI_FOURCC('T','r','g','l') +#define NGL_NODE_UNIFORMBOOL NGLI_FOURCC('U','n','b','1') #define NGL_NODE_UNIFORMINT NGLI_FOURCC('U','n','i','1') #define NGL_NODE_UNIFORMIVEC2 NGLI_FOURCC('U','n','i','2') #define NGL_NODE_UNIFORMIVEC3 NGLI_FOURCC('U','n','i','3') diff --git a/libnodegl/nodes.specs b/libnodegl/nodes.specs index 096ad0b1f1..9e3eca2402 100644 --- a/libnodegl/nodes.specs +++ b/libnodegl/nodes.specs @@ -585,6 +585,9 @@ - [timebase, rational] - [time_anim, Node] +- UniformBool: + - [value, bool] + - UniformInt: - [value, int] diff --git a/libnodegl/nodes_register.h b/libnodegl/nodes_register.h index 0225d2dca6..847a56b2bc 100644 --- a/libnodegl/nodes_register.h +++ b/libnodegl/nodes_register.h @@ -141,6 +141,7 @@ action(NGL_NODE_STREAMEDBUFFERVEC3, ngli_streamedbuffervec3_class) \ action(NGL_NODE_STREAMEDBUFFERVEC4, ngli_streamedbuffervec4_class) \ action(NGL_NODE_STREAMEDBUFFERMAT4, ngli_streamedbuffermat4_class) \ + action(NGL_NODE_UNIFORMBOOL, ngli_uniformbool_class) \ action(NGL_NODE_UNIFORMINT, ngli_uniformint_class) \ action(NGL_NODE_UNIFORMIVEC2, ngli_uniformivec2_class) \ action(NGL_NODE_UNIFORMIVEC3, ngli_uniformivec3_class) \ From 361d513e5ca8c91cdb9f427d64022f4ec87e1362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Wed, 7 Oct 2020 22:54:03 +0200 Subject: [PATCH 144/388] block: add support for UniformBool --- libnodegl/block.c | 4 ++++ libnodegl/doc/libnodegl.md | 2 +- libnodegl/node_block.c | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/libnodegl/block.c b/libnodegl/block.c index 1e09f52692..c896b42996 100644 --- a/libnodegl/block.c +++ b/libnodegl/block.c @@ -35,6 +35,7 @@ void ngli_block_init(struct block *s, enum block_layout layout) static const int strides_map[NGLI_BLOCK_NB_LAYOUTS][NGLI_TYPE_NB] = { [NGLI_BLOCK_LAYOUT_STD140] = { + [NGLI_TYPE_BOOL] = sizeof(int) * 4, [NGLI_TYPE_INT] = sizeof(int) * 4, [NGLI_TYPE_IVEC2] = sizeof(int) * 4, [NGLI_TYPE_IVEC3] = sizeof(int) * 4, @@ -50,6 +51,7 @@ static const int strides_map[NGLI_BLOCK_NB_LAYOUTS][NGLI_TYPE_NB] = { [NGLI_TYPE_MAT4] = sizeof(float) * 4 * 4, }, [NGLI_BLOCK_LAYOUT_STD430] = { + [NGLI_TYPE_BOOL] = sizeof(int) * 1, [NGLI_TYPE_INT] = sizeof(int) * 1, [NGLI_TYPE_IVEC2] = sizeof(int) * 2, [NGLI_TYPE_IVEC3] = sizeof(int) * 4, @@ -67,6 +69,7 @@ static const int strides_map[NGLI_BLOCK_NB_LAYOUTS][NGLI_TYPE_NB] = { }; static const int sizes_map[NGLI_TYPE_NB] = { + [NGLI_TYPE_BOOL] = sizeof(int) * 1, [NGLI_TYPE_INT] = sizeof(int) * 1, [NGLI_TYPE_IVEC2] = sizeof(int) * 2, [NGLI_TYPE_IVEC3] = sizeof(int) * 3, @@ -83,6 +86,7 @@ static const int sizes_map[NGLI_TYPE_NB] = { }; static const int aligns_map[NGLI_TYPE_NB] = { + [NGLI_TYPE_BOOL] = sizeof(int) * 1, [NGLI_TYPE_INT] = sizeof(int) * 1, [NGLI_TYPE_IVEC2] = sizeof(int) * 2, [NGLI_TYPE_IVEC3] = sizeof(int) * 4, diff --git a/libnodegl/doc/libnodegl.md b/libnodegl/doc/libnodegl.md index 80d7a65abd..6170b2b630 100644 --- a/libnodegl/doc/libnodegl.md +++ b/libnodegl/doc/libnodegl.md @@ -172,7 +172,7 @@ Parameter | Live-chg. | Type | Description | Default Parameter | Live-chg. | Type | Description | Default --------- | :-------: | ---- | ----------- | :-----: -`fields` | | [`NodeList`](#parameter-types) ([AnimatedBufferFloat](#animatedbuffer), [AnimatedBufferVec2](#animatedbuffer), [AnimatedBufferVec3](#animatedbuffer), [AnimatedBufferVec4](#animatedbuffer), [StreamedBufferInt](#streamedbufferint), [StreamedBufferIVec2](#streamedbufferivec2), [StreamedBufferIVec3](#streamedbufferivec3), [StreamedBufferIVec4](#streamedbufferivec4), [StreamedBufferUInt](#streamedbufferuint), [StreamedBufferUIVec2](#streamedbufferuivec2), [StreamedBufferUIVec3](#streamedbufferuivec3), [StreamedBufferUIVec4](#streamedbufferuivec4), [StreamedBufferFloat](#streamedbufferfloat), [StreamedBufferVec2](#streamedbuffervec2), [StreamedBufferVec3](#streamedbuffervec3), [StreamedBufferVec4](#streamedbuffervec4), [BufferFloat](#buffer), [BufferVec2](#buffer), [BufferVec3](#buffer), [BufferVec4](#buffer), [BufferInt](#buffer), [BufferIVec2](#buffer), [BufferIVec3](#buffer), [BufferIVec4](#buffer), [BufferUInt](#buffer), [BufferUIVec2](#buffer), [BufferUIVec3](#buffer), [BufferUIVec4](#buffer), [BufferMat4](#buffer), [UniformFloat](#uniformfloat), [UniformVec2](#uniformvec2), [UniformVec3](#uniformvec3), [UniformVec4](#uniformvec4), [UniformInt](#uniformint), [UniformIVec2](#uniformivec2), [UniformIVec3](#uniformivec3), [UniformIVec4](#uniformivec4), [UniformUInt](#uniformuint), [UniformUIVec2](#uniformuivec2), [UniformUIVec3](#uniformuivec3), [UniformUIVec4](#uniformuivec4), [UniformMat4](#uniformmat4), [UniformQuat](#uniformquat), [AnimatedFloat](#animatedfloat), [AnimatedVec2](#animatedvec2), [AnimatedVec3](#animatedvec3), [AnimatedVec4](#animatedvec4), [AnimatedQuat](#animatedquat), [StreamedInt](#streamedint), [StreamedIVec2](#streamedivec2), [StreamedIVec3](#streamedivec3), [StreamedIVec4](#streamedivec4), [StreamedUInt](#streameduint), [StreamedUIVec2](#streameduivec2), [StreamedUIVec3](#streameduivec3), [StreamedUIVec4](#streameduivec4), [StreamedFloat](#streamedfloat), [StreamedVec2](#streamedvec2), [StreamedVec3](#streamedvec3), [StreamedVec4](#streamedvec4), [StreamedMat4](#streamedmat4), [Time](#time)) | block fields defined in the graphic program | +`fields` | | [`NodeList`](#parameter-types) ([AnimatedBufferFloat](#animatedbuffer), [AnimatedBufferVec2](#animatedbuffer), [AnimatedBufferVec3](#animatedbuffer), [AnimatedBufferVec4](#animatedbuffer), [StreamedBufferInt](#streamedbufferint), [StreamedBufferIVec2](#streamedbufferivec2), [StreamedBufferIVec3](#streamedbufferivec3), [StreamedBufferIVec4](#streamedbufferivec4), [StreamedBufferUInt](#streamedbufferuint), [StreamedBufferUIVec2](#streamedbufferuivec2), [StreamedBufferUIVec3](#streamedbufferuivec3), [StreamedBufferUIVec4](#streamedbufferuivec4), [StreamedBufferFloat](#streamedbufferfloat), [StreamedBufferVec2](#streamedbuffervec2), [StreamedBufferVec3](#streamedbuffervec3), [StreamedBufferVec4](#streamedbuffervec4), [BufferFloat](#buffer), [BufferVec2](#buffer), [BufferVec3](#buffer), [BufferVec4](#buffer), [BufferInt](#buffer), [BufferIVec2](#buffer), [BufferIVec3](#buffer), [BufferIVec4](#buffer), [BufferUInt](#buffer), [BufferUIVec2](#buffer), [BufferUIVec3](#buffer), [BufferUIVec4](#buffer), [BufferMat4](#buffer), [UniformBool](#uniformbool), [UniformFloat](#uniformfloat), [UniformVec2](#uniformvec2), [UniformVec3](#uniformvec3), [UniformVec4](#uniformvec4), [UniformInt](#uniformint), [UniformIVec2](#uniformivec2), [UniformIVec3](#uniformivec3), [UniformIVec4](#uniformivec4), [UniformUInt](#uniformuint), [UniformUIVec2](#uniformuivec2), [UniformUIVec3](#uniformuivec3), [UniformUIVec4](#uniformuivec4), [UniformMat4](#uniformmat4), [UniformQuat](#uniformquat), [AnimatedFloat](#animatedfloat), [AnimatedVec2](#animatedvec2), [AnimatedVec3](#animatedvec3), [AnimatedVec4](#animatedvec4), [AnimatedQuat](#animatedquat), [StreamedInt](#streamedint), [StreamedIVec2](#streamedivec2), [StreamedIVec3](#streamedivec3), [StreamedIVec4](#streamedivec4), [StreamedUInt](#streameduint), [StreamedUIVec2](#streameduivec2), [StreamedUIVec3](#streameduivec3), [StreamedUIVec4](#streameduivec4), [StreamedFloat](#streamedfloat), [StreamedVec2](#streamedvec2), [StreamedVec3](#streamedvec3), [StreamedVec4](#streamedvec4), [StreamedMat4](#streamedmat4), [Time](#time)) | block fields defined in the graphic program | `layout` | | [`memory_layout`](#memory_layout-choices) | memory layout set in the graphic program | `std140` diff --git a/libnodegl/node_block.c b/libnodegl/node_block.c index ab719551ad..5291cd6220 100644 --- a/libnodegl/node_block.c +++ b/libnodegl/node_block.c @@ -68,6 +68,7 @@ static const struct param_choices layout_choices = { NGL_NODE_BUFFERUIVEC3, \ NGL_NODE_BUFFERUIVEC4, \ NGL_NODE_BUFFERMAT4, \ + NGL_NODE_UNIFORMBOOL, \ NGL_NODE_UNIFORMFLOAT, \ NGL_NODE_UNIFORMVEC2, \ NGL_NODE_UNIFORMVEC3, \ From ebcd7a58be0da4bca2fae633e23a80a80473cf7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Wed, 7 Oct 2020 22:54:18 +0200 Subject: [PATCH 145/388] compute: add support for UniformBool --- libnodegl/doc/libnodegl.md | 2 +- libnodegl/node_compute.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/libnodegl/doc/libnodegl.md b/libnodegl/doc/libnodegl.md index 6170b2b630..ad05c3c674 100644 --- a/libnodegl/doc/libnodegl.md +++ b/libnodegl/doc/libnodegl.md @@ -264,7 +264,7 @@ Parameter | Live-chg. | Type | Description | Default `nb_group_y` | | [`int`](#parameter-types) | number of work groups to be executed in the y dimension | `0` `nb_group_z` | | [`int`](#parameter-types) | number of work groups to be executed in the z dimension | `0` `program` | | [`Node`](#parameter-types) ([ComputeProgram](#computeprogram)) | compute program to be executed | -`resources` | | [`NodeDict`](#parameter-types) ([Texture2D](#texture2d), [Block](#block), [UniformFloat](#uniformfloat), [UniformVec2](#uniformvec2), [UniformVec3](#uniformvec3), [UniformVec4](#uniformvec4), [UniformQuat](#uniformquat), [UniformInt](#uniformint), [UniformIVec2](#uniformivec2), [UniformIVec3](#uniformivec3), [UniformIVec4](#uniformivec4), [UniformUInt](#uniformuint), [UniformUIVec2](#uniformuivec2), [UniformUIVec3](#uniformuivec3), [UniformUIVec4](#uniformuivec4), [UniformMat4](#uniformmat4), [AnimatedFloat](#animatedfloat), [AnimatedVec2](#animatedvec2), [AnimatedVec3](#animatedvec3), [AnimatedVec4](#animatedvec4), [AnimatedQuat](#animatedquat), [StreamedInt](#streamedint), [StreamedIVec2](#streamedivec2), [StreamedIVec3](#streamedivec3), [StreamedIVec4](#streamedivec4), [StreamedUInt](#streameduint), [StreamedUIVec2](#streameduivec2), [StreamedUIVec3](#streameduivec3), [StreamedUIVec4](#streameduivec4), [StreamedFloat](#streamedfloat), [StreamedVec2](#streamedvec2), [StreamedVec3](#streamedvec3), [StreamedVec4](#streamedvec4), [StreamedMat4](#streamedmat4), [Time](#time)) | resources made accessible to the compute `program` | +`resources` | | [`NodeDict`](#parameter-types) ([Texture2D](#texture2d), [Block](#block), [UniformFloat](#uniformfloat), [UniformVec2](#uniformvec2), [UniformVec3](#uniformvec3), [UniformVec4](#uniformvec4), [UniformQuat](#uniformquat), [UniformBool](#uniformbool), [UniformInt](#uniformint), [UniformIVec2](#uniformivec2), [UniformIVec3](#uniformivec3), [UniformIVec4](#uniformivec4), [UniformUInt](#uniformuint), [UniformUIVec2](#uniformuivec2), [UniformUIVec3](#uniformuivec3), [UniformUIVec4](#uniformuivec4), [UniformMat4](#uniformmat4), [AnimatedFloat](#animatedfloat), [AnimatedVec2](#animatedvec2), [AnimatedVec3](#animatedvec3), [AnimatedVec4](#animatedvec4), [AnimatedQuat](#animatedquat), [StreamedInt](#streamedint), [StreamedIVec2](#streamedivec2), [StreamedIVec3](#streamedivec3), [StreamedIVec4](#streamedivec4), [StreamedUInt](#streameduint), [StreamedUIVec2](#streameduivec2), [StreamedUIVec3](#streameduivec3), [StreamedUIVec4](#streameduivec4), [StreamedFloat](#streamedfloat), [StreamedVec2](#streamedvec2), [StreamedVec3](#streamedvec3), [StreamedVec4](#streamedvec4), [StreamedMat4](#streamedmat4), [Time](#time)) | resources made accessible to the compute `program` | **Source**: [node_compute.c](/libnodegl/node_compute.c) diff --git a/libnodegl/node_compute.c b/libnodegl/node_compute.c index aed9f6fef0..8257d68bba 100644 --- a/libnodegl/node_compute.c +++ b/libnodegl/node_compute.c @@ -53,6 +53,7 @@ struct compute_priv { NGL_NODE_UNIFORMVEC3, \ NGL_NODE_UNIFORMVEC4, \ NGL_NODE_UNIFORMQUAT, \ + NGL_NODE_UNIFORMBOOL, \ NGL_NODE_UNIFORMINT, \ NGL_NODE_UNIFORMIVEC2, \ NGL_NODE_UNIFORMIVEC3, \ From 0132d7304cc229b0f01960edd14d805ebb8d5ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Wed, 7 Oct 2020 22:54:31 +0200 Subject: [PATCH 146/388] render: add support for UniformBool --- libnodegl/doc/libnodegl.md | 4 ++-- libnodegl/node_render.c | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libnodegl/doc/libnodegl.md b/libnodegl/doc/libnodegl.md index ad05c3c674..9d06e6580e 100644 --- a/libnodegl/doc/libnodegl.md +++ b/libnodegl/doc/libnodegl.md @@ -437,8 +437,8 @@ Parameter | Live-chg. | Type | Description | Default --------- | :-------: | ---- | ----------- | :-----: `geometry` | | [`Node`](#parameter-types) ([Circle](#circle), [Geometry](#geometry), [Quad](#quad), [Triangle](#triangle)) | geometry to be rasterized | `program` | | [`Node`](#parameter-types) ([Program](#program)) | program to be executed | -`vert_resources` | | [`NodeDict`](#parameter-types) ([Texture2D](#texture2d), [Texture3D](#texture3d), [TextureCube](#texturecube), [Block](#block), [BufferFloat](#buffer), [BufferVec2](#buffer), [BufferVec3](#buffer), [BufferVec4](#buffer), [StreamedBufferInt](#streamedbufferint), [StreamedBufferIVec2](#streamedbufferivec2), [StreamedBufferIVec3](#streamedbufferivec3), [StreamedBufferIVec4](#streamedbufferivec4), [StreamedBufferUInt](#streamedbufferuint), [StreamedBufferUIVec2](#streamedbufferuivec2), [StreamedBufferUIVec3](#streamedbufferuivec3), [StreamedBufferUIVec4](#streamedbufferuivec4), [StreamedBufferFloat](#streamedbufferfloat), [StreamedBufferVec2](#streamedbuffervec2), [StreamedBufferVec3](#streamedbuffervec3), [StreamedBufferVec4](#streamedbuffervec4), [UniformFloat](#uniformfloat), [UniformVec2](#uniformvec2), [UniformVec3](#uniformvec3), [UniformVec4](#uniformvec4), [UniformQuat](#uniformquat), [UniformInt](#uniformint), [UniformIVec2](#uniformivec2), [UniformIVec3](#uniformivec3), [UniformIVec4](#uniformivec4), [UniformUInt](#uniformuint), [UniformUIVec2](#uniformuivec2), [UniformUIVec3](#uniformuivec3), [UniformUIVec4](#uniformuivec4), [UniformMat4](#uniformmat4), [AnimatedFloat](#animatedfloat), [AnimatedVec2](#animatedvec2), [AnimatedVec3](#animatedvec3), [AnimatedVec4](#animatedvec4), [AnimatedQuat](#animatedquat), [StreamedInt](#streamedint), [StreamedIVec2](#streamedivec2), [StreamedIVec3](#streamedivec3), [StreamedIVec4](#streamedivec4), [StreamedUInt](#streameduint), [StreamedUIVec2](#streameduivec2), [StreamedUIVec3](#streameduivec3), [StreamedUIVec4](#streameduivec4), [StreamedFloat](#streamedfloat), [StreamedVec2](#streamedvec2), [StreamedVec3](#streamedvec3), [StreamedVec4](#streamedvec4), [StreamedMat4](#streamedmat4), [Time](#time)) | resources made accessible to the vertex stage of the `program` | -`frag_resources` | | [`NodeDict`](#parameter-types) ([Texture2D](#texture2d), [Texture3D](#texture3d), [TextureCube](#texturecube), [Block](#block), [BufferFloat](#buffer), [BufferVec2](#buffer), [BufferVec3](#buffer), [BufferVec4](#buffer), [StreamedBufferInt](#streamedbufferint), [StreamedBufferIVec2](#streamedbufferivec2), [StreamedBufferIVec3](#streamedbufferivec3), [StreamedBufferIVec4](#streamedbufferivec4), [StreamedBufferUInt](#streamedbufferuint), [StreamedBufferUIVec2](#streamedbufferuivec2), [StreamedBufferUIVec3](#streamedbufferuivec3), [StreamedBufferUIVec4](#streamedbufferuivec4), [StreamedBufferFloat](#streamedbufferfloat), [StreamedBufferVec2](#streamedbuffervec2), [StreamedBufferVec3](#streamedbuffervec3), [StreamedBufferVec4](#streamedbuffervec4), [UniformFloat](#uniformfloat), [UniformVec2](#uniformvec2), [UniformVec3](#uniformvec3), [UniformVec4](#uniformvec4), [UniformQuat](#uniformquat), [UniformInt](#uniformint), [UniformIVec2](#uniformivec2), [UniformIVec3](#uniformivec3), [UniformIVec4](#uniformivec4), [UniformUInt](#uniformuint), [UniformUIVec2](#uniformuivec2), [UniformUIVec3](#uniformuivec3), [UniformUIVec4](#uniformuivec4), [UniformMat4](#uniformmat4), [AnimatedFloat](#animatedfloat), [AnimatedVec2](#animatedvec2), [AnimatedVec3](#animatedvec3), [AnimatedVec4](#animatedvec4), [AnimatedQuat](#animatedquat), [StreamedInt](#streamedint), [StreamedIVec2](#streamedivec2), [StreamedIVec3](#streamedivec3), [StreamedIVec4](#streamedivec4), [StreamedUInt](#streameduint), [StreamedUIVec2](#streameduivec2), [StreamedUIVec3](#streameduivec3), [StreamedUIVec4](#streameduivec4), [StreamedFloat](#streamedfloat), [StreamedVec2](#streamedvec2), [StreamedVec3](#streamedvec3), [StreamedVec4](#streamedvec4), [StreamedMat4](#streamedmat4), [Time](#time)) | resources made accessible to the fragment stage of the `program` | +`vert_resources` | | [`NodeDict`](#parameter-types) ([Texture2D](#texture2d), [Texture3D](#texture3d), [TextureCube](#texturecube), [Block](#block), [BufferFloat](#buffer), [BufferVec2](#buffer), [BufferVec3](#buffer), [BufferVec4](#buffer), [StreamedBufferInt](#streamedbufferint), [StreamedBufferIVec2](#streamedbufferivec2), [StreamedBufferIVec3](#streamedbufferivec3), [StreamedBufferIVec4](#streamedbufferivec4), [StreamedBufferUInt](#streamedbufferuint), [StreamedBufferUIVec2](#streamedbufferuivec2), [StreamedBufferUIVec3](#streamedbufferuivec3), [StreamedBufferUIVec4](#streamedbufferuivec4), [StreamedBufferFloat](#streamedbufferfloat), [StreamedBufferVec2](#streamedbuffervec2), [StreamedBufferVec3](#streamedbuffervec3), [StreamedBufferVec4](#streamedbuffervec4), [UniformBool](#uniformbool), [UniformFloat](#uniformfloat), [UniformVec2](#uniformvec2), [UniformVec3](#uniformvec3), [UniformVec4](#uniformvec4), [UniformQuat](#uniformquat), [UniformInt](#uniformint), [UniformIVec2](#uniformivec2), [UniformIVec3](#uniformivec3), [UniformIVec4](#uniformivec4), [UniformUInt](#uniformuint), [UniformUIVec2](#uniformuivec2), [UniformUIVec3](#uniformuivec3), [UniformUIVec4](#uniformuivec4), [UniformMat4](#uniformmat4), [AnimatedFloat](#animatedfloat), [AnimatedVec2](#animatedvec2), [AnimatedVec3](#animatedvec3), [AnimatedVec4](#animatedvec4), [AnimatedQuat](#animatedquat), [StreamedInt](#streamedint), [StreamedIVec2](#streamedivec2), [StreamedIVec3](#streamedivec3), [StreamedIVec4](#streamedivec4), [StreamedUInt](#streameduint), [StreamedUIVec2](#streameduivec2), [StreamedUIVec3](#streameduivec3), [StreamedUIVec4](#streameduivec4), [StreamedFloat](#streamedfloat), [StreamedVec2](#streamedvec2), [StreamedVec3](#streamedvec3), [StreamedVec4](#streamedvec4), [StreamedMat4](#streamedmat4), [Time](#time)) | resources made accessible to the vertex stage of the `program` | +`frag_resources` | | [`NodeDict`](#parameter-types) ([Texture2D](#texture2d), [Texture3D](#texture3d), [TextureCube](#texturecube), [Block](#block), [BufferFloat](#buffer), [BufferVec2](#buffer), [BufferVec3](#buffer), [BufferVec4](#buffer), [StreamedBufferInt](#streamedbufferint), [StreamedBufferIVec2](#streamedbufferivec2), [StreamedBufferIVec3](#streamedbufferivec3), [StreamedBufferIVec4](#streamedbufferivec4), [StreamedBufferUInt](#streamedbufferuint), [StreamedBufferUIVec2](#streamedbufferuivec2), [StreamedBufferUIVec3](#streamedbufferuivec3), [StreamedBufferUIVec4](#streamedbufferuivec4), [StreamedBufferFloat](#streamedbufferfloat), [StreamedBufferVec2](#streamedbuffervec2), [StreamedBufferVec3](#streamedbuffervec3), [StreamedBufferVec4](#streamedbuffervec4), [UniformBool](#uniformbool), [UniformFloat](#uniformfloat), [UniformVec2](#uniformvec2), [UniformVec3](#uniformvec3), [UniformVec4](#uniformvec4), [UniformQuat](#uniformquat), [UniformInt](#uniformint), [UniformIVec2](#uniformivec2), [UniformIVec3](#uniformivec3), [UniformIVec4](#uniformivec4), [UniformUInt](#uniformuint), [UniformUIVec2](#uniformuivec2), [UniformUIVec3](#uniformuivec3), [UniformUIVec4](#uniformuivec4), [UniformMat4](#uniformmat4), [AnimatedFloat](#animatedfloat), [AnimatedVec2](#animatedvec2), [AnimatedVec3](#animatedvec3), [AnimatedVec4](#animatedvec4), [AnimatedQuat](#animatedquat), [StreamedInt](#streamedint), [StreamedIVec2](#streamedivec2), [StreamedIVec3](#streamedivec3), [StreamedIVec4](#streamedivec4), [StreamedUInt](#streameduint), [StreamedUIVec2](#streameduivec2), [StreamedUIVec3](#streameduivec3), [StreamedUIVec4](#streameduivec4), [StreamedFloat](#streamedfloat), [StreamedVec2](#streamedvec2), [StreamedVec3](#streamedvec3), [StreamedVec4](#streamedvec4), [StreamedMat4](#streamedmat4), [Time](#time)) | resources made accessible to the fragment stage of the `program` | `attributes` | | [`NodeDict`](#parameter-types) ([BufferFloat](#buffer), [BufferVec2](#buffer), [BufferVec3](#buffer), [BufferVec4](#buffer), [BufferMat4](#buffer)) | extra vertex attributes made accessible to the `program` | `instance_attributes` | | [`NodeDict`](#parameter-types) ([BufferFloat](#buffer), [BufferVec2](#buffer), [BufferVec3](#buffer), [BufferVec4](#buffer), [BufferMat4](#buffer)) | per instance extra vertex attributes made accessible to the `program` | `nb_instances` | | [`int`](#parameter-types) | number of instances to draw | `1` diff --git a/libnodegl/node_render.c b/libnodegl/node_render.c index 9a9400aac2..69bddea158 100644 --- a/libnodegl/node_render.c +++ b/libnodegl/node_render.c @@ -68,6 +68,7 @@ struct render_priv { NGL_NODE_STREAMEDBUFFERVEC2, \ NGL_NODE_STREAMEDBUFFERVEC3, \ NGL_NODE_STREAMEDBUFFERVEC4, \ + NGL_NODE_UNIFORMBOOL, \ NGL_NODE_UNIFORMFLOAT, \ NGL_NODE_UNIFORMVEC2, \ NGL_NODE_UNIFORMVEC3, \ From 517143a73a758431d7142a6f927afa30e1ad1c91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Wed, 7 Oct 2020 22:54:59 +0200 Subject: [PATCH 147/388] tests: add UniformBool data and live-change tests --- pynodegl-utils/pynodegl_utils/tests/data.py | 43 ++++++++++++--------- tests/data.mak | 1 + tests/data.py | 2 + tests/live.mak | 1 + tests/live.py | 2 + tests/refs/data_single_bool_std140.ref | 1 + tests/refs/data_single_bool_std430.ref | 1 + tests/refs/data_single_bool_uniform.ref | 1 + tests/refs/live_single_bool_std140.ref | 3 ++ tests/refs/live_single_bool_std430.ref | 3 ++ tests/refs/live_single_bool_uniform.ref | 3 ++ 11 files changed, 42 insertions(+), 19 deletions(-) create mode 100644 tests/refs/data_single_bool_std140.ref create mode 100644 tests/refs/data_single_bool_std430.ref create mode 100644 tests/refs/data_single_bool_uniform.ref create mode 100644 tests/refs/live_single_bool_std140.ref create mode 100644 tests/refs/live_single_bool_std430.ref create mode 100644 tests/refs/live_single_bool_uniform.ref diff --git a/pynodegl-utils/pynodegl_utils/tests/data.py b/pynodegl-utils/pynodegl_utils/tests/data.py index 4b00fc97fb..8e749270ed 100644 --- a/pynodegl-utils/pynodegl_utils/tests/data.py +++ b/pynodegl-utils/pynodegl_utils/tests/data.py @@ -73,7 +73,7 @@ } ''' -_COMMON_INT_TPL = 'amount += float(%(fields_prefix)s%(field_name)s%(vec_field)s) / 255. * in_rect(rect_%(comp_id)d, var_uvcoord);' +_COMMON_INT_TPL = 'amount += float(%(fields_prefix)s%(field_name)s%(vec_field)s) * %(scale)f * in_rect(rect_%(comp_id)d, var_uvcoord);' _COMMON_FLT_TPL = 'amount += %(fields_prefix)s%(field_name)s%(vec_field)s * in_rect(rect_%(comp_id)d, var_uvcoord);' _RECT_ARRAY_TPL = 'vec4 rect_%(comp_id)d = vec4(x + %(row)f * w / (%(nb_rows)f * float(len)) + float(i) * w / float(len), y + %(col)f * h / %(nb_cols)f, w / %(nb_rows)f / float(len), h / %(nb_cols)f);' @@ -82,33 +82,34 @@ ANIM_DURATION = 5.0 LAYOUTS = ('std140', 'std430', 'uniform') -# row, col, is_int +# row, col, scale _TYPE_SPEC = dict( - float= (1, 1, False), - vec2= (1, 2, False), - vec3= (1, 3, False), - vec4= (1, 4, False), - mat4= (4, 4, False), - int= (1, 1, True), - ivec2= (1, 2, True), - ivec3= (1, 3, True), - ivec4= (1, 4, True), - uint= (1, 1, True), - uvec2= (1, 2, True), - uvec3= (1, 3, True), - uvec4= (1, 4, True), - quat_mat4=(4, 4, False), - quat_vec4=(1, 4, False), + bool= (1, 1, 1.0), + float= (1, 1, None), + vec2= (1, 2, None), + vec3= (1, 3, None), + vec4= (1, 4, None), + mat4= (4, 4, None), + int= (1, 1, 1. / 255.), + ivec2= (1, 2, 1. / 255.), + ivec3= (1, 3, 1. / 255.), + ivec4= (1, 4, 1. / 255.), + uint= (1, 1, 1. / 255.), + uvec2= (1, 2, 1. / 255.), + uvec3= (1, 3, 1. / 255.), + uvec4= (1, 4, 1. / 255.), + quat_mat4=(4, 4, None), + quat_vec4=(1, 4, None), ) def _get_display_glsl_func(layout, field_name, field_type, is_array=False): - rows, cols, is_int = _TYPE_SPEC[field_type] + rows, cols, scale = _TYPE_SPEC[field_type] nb_comp = rows * cols tpl = _ARRAY_TPL if is_array else _SINGLE_TPL rect_tpl = _RECT_ARRAY_TPL if is_array else _RECT_SINGLE_TPL - amount_tpl = _COMMON_INT_TPL if is_int else _COMMON_FLT_TPL + amount_tpl = _COMMON_INT_TPL if scale is not None else _COMMON_FLT_TPL tpl_data = dict( colors_prefix='color_' if layout == 'uniform' else 'colors.', @@ -138,6 +139,9 @@ def _get_display_glsl_func(layout, field_name, field_type, is_array=False): if is_array: tpl_data['vec_field'] = '[i]' + tpl_data['vec_field'] + if scale: + tpl_data['scale'] = scale + tpl_data['comp_id'] = comp_id rect_lines.append(rect_tpl % tpl_data) amount_lines.append(amount_tpl % tpl_data) @@ -289,6 +293,7 @@ def _get_anim_kf(key_cls, data): array_vec2= lambda data: ngl.BufferVec2(data=data), array_vec3= lambda data: ngl.BufferVec3(data=data), array_vec4= lambda data: ngl.BufferVec4(data=data), + single_bool= lambda data: ngl.UniformBool(data), single_float= lambda data: ngl.UniformFloat(data), single_int= lambda data: ngl.UniformInt(data), single_ivec2= lambda data: ngl.UniformIVec2(data), diff --git a/tests/data.mak b/tests/data.mak index 34a8a6357f..6ba62871d4 100644 --- a/tests/data.mak +++ b/tests/data.mak @@ -20,6 +20,7 @@ # DATA_TEST_UNIFORM_NAMES = \ + single_bool \ single_float \ single_int \ single_ivec2 \ diff --git a/tests/data.py b/tests/data.py index 0468998f03..198c7ba9f5 100644 --- a/tests/data.py +++ b/tests/data.py @@ -54,6 +54,7 @@ def _get_data_spec(layout, i_count=6, f_count=7, v2_count=5, v3_count=9, v4_coun one_v3 = gen_floats(3) one_v4 = gen_floats(4) one_i = gen_ints(1)[0] + one_b = True one_iv2 = gen_ints(2) one_iv3 = gen_ints(3) one_iv4 = gen_ints(4) @@ -76,6 +77,7 @@ def _get_data_spec(layout, i_count=6, f_count=7, v2_count=5, v3_count=9, v4_coun spec = [] + spec += [dict(name=f'b_{i}', type='bool', category='single', data=one_b) for i in range(i_count)] spec += [dict(name=f'f_{i}', type='float', category='single', data=one_f) for i in range(f_count)] spec += [dict(name=f'v2_{i}', type='vec2', category='single', data=one_v2) for i in range(v2_count)] spec += [dict(name=f'v3_{i}', type='vec3', category='single', data=one_v3) for i in range(v3_count)] diff --git a/tests/live.mak b/tests/live.mak index f09189bbd4..81bc2e8781 100644 --- a/tests/live.mak +++ b/tests/live.mak @@ -20,6 +20,7 @@ # LIVE_TEST_BASE_NAMES = \ + single_bool \ single_float \ single_int \ single_mat4 \ diff --git a/tests/live.py b/tests/live.py index 0eb3adb6c1..9733a039ad 100644 --- a/tests/live.py +++ b/tests/live.py @@ -38,6 +38,7 @@ def _get_live_spec(layout): + livechange_b = [[True], [False]] livechange_f = [[v] for v in gen_floats(4)[1:3]] livechange_v2 = gen_floats(2), gen_floats(2)[::-1] livechange_v3 = gen_floats(3), gen_floats(3)[::-1] @@ -47,6 +48,7 @@ def _get_live_spec(layout): livechange_quat = livechange_v4 spec = [ + dict(name='b', type='bool', category='single', livechange=livechange_b), dict(name='f', type='float', category='single', livechange=livechange_f), dict(name='v2', type='vec2', category='single', livechange=livechange_v2), dict(name='v3', type='vec3', category='single', livechange=livechange_v3), diff --git a/tests/refs/data_single_bool_std140.ref b/tests/refs/data_single_bool_std140.ref new file mode 100644 index 0000000000..8d4ec7c127 --- /dev/null +++ b/tests/refs/data_single_bool_std140.ref @@ -0,0 +1 @@ +b_0_0:FFFFFFFF b_1_0:FFFFFFFF b_2_0:FFFFFFFF b_3_0:FFFFFFFF b_4_0:FFFFFFFF b_5_0:FFFFFFFF diff --git a/tests/refs/data_single_bool_std430.ref b/tests/refs/data_single_bool_std430.ref new file mode 100644 index 0000000000..8d4ec7c127 --- /dev/null +++ b/tests/refs/data_single_bool_std430.ref @@ -0,0 +1 @@ +b_0_0:FFFFFFFF b_1_0:FFFFFFFF b_2_0:FFFFFFFF b_3_0:FFFFFFFF b_4_0:FFFFFFFF b_5_0:FFFFFFFF diff --git a/tests/refs/data_single_bool_uniform.ref b/tests/refs/data_single_bool_uniform.ref new file mode 100644 index 0000000000..8d4ec7c127 --- /dev/null +++ b/tests/refs/data_single_bool_uniform.ref @@ -0,0 +1 @@ +b_0_0:FFFFFFFF b_1_0:FFFFFFFF b_2_0:FFFFFFFF b_3_0:FFFFFFFF b_4_0:FFFFFFFF b_5_0:FFFFFFFF diff --git a/tests/refs/live_single_bool_std140.ref b/tests/refs/live_single_bool_std140.ref new file mode 100644 index 0000000000..bfce3acf21 --- /dev/null +++ b/tests/refs/live_single_bool_std140.ref @@ -0,0 +1,3 @@ +b_0:000000FF +b_0:FFFFFFFF +b_0:000000FF diff --git a/tests/refs/live_single_bool_std430.ref b/tests/refs/live_single_bool_std430.ref new file mode 100644 index 0000000000..bfce3acf21 --- /dev/null +++ b/tests/refs/live_single_bool_std430.ref @@ -0,0 +1,3 @@ +b_0:000000FF +b_0:FFFFFFFF +b_0:000000FF diff --git a/tests/refs/live_single_bool_uniform.ref b/tests/refs/live_single_bool_uniform.ref new file mode 100644 index 0000000000..bfce3acf21 --- /dev/null +++ b/tests/refs/live_single_bool_uniform.ref @@ -0,0 +1,3 @@ +b_0:000000FF +b_0:FFFFFFFF +b_0:000000FF From bdde95bf832eea1f2fa8bdb7df6c0b3e5638fd60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 6 Oct 2020 17:47:05 +0200 Subject: [PATCH 148/388] utils/seekbar: remove now unused stop_button It was used by the player widget. --- pynodegl-utils/pynodegl_utils/ui/graph_view.py | 2 +- pynodegl-utils/pynodegl_utils/ui/seekbar.py | 13 +------------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/pynodegl-utils/pynodegl_utils/ui/graph_view.py b/pynodegl-utils/pynodegl_utils/ui/graph_view.py index 8c6348b401..8415fe0ea9 100644 --- a/pynodegl-utils/pynodegl_utils/ui/graph_view.py +++ b/pynodegl-utils/pynodegl_utils/ui/graph_view.py @@ -67,7 +67,7 @@ def __init__(self, get_scene_func, config): self._view.setScene(self._scene) self._seek_chkbox = QtWidgets.QCheckBox('Show graph at a given time') - self._seekbar = Seekbar(config, stop_button=False) + self._seekbar = Seekbar(config) self._seekbar.setEnabled(False) hbox = QtWidgets.QHBoxLayout() diff --git a/pynodegl-utils/pynodegl_utils/ui/seekbar.py b/pynodegl-utils/pynodegl_utils/ui/seekbar.py index 4f87339649..e200b25b01 100644 --- a/pynodegl-utils/pynodegl_utils/ui/seekbar.py +++ b/pynodegl-utils/pynodegl_utils/ui/seekbar.py @@ -31,20 +31,17 @@ class Seekbar(QtWidgets.QWidget): pause = QtCore.Signal() seek = QtCore.Signal(float) step = QtCore.Signal(int) - stop = QtCore.Signal() SLIDER_TIMEBASE = 1000 SLIDER_TIMESCALE = 1. / SLIDER_TIMEBASE - def __init__(self, config, stop_button=True): + def __init__(self, config): super().__init__() self._slider = QtWidgets.QSlider(QtCore.Qt.Horizontal) self._time_lbl = QtWidgets.QLabel() self._time_lbl.setFont(QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont)) - stop_btn = QtWidgets.QToolButton() - stop_btn.setText(u'■') self._action_btn = QtWidgets.QToolButton() self._action_btn.setText(u'▶') self._action_btn.setCheckable(True) @@ -56,8 +53,6 @@ def __init__(self, config, stop_button=True): layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) - if stop_button: - layout.addWidget(stop_btn) layout.addWidget(bw_btn) layout.addWidget(self._action_btn) layout.addWidget(fw_btn) @@ -74,7 +69,6 @@ def __init__(self, config, stop_button=True): self._slider.sliderReleased.connect(self._slider_released) self._slider_dragged = False - stop_btn.clicked.connect(self._stop) self._action_btn.clicked.connect(self._toggle_playback) fw_btn.clicked.connect(self._step_fw) bw_btn.clicked.connect(self._step_bw) @@ -108,11 +102,6 @@ def _toggle_playback(self): else: self.play.emit() - @QtCore.Slot() - def _stop(self): - self._set_action('pause') - self.stop.emit() - @QtCore.Slot() def _step_fw(self): self.step.emit(1) From c3a86b2e7e46afa1614124597d79409b2d043da9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 6 Oct 2020 17:54:23 +0200 Subject: [PATCH 149/388] utils/graph_view: remove playback mechanism While it makes sense to observe the resources of a tree at a given time, having a playback that run a subprocess 60 times per second is not exactly practicable. The feature was present because the feature was shared with the widget player, but the complexity added by this real time system is not worth the maintenance burden. --- pynodegl-utils/pynodegl_utils/clock.py | 36 ------------------- .../pynodegl_utils/ui/graph_view.py | 22 ------------ pynodegl-utils/pynodegl_utils/ui/seekbar.py | 31 ---------------- 3 files changed, 89 deletions(-) diff --git a/pynodegl-utils/pynodegl_utils/clock.py b/pynodegl-utils/pynodegl_utils/clock.py index 7fe13dd66f..1dd2477b1f 100644 --- a/pynodegl-utils/pynodegl_utils/clock.py +++ b/pynodegl-utils/pynodegl_utils/clock.py @@ -20,8 +20,6 @@ # under the License. # -import time - class Clock: @@ -29,57 +27,23 @@ class Clock: def __init__(self, framerate, duration): self._playback_index = 0 - self._running_time_offset = 0 - self._running = False self.configure(framerate, duration) def configure(self, framerate, duration): self._framerate = framerate self._duration = duration * self.TIMEBASE - self.reset_running_time() - - def start(self): - if self._running: - return - self.reset_running_time() - self._running = True - - def stop(self): - self._running = False - - def is_running(self): - return self._running - - def reset_running_time(self): - playback_time = self._playback_index * self.TIMEBASE * self._framerate[1] / self._framerate[0] - self._running_time_offset = int(time.time() * self.TIMEBASE) - playback_time def get_playback_time_info(self): - if not self._running: - playback_time = self._playback_index * self._framerate[1] / float(self._framerate[0]) - return (self._playback_index, playback_time) - - playback_time = int(time.time() * self.TIMEBASE) - self._running_time_offset - if playback_time < 0 or playback_time > self._duration: - self._playback_index = 0 - self.reset_running_time() - - self._playback_index = int(round( - playback_time * self._framerate[0] / float(self._framerate[1] * self.TIMEBASE) - )) playback_time = self._playback_index * self._framerate[1] / float(self._framerate[0]) - return (self._playback_index, playback_time) def set_playback_time(self, time): self._playback_index = int(round( time * self._framerate[0] / self._framerate[1] )) - self.reset_running_time() def step_playback_index(self, step): max_duration_index = int(round( self._duration * self._framerate[0] / float(self._framerate[1] * self.TIMEBASE) )) self._playback_index = min(max(self._playback_index + step, 0), max_duration_index) - self.reset_running_time() diff --git a/pynodegl-utils/pynodegl_utils/ui/graph_view.py b/pynodegl-utils/pynodegl_utils/ui/graph_view.py index 8415fe0ea9..4dd5032ab3 100644 --- a/pynodegl-utils/pynodegl_utils/ui/graph_view.py +++ b/pynodegl-utils/pynodegl_utils/ui/graph_view.py @@ -83,28 +83,11 @@ def __init__(self, get_scene_func, config): self._save_btn.clicked.connect(self._save_to_file) self._seek_chkbox.stateChanged.connect(self._seek_check_changed) - self._seekbar.play.connect(self._play) - self._seekbar.pause.connect(self._pause) self._seekbar.seek.connect(self._seek) self._seekbar.step.connect(self._step) - self._timer = QtCore.QTimer() - self._timer.timeout.connect(self._update) - self._clock = clock.Clock(self._framerate, 0.0) - @QtCore.Slot() - def _play(self): - self._timer.start() - self._clock.start() - self._seekbar.set_play_state() - - @QtCore.Slot() - def _pause(self): - self._timer.stop() - self._clock.stop() - self._seekbar.set_pause_state() - @QtCore.Slot(float) def _seek(self, time): self._clock.set_playback_time(time) @@ -113,7 +96,6 @@ def _seek(self, time): @QtCore.Slot(int) def _step(self, step): self._clock.step_playback_index(step) - self._pause() self._update() @QtCore.Slot() @@ -156,16 +138,12 @@ def enter(self): self._duration = cfg['duration'] self._ctx.set_scene_from_string(cfg['scene']) self._clock.configure(self._framerate, self._duration) - self._timer.setInterval(self._framerate[1] * 1000 / self._framerate[0]) # in milliseconds self._update() else: self._reset_ctx() dot_scene = cfg['scene'] self._update_graph(dot_scene) - def leave(self): - self._pause() - def _reset_ctx(self): if not self._ctx: return diff --git a/pynodegl-utils/pynodegl_utils/ui/seekbar.py b/pynodegl-utils/pynodegl_utils/ui/seekbar.py index e200b25b01..f08be6fa9b 100644 --- a/pynodegl-utils/pynodegl_utils/ui/seekbar.py +++ b/pynodegl-utils/pynodegl_utils/ui/seekbar.py @@ -27,8 +27,6 @@ class Seekbar(QtWidgets.QWidget): - play = QtCore.Signal() - pause = QtCore.Signal() seek = QtCore.Signal(float) step = QtCore.Signal(int) @@ -42,10 +40,6 @@ def __init__(self, config): self._time_lbl = QtWidgets.QLabel() self._time_lbl.setFont(QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont)) - self._action_btn = QtWidgets.QToolButton() - self._action_btn.setText(u'▶') - self._action_btn.setCheckable(True) - fw_btn = QtWidgets.QToolButton() fw_btn.setText('>') bw_btn = QtWidgets.QToolButton() @@ -54,7 +48,6 @@ def __init__(self, config): layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(bw_btn) - layout.addWidget(self._action_btn) layout.addWidget(fw_btn) layout.addWidget(self._slider) layout.addWidget(self._time_lbl) @@ -62,24 +55,15 @@ def __init__(self, config): self._frame_index = 0 self._scene_duration = 0 self._framerate = Fraction(*config.get('framerate')) - self._set_action('pause') self._slider.sliderMoved.connect(self._slider_moved) self._slider.sliderPressed.connect(self._slider_pressed) self._slider.sliderReleased.connect(self._slider_released) self._slider_dragged = False - self._action_btn.clicked.connect(self._toggle_playback) fw_btn.clicked.connect(self._step_fw) bw_btn.clicked.connect(self._step_bw) - def _set_action(self, action): - if action == 'play': - self._action_btn.setChecked(True) - elif action == 'pause': - self._action_btn.setChecked(False) - self._current_state = action - @QtCore.Slot(int) def _slider_moved(self, value): # only user move if not self._scene_duration: @@ -95,13 +79,6 @@ def _slider_released(self): self._slider_dragged = False self._refresh() - @QtCore.Slot() - def _toggle_playback(self): - if self._current_state == 'play': - self.pause.emit() - else: - self.play.emit() - @QtCore.Slot() def _step_fw(self): self.step.emit(1) @@ -142,14 +119,6 @@ def set_frame_time(self, frame_index, frame_time): self._frame_index = frame_index self._refresh() - @QtCore.Slot() - def set_play_state(self): - self._set_action('play') - - @QtCore.Slot() - def set_pause_state(self): - self._set_action('pause') - def _refresh(self): t = self._frame_index / self._framerate text = self._get_time_lbl_text(self._frame_index, t) From b7ec34c718f066e164886956b81830b49306768c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 8 Oct 2020 10:10:22 +0200 Subject: [PATCH 150/388] utils/clock: inline it in graph_view It's a now fake clock that shouldn't be needed outside of the graph view. The class should be inlined within the GraphView one at some point, or at least renamed in the future. --- pynodegl-utils/pynodegl_utils/clock.py | 49 ------------------- .../pynodegl_utils/ui/graph_view.py | 31 +++++++++++- 2 files changed, 29 insertions(+), 51 deletions(-) delete mode 100644 pynodegl-utils/pynodegl_utils/clock.py diff --git a/pynodegl-utils/pynodegl_utils/clock.py b/pynodegl-utils/pynodegl_utils/clock.py deleted file mode 100644 index 1dd2477b1f..0000000000 --- a/pynodegl-utils/pynodegl_utils/clock.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2018 GoPro Inc. -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - - -class Clock: - - TIMEBASE = 1000000000 # nanoseconds - - def __init__(self, framerate, duration): - self._playback_index = 0 - self.configure(framerate, duration) - - def configure(self, framerate, duration): - self._framerate = framerate - self._duration = duration * self.TIMEBASE - - def get_playback_time_info(self): - playback_time = self._playback_index * self._framerate[1] / float(self._framerate[0]) - return (self._playback_index, playback_time) - - def set_playback_time(self, time): - self._playback_index = int(round( - time * self._framerate[0] / self._framerate[1] - )) - - def step_playback_index(self, step): - max_duration_index = int(round( - self._duration * self._framerate[0] / float(self._framerate[1] * self.TIMEBASE) - )) - self._playback_index = min(max(self._playback_index + step, 0), max_duration_index) diff --git a/pynodegl-utils/pynodegl_utils/ui/graph_view.py b/pynodegl-utils/pynodegl_utils/ui/graph_view.py index 4dd5032ab3..8dda108e54 100644 --- a/pynodegl-utils/pynodegl_utils/ui/graph_view.py +++ b/pynodegl-utils/pynodegl_utils/ui/graph_view.py @@ -27,7 +27,6 @@ from .seekbar import Seekbar -from pynodegl_utils import clock from pynodegl_utils import misc import pynodegl as ngl @@ -49,6 +48,34 @@ def wheelEvent(self, event): self.setTransform(m) +class _Clock: + + TIMEBASE = 1000000000 # nanoseconds + + def __init__(self, framerate, duration): + self._playback_index = 0 + self.configure(framerate, duration) + + def configure(self, framerate, duration): + self._framerate = framerate + self._duration = duration * self.TIMEBASE + + def get_playback_time_info(self): + playback_time = self._playback_index * self._framerate[1] / float(self._framerate[0]) + return (self._playback_index, playback_time) + + def set_playback_time(self, time): + self._playback_index = int(round( + time * self._framerate[0] / self._framerate[1] + )) + + def step_playback_index(self, step): + max_duration_index = int(round( + self._duration * self._framerate[0] / float(self._framerate[1] * self.TIMEBASE) + )) + self._playback_index = min(max(self._playback_index + step, 0), max_duration_index) + + class GraphView(QtWidgets.QWidget): def __init__(self, get_scene_func, config): @@ -86,7 +113,7 @@ def __init__(self, get_scene_func, config): self._seekbar.seek.connect(self._seek) self._seekbar.step.connect(self._step) - self._clock = clock.Clock(self._framerate, 0.0) + self._clock = _Clock(self._framerate, 0.0) @QtCore.Slot(float) def _seek(self, time): From aaf1a64f57019fff9680cb88a7e4a196336e24d4 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 7 Oct 2020 13:05:52 +0200 Subject: [PATCH 151/388] pass: add curly braces around pipeline type condition block --- libnodegl/pass.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libnodegl/pass.c b/libnodegl/pass.c index 7fc4276756..aed27d97ea 100644 --- a/libnodegl/pass.c +++ b/libnodegl/pass.c @@ -693,13 +693,14 @@ int ngli_pass_exec(struct pass *s) ngli_pipeline_update_uniform(pipeline, fields[NGLI_INFO_FIELD_SAMPLING_MODE].index, &layout); } - if (s->pipeline_type == NGLI_PIPELINE_TYPE_GRAPHICS) + if (s->pipeline_type == NGLI_PIPELINE_TYPE_GRAPHICS) { if (s->indices_buffer) ngli_pipeline_draw_indexed(pipeline, s->indices_buffer, s->indices_format, s->nb_indices, s->nb_instances); else ngli_pipeline_draw(pipeline, s->nb_vertices, s->nb_instances); - else + } else { ngli_pipeline_dispatch(pipeline, params->nb_group_x, params->nb_group_y, params->nb_group_z); + } return 0; } From 7f8a6f888fb0e6f464a179f4c42c755b60da5656 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 7 Oct 2020 11:31:31 +0200 Subject: [PATCH 152/388] rtt: move the rendertarget binding from rtt_draw() to ngli_pass_exec() Previously we were binding (and clearing) the rendertarget before drawing the subgraph. This leads to the folowing scenario (with performance issues): (RTT 1) / \ / \ / \ (RTT 2) (Render) Where we: - started the renderpass of RTT 1 with a clear - started the renderpass of RTT 2 with a clear - resumed the renderpass of RTT 1 with a draw call, which has performance implications because the renderpass needs to reload the previous content of its attachments This commit addresses this issue by binding/clearing at the right time. The same scenario now perform the following steps: - starts the renderpass of RTT 2 - starts the renderpass of RTT 1, clear attachments and perform a draw call --- libnodegl/gctx.c | 4 ++++ libnodegl/node_rtt.c | 31 +++++++++++++++++++++---------- libnodegl/nodes.h | 3 +++ libnodegl/pass.c | 11 +++++++++++ 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/libnodegl/gctx.c b/libnodegl/gctx.c index 1743a4a1f5..f34ab52bf7 100644 --- a/libnodegl/gctx.c +++ b/libnodegl/gctx.c @@ -74,6 +74,10 @@ int ngli_gctx_draw(struct gctx *s, struct ngl_node *scene, double t) goto end; if (scene) { + struct ngl_ctx *ctx = scene->ctx; + ctx->current_rendertarget = ngli_gctx_get_rendertarget(s); + ctx->bind_current_rendertarget = 0; + ctx->clear_current_rendertarget = 0; LOG(DEBUG, "draw scene %s @ t=%f", scene->label, t); ngli_node_draw(scene); } diff --git a/libnodegl/node_rtt.c b/libnodegl/node_rtt.c index 474992c7f7..54bfddcea4 100644 --- a/libnodegl/node_rtt.c +++ b/libnodegl/node_rtt.c @@ -349,10 +349,6 @@ static void rtt_draw(struct ngl_node *node) struct gctx *gctx = ctx->gctx; struct rtt_priv *s = node->priv_data; - struct rendertarget *rt = s->rt; - struct rendertarget *prev_rt = ngli_gctx_get_rendertarget(gctx); - ngli_gctx_set_rendertarget(gctx, rt); - int prev_vp[4] = {0}; ngli_gctx_get_viewport(gctx, prev_vp); @@ -365,20 +361,35 @@ static void rtt_draw(struct ngl_node *node) ngli_gctx_set_clear_color(gctx, s->clear_color); } - if (!(s->features & FEATURE_NO_CLEAR)) { - ngli_gctx_clear_color(gctx); - ngli_gctx_clear_depth_stencil(gctx); - } + struct rendertarget *prev_rendertarget = ctx->current_rendertarget; + int prev_clear = ctx->clear_current_rendertarget; + + ctx->current_rendertarget = s->rt; + ctx->bind_current_rendertarget = 1; + ctx->clear_current_rendertarget = !(s->features & FEATURE_NO_CLEAR); ngli_node_draw(s->child); + if (ctx->bind_current_rendertarget) { + ngli_gctx_set_rendertarget(gctx, ctx->current_rendertarget); + if (ctx->clear_current_rendertarget) { + ngli_gctx_clear_color(gctx); + ngli_gctx_clear_depth_stencil(gctx); + } + ctx->bind_current_rendertarget = 0; + ctx->clear_current_rendertarget = 0; + } + if (s->samples > 0) - ngli_rendertarget_resolve(rt); + ngli_rendertarget_resolve(ctx->current_rendertarget); if (s->invalidate_depth_stencil) ngli_gctx_invalidate_depth_stencil(gctx); - ngli_gctx_set_rendertarget(gctx, prev_rt); + ctx->current_rendertarget = prev_rendertarget; + ctx->bind_current_rendertarget = 1; + ctx->clear_current_rendertarget = prev_clear; + ngli_gctx_set_viewport(gctx, prev_vp); if (s->use_clear_color) diff --git a/libnodegl/nodes.h b/libnodegl/nodes.h index 310ff080e5..4847f49481 100644 --- a/libnodegl/nodes.h +++ b/libnodegl/nodes.h @@ -83,6 +83,9 @@ struct ngl_ctx { struct rnode *rnode_pos; struct ngl_node *scene; struct ngl_config config; + struct rendertarget *current_rendertarget; + int bind_current_rendertarget; + int clear_current_rendertarget; struct darray modelview_matrix_stack; struct darray projection_matrix_stack; struct darray activitycheck_nodes; diff --git a/libnodegl/pass.c b/libnodegl/pass.c index aed27d97ea..f45f3a4cac 100644 --- a/libnodegl/pass.c +++ b/libnodegl/pass.c @@ -694,6 +694,17 @@ int ngli_pass_exec(struct pass *s) } if (s->pipeline_type == NGLI_PIPELINE_TYPE_GRAPHICS) { + if (ctx->bind_current_rendertarget) { + struct gctx *gctx = ctx->gctx; + ngli_gctx_set_rendertarget(gctx, ctx->current_rendertarget); + if (ctx->clear_current_rendertarget) { + ngli_gctx_clear_color(gctx); + ngli_gctx_clear_depth_stencil(gctx); + } + ctx->bind_current_rendertarget = 0; + ctx->clear_current_rendertarget = 0; + } + if (s->indices_buffer) ngli_pipeline_draw_indexed(pipeline, s->indices_buffer, s->indices_format, s->nb_indices, s->nb_instances); else From 7f2887527560e002c3f822b1244504e390a46d68 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 7 Oct 2020 14:19:37 +0200 Subject: [PATCH 153/388] build: add DEBUG_SCENE parameter --- libnodegl/Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libnodegl/Makefile b/libnodegl/Makefile index 0c499957a4..2806f851f6 100644 --- a/libnodegl/Makefile +++ b/libnodegl/Makefile @@ -38,6 +38,10 @@ ifeq ($(DEBUG_MEM),yes) PROJECT_CFLAGS += -DDEBUG_MEM endif +ifeq ($(DEBUG_SCENE),yes) + PROJECT_CFLAGS += -DDEBUG_SCENE +endif + LD_SYM_FILE = $(LIB_BASENAME).symexport LD_SYM_OPTION = --version-script LD_SYM_DATA = "{\n\tglobal: ngl_*;\n\tlocal: *;\n};\n" From 8a9fcdbdfd2ae41e7c28526911c68dacad233773 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 6 Oct 2020 17:59:56 +0200 Subject: [PATCH 154/388] rtt: detect sub optimal render passes --- libnodegl/node_rtt.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/libnodegl/node_rtt.c b/libnodegl/node_rtt.c index 54bfddcea4..3244afdc28 100644 --- a/libnodegl/node_rtt.c +++ b/libnodegl/node_rtt.c @@ -119,6 +119,29 @@ static int rtt_init(struct ngl_node *node) return 0; } +#ifdef DEBUG_SCENE +struct renderpass_children_info { + int has_rtt_or_compute; + int render_counts[2]; // number of render nodes before and after the first rtt or compute node (renderpass interruption) +}; + +static void get_renderpass_children_info(const struct ngl_node *node, struct renderpass_children_info *info) +{ + const struct ngl_node **children = ngli_darray_data(&node->children); + for (int i = 0; i < ngli_darray_count(&node->children); i++) { + const struct ngl_node *child = children[i]; + if (child->class->id == NGL_NODE_RENDERTOTEXTURE || + child->class->id == NGL_NODE_COMPUTE) { + info->has_rtt_or_compute = 1; + } else if (child->class->id == NGL_NODE_RENDER) { + info->render_counts[info->has_rtt_or_compute ? 1 : 0] += 1; + } else { + get_renderpass_children_info(child, info); + } + } +} +#endif + static int rtt_prepare(struct ngl_node *node) { struct ngl_ctx *ctx = node->ctx; @@ -126,6 +149,13 @@ static int rtt_prepare(struct ngl_node *node) struct rnode *rnode = ctx->rnode_pos; struct rtt_priv *s = node->priv_data; +#ifdef DEBUG_SCENE + struct renderpass_children_info info = {0}; + get_renderpass_children_info(s->child, &info); + if (info.render_counts[0] && info.render_counts[1]) + LOG(WARNING, "the underlying render pass might not be optimal as it contains a rtt or compute node in the middle of it"); +#endif + struct rendertarget_desc desc = {0}; for (int i = 0; i < s->nb_color_textures; i++) { const struct texture_priv *texture_priv = s->color_textures[i]->priv_data; From c4f812a5a03823992430bff3fe77c7ef0488c39d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 12 Oct 2020 15:25:04 +0200 Subject: [PATCH 155/388] media: prevent Media nodes from being shared --- libnodegl/node_media.c | 16 ++++++++++++++++ libnodegl/nodes.h | 1 + tests/api.mak | 1 + tests/api.py | 16 ++++++++++++++++ 4 files changed, 34 insertions(+) diff --git a/libnodegl/node_media.c b/libnodegl/node_media.c index 90fffa2241..fffa6f179c 100644 --- a/libnodegl/node_media.c +++ b/libnodegl/node_media.c @@ -213,6 +213,21 @@ static int media_init(struct ngl_node *node) return 0; } +static int media_prepare(struct ngl_node *node) +{ + struct media_priv *s = node->priv_data; + if (s->nb_parents++) { + /* + * On Android, the frame can only be uploaded once and each subsequent + * upload will be a noop which results in an empty texture. This + * limitation prevents us from sharing the Media node. + */ + LOG(ERROR, "Media nodes can not be shared, the Texture should be shared instead"); + return NGL_ERROR_INVALID_USAGE; + } + return 0; +} + static int media_prefetch(struct ngl_node *node) { struct media_priv *s = node->priv_data; @@ -310,6 +325,7 @@ const struct node_class ngli_media_class = { .id = NGL_NODE_MEDIA, .name = "Media", .init = media_init, + .prepare = media_prepare, .prefetch = media_prefetch, .update = media_update, .release = media_release, diff --git a/libnodegl/nodes.h b/libnodegl/nodes.h index 4847f49481..488a57a932 100644 --- a/libnodegl/nodes.h +++ b/libnodegl/nodes.h @@ -306,6 +306,7 @@ struct media_priv { struct sxplayer_ctx *player; struct sxplayer_frame *frame; + int nb_parents; #if defined(TARGET_ANDROID) struct texture *android_texture; diff --git a/tests/api.mak b/tests/api.mak index c48c48bf4a..084eb4ae07 100644 --- a/tests/api.mak +++ b/tests/api.mak @@ -29,5 +29,6 @@ API_TEST_NAMES = \ capture_buffer_lifetime \ hud \ text_live_change \ + media_sharing_failure \ $(eval $(call DECLARE_SIMPLE_TESTS,api,$(API_TEST_NAMES))) diff --git a/tests/api.py b/tests/api.py index a680002496..26f8598399 100644 --- a/tests/api.py +++ b/tests/api.py @@ -167,3 +167,19 @@ def api_text_live_change(width=320, height=240): crc = zlib.crc32(capture_buffer) assert crc != last_crc last_crc = crc + + +def _ret_to_fourcc(ret): + if ret >= 0: + return None + x = -ret + return chr(x>>24) + chr(x>>16 & 0xff) + chr(x>>8 & 0xff) + chr(x&0xff) + + +def api_media_sharing_failure(): + import struct + ctx = ngl.Context() + assert ctx.configure(offscreen=1, width=16, height=16, backend=_backend) == 0 + m = ngl.Media('/dev/null') + scene = ngl.Group(children=(m, m)) + assert _ret_to_fourcc(ctx.set_scene(scene)) == 'Eusg' # Usage error From d977f9259b21a1fdc84d557d5458fc5506207709 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 1 Oct 2020 14:45:49 +0200 Subject: [PATCH 156/388] pipeline_gl: use ngli_darray_get() in update functions --- libnodegl/pipeline_gl.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/libnodegl/pipeline_gl.c b/libnodegl/pipeline_gl.c index fb4e544a2d..d2c22964cf 100644 --- a/libnodegl/pipeline_gl.c +++ b/libnodegl/pipeline_gl.c @@ -490,10 +490,10 @@ int ngli_pipeline_gl_update_attribute(struct pipeline *s, int index, struct buff return NGL_ERROR_NOT_FOUND; ngli_assert(s->type == NGLI_PIPELINE_TYPE_GRAPHICS); - ngli_assert(index >= 0 && index < ngli_darray_count(&s->attribute_descs)); - struct attribute_desc *descs = ngli_darray_data(&s->attribute_descs); - struct attribute_desc *desc = &descs[index]; + struct attribute_desc *desc = ngli_darray_get(&s->attribute_descs, index); + ngli_assert(desc); + struct pipeline_attribute *attribute = &desc->attribute; if (!attribute->buffer && buffer) @@ -524,9 +524,9 @@ int ngli_pipeline_gl_update_uniform(struct pipeline *s, int index, const void *d if (index == -1) return NGL_ERROR_NOT_FOUND; - ngli_assert(index >= 0 && index < ngli_darray_count(&s->uniform_descs)); - struct uniform_desc *descs = ngli_darray_data(&s->uniform_descs); - struct uniform_desc *desc = &descs[index]; + struct uniform_desc *desc = ngli_darray_get(&s->uniform_descs, index); + ngli_assert(desc); + struct pipeline_uniform *pipeline_uniform = &desc->uniform; if (data) { struct gctx *gctx = s->gctx; @@ -546,9 +546,10 @@ int ngli_pipeline_gl_update_texture(struct pipeline *s, int index, struct textur if (index == -1) return NGL_ERROR_NOT_FOUND; - ngli_assert(index >= 0 && index < ngli_darray_count(&s->texture_descs)); - struct texture_desc *descs = ngli_darray_data(&s->texture_descs); - struct pipeline_texture *pipeline_texture = &descs[index].texture; + struct texture_desc *desc = ngli_darray_get(&s->texture_descs, index); + ngli_assert(desc); + + struct pipeline_texture *pipeline_texture = &desc->texture; pipeline_texture->texture = texture; return 0; From 452672b02a48211f4e2ff3ce7ba4cb9b92b64f05 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 1 Oct 2020 14:47:21 +0200 Subject: [PATCH 157/388] pipeline_gl: prefix desc local variables in update functions --- libnodegl/pipeline_gl.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/libnodegl/pipeline_gl.c b/libnodegl/pipeline_gl.c index d2c22964cf..ab5ae0c1c7 100644 --- a/libnodegl/pipeline_gl.c +++ b/libnodegl/pipeline_gl.c @@ -491,10 +491,10 @@ int ngli_pipeline_gl_update_attribute(struct pipeline *s, int index, struct buff ngli_assert(s->type == NGLI_PIPELINE_TYPE_GRAPHICS); - struct attribute_desc *desc = ngli_darray_get(&s->attribute_descs, index); - ngli_assert(desc); + struct attribute_desc *attr_desc = ngli_darray_get(&s->attribute_descs, index); + ngli_assert(attr_desc); - struct pipeline_attribute *attribute = &desc->attribute; + struct pipeline_attribute *attribute = &attr_desc->attribute; if (!attribute->buffer && buffer) s->nb_unbound_attributes--; @@ -524,17 +524,17 @@ int ngli_pipeline_gl_update_uniform(struct pipeline *s, int index, const void *d if (index == -1) return NGL_ERROR_NOT_FOUND; - struct uniform_desc *desc = ngli_darray_get(&s->uniform_descs, index); - ngli_assert(desc); + struct uniform_desc *uniform_desc = ngli_darray_get(&s->uniform_descs, index); + ngli_assert(uniform_desc); - struct pipeline_uniform *pipeline_uniform = &desc->uniform; + struct pipeline_uniform *pipeline_uniform = &uniform_desc->uniform; if (data) { struct gctx *gctx = s->gctx; struct gctx_gl *gctx_gl = (struct gctx_gl *)gctx; struct glcontext *gl = gctx_gl->glcontext; struct program_gl *program_gl = (struct program_gl *)s->program; ngli_glstate_use_program(gctx, program_gl->id); - desc->set(gl, desc->location, pipeline_uniform->count, data); + uniform_desc->set(gl, uniform_desc->location, pipeline_uniform->count, data); } pipeline_uniform->data = NULL; @@ -546,10 +546,10 @@ int ngli_pipeline_gl_update_texture(struct pipeline *s, int index, struct textur if (index == -1) return NGL_ERROR_NOT_FOUND; - struct texture_desc *desc = ngli_darray_get(&s->texture_descs, index); - ngli_assert(desc); + struct texture_desc *texture_desc = ngli_darray_get(&s->texture_descs, index); + ngli_assert(texture_desc); - struct pipeline_texture *pipeline_texture = &desc->texture; + struct pipeline_texture *pipeline_texture = &texture_desc->texture; pipeline_texture->texture = texture; return 0; From 13c2e3cf0e7a0ff353fb9a26df59c6920dec1e42 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 1 Oct 2020 15:56:48 +0200 Subject: [PATCH 158/388] pipeline: add ngli_pipeline_update_buffer() --- libnodegl/gctx.h | 1 + libnodegl/gctx_gl.c | 2 ++ libnodegl/pipeline.c | 5 +++++ libnodegl/pipeline.h | 1 + libnodegl/pipeline_gl.c | 27 +++++++++++++++++++++++++++ libnodegl/pipeline_gl.h | 1 + 6 files changed, 37 insertions(+) diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index 12dfc005ac..7ba85bff5b 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -77,6 +77,7 @@ struct gctx_class { int (*pipeline_update_attribute)(struct pipeline *s, int index, struct buffer *buffer); int (*pipeline_update_uniform)(struct pipeline *s, int index, const void *value); int (*pipeline_update_texture)(struct pipeline *s, int index, struct texture *texture); + int (*pipeline_update_buffer)(struct pipeline *s, int index, struct buffer *buffer); void (*pipeline_draw)(struct pipeline *s, int nb_vertices, int nb_instances); void (*pipeline_draw_indexed)(struct pipeline *s, struct buffer *indices, int indices_format, int nb_indices, int nb_instances); void (*pipeline_dispatch)(struct pipeline *s, int nb_group_x, int nb_group_y, int nb_group_z); diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 55789d08f1..93f6f8b455 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -560,6 +560,7 @@ const struct gctx_class ngli_gctx_gl = { .pipeline_update_attribute = ngli_pipeline_gl_update_attribute, .pipeline_update_uniform = ngli_pipeline_gl_update_uniform, .pipeline_update_texture = ngli_pipeline_gl_update_texture, + .pipeline_update_buffer = ngli_pipeline_gl_update_buffer, .pipeline_draw = ngli_pipeline_gl_draw, .pipeline_draw_indexed = ngli_pipeline_gl_draw_indexed, .pipeline_dispatch = ngli_pipeline_gl_dispatch, @@ -629,6 +630,7 @@ const struct gctx_class ngli_gctx_gles = { .pipeline_update_attribute = ngli_pipeline_gl_update_attribute, .pipeline_update_uniform = ngli_pipeline_gl_update_uniform, .pipeline_update_texture = ngli_pipeline_gl_update_texture, + .pipeline_update_buffer = ngli_pipeline_gl_update_buffer, .pipeline_draw = ngli_pipeline_gl_draw, .pipeline_draw_indexed = ngli_pipeline_gl_draw_indexed, .pipeline_dispatch = ngli_pipeline_gl_dispatch, diff --git a/libnodegl/pipeline.c b/libnodegl/pipeline.c index b223ac3fef..fed3d3b3f6 100644 --- a/libnodegl/pipeline.c +++ b/libnodegl/pipeline.c @@ -48,6 +48,11 @@ int ngli_pipeline_update_texture(struct pipeline *s, int index, struct texture * return s->gctx->class->pipeline_update_texture(s, index, texture); } +int ngli_pipeline_update_buffer(struct pipeline *s, int index, struct buffer *buffer) +{ + return s->gctx->class->pipeline_update_buffer(s, index, buffer); +} + void ngli_pipeline_draw(struct pipeline *s, int nb_vertices, int nb_instances) { return s->gctx->class->pipeline_draw(s, nb_vertices, nb_instances); diff --git a/libnodegl/pipeline.h b/libnodegl/pipeline.h index 2ffc09aefb..1bda1f8463 100644 --- a/libnodegl/pipeline.h +++ b/libnodegl/pipeline.h @@ -108,6 +108,7 @@ int ngli_pipeline_init(struct pipeline *s, const struct pipeline_params *params) int ngli_pipeline_update_attribute(struct pipeline *s, int index, struct buffer *buffer); int ngli_pipeline_update_uniform(struct pipeline *s, int index, const void *value); int ngli_pipeline_update_texture(struct pipeline *s, int index, struct texture *texture); +int ngli_pipeline_update_buffer(struct pipeline *s, int index, struct buffer *buffer); void ngli_pipeline_draw(struct pipeline *s, int nb_vertices, int nb_instances); void ngli_pipeline_draw_indexed(struct pipeline *s, struct buffer *indices, int indices_format, int nb_indices, int nb_instances); void ngli_pipeline_dispatch(struct pipeline *s, int nb_group_x, int nb_group_y, int nb_group_z); diff --git a/libnodegl/pipeline_gl.c b/libnodegl/pipeline_gl.c index ab5ae0c1c7..ab76e9a227 100644 --- a/libnodegl/pipeline_gl.c +++ b/libnodegl/pipeline_gl.c @@ -555,6 +555,33 @@ int ngli_pipeline_gl_update_texture(struct pipeline *s, int index, struct textur return 0; } +int ngli_pipeline_gl_update_buffer(struct pipeline *s, int index, struct buffer *buffer) +{ + if (index == -1) + return NGL_ERROR_NOT_FOUND; + + struct buffer_desc *buffer_desc = ngli_darray_get(&s->buffer_descs, index); + ngli_assert(buffer_desc); + + struct pipeline_buffer *pipeline_buffer = &buffer_desc->buffer; + + if (buffer) { + struct gctx_gl *gctx_gl = (struct gctx_gl *)s->gctx; + struct glcontext *gl = gctx_gl->glcontext; + const struct limits *limits = &gl->limits; + if (buffer_desc->type == NGLI_TYPE_UNIFORM_BUFFER && + buffer->size > limits->max_uniform_block_size) { + LOG(ERROR, "buffer %s size (%d) exceeds max uniform block size (%d)", + pipeline_buffer->name, buffer->size, limits->max_uniform_block_size); + return NGL_ERROR_LIMIT_EXCEEDED; + } + } + + pipeline_buffer->buffer = buffer; + + return 0; +} + void ngli_pipeline_gl_draw(struct pipeline *s, int nb_vertices, int nb_instances) { struct gctx *gctx = s->gctx; diff --git a/libnodegl/pipeline_gl.h b/libnodegl/pipeline_gl.h index a800e2de6e..d68a0d75ce 100644 --- a/libnodegl/pipeline_gl.h +++ b/libnodegl/pipeline_gl.h @@ -40,6 +40,7 @@ int ngli_pipeline_gl_init(struct pipeline *s, const struct pipeline_params *para int ngli_pipeline_gl_update_attribute(struct pipeline *s, int index, struct buffer *buffer); int ngli_pipeline_gl_update_uniform(struct pipeline *s, int index, const void *value); int ngli_pipeline_gl_update_texture(struct pipeline *s, int index, struct texture *texture); +int ngli_pipeline_gl_update_buffer(struct pipeline *s, int index, struct buffer *buffer); void ngli_pipeline_gl_draw(struct pipeline *s, int nb_vertices, int nb_instances); void ngli_pipeline_gl_draw_indexed(struct pipeline *s, struct buffer *indices, int indices_format, int nb_indices, int nb_instances); void ngli_pipeline_gl_dispatch(struct pipeline *s, int nb_group_x, int nb_group_y, int nb_group_z); From 205bbd3437666aecb75355ac44aca852ada596dd Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Wed, 30 Sep 2020 18:23:58 -0700 Subject: [PATCH 159/388] pipeline: decouple resources from pipeline initialization The pipeline API should only be about creating and binding a pipeline. Resources binding and draw calls (draw, draw indexed, dispatch) should belong to separate modules. This commit is a first step in this direction as it decouples the graphics resources binding from the pipeline initialization. Signed-off-by: Matthieu Bouron --- libnodegl/gctx.h | 1 + libnodegl/gctx_gl.c | 2 + libnodegl/hwconv.c | 7 +- libnodegl/node_hud.c | 7 +- libnodegl/node_text.c | 11 ++- libnodegl/pass.c | 7 +- libnodegl/pgcraft.c | 209 +++++++++++++++++++++++----------------- libnodegl/pgcraft.h | 29 ++++-- libnodegl/pipeline.c | 5 + libnodegl/pipeline.h | 32 +++--- libnodegl/pipeline_gl.c | 195 +++++++++++++++++++++---------------- libnodegl/pipeline_gl.h | 1 + 12 files changed, 306 insertions(+), 200 deletions(-) diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index 7ba85bff5b..cdeeb6f47a 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -74,6 +74,7 @@ struct gctx_class { struct pipeline *(*pipeline_create)(struct gctx *ctx); int (*pipeline_init)(struct pipeline *s, const struct pipeline_params *params); + int (*pipeline_set_resources)(struct pipeline *s, const struct pipeline_resource_params *data_params); int (*pipeline_update_attribute)(struct pipeline *s, int index, struct buffer *buffer); int (*pipeline_update_uniform)(struct pipeline *s, int index, const void *value); int (*pipeline_update_texture)(struct pipeline *s, int index, struct texture *texture); diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 93f6f8b455..97f50223d8 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -557,6 +557,7 @@ const struct gctx_class ngli_gctx_gl = { .pipeline_create = ngli_pipeline_gl_create, .pipeline_init = ngli_pipeline_gl_init, + .pipeline_set_resources = ngli_pipeline_gl_set_resources, .pipeline_update_attribute = ngli_pipeline_gl_update_attribute, .pipeline_update_uniform = ngli_pipeline_gl_update_uniform, .pipeline_update_texture = ngli_pipeline_gl_update_texture, @@ -627,6 +628,7 @@ const struct gctx_class ngli_gctx_gles = { .pipeline_create = ngli_pipeline_gl_create, .pipeline_init = ngli_pipeline_gl_init, + .pipeline_set_resources = ngli_pipeline_gl_set_resources, .pipeline_update_attribute = ngli_pipeline_gl_update_attribute, .pipeline_update_uniform = ngli_pipeline_gl_update_uniform, .pipeline_update_texture = ngli_pipeline_gl_update_texture, diff --git a/libnodegl/hwconv.c b/libnodegl/hwconv.c index 0489dada16..037190b0e7 100644 --- a/libnodegl/hwconv.c +++ b/libnodegl/hwconv.c @@ -149,7 +149,8 @@ int ngli_hwconv_init(struct hwconv *hwconv, struct ngl_ctx *ctx, if (!hwconv->crafter) return NGL_ERROR_MEMORY; - ret = ngli_pgcraft_craft(hwconv->crafter, &pipeline_params, &crafter_params); + struct pipeline_resource_params pipeline_resource_params = {0}; + ret = ngli_pgcraft_craft(hwconv->crafter, &pipeline_params, &pipeline_resource_params, &crafter_params); if (ret < 0) return ret; @@ -161,6 +162,10 @@ int ngli_hwconv_init(struct hwconv *hwconv, struct ngl_ctx *ctx, if (ret < 0) return ret; + ret = ngli_pipeline_set_resources(hwconv->pipeline, &pipeline_resource_params); + if (ret < 0) + return ret; + return 0; } diff --git a/libnodegl/node_hud.c b/libnodegl/node_hud.c index f2a1214e5b..6718a98110 100644 --- a/libnodegl/node_hud.c +++ b/libnodegl/node_hud.c @@ -1341,7 +1341,8 @@ static int hud_init(struct ngl_node *node) if (!s->crafter) return NGL_ERROR_MEMORY; - ret = ngli_pgcraft_craft(s->crafter, &pipeline_params, &crafter_params); + struct pipeline_resource_params pipeline_resource_params = {0}; + ret = ngli_pgcraft_craft(s->crafter, &pipeline_params, &pipeline_resource_params, &crafter_params); if (ret < 0) return ret; @@ -1353,6 +1354,10 @@ static int hud_init(struct ngl_node *node) if (ret < 0) return ret; + ret = ngli_pipeline_set_resources(s->pipeline, &pipeline_resource_params); + if (ret < 0) + return ret; + s->modelview_matrix_index = ngli_pgcraft_get_uniform_index(s->crafter, "modelview_matrix", NGLI_PROGRAM_SHADER_VERT); s->projection_matrix_index = ngli_pgcraft_get_uniform_index(s->crafter, "projection_matrix", NGLI_PROGRAM_SHADER_VERT); diff --git a/libnodegl/node_text.c b/libnodegl/node_text.c index 8157a52533..e72ce294bd 100644 --- a/libnodegl/node_text.c +++ b/libnodegl/node_text.c @@ -483,7 +483,8 @@ static int init_subdesc(struct ngl_node *node, if (!desc->crafter) return NGL_ERROR_MEMORY; - int ret = ngli_pgcraft_craft(desc->crafter, pipeline_params, crafter_params); + struct pipeline_resource_params pipeline_resource_params = {0}; + int ret = ngli_pgcraft_craft(desc->crafter, pipeline_params, &pipeline_resource_params, crafter_params); if (ret < 0) return ret; @@ -495,6 +496,10 @@ static int init_subdesc(struct ngl_node *node, if (ret < 0) return ret; + ret = ngli_pipeline_set_resources(desc->pipeline, &pipeline_resource_params); + if (ret < 0) + return ret; + desc->modelview_matrix_index = ngli_pgcraft_get_uniform_index(desc->crafter, "modelview_matrix", NGLI_PROGRAM_SHADER_VERT); desc->projection_matrix_index = ngli_pgcraft_get_uniform_index(desc->crafter, "projection_matrix", NGLI_PROGRAM_SHADER_VERT); desc->color_index = ngli_pgcraft_get_uniform_index(desc->crafter, "color", NGLI_PROGRAM_SHADER_FRAG); @@ -617,8 +622,8 @@ static int fg_prepare(struct ngl_node *node, struct pipeline_subdesc *desc) if (ret < 0) return ret; - ngli_assert(!strcmp("position", pipeline_params.attributes[0].name)); - ngli_assert(!strcmp("uvcoord", pipeline_params.attributes[1].name)); + ngli_assert(!strcmp("position", pipeline_params.attributes_desc[0].name)); + ngli_assert(!strcmp("uvcoord", pipeline_params.attributes_desc[1].name)); return 0; } diff --git a/libnodegl/pass.c b/libnodegl/pass.c index f45f3a4cac..a04c881995 100644 --- a/libnodegl/pass.c +++ b/libnodegl/pass.c @@ -486,7 +486,8 @@ int ngli_pass_prepare(struct pass *s) if (!desc->crafter) return NGL_ERROR_MEMORY; - int ret = ngli_pgcraft_craft(desc->crafter, &pipeline_params, &crafter_params); + struct pipeline_resource_params pipeline_resource_params = {0}; + int ret = ngli_pgcraft_craft(desc->crafter, &pipeline_params, &pipeline_resource_params, &crafter_params); if (ret < 0) return ret; @@ -498,6 +499,10 @@ int ngli_pass_prepare(struct pass *s) if (ret < 0) return ret; + ret = ngli_pipeline_set_resources(desc->pipeline, &pipeline_resource_params); + if (ret < 0) + return ret; + desc->modelview_matrix_index = ngli_pgcraft_get_uniform_index(desc->crafter, "ngl_modelview_matrix", NGLI_PROGRAM_SHADER_VERT); desc->projection_matrix_index = ngli_pgcraft_get_uniform_index(desc->crafter, "ngl_projection_matrix", NGLI_PROGRAM_SHADER_VERT); desc->normal_matrix_index = ngli_pgcraft_get_uniform_index(desc->crafter, "ngl_normal_matrix", NGLI_PROGRAM_SHADER_VERT); diff --git a/libnodegl/pgcraft.c b/libnodegl/pgcraft.c index 2bb136d13b..0661f9337a 100644 --- a/libnodegl/pgcraft.c +++ b/libnodegl/pgcraft.c @@ -170,12 +170,11 @@ static int inject_uniform(struct pgcraft *s, struct bstr *b, if (uniform->stage != stage) return 0; - struct pipeline_uniform pl_uniform = { + struct pipeline_uniform_desc pl_uniform_desc = { .type = uniform->type, .count = NGLI_MAX(uniform->count, 1), - .data = uniform->data, }; - snprintf(pl_uniform.name, sizeof(pl_uniform.name), "%s", uniform->name); + snprintf(pl_uniform_desc.name, sizeof(pl_uniform_desc.name), "%s", uniform->name); const char *type = get_glsl_type(uniform->type); const char *precision = get_precision_qualifier(s, uniform->type, uniform->precision, "highp"); @@ -184,7 +183,9 @@ static int inject_uniform(struct pgcraft *s, struct bstr *b, else ngli_bstr_printf(b, "uniform %s %s %s;\n", precision, type, uniform->name); - if (!ngli_darray_push(&s->pipeline_uniforms, &pl_uniform)) + if (!ngli_darray_push(&s->pipeline_info.desc.uniforms, &pl_uniform_desc)) + return NGL_ERROR_MEMORY; + if (!ngli_darray_push(&s->pipeline_info.data.uniforms, &uniform->data)) return NGL_ERROR_MEMORY; return 0; } @@ -305,17 +306,16 @@ static int inject_texture_info(struct pgcraft *s, struct pgcraft_texture_info *i struct bstr *b = s->shaders[stage]; if (is_sampler_or_image(field->type)) { - struct pipeline_texture pl_texture = { + struct pipeline_texture_desc pl_texture_desc = { .type = field->type, .location = -1, .binding = -1, - .texture = info->texture, }; - snprintf(pl_texture.name, sizeof(pl_texture.name), "%s", field->name); + snprintf(pl_texture_desc.name, sizeof(pl_texture_desc.name), "%s", field->name); int *next_bind = s->next_bindings[BIND_ID(stage, NGLI_BINDING_TYPE_TEXTURE)]; if (next_bind) - pl_texture.binding = (*next_bind)++; + pl_texture_desc.binding = (*next_bind)++; if (field->type == NGLI_TYPE_IMAGE_2D) { if (info->format == NGLI_TYPE_NONE) { @@ -329,18 +329,20 @@ static int inject_texture_info(struct pgcraft *s, struct pgcraft_texture_info *i } ngli_bstr_printf(b, "layout(%s", format); - if (pl_texture.binding != -1) - ngli_bstr_printf(b, ", binding=%d", pl_texture.binding); + if (pl_texture_desc.binding != -1) + ngli_bstr_printf(b, ", binding=%d", pl_texture_desc.binding); ngli_bstr_printf(b, ") %s ", info->writable ? "writeonly" : "readonly"); - } else if (pl_texture.binding != -1) { - ngli_bstr_printf(b, "layout(binding=%d) ", pl_texture.binding); + } else if (pl_texture_desc.binding != -1) { + ngli_bstr_printf(b, "layout(binding=%d) ", pl_texture_desc.binding); } const char *type = get_glsl_type(field->type); const char *precision = get_precision_qualifier(s, field->type, info->precision, "lowp"); ngli_bstr_printf(b, "uniform %s %s %s;\n", precision, type, field->name); - if (!ngli_darray_push(&s->pipeline_textures, &pl_texture)) + if (!ngli_darray_push(&s->pipeline_info.desc.textures, &pl_texture_desc)) + return NGL_ERROR_MEMORY; + if (!ngli_darray_push(&s->pipeline_info.data.textures, &info->texture)) return NGL_ERROR_MEMORY; } else { struct pgcraft_uniform uniform = { @@ -382,13 +384,12 @@ static int inject_block(struct pgcraft *s, struct bstr *b, const struct block *block = named_block->block; - struct pipeline_buffer pl_buffer = { + struct pipeline_buffer_desc pl_buffer_desc = { .type = block->type, .binding = -1, - .buffer = named_block->buffer, }; - int len = snprintf(pl_buffer.name, sizeof(pl_buffer.name), "%s_block", named_block->name); - if (len >= sizeof(pl_buffer.name)) { + int len = snprintf(pl_buffer_desc.name, sizeof(pl_buffer_desc.name), "%s_block", named_block->name); + if (len >= sizeof(pl_buffer_desc.name)) { LOG(ERROR, "block name \"%s\" is too long", named_block->name); return NGL_ERROR_MEMORY; } @@ -397,8 +398,8 @@ static int inject_block(struct pgcraft *s, struct bstr *b, const int bind_type = block->type == NGLI_TYPE_UNIFORM_BUFFER ? NGLI_BINDING_TYPE_UBO : NGLI_BINDING_TYPE_SSBO; int *next_bind = s->next_bindings[BIND_ID(stage, bind_type)]; if (next_bind) { - pl_buffer.binding = (*next_bind)++; - ngli_bstr_printf(b, "layout(%s,binding=%d)", layout, pl_buffer.binding); + pl_buffer_desc.binding = (*next_bind)++; + ngli_bstr_printf(b, "layout(%s,binding=%d)", layout, pl_buffer_desc.binding); } else { ngli_bstr_printf(b, "layout(%s)", layout); } @@ -419,7 +420,9 @@ static int inject_block(struct pgcraft *s, struct bstr *b, const char *instance_name = named_block->instance_name ? named_block->instance_name : named_block->name; ngli_bstr_printf(b, "} %s;\n", instance_name); - if (!ngli_darray_push(&s->pipeline_buffers, &pl_buffer)) + if (!ngli_darray_push(&s->pipeline_info.desc.buffers, &pl_buffer_desc)) + return NGL_ERROR_MEMORY; + if (!ngli_darray_push(&s->pipeline_info.data.buffers, &named_block->buffer)) return NGL_ERROR_MEMORY; return 0; } @@ -441,17 +444,18 @@ static int inject_attribute(struct pgcraft *s, struct bstr *b, for (int i = 0; i < attribute_count; i++) { /* negative location offset trick is for probe_pipeline_attribute() */ const int loc = base_location != -1 ? base_location + i : -1 - i; - struct pipeline_attribute pl_attribute = { + struct pipeline_attribute_desc pl_attribute_desc = { .location = loc, .format = attribute->format, .stride = attribute->stride, .offset = attribute->offset + i * attribute_offset, .rate = attribute->rate, - .buffer = attribute->buffer, }; - snprintf(pl_attribute.name, sizeof(pl_attribute.name), "%s", attribute->name); + snprintf(pl_attribute_desc.name, sizeof(pl_attribute_desc.name), "%s", attribute->name); - if (!ngli_darray_push(&s->pipeline_attributes, &pl_attribute)) + if (!ngli_darray_push(&s->pipeline_info.desc.attributes, &pl_attribute_desc)) + return NGL_ERROR_MEMORY; + if (!ngli_darray_push(&s->pipeline_info.data.attributes, &attribute->buffer)) return NGL_ERROR_MEMORY; } @@ -803,8 +807,8 @@ static int craft_comp(struct pgcraft *s, const struct pgcraft_params *params) static int probe_pipeline_uniform(const struct hmap *info_map, void *arg) { - struct pipeline_uniform *elem = arg; - const struct program_variable_info *info = ngli_hmap_get(info_map, elem->name); + struct pipeline_uniform_desc *elem_desc = arg; + const struct program_variable_info *info = ngli_hmap_get(info_map, elem_desc->name); if (!info) return NGL_ERROR_NOT_FOUND; return 0; @@ -812,40 +816,40 @@ static int probe_pipeline_uniform(const struct hmap *info_map, void *arg) static int probe_pipeline_buffer(const struct hmap *info_map, void *arg) { - struct pipeline_buffer *elem = arg; - if (elem->binding != -1) + struct pipeline_buffer_desc *elem_desc = arg; + if (elem_desc->binding != -1) return 0; - const struct program_variable_info *info = ngli_hmap_get(info_map, elem->name); + const struct program_variable_info *info = ngli_hmap_get(info_map, elem_desc->name); if (!info) return NGL_ERROR_NOT_FOUND; - elem->binding = info->binding; - return elem->binding != -1 ? 0 : NGL_ERROR_NOT_FOUND; + elem_desc->binding = info->binding; + return elem_desc->binding != -1 ? 0 : NGL_ERROR_NOT_FOUND; } static int probe_pipeline_texture(const struct hmap *info_map, void *arg) { - struct pipeline_texture *elem = arg; - if (elem->location != -1) + struct pipeline_texture_desc *elem_desc = arg; + if (elem_desc->location != -1) return 0; - const struct program_variable_info *info = ngli_hmap_get(info_map, elem->name); + const struct program_variable_info *info = ngli_hmap_get(info_map, elem_desc->name); if (!info) return NGL_ERROR_NOT_FOUND; - elem->location = info->location; - if (elem->binding == -1) - elem->binding = info->binding; - return elem->location != -1 ? 0 : NGL_ERROR_NOT_FOUND; + elem_desc->location = info->location; + if (elem_desc->binding == -1) + elem_desc->binding = info->binding; + return elem_desc->location != -1 ? 0 : NGL_ERROR_NOT_FOUND; } static int probe_pipeline_attribute(const struct hmap *info_map, void *arg) { - struct pipeline_attribute *elem = arg; - if (elem->location >= 0) // can be ≤ -1 if there is a location offset so we don't check ≠ -1 here + struct pipeline_attribute_desc *elem_desc = arg; + if (elem_desc->location >= 0) // can be ≤ -1 if there is a location offset so we don't check ≠ -1 here return 0; - const struct program_variable_info *info = ngli_hmap_get(info_map, elem->name); + const struct program_variable_info *info = ngli_hmap_get(info_map, elem_desc->name); if (!info || info->location == -1) return NGL_ERROR_NOT_FOUND; - const int loc_offset = -elem->location - 1; // reverse location offset trick from inject_attribute() - elem->location = info->location + loc_offset; + const int loc_offset = -elem_desc->location - 1; // reverse location offset trick from inject_attribute() + elem_desc->location = info->location + loc_offset; return 0; } @@ -853,26 +857,33 @@ typedef int (*probe_func_type)(const struct hmap *info_map, void *arg); static int filter_pipeline_elems(struct pgcraft *s, probe_func_type probe_func, const struct hmap *info_map, - struct darray *src, struct darray *dst) + struct darray *src_desc, struct darray *src_data, + struct darray *dst_desc, struct darray *dst_data) { - uint8_t *elems = ngli_darray_data(src); - for (int i = 0; i < ngli_darray_count(src); i++) { - void *elem = elems + i * src->element_size; - if (info_map && probe_func(info_map, elem) < 0) + uint8_t *desc_elems = ngli_darray_data(src_desc); + uint8_t *data_elems = ngli_darray_data(src_data); + int num_elems = ngli_darray_count(src_desc); + for (int i = 0; i < num_elems; i++) { + void *desc_elem = desc_elems + i * src_desc->element_size; + void *data_elem = data_elems + i * src_data->element_size; + if (info_map && probe_func(info_map, desc_elem) < 0) continue; - if (!ngli_darray_push(dst, elem)) + if (!ngli_darray_push(dst_desc, desc_elem)) + return NGL_ERROR_MEMORY; + if (!ngli_darray_push(dst_data, data_elem)) return NGL_ERROR_MEMORY; } - ngli_darray_reset(src); + ngli_darray_reset(src_desc); + ngli_darray_reset(src_data); return 0; } static int get_uniform_index(const struct pgcraft *s, const char *name) { - const struct pipeline_uniform *pipeline_uniforms = ngli_darray_data(&s->filtered_pipeline_uniforms); - for (int i = 0; i < ngli_darray_count(&s->filtered_pipeline_uniforms); i++) { - const struct pipeline_uniform *pipeline_uniform = &pipeline_uniforms[i]; - if (!strcmp(pipeline_uniform->name, name)) + const struct pipeline_uniform_desc *pipeline_uniform_descs = ngli_darray_data(&s->filtered_pipeline_info.desc.uniforms); + for (int i = 0; i < ngli_darray_count(&s->filtered_pipeline_info.desc.uniforms); i++) { + const struct pipeline_uniform_desc *pipeline_uniform_desc = &pipeline_uniform_descs[i]; + if (!strcmp(pipeline_uniform_desc->name, name)) return i; } return -1; @@ -880,10 +891,10 @@ static int get_uniform_index(const struct pgcraft *s, const char *name) static int get_texture_index(const struct pgcraft *s, const char *name) { - const struct pipeline_texture *pipeline_textures = ngli_darray_data(&s->filtered_pipeline_textures); - for (int i = 0; i < ngli_darray_count(&s->filtered_pipeline_textures); i++) { - const struct pipeline_texture *pipeline_texture = &pipeline_textures[i]; - if (!strcmp(pipeline_texture->name, name)) + const struct pipeline_texture_desc *pipeline_texture_descs = ngli_darray_data(&s->filtered_pipeline_info.desc.textures); + for (int i = 0; i < ngli_darray_count(&s->filtered_pipeline_info.desc.textures); i++) { + const struct pipeline_texture_desc *pipeline_texture_desc = &pipeline_texture_descs[i]; + if (!strcmp(pipeline_texture_desc->name, name)) return i; } return -1; @@ -924,10 +935,12 @@ static int probe_pipeline_elems(struct pgcraft *s) const struct hmap *buffers_info = s->program->buffer_blocks; const struct hmap *attributes_info = s->program->attributes; - if ((ret = filter_pipeline_elems(s, probe_pipeline_uniform, uniforms_info, &s->pipeline_uniforms, &s->filtered_pipeline_uniforms)) < 0 || - (ret = filter_pipeline_elems(s, probe_pipeline_buffer, buffers_info, &s->pipeline_buffers, &s->filtered_pipeline_buffers)) < 0 || - (ret = filter_pipeline_elems(s, probe_pipeline_texture, uniforms_info, &s->pipeline_textures, &s->filtered_pipeline_textures)) < 0 || - (ret = filter_pipeline_elems(s, probe_pipeline_attribute, attributes_info, &s->pipeline_attributes, &s->filtered_pipeline_attributes)) < 0) + struct pgcraft_pipeline_info *info = &s->pipeline_info; + struct pgcraft_pipeline_info *finfo = &s->filtered_pipeline_info; + if ((ret = filter_pipeline_elems(s, probe_pipeline_uniform, uniforms_info, &info->desc.uniforms, &info->data.uniforms, &finfo->desc.uniforms, &finfo->data.uniforms)) < 0 || + (ret = filter_pipeline_elems(s, probe_pipeline_buffer, buffers_info, &info->desc.buffers, &info->data.buffers, &finfo->desc.buffers, &finfo->data.buffers)) < 0 || + (ret = filter_pipeline_elems(s, probe_pipeline_texture, uniforms_info, &info->desc.textures, &info->data.textures, &finfo->desc.textures, &finfo->data.textures)) < 0 || + (ret = filter_pipeline_elems(s, probe_pipeline_attribute, attributes_info, &info->desc.attributes, &info->data.attributes, &finfo->desc.attributes, &finfo->data.attributes)) < 0) return ret; probe_texture_infos(s); @@ -1025,15 +1038,25 @@ struct pgcraft *ngli_pgcraft_create(struct ngl_ctx *ctx) ngli_darray_init(&s->texture_infos, sizeof(struct pgcraft_texture_info), 0); - ngli_darray_init(&s->pipeline_uniforms, sizeof(struct pipeline_uniform), 0); - ngli_darray_init(&s->pipeline_textures, sizeof(struct pipeline_texture), 0); - ngli_darray_init(&s->pipeline_buffers, sizeof(struct pipeline_buffer), 0); - ngli_darray_init(&s->pipeline_attributes, sizeof(struct pipeline_attribute), 0); + ngli_darray_init(&s->pipeline_info.desc.uniforms, sizeof(struct pipeline_uniform_desc), 0); + ngli_darray_init(&s->pipeline_info.desc.textures, sizeof(struct pipeline_texture_desc), 0); + ngli_darray_init(&s->pipeline_info.desc.buffers, sizeof(struct pipeline_buffer_desc), 0); + ngli_darray_init(&s->pipeline_info.desc.attributes, sizeof(struct pipeline_attribute_desc), 0); + + ngli_darray_init(&s->filtered_pipeline_info.desc.uniforms, sizeof(struct pipeline_uniform_desc), 0); + ngli_darray_init(&s->filtered_pipeline_info.desc.textures, sizeof(struct pipeline_texture_desc), 0); + ngli_darray_init(&s->filtered_pipeline_info.desc.buffers, sizeof(struct pipeline_buffer_desc), 0); + ngli_darray_init(&s->filtered_pipeline_info.desc.attributes, sizeof(struct pipeline_attribute_desc), 0); + + ngli_darray_init(&s->pipeline_info.data.uniforms, sizeof(void *), 0); + ngli_darray_init(&s->pipeline_info.data.textures, sizeof(struct texture *), 0); + ngli_darray_init(&s->pipeline_info.data.buffers, sizeof(struct buffer *), 0); + ngli_darray_init(&s->pipeline_info.data.attributes, sizeof(struct buffer *), 0); - ngli_darray_init(&s->filtered_pipeline_uniforms, sizeof(struct pipeline_uniform), 0); - ngli_darray_init(&s->filtered_pipeline_textures, sizeof(struct pipeline_texture), 0); - ngli_darray_init(&s->filtered_pipeline_buffers, sizeof(struct pipeline_buffer), 0); - ngli_darray_init(&s->filtered_pipeline_attributes, sizeof(struct pipeline_attribute), 0); + ngli_darray_init(&s->filtered_pipeline_info.data.uniforms, sizeof(void *), 0); + ngli_darray_init(&s->filtered_pipeline_info.data.textures, sizeof(struct texture *), 0); + ngli_darray_init(&s->filtered_pipeline_info.data.buffers, sizeof(struct buffer *), 0); + ngli_darray_init(&s->filtered_pipeline_info.data.attributes, sizeof(struct buffer *), 0); return s; } @@ -1090,7 +1113,8 @@ static int get_program_graphics(struct pgcraft *s, const struct pgcraft_params * } int ngli_pgcraft_craft(struct pgcraft *s, - struct pipeline_params *dst_params, + struct pipeline_params *dst_desc_params, + struct pipeline_resource_params *dst_data_params, const struct pgcraft_params *params) { int ret = params->comp_base ? get_program_compute(s, params) @@ -1102,15 +1126,24 @@ int ngli_pgcraft_craft(struct pgcraft *s, if (ret < 0) return ret; - dst_params->program = s->program; - dst_params->uniforms = ngli_darray_data(&s->filtered_pipeline_uniforms); - dst_params->nb_uniforms = ngli_darray_count(&s->filtered_pipeline_uniforms); - dst_params->textures = ngli_darray_data(&s->filtered_pipeline_textures); - dst_params->nb_textures = ngli_darray_count(&s->filtered_pipeline_textures); - dst_params->attributes = ngli_darray_data(&s->filtered_pipeline_attributes); - dst_params->nb_attributes = ngli_darray_count(&s->filtered_pipeline_attributes); - dst_params->buffers = ngli_darray_data(&s->filtered_pipeline_buffers); - dst_params->nb_buffers = ngli_darray_count(&s->filtered_pipeline_buffers); + dst_desc_params->program = s->program; + dst_desc_params->uniforms_desc = ngli_darray_data(&s->filtered_pipeline_info.desc.uniforms); + dst_desc_params->nb_uniforms = ngli_darray_count(&s->filtered_pipeline_info.desc.uniforms); + dst_desc_params->textures_desc = ngli_darray_data(&s->filtered_pipeline_info.desc.textures); + dst_desc_params->nb_textures = ngli_darray_count(&s->filtered_pipeline_info.desc.textures); + dst_desc_params->attributes_desc = ngli_darray_data(&s->filtered_pipeline_info.desc.attributes); + dst_desc_params->nb_attributes = ngli_darray_count(&s->filtered_pipeline_info.desc.attributes); + dst_desc_params->buffers_desc = ngli_darray_data(&s->filtered_pipeline_info.desc.buffers); + dst_desc_params->nb_buffers = ngli_darray_count(&s->filtered_pipeline_info.desc.buffers); + + dst_data_params->uniforms = ngli_darray_data(&s->filtered_pipeline_info.data.uniforms); + dst_data_params->nb_uniforms = ngli_darray_count(&s->filtered_pipeline_info.data.uniforms); + dst_data_params->textures = ngli_darray_data(&s->filtered_pipeline_info.data.textures); + dst_data_params->nb_textures = ngli_darray_count(&s->filtered_pipeline_info.data.textures); + dst_data_params->attributes = ngli_darray_data(&s->filtered_pipeline_info.data.attributes); + dst_data_params->nb_attributes = ngli_darray_count(&s->filtered_pipeline_info.data.attributes); + dst_data_params->buffers = ngli_darray_data(&s->filtered_pipeline_info.data.buffers); + dst_data_params->nb_buffers = ngli_darray_count(&s->filtered_pipeline_info.data.buffers); return 0; } @@ -1132,15 +1165,15 @@ void ngli_pgcraft_freep(struct pgcraft **sp) for (int i = 0; i < NGLI_ARRAY_NB(s->shaders); i++) ngli_bstr_freep(&s->shaders[i]); - ngli_darray_reset(&s->pipeline_uniforms); - ngli_darray_reset(&s->pipeline_textures); - ngli_darray_reset(&s->pipeline_buffers); - ngli_darray_reset(&s->pipeline_attributes); + ngli_darray_reset(&s->pipeline_info.desc.uniforms); + ngli_darray_reset(&s->pipeline_info.desc.textures); + ngli_darray_reset(&s->pipeline_info.desc.buffers); + ngli_darray_reset(&s->pipeline_info.desc.attributes); - ngli_darray_reset(&s->filtered_pipeline_uniforms); - ngli_darray_reset(&s->filtered_pipeline_textures); - ngli_darray_reset(&s->filtered_pipeline_buffers); - ngli_darray_reset(&s->filtered_pipeline_attributes); + ngli_darray_reset(&s->filtered_pipeline_info.desc.uniforms); + ngli_darray_reset(&s->filtered_pipeline_info.desc.textures); + ngli_darray_reset(&s->filtered_pipeline_info.desc.buffers); + ngli_darray_reset(&s->filtered_pipeline_info.desc.attributes); ngli_freep(sp); } diff --git a/libnodegl/pgcraft.h b/libnodegl/pgcraft.h index a286d32ba3..ffadbf0edd 100644 --- a/libnodegl/pgcraft.h +++ b/libnodegl/pgcraft.h @@ -149,6 +149,21 @@ enum { #define NB_BINDINGS (NGLI_PROGRAM_SHADER_NB * NGLI_BINDING_TYPE_NB) #define BIND_ID(stage, type) ((stage) * NGLI_BINDING_TYPE_NB + (type)) +struct pgcraft_pipeline_info { + struct { + struct darray uniforms; // uniform_desc + struct darray textures; // texture_desc + struct darray buffers; // buffer_desc + struct darray attributes; // attribute_desc + } desc; + struct { + struct darray uniforms; // uniform data pointer + struct darray textures; // texture pointer + struct darray buffers; // buffer pointer + struct darray attributes; // attribute pointer + } data; +}; + struct pgcraft { struct darray texture_infos; // pgcraft_texture_info @@ -156,15 +171,8 @@ struct pgcraft { struct ngl_ctx *ctx; struct bstr *shaders[NGLI_PROGRAM_SHADER_NB]; - struct darray pipeline_uniforms; - struct darray pipeline_textures; - struct darray pipeline_buffers; - struct darray pipeline_attributes; - - struct darray filtered_pipeline_uniforms; - struct darray filtered_pipeline_textures; - struct darray filtered_pipeline_buffers; - struct darray filtered_pipeline_attributes; + struct pgcraft_pipeline_info pipeline_info; + struct pgcraft_pipeline_info filtered_pipeline_info; struct darray vert_out_vars; // pgcraft_iovar @@ -191,7 +199,8 @@ struct pgcraft { struct pgcraft *ngli_pgcraft_create(struct ngl_ctx *ctx); int ngli_pgcraft_craft(struct pgcraft *s, - struct pipeline_params *dst, + struct pipeline_params *dst_desc_params, + struct pipeline_resource_params *dst_data_params, const struct pgcraft_params *params); int ngli_pgcraft_get_uniform_index(const struct pgcraft *s, const char *name, int stage); diff --git a/libnodegl/pipeline.c b/libnodegl/pipeline.c index fed3d3b3f6..9d4de65c22 100644 --- a/libnodegl/pipeline.c +++ b/libnodegl/pipeline.c @@ -33,6 +33,11 @@ int ngli_pipeline_init(struct pipeline *s, const struct pipeline_params *params) return s->gctx->class->pipeline_init(s, params); } +int ngli_pipeline_set_resources(struct pipeline *s, const struct pipeline_resource_params *data_params) +{ + return s->gctx->class->pipeline_set_resources(s, data_params); +} + int ngli_pipeline_update_attribute(struct pipeline *s, int index, struct buffer *buffer) { return s->gctx->class->pipeline_update_attribute(s, index, buffer); diff --git a/libnodegl/pipeline.h b/libnodegl/pipeline.h index 1bda1f8463..5197555cc0 100644 --- a/libnodegl/pipeline.h +++ b/libnodegl/pipeline.h @@ -31,36 +31,32 @@ struct gctx; -struct pipeline_uniform { +struct pipeline_uniform_desc { char name[MAX_ID_LEN]; int type; int count; - const void *data; }; -struct pipeline_texture { +struct pipeline_texture_desc { char name[MAX_ID_LEN]; int type; int location; int binding; - struct texture *texture; }; -struct pipeline_buffer { +struct pipeline_buffer_desc { char name[MAX_ID_LEN]; int type; int binding; - struct buffer *buffer; }; -struct pipeline_attribute { +struct pipeline_attribute_desc { char name[MAX_ID_LEN]; int location; int format; int stride; int offset; int rate; - struct buffer *buffer; }; struct pipeline_graphics { @@ -79,13 +75,24 @@ struct pipeline_params { const struct pipeline_graphics graphics; const struct program *program; - const struct pipeline_texture *textures; + const struct pipeline_texture_desc *textures_desc; int nb_textures; - const struct pipeline_uniform *uniforms; + const struct pipeline_uniform_desc *uniforms_desc; int nb_uniforms; - const struct pipeline_buffer *buffers; + const struct pipeline_buffer_desc *buffers_desc; int nb_buffers; - const struct pipeline_attribute *attributes; + const struct pipeline_attribute_desc *attributes_desc; + int nb_attributes; +}; + +struct pipeline_resource_params { + struct texture **textures; + int nb_textures; + void **uniforms; + int nb_uniforms; + struct buffer **buffers; + int nb_buffers; + struct buffer **attributes; int nb_attributes; }; @@ -105,6 +112,7 @@ struct pipeline { struct pipeline *ngli_pipeline_create(struct gctx *gctx); int ngli_pipeline_init(struct pipeline *s, const struct pipeline_params *params); +int ngli_pipeline_set_resources(struct pipeline *s, const struct pipeline_resource_params *data_params); int ngli_pipeline_update_attribute(struct pipeline *s, int index, struct buffer *buffer); int ngli_pipeline_update_uniform(struct pipeline *s, int index, const void *value); int ngli_pipeline_update_texture(struct pipeline *s, int index, struct texture *texture); diff --git a/libnodegl/pipeline_gl.c b/libnodegl/pipeline_gl.c index ab76e9a227..d3c0c19941 100644 --- a/libnodegl/pipeline_gl.c +++ b/libnodegl/pipeline_gl.c @@ -38,21 +38,25 @@ typedef void (*set_uniform_func)(struct glcontext *gl, GLint location, int count struct uniform_desc { GLuint location; - struct pipeline_uniform uniform; set_uniform_func set; + struct pipeline_uniform_desc desc; + const void *data; }; struct texture_desc { - struct pipeline_texture texture; + struct pipeline_texture_desc desc; + const struct texture *texture; }; struct buffer_desc { GLuint type; - struct pipeline_buffer buffer; + struct pipeline_buffer_desc desc; + const struct buffer *buffer; }; struct attribute_desc { - struct pipeline_attribute attribute; + struct pipeline_attribute_desc desc; + const struct buffer *buffer; }; static void set_uniform_1iv(struct glcontext *gl, GLint location, int count, const void *data) @@ -154,26 +158,26 @@ static int build_uniform_descs(struct pipeline *s, const struct pipeline_params struct glcontext *gl = gctx_gl->glcontext; for (int i = 0; i < params->nb_uniforms; i++) { - const struct pipeline_uniform *uniform = ¶ms->uniforms[i]; - const struct program_variable_info *info = ngli_hmap_get(program->uniforms, uniform->name); + const struct pipeline_uniform_desc *uniform_desc = ¶ms->uniforms_desc[i]; + const struct program_variable_info *info = ngli_hmap_get(program->uniforms, uniform_desc->name); if (!info) continue; if (!(gl->features & NGLI_FEATURE_UINT_UNIFORMS) && - (uniform->type == NGLI_TYPE_UINT || - uniform->type == NGLI_TYPE_UIVEC2 || - uniform->type == NGLI_TYPE_UIVEC3 || - uniform->type == NGLI_TYPE_UIVEC4)) { + (uniform_desc->type == NGLI_TYPE_UINT || + uniform_desc->type == NGLI_TYPE_UIVEC2 || + uniform_desc->type == NGLI_TYPE_UIVEC3 || + uniform_desc->type == NGLI_TYPE_UIVEC4)) { LOG(ERROR, "context does not support unsigned int uniform flavours"); return NGL_ERROR_UNSUPPORTED; } - const set_uniform_func set_func = set_uniform_func_map[uniform->type]; + const set_uniform_func set_func = set_uniform_func_map[uniform_desc->type]; ngli_assert(set_func); struct uniform_desc desc = { .location = info->location, - .uniform = *uniform, .set = set_func, + .desc = *uniform_desc, }; if (!ngli_darray_push(&s->uniform_descs, &desc)) return NGL_ERROR_MEMORY; @@ -186,10 +190,9 @@ static void set_uniforms(struct pipeline *s, struct glcontext *gl) { const struct uniform_desc *descs = ngli_darray_data(&s->uniform_descs); for (int i = 0; i < ngli_darray_count(&s->uniform_descs); i++) { - const struct uniform_desc *desc = &descs[i]; - const struct pipeline_uniform *uniform = &desc->uniform; - if (uniform->data) - desc->set(gl, desc->location, uniform->count, uniform->data); + const struct uniform_desc *uniform_desc = &descs[i]; + if (uniform_desc->data) + uniform_desc->set(gl, uniform_desc->location, uniform_desc->desc.count, uniform_desc->data); } } @@ -198,27 +201,27 @@ static int build_texture_descs(struct pipeline *s, const struct pipeline_params struct pipeline_gl *s_priv = (struct pipeline_gl *)s; for (int i = 0; i < params->nb_textures; i++) { - const struct pipeline_texture *texture = ¶ms->textures[i]; + const struct pipeline_texture_desc *texture_desc = ¶ms->textures_desc[i]; - if (texture->type == NGLI_TYPE_IMAGE_2D) { + if (texture_desc->type == NGLI_TYPE_IMAGE_2D) { struct gctx_gl *gctx_gl = (struct gctx_gl *)s->gctx; struct glcontext *gl = gctx_gl->glcontext; const struct limits *limits = &gl->limits; int max_nb_textures = NGLI_MIN(limits->max_texture_image_units, sizeof(s_priv->used_texture_units) * 8); - if (texture->binding >= max_nb_textures) { + if (texture_desc->binding >= max_nb_textures) { LOG(ERROR, "maximum number (%d) of texture unit reached", max_nb_textures); return NGL_ERROR_LIMIT_EXCEEDED; } - if (s_priv->used_texture_units & (1ULL << texture->binding)) { - LOG(ERROR, "texture unit %d is already used by another image", texture->binding); + if (s_priv->used_texture_units & (1ULL << texture_desc->binding)) { + LOG(ERROR, "texture unit %d is already used by another image", texture_desc->binding); return NGL_ERROR_INVALID_DATA; } - s_priv->used_texture_units |= 1ULL << texture->binding; + s_priv->used_texture_units |= 1ULL << texture_desc->binding; } struct texture_desc desc = { - .texture = *texture, + .desc = *texture_desc, }; if (!ngli_darray_push(&s->texture_descs, &desc)) return NGL_ERROR_MEMORY; @@ -245,12 +248,11 @@ static void set_textures(struct pipeline *s, struct glcontext *gl) uint64_t texture_units = s_priv->used_texture_units; const struct texture_desc *descs = ngli_darray_data(&s->texture_descs); for (int i = 0; i < ngli_darray_count(&s->texture_descs); i++) { - const struct texture_desc *desc = &descs[i]; - const struct pipeline_texture *pipeline_texture = &desc->texture; - const struct texture *texture = pipeline_texture->texture; + const struct texture_desc *texture_desc = &descs[i]; + const struct texture *texture = texture_desc->texture; const struct texture_gl *texture_gl = (const struct texture_gl *)texture; - if (pipeline_texture->type == NGLI_TYPE_IMAGE_2D) { + if (texture_desc->desc.type == NGLI_TYPE_IMAGE_2D) { GLuint texture_id = 0; GLenum access = GL_READ_WRITE; GLenum internal_format = GL_RGBA8; @@ -260,12 +262,12 @@ static void set_textures(struct pipeline *s, struct glcontext *gl) access = ngli_texture_get_gl_access(params->access); internal_format = texture_gl->internal_format; } - ngli_glBindImageTexture(gl, pipeline_texture->binding, texture_id, 0, GL_FALSE, 0, access, internal_format); + ngli_glBindImageTexture(gl, texture_desc->desc.binding, texture_id, 0, GL_FALSE, 0, access, internal_format); } else { const int texture_index = acquire_next_available_texture_unit(&texture_units); if (texture_index < 0) return; - ngli_glUniform1i(gl, pipeline_texture->location, texture_index); + ngli_glUniform1i(gl, texture_desc->desc.location, texture_index); ngli_glActiveTexture(gl, GL_TEXTURE0 + texture_index); if (texture) { ngli_glBindTexture(gl, texture_gl->target, texture_gl->id); @@ -284,33 +286,21 @@ static void set_buffers(struct pipeline *s, struct glcontext *gl) { const struct buffer_desc *descs = ngli_darray_data(&s->buffer_descs); for (int i = 0; i < ngli_darray_count(&s->buffer_descs); i++) { - const struct buffer_desc *desc = &descs[i]; - const struct pipeline_buffer *pipeline_buffer = &desc->buffer; - const struct buffer *buffer = pipeline_buffer->buffer; + const struct buffer_desc *buffer_desc = &descs[i]; + const struct buffer *buffer = buffer_desc->buffer; const struct buffer_gl *buffer_gl = (const struct buffer_gl *)buffer; - ngli_glBindBufferBase(gl, desc->type, pipeline_buffer->binding, buffer_gl->id); + ngli_glBindBufferBase(gl, buffer_desc->type, buffer_desc->desc.binding, buffer_gl->id); } } static int build_buffer_descs(struct pipeline *s, const struct pipeline_params *params) { for (int i = 0; i < params->nb_buffers; i++) { - const struct pipeline_buffer *pipeline_buffer = ¶ms->buffers[i]; - const struct buffer *buffer = pipeline_buffer->buffer; - - struct gctx_gl *gctx_gl = (struct gctx_gl *)s->gctx; - struct glcontext *gl = gctx_gl->glcontext; - const struct limits *limits = &gl->limits; - if (pipeline_buffer->type == NGLI_TYPE_UNIFORM_BUFFER && - buffer->size > limits->max_uniform_block_size) { - LOG(ERROR, "buffer %s size (%d) exceeds max uniform block size (%d)", - pipeline_buffer->name, buffer->size, limits->max_uniform_block_size); - return NGL_ERROR_LIMIT_EXCEEDED; - } + const struct pipeline_buffer_desc *pipeline_buffer_desc = ¶ms->buffers_desc[i]; struct buffer_desc desc = { - .type = ngli_type_get_gl_type(pipeline_buffer->type), - .buffer = *pipeline_buffer, + .type = ngli_type_get_gl_type(pipeline_buffer_desc->type), + .desc = *pipeline_buffer_desc, }; if (!ngli_darray_push(&s->buffer_descs, &desc)) return NGL_ERROR_MEMORY; @@ -323,21 +313,20 @@ static void set_vertex_attribs(const struct pipeline *s, struct glcontext *gl) { const struct attribute_desc *descs = ngli_darray_data(&s->attribute_descs); for (int i = 0; i < ngli_darray_count(&s->attribute_descs); i++) { - const struct attribute_desc *desc = &descs[i]; - const struct pipeline_attribute *attribute = &desc->attribute; - const struct buffer *buffer = attribute->buffer; + const struct attribute_desc *attr_desc = &descs[i]; + const struct buffer *buffer = attr_desc->buffer; const struct buffer_gl *buffer_gl = (const struct buffer_gl *)buffer; - const GLuint location = attribute->location; - const GLuint size = ngli_format_get_nb_comp(attribute->format); - const GLint stride = attribute->stride; + const GLuint location = attr_desc->desc.location; + const GLuint size = ngli_format_get_nb_comp(attr_desc->desc.format); + const GLint stride = attr_desc->desc.stride; ngli_glEnableVertexAttribArray(gl, location); - if ((gl->features & NGLI_FEATURE_INSTANCED_ARRAY) && attribute->rate > 0) - ngli_glVertexAttribDivisor(gl, location, attribute->rate); + if ((gl->features & NGLI_FEATURE_INSTANCED_ARRAY) && attr_desc->desc.rate > 0) + ngli_glVertexAttribDivisor(gl, location, attr_desc->desc.rate); if (buffer_gl) { ngli_glBindBuffer(gl, GL_ARRAY_BUFFER, buffer_gl->id); - ngli_glVertexAttribPointer(gl, location, size, GL_FLOAT, GL_FALSE, stride, (void*)(uintptr_t)(attribute->offset)); + ngli_glVertexAttribPointer(gl, location, size, GL_FLOAT, GL_FALSE, stride, (void*)(uintptr_t)(attr_desc->desc.offset)); } } } @@ -346,9 +335,8 @@ static void reset_vertex_attribs(const struct pipeline *s, struct glcontext *gl) { const struct attribute_desc *descs = ngli_darray_data(&s->attribute_descs); for (int i = 0; i < ngli_darray_count(&s->attribute_descs); i++) { - const struct attribute_desc *desc = &descs[i]; - const struct pipeline_attribute *attribute = &desc->attribute; - const GLuint location = attribute->location; + const struct attribute_desc *attr_desc = &descs[i]; + const GLuint location = attr_desc->desc.location; ngli_glDisableVertexAttribArray(gl, location); if (gl->features & NGLI_FEATURE_INSTANCED_ARRAY) ngli_glVertexAttribDivisor(gl, location, 0); @@ -361,22 +349,20 @@ static int build_attribute_descs(struct pipeline *s, const struct pipeline_param struct glcontext *gl = gctx_gl->glcontext; for (int i = 0; i < params->nb_attributes; i++) { - const struct pipeline_attribute *attribute = ¶ms->attributes[i]; + const struct pipeline_attribute_desc *pipeline_attribute_desc = ¶ms->attributes_desc[i]; - if (attribute->rate > 0 && !(gl->features & NGLI_FEATURE_INSTANCED_ARRAY)) { + if (pipeline_attribute_desc->rate > 0 && !(gl->features & NGLI_FEATURE_INSTANCED_ARRAY)) { LOG(ERROR, "context does not support instanced arrays"); return NGL_ERROR_UNSUPPORTED; } - if (!attribute->buffer) - s->nb_unbound_attributes++; - struct attribute_desc desc = { - .attribute = *attribute, + .desc = *pipeline_attribute_desc, }; if (!ngli_darray_push(&s->attribute_descs, &desc)) return NGL_ERROR_MEMORY; } + s->nb_unbound_attributes = params->nb_attributes; return 0; } @@ -391,6 +377,18 @@ static GLenum get_gl_indices_type(int indices_format) return gl_indices_type_map[indices_format]; } +static void init_vertex_attribs(const struct pipeline *s, struct glcontext *gl) +{ + const struct attribute_desc *descs = ngli_darray_data(&s->attribute_descs); + for (int i = 0; i < ngli_darray_count(&s->attribute_descs); i++) { + const struct attribute_desc *attr_desc = &descs[i]; + const GLuint location = attr_desc->desc.location; + ngli_glEnableVertexAttribArray(gl, location); + if ((gl->features & NGLI_FEATURE_INSTANCED_ARRAY) && attr_desc->desc.rate > 0) + ngli_glVertexAttribDivisor(gl, location, attr_desc->desc.rate); + } +} + static void bind_vertex_attribs(const struct pipeline *s, struct glcontext *gl) { const struct pipeline_gl *s_priv = (const struct pipeline_gl *)s; @@ -419,7 +417,7 @@ static int pipeline_graphics_init(struct pipeline *s, const struct pipeline_para if (gl->features & NGLI_FEATURE_VERTEX_ARRAY_OBJECT) { ngli_glGenVertexArrays(gl, 1, &s_priv->vao_id); ngli_glBindVertexArray(gl, s_priv->vao_id); - set_vertex_attribs(s, gl); + init_vertex_attribs(s, gl); } return 0; @@ -479,6 +477,40 @@ int ngli_pipeline_gl_init(struct pipeline *s, const struct pipeline_params *para return 0; } +int ngli_pipeline_gl_set_resources(struct pipeline *s, const struct pipeline_resource_params *data_params) +{ + ngli_assert(ngli_darray_count(&s->attribute_descs) == data_params->nb_attributes); + for (int i = 0; i < data_params->nb_attributes; i++) { + int ret = ngli_pipeline_gl_update_attribute(s, i, data_params->attributes[i]); + if (ret < 0) + return ret; + } + + ngli_assert(ngli_darray_count(&s->buffer_descs) == data_params->nb_buffers); + for (int i = 0; i < data_params->nb_buffers; i++) { + int ret = ngli_pipeline_gl_update_buffer(s, i, data_params->buffers[i]); + if (ret < 0) + return ret; + } + + ngli_assert(ngli_darray_count(&s->texture_descs) == data_params->nb_textures); + for (int i = 0; i < data_params->nb_textures; i++) { + int ret = ngli_pipeline_gl_update_texture(s, i, data_params->textures[i]); + if (ret < 0) + return ret; + } + + ngli_assert(ngli_darray_count(&s->uniform_descs) == data_params->nb_uniforms); + for (int i = 0; i < data_params->nb_uniforms; i++) { + struct uniform_desc *uniform_desc = ngli_darray_get(&s->uniform_descs, i); + ngli_assert(uniform_desc); + const void *uniform_data = data_params->uniforms[i]; + uniform_desc->data = uniform_data; + } + + return 0; +} + int ngli_pipeline_gl_update_attribute(struct pipeline *s, int index, struct buffer *buffer) { struct gctx *gctx = s->gctx; @@ -494,26 +526,25 @@ int ngli_pipeline_gl_update_attribute(struct pipeline *s, int index, struct buff struct attribute_desc *attr_desc = ngli_darray_get(&s->attribute_descs, index); ngli_assert(attr_desc); - struct pipeline_attribute *attribute = &attr_desc->attribute; - - if (!attribute->buffer && buffer) + const struct buffer *current_buffer = attr_desc->buffer; + if (!current_buffer && buffer) s->nb_unbound_attributes--; - else if (attribute->buffer && !buffer) + else if (current_buffer && !buffer) s->nb_unbound_attributes++; - attribute->buffer = buffer; + attr_desc->buffer = buffer; if (!buffer) return 0; if (gl->features & NGLI_FEATURE_VERTEX_ARRAY_OBJECT) { - const GLuint location = attribute->location; - const GLuint size = ngli_format_get_nb_comp(attribute->format); - const GLint stride = attribute->stride; + const GLuint location = attr_desc->desc.location; + const GLuint size = ngli_format_get_nb_comp(attr_desc->desc.format); + const GLint stride = attr_desc->desc.stride; struct buffer_gl *buffer_gl = (struct buffer_gl *)buffer; ngli_glBindVertexArray(gl, s_priv->vao_id); ngli_glBindBuffer(gl, GL_ARRAY_BUFFER, buffer_gl->id); - ngli_glVertexAttribPointer(gl, location, size, GL_FLOAT, GL_FALSE, stride, (void*)(uintptr_t)(attribute->offset)); + ngli_glVertexAttribPointer(gl, location, size, GL_FLOAT, GL_FALSE, stride, (void*)(uintptr_t)(attr_desc->desc.offset)); } return 0; @@ -527,16 +558,15 @@ int ngli_pipeline_gl_update_uniform(struct pipeline *s, int index, const void *d struct uniform_desc *uniform_desc = ngli_darray_get(&s->uniform_descs, index); ngli_assert(uniform_desc); - struct pipeline_uniform *pipeline_uniform = &uniform_desc->uniform; if (data) { struct gctx *gctx = s->gctx; struct gctx_gl *gctx_gl = (struct gctx_gl *)gctx; struct glcontext *gl = gctx_gl->glcontext; struct program_gl *program_gl = (struct program_gl *)s->program; ngli_glstate_use_program(gctx, program_gl->id); - uniform_desc->set(gl, uniform_desc->location, pipeline_uniform->count, data); + uniform_desc->set(gl, uniform_desc->location, uniform_desc->desc.count, data); } - pipeline_uniform->data = NULL; + uniform_desc->data = NULL; return 0; } @@ -549,8 +579,7 @@ int ngli_pipeline_gl_update_texture(struct pipeline *s, int index, struct textur struct texture_desc *texture_desc = ngli_darray_get(&s->texture_descs, index); ngli_assert(texture_desc); - struct pipeline_texture *pipeline_texture = &texture_desc->texture; - pipeline_texture->texture = texture; + texture_desc->texture = texture; return 0; } @@ -563,8 +592,6 @@ int ngli_pipeline_gl_update_buffer(struct pipeline *s, int index, struct buffer struct buffer_desc *buffer_desc = ngli_darray_get(&s->buffer_descs, index); ngli_assert(buffer_desc); - struct pipeline_buffer *pipeline_buffer = &buffer_desc->buffer; - if (buffer) { struct gctx_gl *gctx_gl = (struct gctx_gl *)s->gctx; struct glcontext *gl = gctx_gl->glcontext; @@ -572,12 +599,12 @@ int ngli_pipeline_gl_update_buffer(struct pipeline *s, int index, struct buffer if (buffer_desc->type == NGLI_TYPE_UNIFORM_BUFFER && buffer->size > limits->max_uniform_block_size) { LOG(ERROR, "buffer %s size (%d) exceeds max uniform block size (%d)", - pipeline_buffer->name, buffer->size, limits->max_uniform_block_size); + buffer_desc->desc.name, buffer->size, limits->max_uniform_block_size); return NGL_ERROR_LIMIT_EXCEEDED; } } - pipeline_buffer->buffer = buffer; + buffer_desc->buffer = buffer; return 0; } diff --git a/libnodegl/pipeline_gl.h b/libnodegl/pipeline_gl.h index d68a0d75ce..4d2bd50011 100644 --- a/libnodegl/pipeline_gl.h +++ b/libnodegl/pipeline_gl.h @@ -37,6 +37,7 @@ struct pipeline_gl { struct pipeline *ngli_pipeline_gl_create(struct gctx *gctx); int ngli_pipeline_gl_init(struct pipeline *s, const struct pipeline_params *params); +int ngli_pipeline_gl_set_resources(struct pipeline *s, const struct pipeline_resource_params *data_params); int ngli_pipeline_gl_update_attribute(struct pipeline *s, int index, struct buffer *buffer); int ngli_pipeline_gl_update_uniform(struct pipeline *s, int index, const void *value); int ngli_pipeline_gl_update_texture(struct pipeline *s, int index, struct texture *texture); From 92280e6c5f6cc34bd6a2a9cc16ca96727b6e2645 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 15 Oct 2020 15:54:39 +0200 Subject: [PATCH 160/388] pipeline: move darray description fields to pipeline_gl structure These fields are specific to the OpenGL backend. --- libnodegl/pipeline.h | 6 --- libnodegl/pipeline_gl.c | 104 +++++++++++++++++++++++++--------------- libnodegl/pipeline_gl.h | 6 +++ 3 files changed, 71 insertions(+), 45 deletions(-) diff --git a/libnodegl/pipeline.h b/libnodegl/pipeline.h index 5197555cc0..0bba00cc97 100644 --- a/libnodegl/pipeline.h +++ b/libnodegl/pipeline.h @@ -102,12 +102,6 @@ struct pipeline { int type; struct pipeline_graphics graphics; const struct program *program; - - struct darray uniform_descs; - struct darray texture_descs; - struct darray buffer_descs; - struct darray attribute_descs; - int nb_unbound_attributes; }; struct pipeline *ngli_pipeline_create(struct gctx *gctx); diff --git a/libnodegl/pipeline_gl.c b/libnodegl/pipeline_gl.c index d3c0c19941..03fcc258f9 100644 --- a/libnodegl/pipeline_gl.c +++ b/libnodegl/pipeline_gl.c @@ -149,6 +149,7 @@ static const set_uniform_func set_uniform_func_map[NGLI_TYPE_NB] = { static int build_uniform_descs(struct pipeline *s, const struct pipeline_params *params) { + struct pipeline_gl *s_priv = (struct pipeline_gl *)s; const struct program *program = params->program; if (!program->uniforms) @@ -179,7 +180,7 @@ static int build_uniform_descs(struct pipeline *s, const struct pipeline_params .set = set_func, .desc = *uniform_desc, }; - if (!ngli_darray_push(&s->uniform_descs, &desc)) + if (!ngli_darray_push(&s_priv->uniform_descs, &desc)) return NGL_ERROR_MEMORY; } @@ -188,8 +189,10 @@ static int build_uniform_descs(struct pipeline *s, const struct pipeline_params static void set_uniforms(struct pipeline *s, struct glcontext *gl) { - const struct uniform_desc *descs = ngli_darray_data(&s->uniform_descs); - for (int i = 0; i < ngli_darray_count(&s->uniform_descs); i++) { + struct pipeline_gl *s_priv = (struct pipeline_gl *)s; + + const struct uniform_desc *descs = ngli_darray_data(&s_priv->uniform_descs); + for (int i = 0; i < ngli_darray_count(&s_priv->uniform_descs); i++) { const struct uniform_desc *uniform_desc = &descs[i]; if (uniform_desc->data) uniform_desc->set(gl, uniform_desc->location, uniform_desc->desc.count, uniform_desc->data); @@ -223,7 +226,7 @@ static int build_texture_descs(struct pipeline *s, const struct pipeline_params struct texture_desc desc = { .desc = *texture_desc, }; - if (!ngli_darray_push(&s->texture_descs, &desc)) + if (!ngli_darray_push(&s_priv->texture_descs, &desc)) return NGL_ERROR_MEMORY; } @@ -246,8 +249,8 @@ static void set_textures(struct pipeline *s, struct glcontext *gl) { struct pipeline_gl *s_priv = (struct pipeline_gl *)s; uint64_t texture_units = s_priv->used_texture_units; - const struct texture_desc *descs = ngli_darray_data(&s->texture_descs); - for (int i = 0; i < ngli_darray_count(&s->texture_descs); i++) { + const struct texture_desc *descs = ngli_darray_data(&s_priv->texture_descs); + for (int i = 0; i < ngli_darray_count(&s_priv->texture_descs); i++) { const struct texture_desc *texture_desc = &descs[i]; const struct texture *texture = texture_desc->texture; const struct texture_gl *texture_gl = (const struct texture_gl *)texture; @@ -284,8 +287,10 @@ static void set_textures(struct pipeline *s, struct glcontext *gl) static void set_buffers(struct pipeline *s, struct glcontext *gl) { - const struct buffer_desc *descs = ngli_darray_data(&s->buffer_descs); - for (int i = 0; i < ngli_darray_count(&s->buffer_descs); i++) { + struct pipeline_gl *s_priv = (struct pipeline_gl *)s; + + const struct buffer_desc *descs = ngli_darray_data(&s_priv->buffer_descs); + for (int i = 0; i < ngli_darray_count(&s_priv->buffer_descs); i++) { const struct buffer_desc *buffer_desc = &descs[i]; const struct buffer *buffer = buffer_desc->buffer; const struct buffer_gl *buffer_gl = (const struct buffer_gl *)buffer; @@ -295,6 +300,8 @@ static void set_buffers(struct pipeline *s, struct glcontext *gl) static int build_buffer_descs(struct pipeline *s, const struct pipeline_params *params) { + struct pipeline_gl *s_priv = (struct pipeline_gl *)s; + for (int i = 0; i < params->nb_buffers; i++) { const struct pipeline_buffer_desc *pipeline_buffer_desc = ¶ms->buffers_desc[i]; @@ -302,7 +309,7 @@ static int build_buffer_descs(struct pipeline *s, const struct pipeline_params * .type = ngli_type_get_gl_type(pipeline_buffer_desc->type), .desc = *pipeline_buffer_desc, }; - if (!ngli_darray_push(&s->buffer_descs, &desc)) + if (!ngli_darray_push(&s_priv->buffer_descs, &desc)) return NGL_ERROR_MEMORY; } @@ -311,8 +318,10 @@ static int build_buffer_descs(struct pipeline *s, const struct pipeline_params * static void set_vertex_attribs(const struct pipeline *s, struct glcontext *gl) { - const struct attribute_desc *descs = ngli_darray_data(&s->attribute_descs); - for (int i = 0; i < ngli_darray_count(&s->attribute_descs); i++) { + struct pipeline_gl *s_priv = (struct pipeline_gl *)s; + + const struct attribute_desc *descs = ngli_darray_data(&s_priv->attribute_descs); + for (int i = 0; i < ngli_darray_count(&s_priv->attribute_descs); i++) { const struct attribute_desc *attr_desc = &descs[i]; const struct buffer *buffer = attr_desc->buffer; const struct buffer_gl *buffer_gl = (const struct buffer_gl *)buffer; @@ -333,8 +342,10 @@ static void set_vertex_attribs(const struct pipeline *s, struct glcontext *gl) static void reset_vertex_attribs(const struct pipeline *s, struct glcontext *gl) { - const struct attribute_desc *descs = ngli_darray_data(&s->attribute_descs); - for (int i = 0; i < ngli_darray_count(&s->attribute_descs); i++) { + struct pipeline_gl *s_priv = (struct pipeline_gl *)s; + + const struct attribute_desc *descs = ngli_darray_data(&s_priv->attribute_descs); + for (int i = 0; i < ngli_darray_count(&s_priv->attribute_descs); i++) { const struct attribute_desc *attr_desc = &descs[i]; const GLuint location = attr_desc->desc.location; ngli_glDisableVertexAttribArray(gl, location); @@ -345,6 +356,7 @@ static void reset_vertex_attribs(const struct pipeline *s, struct glcontext *gl) static int build_attribute_descs(struct pipeline *s, const struct pipeline_params *params) { + struct pipeline_gl *s_priv = (struct pipeline_gl *)s; struct gctx_gl *gctx_gl = (struct gctx_gl *)s->gctx; struct glcontext *gl = gctx_gl->glcontext; @@ -359,10 +371,10 @@ static int build_attribute_descs(struct pipeline *s, const struct pipeline_param struct attribute_desc desc = { .desc = *pipeline_attribute_desc, }; - if (!ngli_darray_push(&s->attribute_descs, &desc)) + if (!ngli_darray_push(&s_priv->attribute_descs, &desc)) return NGL_ERROR_MEMORY; } - s->nb_unbound_attributes = params->nb_attributes; + s_priv->nb_unbound_attributes = params->nb_attributes; return 0; } @@ -379,8 +391,10 @@ static GLenum get_gl_indices_type(int indices_format) static void init_vertex_attribs(const struct pipeline *s, struct glcontext *gl) { - const struct attribute_desc *descs = ngli_darray_data(&s->attribute_descs); - for (int i = 0; i < ngli_darray_count(&s->attribute_descs); i++) { + struct pipeline_gl *s_priv = (struct pipeline_gl *)s; + + const struct attribute_desc *descs = ngli_darray_data(&s_priv->attribute_descs); + for (int i = 0; i < ngli_darray_count(&s_priv->attribute_descs); i++) { const struct attribute_desc *attr_desc = &descs[i]; const GLuint location = attr_desc->desc.location; ngli_glEnableVertexAttribArray(gl, location); @@ -447,14 +461,16 @@ struct pipeline *ngli_pipeline_gl_create(struct gctx *gctx) int ngli_pipeline_gl_init(struct pipeline *s, const struct pipeline_params *params) { + struct pipeline_gl *s_priv = (struct pipeline_gl *)s; + s->type = params->type; s->graphics = params->graphics; s->program = params->program; - ngli_darray_init(&s->uniform_descs, sizeof(struct uniform_desc), 0); - ngli_darray_init(&s->texture_descs, sizeof(struct texture_desc), 0); - ngli_darray_init(&s->buffer_descs, sizeof(struct buffer_desc), 0); - ngli_darray_init(&s->attribute_descs, sizeof(struct attribute_desc), 0); + ngli_darray_init(&s_priv->uniform_descs, sizeof(struct uniform_desc), 0); + ngli_darray_init(&s_priv->texture_descs, sizeof(struct texture_desc), 0); + ngli_darray_init(&s_priv->buffer_descs, sizeof(struct buffer_desc), 0); + ngli_darray_init(&s_priv->attribute_descs, sizeof(struct attribute_desc), 0); int ret; if ((ret = build_uniform_descs(s, params)) < 0 || @@ -479,30 +495,32 @@ int ngli_pipeline_gl_init(struct pipeline *s, const struct pipeline_params *para int ngli_pipeline_gl_set_resources(struct pipeline *s, const struct pipeline_resource_params *data_params) { - ngli_assert(ngli_darray_count(&s->attribute_descs) == data_params->nb_attributes); + struct pipeline_gl *s_priv = (struct pipeline_gl *)s; + + ngli_assert(ngli_darray_count(&s_priv->attribute_descs) == data_params->nb_attributes); for (int i = 0; i < data_params->nb_attributes; i++) { int ret = ngli_pipeline_gl_update_attribute(s, i, data_params->attributes[i]); if (ret < 0) return ret; } - ngli_assert(ngli_darray_count(&s->buffer_descs) == data_params->nb_buffers); + ngli_assert(ngli_darray_count(&s_priv->buffer_descs) == data_params->nb_buffers); for (int i = 0; i < data_params->nb_buffers; i++) { int ret = ngli_pipeline_gl_update_buffer(s, i, data_params->buffers[i]); if (ret < 0) return ret; } - ngli_assert(ngli_darray_count(&s->texture_descs) == data_params->nb_textures); + ngli_assert(ngli_darray_count(&s_priv->texture_descs) == data_params->nb_textures); for (int i = 0; i < data_params->nb_textures; i++) { int ret = ngli_pipeline_gl_update_texture(s, i, data_params->textures[i]); if (ret < 0) return ret; } - ngli_assert(ngli_darray_count(&s->uniform_descs) == data_params->nb_uniforms); + ngli_assert(ngli_darray_count(&s_priv->uniform_descs) == data_params->nb_uniforms); for (int i = 0; i < data_params->nb_uniforms; i++) { - struct uniform_desc *uniform_desc = ngli_darray_get(&s->uniform_descs, i); + struct uniform_desc *uniform_desc = ngli_darray_get(&s_priv->uniform_descs, i); ngli_assert(uniform_desc); const void *uniform_data = data_params->uniforms[i]; uniform_desc->data = uniform_data; @@ -523,14 +541,14 @@ int ngli_pipeline_gl_update_attribute(struct pipeline *s, int index, struct buff ngli_assert(s->type == NGLI_PIPELINE_TYPE_GRAPHICS); - struct attribute_desc *attr_desc = ngli_darray_get(&s->attribute_descs, index); + struct attribute_desc *attr_desc = ngli_darray_get(&s_priv->attribute_descs, index); ngli_assert(attr_desc); const struct buffer *current_buffer = attr_desc->buffer; if (!current_buffer && buffer) - s->nb_unbound_attributes--; + s_priv->nb_unbound_attributes--; else if (current_buffer && !buffer) - s->nb_unbound_attributes++; + s_priv->nb_unbound_attributes++; attr_desc->buffer = buffer; @@ -552,10 +570,12 @@ int ngli_pipeline_gl_update_attribute(struct pipeline *s, int index, struct buff int ngli_pipeline_gl_update_uniform(struct pipeline *s, int index, const void *data) { + struct pipeline_gl *s_priv = (struct pipeline_gl *)s; + if (index == -1) return NGL_ERROR_NOT_FOUND; - struct uniform_desc *uniform_desc = ngli_darray_get(&s->uniform_descs, index); + struct uniform_desc *uniform_desc = ngli_darray_get(&s_priv->uniform_descs, index); ngli_assert(uniform_desc); if (data) { @@ -573,10 +593,12 @@ int ngli_pipeline_gl_update_uniform(struct pipeline *s, int index, const void *d int ngli_pipeline_gl_update_texture(struct pipeline *s, int index, struct texture *texture) { + struct pipeline_gl *s_priv = (struct pipeline_gl *)s; + if (index == -1) return NGL_ERROR_NOT_FOUND; - struct texture_desc *texture_desc = ngli_darray_get(&s->texture_descs, index); + struct texture_desc *texture_desc = ngli_darray_get(&s_priv->texture_descs, index); ngli_assert(texture_desc); texture_desc->texture = texture; @@ -586,10 +608,12 @@ int ngli_pipeline_gl_update_texture(struct pipeline *s, int index, struct textur int ngli_pipeline_gl_update_buffer(struct pipeline *s, int index, struct buffer *buffer) { + struct pipeline_gl *s_priv = (struct pipeline_gl *)s; + if (index == -1) return NGL_ERROR_NOT_FOUND; - struct buffer_desc *buffer_desc = ngli_darray_get(&s->buffer_descs, index); + struct buffer_desc *buffer_desc = ngli_darray_get(&s_priv->buffer_descs, index); ngli_assert(buffer_desc); if (buffer) { @@ -611,6 +635,7 @@ int ngli_pipeline_gl_update_buffer(struct pipeline *s, int index, struct buffer void ngli_pipeline_gl_draw(struct pipeline *s, int nb_vertices, int nb_instances) { + struct pipeline_gl *s_priv = (struct pipeline_gl *)s; struct gctx *gctx = s->gctx; struct gctx_gl *gctx_gl = (struct gctx_gl *)gctx; struct glcontext *gl = gctx_gl->glcontext; @@ -625,7 +650,7 @@ void ngli_pipeline_gl_draw(struct pipeline *s, int nb_vertices, int nb_instances set_textures(s, gl); bind_vertex_attribs(s, gl); - if (s->nb_unbound_attributes) { + if (s_priv->nb_unbound_attributes) { LOG(ERROR, "pipeline has unbound vertex attributes"); return; } @@ -646,6 +671,7 @@ void ngli_pipeline_gl_draw(struct pipeline *s, int nb_vertices, int nb_instances void ngli_pipeline_gl_draw_indexed(struct pipeline *s, struct buffer *indices, int indices_format, int nb_indices, int nb_instances) { + struct pipeline_gl *s_priv = (struct pipeline_gl *)s; struct gctx *gctx = s->gctx; struct gctx_gl *gctx_gl = (struct gctx_gl *)gctx; struct glcontext *gl = gctx_gl->glcontext; @@ -660,7 +686,7 @@ void ngli_pipeline_gl_draw_indexed(struct pipeline *s, struct buffer *indices, i set_textures(s, gl); bind_vertex_attribs(s, gl); - if (s->nb_unbound_attributes) { + if (s_priv->nb_unbound_attributes) { LOG(ERROR, "pipeline has unbound vertex attributes"); return; } @@ -707,15 +733,15 @@ void ngli_pipeline_gl_freep(struct pipeline **sp) return; struct pipeline *s = *sp; - ngli_darray_reset(&s->uniform_descs); - ngli_darray_reset(&s->texture_descs); - ngli_darray_reset(&s->buffer_descs); - ngli_darray_reset(&s->attribute_descs); + struct pipeline_gl *s_priv = (struct pipeline_gl *)s; + ngli_darray_reset(&s_priv->uniform_descs); + ngli_darray_reset(&s_priv->texture_descs); + ngli_darray_reset(&s_priv->buffer_descs); + ngli_darray_reset(&s_priv->attribute_descs); struct gctx *gctx = s->gctx; struct gctx_gl *gctx_gl = (struct gctx_gl *)gctx; struct glcontext *gl = gctx_gl->glcontext; - struct pipeline_gl *s_priv = (struct pipeline_gl *)s; ngli_glDeleteVertexArrays(gl, 1, &s_priv->vao_id); ngli_freep(sp); diff --git a/libnodegl/pipeline_gl.h b/libnodegl/pipeline_gl.h index 4d2bd50011..0b1d9547e7 100644 --- a/libnodegl/pipeline_gl.h +++ b/libnodegl/pipeline_gl.h @@ -31,6 +31,12 @@ struct glcontext; struct pipeline_gl { struct pipeline parent; + struct darray uniform_descs; + struct darray texture_descs; + struct darray buffer_descs; + struct darray attribute_descs; + int nb_unbound_attributes; + uint64_t used_texture_units; GLuint vao_id; }; From 00a7cda228a48eea1eb3581e2fbaf3a3cbf394b2 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 15 Oct 2020 16:23:08 +0200 Subject: [PATCH 161/388] pipeline_gl: rename *_desc structures, variables and fields to *_binding --- libnodegl/pipeline_gl.c | 184 ++++++++++++++++++++-------------------- libnodegl/pipeline_gl.h | 8 +- 2 files changed, 96 insertions(+), 96 deletions(-) diff --git a/libnodegl/pipeline_gl.c b/libnodegl/pipeline_gl.c index 03fcc258f9..471a6f4463 100644 --- a/libnodegl/pipeline_gl.c +++ b/libnodegl/pipeline_gl.c @@ -36,25 +36,25 @@ typedef void (*set_uniform_func)(struct glcontext *gl, GLint location, int count, const void *data); -struct uniform_desc { +struct uniform_binding { GLuint location; set_uniform_func set; struct pipeline_uniform_desc desc; const void *data; }; -struct texture_desc { +struct texture_binding { struct pipeline_texture_desc desc; const struct texture *texture; }; -struct buffer_desc { +struct buffer_binding { GLuint type; struct pipeline_buffer_desc desc; const struct buffer *buffer; }; -struct attribute_desc { +struct attribute_binding { struct pipeline_attribute_desc desc; const struct buffer *buffer; }; @@ -147,7 +147,7 @@ static const set_uniform_func set_uniform_func_map[NGLI_TYPE_NB] = { [NGLI_TYPE_MAT4] = set_uniform_mat4fv, }; -static int build_uniform_descs(struct pipeline *s, const struct pipeline_params *params) +static int build_uniform_bindings(struct pipeline *s, const struct pipeline_params *params) { struct pipeline_gl *s_priv = (struct pipeline_gl *)s; const struct program *program = params->program; @@ -175,12 +175,12 @@ static int build_uniform_descs(struct pipeline *s, const struct pipeline_params const set_uniform_func set_func = set_uniform_func_map[uniform_desc->type]; ngli_assert(set_func); - struct uniform_desc desc = { + struct uniform_binding binding = { .location = info->location, .set = set_func, .desc = *uniform_desc, }; - if (!ngli_darray_push(&s_priv->uniform_descs, &desc)) + if (!ngli_darray_push(&s_priv->uniform_bindings, &binding)) return NGL_ERROR_MEMORY; } @@ -191,15 +191,15 @@ static void set_uniforms(struct pipeline *s, struct glcontext *gl) { struct pipeline_gl *s_priv = (struct pipeline_gl *)s; - const struct uniform_desc *descs = ngli_darray_data(&s_priv->uniform_descs); - for (int i = 0; i < ngli_darray_count(&s_priv->uniform_descs); i++) { - const struct uniform_desc *uniform_desc = &descs[i]; - if (uniform_desc->data) - uniform_desc->set(gl, uniform_desc->location, uniform_desc->desc.count, uniform_desc->data); + const struct uniform_binding *bindings = ngli_darray_data(&s_priv->uniform_bindings); + for (int i = 0; i < ngli_darray_count(&s_priv->uniform_bindings); i++) { + const struct uniform_binding *uniform_binding = &bindings[i]; + if (uniform_binding->data) + uniform_binding->set(gl, uniform_binding->location, uniform_binding->desc.count, uniform_binding->data); } } -static int build_texture_descs(struct pipeline *s, const struct pipeline_params *params) +static int build_texture_bindings(struct pipeline *s, const struct pipeline_params *params) { struct pipeline_gl *s_priv = (struct pipeline_gl *)s; @@ -223,10 +223,10 @@ static int build_texture_descs(struct pipeline *s, const struct pipeline_params s_priv->used_texture_units |= 1ULL << texture_desc->binding; } - struct texture_desc desc = { + struct texture_binding binding = { .desc = *texture_desc, }; - if (!ngli_darray_push(&s_priv->texture_descs, &desc)) + if (!ngli_darray_push(&s_priv->texture_bindings, &binding)) return NGL_ERROR_MEMORY; } @@ -249,13 +249,13 @@ static void set_textures(struct pipeline *s, struct glcontext *gl) { struct pipeline_gl *s_priv = (struct pipeline_gl *)s; uint64_t texture_units = s_priv->used_texture_units; - const struct texture_desc *descs = ngli_darray_data(&s_priv->texture_descs); - for (int i = 0; i < ngli_darray_count(&s_priv->texture_descs); i++) { - const struct texture_desc *texture_desc = &descs[i]; - const struct texture *texture = texture_desc->texture; + const struct texture_binding *bindings = ngli_darray_data(&s_priv->texture_bindings); + for (int i = 0; i < ngli_darray_count(&s_priv->texture_bindings); i++) { + const struct texture_binding *texture_binding = &bindings[i]; + const struct texture *texture = texture_binding->texture; const struct texture_gl *texture_gl = (const struct texture_gl *)texture; - if (texture_desc->desc.type == NGLI_TYPE_IMAGE_2D) { + if (texture_binding->desc.type == NGLI_TYPE_IMAGE_2D) { GLuint texture_id = 0; GLenum access = GL_READ_WRITE; GLenum internal_format = GL_RGBA8; @@ -265,12 +265,12 @@ static void set_textures(struct pipeline *s, struct glcontext *gl) access = ngli_texture_get_gl_access(params->access); internal_format = texture_gl->internal_format; } - ngli_glBindImageTexture(gl, texture_desc->desc.binding, texture_id, 0, GL_FALSE, 0, access, internal_format); + ngli_glBindImageTexture(gl, texture_binding->desc.binding, texture_id, 0, GL_FALSE, 0, access, internal_format); } else { const int texture_index = acquire_next_available_texture_unit(&texture_units); if (texture_index < 0) return; - ngli_glUniform1i(gl, texture_desc->desc.location, texture_index); + ngli_glUniform1i(gl, texture_binding->desc.location, texture_index); ngli_glActiveTexture(gl, GL_TEXTURE0 + texture_index); if (texture) { ngli_glBindTexture(gl, texture_gl->target, texture_gl->id); @@ -289,27 +289,27 @@ static void set_buffers(struct pipeline *s, struct glcontext *gl) { struct pipeline_gl *s_priv = (struct pipeline_gl *)s; - const struct buffer_desc *descs = ngli_darray_data(&s_priv->buffer_descs); - for (int i = 0; i < ngli_darray_count(&s_priv->buffer_descs); i++) { - const struct buffer_desc *buffer_desc = &descs[i]; - const struct buffer *buffer = buffer_desc->buffer; + const struct buffer_binding *bindings = ngli_darray_data(&s_priv->buffer_bindings); + for (int i = 0; i < ngli_darray_count(&s_priv->buffer_bindings); i++) { + const struct buffer_binding *buffer_binding = &bindings[i]; + const struct buffer *buffer = buffer_binding->buffer; const struct buffer_gl *buffer_gl = (const struct buffer_gl *)buffer; - ngli_glBindBufferBase(gl, buffer_desc->type, buffer_desc->desc.binding, buffer_gl->id); + ngli_glBindBufferBase(gl, buffer_binding->type, buffer_binding->desc.binding, buffer_gl->id); } } -static int build_buffer_descs(struct pipeline *s, const struct pipeline_params *params) +static int build_buffer_bindings(struct pipeline *s, const struct pipeline_params *params) { struct pipeline_gl *s_priv = (struct pipeline_gl *)s; for (int i = 0; i < params->nb_buffers; i++) { const struct pipeline_buffer_desc *pipeline_buffer_desc = ¶ms->buffers_desc[i]; - struct buffer_desc desc = { + struct buffer_binding binding = { .type = ngli_type_get_gl_type(pipeline_buffer_desc->type), .desc = *pipeline_buffer_desc, }; - if (!ngli_darray_push(&s_priv->buffer_descs, &desc)) + if (!ngli_darray_push(&s_priv->buffer_bindings, &binding)) return NGL_ERROR_MEMORY; } @@ -320,22 +320,22 @@ static void set_vertex_attribs(const struct pipeline *s, struct glcontext *gl) { struct pipeline_gl *s_priv = (struct pipeline_gl *)s; - const struct attribute_desc *descs = ngli_darray_data(&s_priv->attribute_descs); - for (int i = 0; i < ngli_darray_count(&s_priv->attribute_descs); i++) { - const struct attribute_desc *attr_desc = &descs[i]; - const struct buffer *buffer = attr_desc->buffer; + const struct attribute_binding *bindings = ngli_darray_data(&s_priv->attribute_bindings); + for (int i = 0; i < ngli_darray_count(&s_priv->attribute_bindings); i++) { + const struct attribute_binding *attribute_binding = &bindings[i]; + const struct buffer *buffer = attribute_binding->buffer; const struct buffer_gl *buffer_gl = (const struct buffer_gl *)buffer; - const GLuint location = attr_desc->desc.location; - const GLuint size = ngli_format_get_nb_comp(attr_desc->desc.format); - const GLint stride = attr_desc->desc.stride; + const GLuint location = attribute_binding->desc.location; + const GLuint size = ngli_format_get_nb_comp(attribute_binding->desc.format); + const GLint stride = attribute_binding->desc.stride; ngli_glEnableVertexAttribArray(gl, location); - if ((gl->features & NGLI_FEATURE_INSTANCED_ARRAY) && attr_desc->desc.rate > 0) - ngli_glVertexAttribDivisor(gl, location, attr_desc->desc.rate); + if ((gl->features & NGLI_FEATURE_INSTANCED_ARRAY) && attribute_binding->desc.rate > 0) + ngli_glVertexAttribDivisor(gl, location, attribute_binding->desc.rate); if (buffer_gl) { ngli_glBindBuffer(gl, GL_ARRAY_BUFFER, buffer_gl->id); - ngli_glVertexAttribPointer(gl, location, size, GL_FLOAT, GL_FALSE, stride, (void*)(uintptr_t)(attr_desc->desc.offset)); + ngli_glVertexAttribPointer(gl, location, size, GL_FLOAT, GL_FALSE, stride, (void*)(uintptr_t)(attribute_binding->desc.offset)); } } } @@ -344,17 +344,17 @@ static void reset_vertex_attribs(const struct pipeline *s, struct glcontext *gl) { struct pipeline_gl *s_priv = (struct pipeline_gl *)s; - const struct attribute_desc *descs = ngli_darray_data(&s_priv->attribute_descs); - for (int i = 0; i < ngli_darray_count(&s_priv->attribute_descs); i++) { - const struct attribute_desc *attr_desc = &descs[i]; - const GLuint location = attr_desc->desc.location; + const struct attribute_binding *bindings = ngli_darray_data(&s_priv->attribute_bindings); + for (int i = 0; i < ngli_darray_count(&s_priv->attribute_bindings); i++) { + const struct attribute_binding *attribute_binding = &bindings[i]; + const GLuint location = attribute_binding->desc.location; ngli_glDisableVertexAttribArray(gl, location); if (gl->features & NGLI_FEATURE_INSTANCED_ARRAY) ngli_glVertexAttribDivisor(gl, location, 0); } } -static int build_attribute_descs(struct pipeline *s, const struct pipeline_params *params) +static int build_attribute_bindings(struct pipeline *s, const struct pipeline_params *params) { struct pipeline_gl *s_priv = (struct pipeline_gl *)s; struct gctx_gl *gctx_gl = (struct gctx_gl *)s->gctx; @@ -368,10 +368,10 @@ static int build_attribute_descs(struct pipeline *s, const struct pipeline_param return NGL_ERROR_UNSUPPORTED; } - struct attribute_desc desc = { + struct attribute_binding desc = { .desc = *pipeline_attribute_desc, }; - if (!ngli_darray_push(&s_priv->attribute_descs, &desc)) + if (!ngli_darray_push(&s_priv->attribute_bindings, &desc)) return NGL_ERROR_MEMORY; } s_priv->nb_unbound_attributes = params->nb_attributes; @@ -393,13 +393,13 @@ static void init_vertex_attribs(const struct pipeline *s, struct glcontext *gl) { struct pipeline_gl *s_priv = (struct pipeline_gl *)s; - const struct attribute_desc *descs = ngli_darray_data(&s_priv->attribute_descs); - for (int i = 0; i < ngli_darray_count(&s_priv->attribute_descs); i++) { - const struct attribute_desc *attr_desc = &descs[i]; - const GLuint location = attr_desc->desc.location; + const struct attribute_binding *descs = ngli_darray_data(&s_priv->attribute_bindings); + for (int i = 0; i < ngli_darray_count(&s_priv->attribute_bindings); i++) { + const struct attribute_binding *attribute_binding = &descs[i]; + const GLuint location = attribute_binding->desc.location; ngli_glEnableVertexAttribArray(gl, location); - if ((gl->features & NGLI_FEATURE_INSTANCED_ARRAY) && attr_desc->desc.rate > 0) - ngli_glVertexAttribDivisor(gl, location, attr_desc->desc.rate); + if ((gl->features & NGLI_FEATURE_INSTANCED_ARRAY) && attribute_binding->desc.rate > 0) + ngli_glVertexAttribDivisor(gl, location, attribute_binding->desc.rate); } } @@ -424,7 +424,7 @@ static int pipeline_graphics_init(struct pipeline *s, const struct pipeline_para struct gctx_gl *gctx_gl = (struct gctx_gl *)s->gctx; struct glcontext *gl = gctx_gl->glcontext; - int ret = build_attribute_descs(s, params); + int ret = build_attribute_bindings(s, params); if (ret < 0) return ret; @@ -467,15 +467,15 @@ int ngli_pipeline_gl_init(struct pipeline *s, const struct pipeline_params *para s->graphics = params->graphics; s->program = params->program; - ngli_darray_init(&s_priv->uniform_descs, sizeof(struct uniform_desc), 0); - ngli_darray_init(&s_priv->texture_descs, sizeof(struct texture_desc), 0); - ngli_darray_init(&s_priv->buffer_descs, sizeof(struct buffer_desc), 0); - ngli_darray_init(&s_priv->attribute_descs, sizeof(struct attribute_desc), 0); + ngli_darray_init(&s_priv->uniform_bindings, sizeof(struct uniform_binding), 0); + ngli_darray_init(&s_priv->texture_bindings, sizeof(struct texture_binding), 0); + ngli_darray_init(&s_priv->buffer_bindings, sizeof(struct buffer_binding), 0); + ngli_darray_init(&s_priv->attribute_bindings, sizeof(struct attribute_binding), 0); int ret; - if ((ret = build_uniform_descs(s, params)) < 0 || - (ret = build_texture_descs(s, params)) < 0 || - (ret = build_buffer_descs(s, params)) < 0) + if ((ret = build_uniform_bindings(s, params)) < 0 || + (ret = build_texture_bindings(s, params)) < 0 || + (ret = build_buffer_bindings(s, params)) < 0) return ret; if (params->type == NGLI_PIPELINE_TYPE_GRAPHICS) { @@ -497,33 +497,33 @@ int ngli_pipeline_gl_set_resources(struct pipeline *s, const struct pipeline_res { struct pipeline_gl *s_priv = (struct pipeline_gl *)s; - ngli_assert(ngli_darray_count(&s_priv->attribute_descs) == data_params->nb_attributes); + ngli_assert(ngli_darray_count(&s_priv->attribute_bindings) == data_params->nb_attributes); for (int i = 0; i < data_params->nb_attributes; i++) { int ret = ngli_pipeline_gl_update_attribute(s, i, data_params->attributes[i]); if (ret < 0) return ret; } - ngli_assert(ngli_darray_count(&s_priv->buffer_descs) == data_params->nb_buffers); + ngli_assert(ngli_darray_count(&s_priv->buffer_bindings) == data_params->nb_buffers); for (int i = 0; i < data_params->nb_buffers; i++) { int ret = ngli_pipeline_gl_update_buffer(s, i, data_params->buffers[i]); if (ret < 0) return ret; } - ngli_assert(ngli_darray_count(&s_priv->texture_descs) == data_params->nb_textures); + ngli_assert(ngli_darray_count(&s_priv->texture_bindings) == data_params->nb_textures); for (int i = 0; i < data_params->nb_textures; i++) { int ret = ngli_pipeline_gl_update_texture(s, i, data_params->textures[i]); if (ret < 0) return ret; } - ngli_assert(ngli_darray_count(&s_priv->uniform_descs) == data_params->nb_uniforms); + ngli_assert(ngli_darray_count(&s_priv->uniform_bindings) == data_params->nb_uniforms); for (int i = 0; i < data_params->nb_uniforms; i++) { - struct uniform_desc *uniform_desc = ngli_darray_get(&s_priv->uniform_descs, i); - ngli_assert(uniform_desc); + struct uniform_binding *uniform_binding = ngli_darray_get(&s_priv->uniform_bindings, i); + ngli_assert(uniform_binding); const void *uniform_data = data_params->uniforms[i]; - uniform_desc->data = uniform_data; + uniform_binding->data = uniform_data; } return 0; @@ -541,28 +541,28 @@ int ngli_pipeline_gl_update_attribute(struct pipeline *s, int index, struct buff ngli_assert(s->type == NGLI_PIPELINE_TYPE_GRAPHICS); - struct attribute_desc *attr_desc = ngli_darray_get(&s_priv->attribute_descs, index); - ngli_assert(attr_desc); + struct attribute_binding *attribute_binding = ngli_darray_get(&s_priv->attribute_bindings, index); + ngli_assert(attribute_binding); - const struct buffer *current_buffer = attr_desc->buffer; + const struct buffer *current_buffer = attribute_binding->buffer; if (!current_buffer && buffer) s_priv->nb_unbound_attributes--; else if (current_buffer && !buffer) s_priv->nb_unbound_attributes++; - attr_desc->buffer = buffer; + attribute_binding->buffer = buffer; if (!buffer) return 0; if (gl->features & NGLI_FEATURE_VERTEX_ARRAY_OBJECT) { - const GLuint location = attr_desc->desc.location; - const GLuint size = ngli_format_get_nb_comp(attr_desc->desc.format); - const GLint stride = attr_desc->desc.stride; + const GLuint location = attribute_binding->desc.location; + const GLuint size = ngli_format_get_nb_comp(attribute_binding->desc.format); + const GLint stride = attribute_binding->desc.stride; struct buffer_gl *buffer_gl = (struct buffer_gl *)buffer; ngli_glBindVertexArray(gl, s_priv->vao_id); ngli_glBindBuffer(gl, GL_ARRAY_BUFFER, buffer_gl->id); - ngli_glVertexAttribPointer(gl, location, size, GL_FLOAT, GL_FALSE, stride, (void*)(uintptr_t)(attr_desc->desc.offset)); + ngli_glVertexAttribPointer(gl, location, size, GL_FLOAT, GL_FALSE, stride, (void*)(uintptr_t)(attribute_binding->desc.offset)); } return 0; @@ -575,8 +575,8 @@ int ngli_pipeline_gl_update_uniform(struct pipeline *s, int index, const void *d if (index == -1) return NGL_ERROR_NOT_FOUND; - struct uniform_desc *uniform_desc = ngli_darray_get(&s_priv->uniform_descs, index); - ngli_assert(uniform_desc); + struct uniform_binding *uniform_binding = ngli_darray_get(&s_priv->uniform_bindings, index); + ngli_assert(uniform_binding); if (data) { struct gctx *gctx = s->gctx; @@ -584,9 +584,9 @@ int ngli_pipeline_gl_update_uniform(struct pipeline *s, int index, const void *d struct glcontext *gl = gctx_gl->glcontext; struct program_gl *program_gl = (struct program_gl *)s->program; ngli_glstate_use_program(gctx, program_gl->id); - uniform_desc->set(gl, uniform_desc->location, uniform_desc->desc.count, data); + uniform_binding->set(gl, uniform_binding->location, uniform_binding->desc.count, data); } - uniform_desc->data = NULL; + uniform_binding->data = NULL; return 0; } @@ -598,10 +598,10 @@ int ngli_pipeline_gl_update_texture(struct pipeline *s, int index, struct textur if (index == -1) return NGL_ERROR_NOT_FOUND; - struct texture_desc *texture_desc = ngli_darray_get(&s_priv->texture_descs, index); - ngli_assert(texture_desc); + struct texture_binding *texture_binding = ngli_darray_get(&s_priv->texture_bindings, index); + ngli_assert(texture_binding); - texture_desc->texture = texture; + texture_binding->texture = texture; return 0; } @@ -613,22 +613,22 @@ int ngli_pipeline_gl_update_buffer(struct pipeline *s, int index, struct buffer if (index == -1) return NGL_ERROR_NOT_FOUND; - struct buffer_desc *buffer_desc = ngli_darray_get(&s_priv->buffer_descs, index); - ngli_assert(buffer_desc); + struct buffer_binding *buffer_binding = ngli_darray_get(&s_priv->buffer_bindings, index); + ngli_assert(buffer_binding); if (buffer) { struct gctx_gl *gctx_gl = (struct gctx_gl *)s->gctx; struct glcontext *gl = gctx_gl->glcontext; const struct limits *limits = &gl->limits; - if (buffer_desc->type == NGLI_TYPE_UNIFORM_BUFFER && + if (buffer_binding->type == NGLI_TYPE_UNIFORM_BUFFER && buffer->size > limits->max_uniform_block_size) { LOG(ERROR, "buffer %s size (%d) exceeds max uniform block size (%d)", - buffer_desc->desc.name, buffer->size, limits->max_uniform_block_size); + buffer_binding->desc.name, buffer->size, limits->max_uniform_block_size); return NGL_ERROR_LIMIT_EXCEEDED; } } - buffer_desc->buffer = buffer; + buffer_binding->buffer = buffer; return 0; } @@ -734,10 +734,10 @@ void ngli_pipeline_gl_freep(struct pipeline **sp) struct pipeline *s = *sp; struct pipeline_gl *s_priv = (struct pipeline_gl *)s; - ngli_darray_reset(&s_priv->uniform_descs); - ngli_darray_reset(&s_priv->texture_descs); - ngli_darray_reset(&s_priv->buffer_descs); - ngli_darray_reset(&s_priv->attribute_descs); + ngli_darray_reset(&s_priv->uniform_bindings); + ngli_darray_reset(&s_priv->texture_bindings); + ngli_darray_reset(&s_priv->buffer_bindings); + ngli_darray_reset(&s_priv->attribute_bindings); struct gctx *gctx = s->gctx; struct gctx_gl *gctx_gl = (struct gctx_gl *)gctx; diff --git a/libnodegl/pipeline_gl.h b/libnodegl/pipeline_gl.h index 0b1d9547e7..0c1c95912d 100644 --- a/libnodegl/pipeline_gl.h +++ b/libnodegl/pipeline_gl.h @@ -31,10 +31,10 @@ struct glcontext; struct pipeline_gl { struct pipeline parent; - struct darray uniform_descs; - struct darray texture_descs; - struct darray buffer_descs; - struct darray attribute_descs; + struct darray uniform_bindings; // uniform_binding + struct darray texture_bindings; // texture_binding + struct darray buffer_bindings; // buffer_binding + struct darray attribute_bindings; // attribute_binding int nb_unbound_attributes; uint64_t used_texture_units; From 1acbdd4eac1d71e33ccc77e7e74605e29f2fd930 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 9 Oct 2020 14:11:53 +0200 Subject: [PATCH 162/388] tools/player: only enable blending on the UI elements --- ngl-tools/player.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/ngl-tools/player.c b/ngl-tools/player.c index a2812cc147..948d57d52a 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -348,16 +348,18 @@ static struct ngl_node *add_progress_bar(struct ngl_node *scene) struct ngl_node *v_duration = ngl_node_create(NGL_NODE_UNIFORMFLOAT); struct ngl_node *v_opacity = ngl_node_create(NGL_NODE_UNIFORMFLOAT); struct ngl_node *coord = ngl_node_create(NGL_NODE_IOVEC2); - struct ngl_node *group = ngl_node_create(NGL_NODE_GROUP); + struct ngl_node *ui_group = ngl_node_create(NGL_NODE_GROUP); struct ngl_node *gcfg = ngl_node_create(NGL_NODE_GRAPHICCONFIG); + struct ngl_node *group = ngl_node_create(NGL_NODE_GROUP); if (!text || !quad || !program || !render || !time || !v_duration || !v_opacity || !coord || !group || !gcfg) { - ngl_node_unrefp(&gcfg); + ngl_node_unrefp(&group); goto end; } - struct ngl_node *children[] = {scene, render, text}; + struct ngl_node *ui_children[] = {render, text}; + struct ngl_node *children[] = {scene, gcfg}; ngl_node_param_set(quad, "corner", bar_corner); ngl_node_param_set(quad, "width", bar_width); @@ -376,15 +378,17 @@ static struct ngl_node *add_progress_bar(struct ngl_node *scene) ngl_node_param_set(render, "frag_resources", "duration", v_duration); ngl_node_param_set(render, "frag_resources", "opacity", v_opacity); - ngl_node_param_add(group, "children", ARRAY_NB(children), children); + ngl_node_param_add(ui_group, "children", ARRAY_NB(ui_children), ui_children); - ngl_node_param_set(gcfg, "child", group); + ngl_node_param_set(gcfg, "child", ui_group); ngl_node_param_set(gcfg, "blend", 1); ngl_node_param_set(gcfg, "blend_src_factor", "src_alpha"); ngl_node_param_set(gcfg, "blend_dst_factor", "one_minus_src_alpha"); ngl_node_param_set(gcfg, "blend_src_factor_a", "zero"); ngl_node_param_set(gcfg, "blend_dst_factor_a", "one"); + ngl_node_param_add(group, "children", ARRAY_NB(children), children); + ngl_node_param_set(text, "box_corner", text_corner); ngl_node_param_set(text, "box_width", text_width); ngl_node_param_set(text, "box_height", text_height); @@ -404,9 +408,9 @@ static struct ngl_node *add_progress_bar(struct ngl_node *scene) ngl_node_unrefp(&v_duration); ngl_node_unrefp(&v_opacity); ngl_node_unrefp(&coord); - ngl_node_unrefp(&group); + ngl_node_unrefp(&gcfg); - return gcfg; + return group; } static int set_scene(struct ngl_node *scene) From 9acb0473fe0a04720ed9a6234d0c391c058edbc4 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 14 Oct 2020 11:09:07 +0200 Subject: [PATCH 163/388] hud, text: bind rendertarget if needed Forgotten in 7f8a6f888fb0e6f464a179f4c42c755b60da5656. --- libnodegl/node_hud.c | 11 +++++++++++ libnodegl/node_text.c | 12 ++++++++++++ 2 files changed, 23 insertions(+) diff --git a/libnodegl/node_hud.c b/libnodegl/node_hud.c index 6718a98110..edcbf3bbe5 100644 --- a/libnodegl/node_hud.c +++ b/libnodegl/node_hud.c @@ -1414,6 +1414,17 @@ static void hud_draw(struct ngl_node *node) if (ret < 0) return; + if (ctx->bind_current_rendertarget) { + struct gctx *gctx = ctx->gctx; + ngli_gctx_set_rendertarget(gctx, ctx->current_rendertarget); + if (ctx->clear_current_rendertarget) { + ngli_gctx_clear_color(gctx); + ngli_gctx_clear_depth_stencil(gctx); + } + ctx->bind_current_rendertarget = 0; + ctx->clear_current_rendertarget = 0; + } + const float *modelview_matrix = ngli_darray_tail(&ctx->modelview_matrix_stack); const float *projection_matrix = ngli_darray_tail(&ctx->projection_matrix_stack); ngli_pipeline_update_uniform(s->pipeline, s->modelview_matrix_index, modelview_matrix); diff --git a/libnodegl/node_text.c b/libnodegl/node_text.c index e72ce294bd..ea6abab970 100644 --- a/libnodegl/node_text.c +++ b/libnodegl/node_text.c @@ -26,6 +26,7 @@ #include "nodes.h" #include "darray.h" #include "drawutils.h" +#include "gctx.h" #include "log.h" #include "math_utils.h" #include "pgcache.h" @@ -675,6 +676,17 @@ static void text_draw(struct ngl_node *node) struct pipeline_desc *descs = ngli_darray_data(&s->pipeline_descs); struct pipeline_desc *desc = &descs[ctx->rnode_pos->id]; + if (ctx->bind_current_rendertarget) { + struct gctx *gctx = ctx->gctx; + ngli_gctx_set_rendertarget(gctx, ctx->current_rendertarget); + if (ctx->clear_current_rendertarget) { + ngli_gctx_clear_color(gctx); + ngli_gctx_clear_depth_stencil(gctx); + } + ctx->bind_current_rendertarget = 0; + ctx->clear_current_rendertarget = 0; + } + struct pipeline_subdesc *bg_desc = &desc->bg; ngli_pipeline_update_uniform(bg_desc->pipeline, bg_desc->modelview_matrix_index, modelview_matrix); ngli_pipeline_update_uniform(bg_desc->pipeline, bg_desc->projection_matrix_index, projection_matrix); From f440ee665745a98f7f228d59aa00c25ab31c3783 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 5 Oct 2020 11:50:56 +0200 Subject: [PATCH 164/388] gctx_gl: move rendertarget field --- libnodegl/gctx_gl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libnodegl/gctx_gl.h b/libnodegl/gctx_gl.h index a34945878c..d61d3afc88 100644 --- a/libnodegl/gctx_gl.h +++ b/libnodegl/gctx_gl.h @@ -44,9 +44,9 @@ struct gctx_gl { struct gctx parent; struct glcontext *glcontext; struct glstate glstate; - struct rendertarget *rendertarget; struct graphicstate default_graphicstate; struct rendertarget_desc default_rendertarget_desc; + struct rendertarget *rendertarget; int viewport[4]; int scissor[4]; float clear_color[4]; From afbbabc899d7eb5f9c9698e1b70f14d1898ca9cc Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 12 Oct 2020 14:29:30 +0200 Subject: [PATCH 165/388] tests/rtt: specify clear color The RTT node will stop inheriting from the parent clear color, thus, we need to specify it where it is needed. --- tests/rtt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/rtt.py b/tests/rtt.py index 8c3bf1740c..500fea068e 100644 --- a/tests/rtt.py +++ b/tests/rtt.py @@ -139,7 +139,8 @@ def _get_rtt_scene(cfg, features='depth', texture_ds_format=None, samples=0, mip [texture], features=features, depth_texture=texture_depth, - samples=samples + samples=samples, + clear_color=(0, 0, 0, 1), ) quad = ngl.Quad((-1, -1, 0), (2, 0, 0), (0, 2, 0)) From 2e5d8c34836fd0c2199430154d3fc61bd1336919 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 12 Oct 2020 14:32:24 +0200 Subject: [PATCH 166/388] tests/texture: specify clear color in texture_scissor test The RTT node will stop inheriting from the parent clear color, thus, we need to specify it where it is needed. --- tests/texture.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/texture.py b/tests/texture.py index 82bae83084..5639c2de31 100644 --- a/tests/texture.py +++ b/tests/texture.py @@ -204,7 +204,7 @@ def texture_scissor(cfg): render.update_frag_resources(color=color) graphic_config = ngl.GraphicConfig(render, scissor_test=True, scissor=(32, 32, 32, 32)) texture = ngl.Texture2D(width=64, height=64) - rtt = ngl.RenderToTexture(graphic_config, [texture]) + rtt = ngl.RenderToTexture(graphic_config, [texture], clear_color=(0, 0, 0, 1)) program = ngl.Program(vertex=cfg.get_vert('texture'), fragment=cfg.get_frag('texture')) program.update_vert_out_vars(var_tex0_coord=ngl.IOVec2(), var_uvcoord=ngl.IOVec2()) From 50c23b5ad921b5ee0364c849eb9fda900d7ff12c Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 12 Oct 2020 10:25:27 +0200 Subject: [PATCH 167/388] rtt: stop inheriting from parent clear color Makes the rtt clear color default to (0, 0, 0, 0). --- libnodegl/doc/libnodegl.md | 2 +- libnodegl/node_rtt.c | 17 ++++------------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/libnodegl/doc/libnodegl.md b/libnodegl/doc/libnodegl.md index 9d06e6580e..f1f760adaf 100644 --- a/libnodegl/doc/libnodegl.md +++ b/libnodegl/doc/libnodegl.md @@ -455,7 +455,7 @@ Parameter | Live-chg. | Type | Description | Default `color_textures` | | [`NodeList`](#parameter-types) ([Texture2D](#texture2d), [TextureCube](#texturecube)) | destination color texture | `depth_texture` | | [`Node`](#parameter-types) ([Texture2D](#texture2d)) | destination depth (and potentially combined stencil) texture | `samples` | | [`int`](#parameter-types) | number of samples used for multisampling anti-aliasing | `0` -`clear_color` | | [`vec4`](#parameter-types) | color used to clear the `color_texture` | (`-1`,`-1`,`-1`,`-1`) +`clear_color` | | [`vec4`](#parameter-types) | color used to clear the `color_texture` | (`0`,`0`,`0`,`0`) `features` | | [`framebuffer_features`](#framebuffer_features-choices) | framebuffer feature mask | `0` `vflip` | | [`bool`](#parameter-types) | apply a vertical flip to `color_texture` and `depth_texture` transformation matrices to match the `node.gl` uv coordinates system | `1` diff --git a/libnodegl/node_rtt.c b/libnodegl/node_rtt.c index 3244afdc28..3f3c239404 100644 --- a/libnodegl/node_rtt.c +++ b/libnodegl/node_rtt.c @@ -41,7 +41,6 @@ struct rtt_priv { int features; int vflip; - int use_clear_color; int invalidate_depth_stencil; int width; int height; @@ -54,7 +53,6 @@ struct rtt_priv { struct texture *ms_depth; }; -#define DEFAULT_CLEAR_COLOR {-1.0f, -1.0f, -1.0f, -1.0f} #define FEATURE_DEPTH (1 << 0) #define FEATURE_STENCIL (1 << 1) #define FEATURE_NO_CLEAR (1 << 2) @@ -83,7 +81,7 @@ static const struct node_param rtt_params[] = { .desc=NGLI_DOCSTRING("destination depth (and potentially combined stencil) texture")}, {"samples", PARAM_TYPE_INT, OFFSET(samples), .desc=NGLI_DOCSTRING("number of samples used for multisampling anti-aliasing")}, - {"clear_color", PARAM_TYPE_VEC4, OFFSET(clear_color), {.vec=DEFAULT_CLEAR_COLOR}, + {"clear_color", PARAM_TYPE_VEC4, OFFSET(clear_color), .desc=NGLI_DOCSTRING("color used to clear the `color_texture`")}, {"features", PARAM_TYPE_FLAGS, OFFSET(features), .choices=&feature_choices, @@ -113,9 +111,6 @@ static int rtt_init(struct ngl_node *node) } } - static const float clear_color[4] = DEFAULT_CLEAR_COLOR; - s->use_clear_color = memcmp(s->clear_color, clear_color, sizeof(s->clear_color)); - return 0; } @@ -386,10 +381,8 @@ static void rtt_draw(struct ngl_node *node) ngli_gctx_set_viewport(gctx, vp); float prev_clear_color[4] = {0}; - if (s->use_clear_color) { - ngli_gctx_get_clear_color(gctx, prev_clear_color); - ngli_gctx_set_clear_color(gctx, s->clear_color); - } + ngli_gctx_get_clear_color(gctx, prev_clear_color); + ngli_gctx_set_clear_color(gctx, s->clear_color); struct rendertarget *prev_rendertarget = ctx->current_rendertarget; int prev_clear = ctx->clear_current_rendertarget; @@ -421,9 +414,7 @@ static void rtt_draw(struct ngl_node *node) ctx->clear_current_rendertarget = prev_clear; ngli_gctx_set_viewport(gctx, prev_vp); - - if (s->use_clear_color) - ngli_gctx_set_clear_color(gctx, prev_clear_color); + ngli_gctx_set_clear_color(gctx, prev_clear_color); for (int i = 0; i < s->nb_color_textures; i++) { struct texture_priv *texture_priv = s->color_textures[i]->priv_data; From 38975ac16f33e01a91ec7924c5dace6087cae7be Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 14 Oct 2020 10:49:35 +0200 Subject: [PATCH 168/388] rtt: remove the no_clear feature This feature is currently not used and tested. --- libnodegl/doc/libnodegl.md | 1 - libnodegl/node_rtt.c | 8 ++------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/libnodegl/doc/libnodegl.md b/libnodegl/doc/libnodegl.md index f1f760adaf..3ee00f3f06 100644 --- a/libnodegl/doc/libnodegl.md +++ b/libnodegl/doc/libnodegl.md @@ -1390,7 +1390,6 @@ Constant | Description -------- | ----------- `depth` | add depth buffer `stencil` | add stencil buffer -`no_clear` | not cleared between draws (non-deterministic) ## valign choices diff --git a/libnodegl/node_rtt.c b/libnodegl/node_rtt.c index 3f3c239404..f15e708008 100644 --- a/libnodegl/node_rtt.c +++ b/libnodegl/node_rtt.c @@ -55,14 +55,12 @@ struct rtt_priv { #define FEATURE_DEPTH (1 << 0) #define FEATURE_STENCIL (1 << 1) -#define FEATURE_NO_CLEAR (1 << 2) static const struct param_choices feature_choices = { .name = "framebuffer_features", .consts = { {"depth", FEATURE_DEPTH, .desc=NGLI_DOCSTRING("add depth buffer")}, {"stencil", FEATURE_STENCIL, .desc=NGLI_DOCSTRING("add stencil buffer")}, - {"no_clear",FEATURE_NO_CLEAR,.desc=NGLI_DOCSTRING("not cleared between draws (non-deterministic)")}, {NULL} } }; @@ -314,9 +312,7 @@ static int rtt_prefetch(struct ngl_node *node) if (ret < 0) return ret; rt_params.depth_stencil.attachment = depth; - - if (!(s->features & FEATURE_NO_CLEAR)) - s->invalidate_depth_stencil = 1; + s->invalidate_depth_stencil = 1; } } @@ -389,7 +385,7 @@ static void rtt_draw(struct ngl_node *node) ctx->current_rendertarget = s->rt; ctx->bind_current_rendertarget = 1; - ctx->clear_current_rendertarget = !(s->features & FEATURE_NO_CLEAR); + ctx->clear_current_rendertarget = 1; ngli_node_draw(s->child); From 0695ac69212c9b0a4901a084b9384f33f78a52a5 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 13 Oct 2020 21:41:37 +0200 Subject: [PATCH 169/388] glfeatures: cosmetics --- libnodegl/glfeatures_data.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libnodegl/glfeatures_data.h b/libnodegl/glfeatures_data.h index 267f261fac..382c1dc3b4 100644 --- a/libnodegl/glfeatures_data.h +++ b/libnodegl/glfeatures_data.h @@ -252,11 +252,11 @@ static const struct glfeature { OFFSET(Uniform4uiv), -1} }, { - .name = "khr_debug", - .flag = NGLI_FEATURE_KHR_DEBUG, - .version = 430, - .es_version = 320, - .funcs_offsets = (const size_t[]){OFFSET(DebugMessageCallback), - -1} + .name = "khr_debug", + .flag = NGLI_FEATURE_KHR_DEBUG, + .version = 430, + .es_version = 320, + .funcs_offsets = (const size_t[]){OFFSET(DebugMessageCallback), + -1} } }; From ba594026909b978a049bfccdc7b62021951a96a0 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 12 Oct 2020 09:53:59 +0200 Subject: [PATCH 170/388] glfeatures: add clear_buffer feature --- libnodegl/features.h | 1 + libnodegl/gen-gl-wrappers.py | 4 ++++ libnodegl/gldefinitions_data.h | 2 ++ libnodegl/glfeatures_data.h | 8 ++++++++ libnodegl/glfunctions.h | 4 +++- libnodegl/glwrappers.h | 16 ++++++++++++++-- 6 files changed, 32 insertions(+), 3 deletions(-) diff --git a/libnodegl/features.h b/libnodegl/features.h index 05679caaff..5599839685 100644 --- a/libnodegl/features.h +++ b/libnodegl/features.h @@ -54,6 +54,7 @@ #define NGLI_FEATURE_UINT_UNIFORMS (1 << 29) #define NGLI_FEATURE_EGL_ANDROID_GET_IMAGE_NATIVE_CLIENT_BUFFER (1 << 30) #define NGLI_FEATURE_KHR_DEBUG (1ULL << 31) +#define NGLI_FEATURE_CLEAR_BUFFER (1ULL << 32) #define NGLI_FEATURE_COMPUTE_SHADER_ALL (NGLI_FEATURE_COMPUTE_SHADER | \ NGLI_FEATURE_PROGRAM_INTERFACE_QUERY | \ diff --git a/libnodegl/gen-gl-wrappers.py b/libnodegl/gen-gl-wrappers.py index 1ba3eda76e..b32b250ce8 100644 --- a/libnodegl/gen-gl-wrappers.py +++ b/libnodegl/gen-gl-wrappers.py @@ -107,6 +107,10 @@ 'glDrawBuffer', 'glDrawBuffers', + # Clear Buffer + 'glClearBufferfv', + 'glClearBufferfi', + # UInt uniforms 'glUniform1uiv', 'glUniform2uiv', diff --git a/libnodegl/gldefinitions_data.h b/libnodegl/gldefinitions_data.h index b9e02b5a67..dbe37fc925 100644 --- a/libnodegl/gldefinitions_data.h +++ b/libnodegl/gldefinitions_data.h @@ -36,6 +36,8 @@ static const struct gldefinition { {"glBufferSubData", offsetof(struct glfunctions, BufferSubData), M}, {"glCheckFramebufferStatus", offsetof(struct glfunctions, CheckFramebufferStatus), M}, {"glClear", offsetof(struct glfunctions, Clear), M}, + {"glClearBufferfi", offsetof(struct glfunctions, ClearBufferfi), 0}, + {"glClearBufferfv", offsetof(struct glfunctions, ClearBufferfv), 0}, {"glClearColor", offsetof(struct glfunctions, ClearColor), M}, {"glClientWaitSync", offsetof(struct glfunctions, ClientWaitSync), 0}, {"glColorMask", offsetof(struct glfunctions, ColorMask), M}, diff --git a/libnodegl/glfeatures_data.h b/libnodegl/glfeatures_data.h index 382c1dc3b4..211eaf35d9 100644 --- a/libnodegl/glfeatures_data.h +++ b/libnodegl/glfeatures_data.h @@ -258,5 +258,13 @@ static const struct glfeature { .es_version = 320, .funcs_offsets = (const size_t[]){OFFSET(DebugMessageCallback), -1} + }, { + .name = "clear_buffer", + .flag = NGLI_FEATURE_CLEAR_BUFFER, + .version = 300, + .es_version = 300, + .funcs_offsets = (const size_t[]){OFFSET(ClearBufferfv), + OFFSET(ClearBufferfi), + -1} } }; diff --git a/libnodegl/glfunctions.h b/libnodegl/glfunctions.h index acbcad7560..83e8d77652 100644 --- a/libnodegl/glfunctions.h +++ b/libnodegl/glfunctions.h @@ -29,6 +29,8 @@ struct glfunctions { NGLI_GL_APIENTRY void (*BufferSubData)(GLenum target, GLintptr offset, GLsizeiptr size, const void * data); NGLI_GL_APIENTRY GLenum (*CheckFramebufferStatus)(GLenum target); NGLI_GL_APIENTRY void (*Clear)(GLbitfield mask); + NGLI_GL_APIENTRY void (*ClearBufferfi)(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil); + NGLI_GL_APIENTRY void (*ClearBufferfv)(GLenum buffer, GLint drawbuffer, const GLfloat * value); NGLI_GL_APIENTRY void (*ClearColor)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); NGLI_GL_APIENTRY GLenum (*ClientWaitSync)(GLsync sync, GLbitfield flags, GLuint64 timeout); NGLI_GL_APIENTRY void (*ColorMask)(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); @@ -116,7 +118,7 @@ struct glfunctions { NGLI_GL_APIENTRY void (*RenderbufferStorage)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); NGLI_GL_APIENTRY void (*RenderbufferStorageMultisample)(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); NGLI_GL_APIENTRY void (*Scissor)(GLint x, GLint y, GLsizei width, GLsizei height); - NGLI_GL_APIENTRY void (*ShaderBinary)(GLsizei count, const GLuint * shaders, GLenum binaryformat, const void * binary, GLsizei length); + NGLI_GL_APIENTRY void (*ShaderBinary)(GLsizei count, const GLuint * shaders, GLenum binaryFormat, const void * binary, GLsizei length); NGLI_GL_APIENTRY void (*ShaderSource)(GLuint shader, GLsizei count, const GLchar *const* string, const GLint * length); NGLI_GL_APIENTRY void (*StencilFunc)(GLenum func, GLint ref, GLuint mask); NGLI_GL_APIENTRY void (*StencilFuncSeparate)(GLenum face, GLenum func, GLint ref, GLuint mask); diff --git a/libnodegl/glwrappers.h b/libnodegl/glwrappers.h index 435ff110f3..7319f8250b 100644 --- a/libnodegl/glwrappers.h +++ b/libnodegl/glwrappers.h @@ -147,6 +147,18 @@ static inline void ngli_glClear(const struct glcontext *gl, GLbitfield mask) check_error_code(gl, "glClear"); } +static inline void ngli_glClearBufferfi(const struct glcontext *gl, GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) +{ + gl->funcs.ClearBufferfi(buffer, drawbuffer, depth, stencil); + check_error_code(gl, "glClearBufferfi"); +} + +static inline void ngli_glClearBufferfv(const struct glcontext *gl, GLenum buffer, GLint drawbuffer, const GLfloat * value) +{ + gl->funcs.ClearBufferfv(buffer, drawbuffer, value); + check_error_code(gl, "glClearBufferfv"); +} + static inline void ngli_glClearColor(const struct glcontext *gl, GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) { gl->funcs.ClearColor(red, green, blue, alpha); @@ -679,9 +691,9 @@ static inline void ngli_glScissor(const struct glcontext *gl, GLint x, GLint y, check_error_code(gl, "glScissor"); } -static inline void ngli_glShaderBinary(const struct glcontext *gl, GLsizei count, const GLuint * shaders, GLenum binaryformat, const void * binary, GLsizei length) +static inline void ngli_glShaderBinary(const struct glcontext *gl, GLsizei count, const GLuint * shaders, GLenum binaryFormat, const void * binary, GLsizei length) { - gl->funcs.ShaderBinary(count, shaders, binaryformat, binary, length); + gl->funcs.ShaderBinary(count, shaders, binaryFormat, binary, length); check_error_code(gl, "glShaderBinary"); } From 65e806122bd7472d52d2cf60dd65da3da7eafaa3 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 14 Oct 2020 13:04:52 +0200 Subject: [PATCH 171/388] rendertarget: specify load and store operations in attachment structure Makes the rendertarget API closer to the renderpass APIs that can be found in modern APIS such as Vulkan, Metal and DirectX 12. This helps improving performance on newer APIs as the render pass knows if it needs to load or discard the previous content of its attachments. --- libnodegl/gctx.c | 6 ++- libnodegl/gctx_gl.c | 29 +++++++++-- libnodegl/hwconv.c | 4 +- libnodegl/node_hud.c | 5 -- libnodegl/node_rtt.c | 75 +++++++++++++++++++---------- libnodegl/node_text.c | 5 -- libnodegl/nodes.h | 2 +- libnodegl/pass.c | 5 -- libnodegl/rendertarget.h | 14 ++++++ libnodegl/rendertarget_gl.c | 95 +++++++++++++++++++++++++++++++++++++ libnodegl/rendertarget_gl.h | 7 +++ 11 files changed, 199 insertions(+), 48 deletions(-) diff --git a/libnodegl/gctx.c b/libnodegl/gctx.c index f34ab52bf7..3c2ed025bb 100644 --- a/libnodegl/gctx.c +++ b/libnodegl/gctx.c @@ -75,9 +75,11 @@ int ngli_gctx_draw(struct gctx *s, struct ngl_node *scene, double t) if (scene) { struct ngl_ctx *ctx = scene->ctx; - ctx->current_rendertarget = ngli_gctx_get_rendertarget(s); + struct rendertarget *rt = ngli_gctx_get_rendertarget(s); + ctx->available_rendertargets[0] = rt; + ctx->available_rendertargets[1] = rt; + ctx->current_rendertarget = rt; ctx->bind_current_rendertarget = 0; - ctx->clear_current_rendertarget = 0; LOG(DEBUG, "draw scene %s @ t=%f", scene->label, t); ngli_node_draw(scene); } diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 97f50223d8..837776e461 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -170,9 +170,17 @@ static int offscreen_rendertarget_init(struct gctx *s) .colors[0] = { .attachment = config->samples ? s_priv->ms_color : s_priv->color, .resolve_target = config->samples ? s_priv->color : NULL, + .load_op = NGLI_LOAD_OP_LOAD, + .clear_value[0] = config->clear_color[0], + .clear_value[1] = config->clear_color[1], + .clear_value[2] = config->clear_color[2], + .clear_value[3] = config->clear_color[3], + .store_op = NGLI_STORE_OP_STORE, }, .depth_stencil = { .attachment = s_priv->depth, + .load_op = NGLI_LOAD_OP_LOAD, + .store_op = NGLI_STORE_OP_STORE, }, }; @@ -319,9 +327,12 @@ static int gl_resize(struct gctx *s, int width, int height, const int *viewport) static int gl_pre_draw(struct gctx *s, double t) { - ngli_gctx_clear_color(s); - ngli_gctx_clear_depth_stencil(s); - + struct gctx_gl *s_priv = (struct gctx_gl *)s; + struct glcontext *gl = s_priv->glcontext; + const struct ngl_config *config = &s->config; + const float *color = config->clear_color; + ngli_glClearColor(gl, color[0], color[1], color[2], color[3]); + ngli_glClear(gl, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); return 0; } @@ -404,10 +415,22 @@ static void gl_set_rendertarget(struct gctx *s, struct rendertarget *rt) if (rt == s_priv->rendertarget) return; + if (s_priv->rendertarget) { + ngli_rendertarget_gl_invalidate(s_priv->rendertarget); + } + struct rendertarget_gl *rt_gl = (struct rendertarget_gl *)rt; const GLuint fbo_id = rt_gl ? rt_gl->id : ngli_glcontext_get_default_framebuffer(gl); ngli_glBindFramebuffer(gl, GL_FRAMEBUFFER, fbo_id); + if (rt) { + const int scissor_test = s_priv->glstate.scissor_test; + ngli_glDisable(gl, GL_SCISSOR_TEST); + ngli_rendertarget_gl_clear(rt); + if (scissor_test) + ngli_glEnable(gl, GL_SCISSOR_TEST); + } + s_priv->rendertarget = rt; } diff --git a/libnodegl/hwconv.c b/libnodegl/hwconv.c index 037190b0e7..1bcaeeae3b 100644 --- a/libnodegl/hwconv.c +++ b/libnodegl/hwconv.c @@ -77,6 +77,8 @@ int ngli_hwconv_init(struct hwconv *hwconv, struct ngl_ctx *ctx, .nb_colors = 1, .colors[0] = { .attachment = texture, + .load_op = NGLI_LOAD_OP_CLEAR, + .store_op = NGLI_STORE_OP_STORE, } }; hwconv->rt = ngli_rendertarget_create(gctx); @@ -185,8 +187,6 @@ int ngli_hwconv_convert_image(struct hwconv *hwconv, const struct image *image) const int vp[4] = {0, 0, rt->width, rt->height}; ngli_gctx_set_viewport(gctx, vp); - ngli_gctx_clear_color(gctx); - struct pipeline *pipeline = hwconv->pipeline; struct darray *texture_infos_array = &hwconv->crafter->texture_infos; diff --git a/libnodegl/node_hud.c b/libnodegl/node_hud.c index edcbf3bbe5..8944ca5c97 100644 --- a/libnodegl/node_hud.c +++ b/libnodegl/node_hud.c @@ -1417,12 +1417,7 @@ static void hud_draw(struct ngl_node *node) if (ctx->bind_current_rendertarget) { struct gctx *gctx = ctx->gctx; ngli_gctx_set_rendertarget(gctx, ctx->current_rendertarget); - if (ctx->clear_current_rendertarget) { - ngli_gctx_clear_color(gctx); - ngli_gctx_clear_depth_stencil(gctx); - } ctx->bind_current_rendertarget = 0; - ctx->clear_current_rendertarget = 0; } const float *modelview_matrix = ngli_darray_tail(&ctx->modelview_matrix_stack); diff --git a/libnodegl/node_rtt.c b/libnodegl/node_rtt.c index f15e708008..7bc3a5d5e9 100644 --- a/libnodegl/node_rtt.c +++ b/libnodegl/node_rtt.c @@ -41,11 +41,13 @@ struct rtt_priv { int features; int vflip; - int invalidate_depth_stencil; + int use_rt_resume; int width; int height; struct rendertarget *rt; + struct rendertarget *rt_resume; + struct rendertarget *available_rendertargets[2]; struct texture *depth; struct texture *ms_colors[NGLI_MAX_COLOR_ATTACHMENTS]; @@ -112,7 +114,6 @@ static int rtt_init(struct ngl_node *node) return 0; } -#ifdef DEBUG_SCENE struct renderpass_children_info { int has_rtt_or_compute; int render_counts[2]; // number of render nodes before and after the first rtt or compute node (renderpass interruption) @@ -133,7 +134,6 @@ static void get_renderpass_children_info(const struct ngl_node *node, struct ren } } } -#endif static int rtt_prepare(struct ngl_node *node) { @@ -142,12 +142,14 @@ static int rtt_prepare(struct ngl_node *node) struct rnode *rnode = ctx->rnode_pos; struct rtt_priv *s = node->priv_data; -#ifdef DEBUG_SCENE struct renderpass_children_info info = {0}; get_renderpass_children_info(s->child, &info); - if (info.render_counts[0] && info.render_counts[1]) + if (info.render_counts[0] && info.render_counts[1]) { +#ifdef DEBUG_SCENE LOG(WARNING, "the underlying render pass might not be optimal as it contains a rtt or compute node in the middle of it"); #endif + s->use_rt_resume = 1; + } struct rendertarget_desc desc = {0}; for (int i = 0; i < s->nb_color_textures; i++) { @@ -258,9 +260,17 @@ static int rtt_prefetch(struct ngl_node *node) rt_params.colors[rt_params.nb_colors].attachment_layer = 0; rt_params.colors[rt_params.nb_colors].resolve_target = texture; rt_params.colors[rt_params.nb_colors].resolve_target_layer = j; + rt_params.colors[rt_params.nb_colors].load_op = NGLI_LOAD_OP_CLEAR; + float *clear_value = rt_params.colors[rt_params.nb_colors].clear_value; + memcpy(clear_value, s->clear_color, sizeof(s->clear_color)); + rt_params.colors[rt_params.nb_colors].store_op = NGLI_STORE_OP_STORE; } else { rt_params.colors[rt_params.nb_colors].attachment = texture; rt_params.colors[rt_params.nb_colors].attachment_layer = j; + rt_params.colors[rt_params.nb_colors].load_op = NGLI_LOAD_OP_CLEAR; + float *clear_value = rt_params.colors[rt_params.nb_colors].clear_value; + memcpy(clear_value, s->clear_color, sizeof(s->clear_color)); + rt_params.colors[rt_params.nb_colors].store_op = NGLI_STORE_OP_STORE; } rt_params.nb_colors++; } @@ -288,8 +298,12 @@ static int rtt_prefetch(struct ngl_node *node) return ret; rt_params.depth_stencil.attachment = ms_texture; rt_params.depth_stencil.resolve_target = texture; + rt_params.depth_stencil.load_op = NGLI_LOAD_OP_CLEAR; + rt_params.depth_stencil.store_op = NGLI_STORE_OP_DONT_CARE; } else { rt_params.depth_stencil.attachment = texture; + rt_params.depth_stencil.load_op = NGLI_LOAD_OP_CLEAR; + rt_params.depth_stencil.store_op = NGLI_STORE_OP_STORE; } } else { if (s->features & FEATURE_STENCIL) @@ -312,7 +326,8 @@ static int rtt_prefetch(struct ngl_node *node) if (ret < 0) return ret; rt_params.depth_stencil.attachment = depth; - s->invalidate_depth_stencil = 1; + rt_params.depth_stencil.load_op = NGLI_LOAD_OP_CLEAR; + rt_params.depth_stencil.store_op = s->use_rt_resume ? NGLI_STORE_OP_STORE : NGLI_LOAD_OP_DONT_CARE; } } @@ -323,6 +338,24 @@ static int rtt_prefetch(struct ngl_node *node) if (ret < 0) return ret; + s->available_rendertargets[0] = s->rt; + s->available_rendertargets[1] = s->rt; + + if (s->use_rt_resume) { + for (int i = 0; i < rt_params.nb_colors; i++) + rt_params.colors[i].load_op = NGLI_LOAD_OP_LOAD; + rt_params.depth_stencil.load_op = NGLI_LOAD_OP_LOAD; + rt_params.depth_stencil.store_op = s->depth_texture ? NGLI_STORE_OP_STORE : NGLI_LOAD_OP_DONT_CARE; + + s->rt_resume = ngli_rendertarget_create(gctx); + if (!s->rt_resume) + return NGL_ERROR_MEMORY; + ret = ngli_rendertarget_init(s->rt_resume, &rt_params); + if (ret < 0) + return ret; + s->available_rendertargets[1] = s->rt_resume; + } + if (s->vflip) { /* flip vertically the color and depth textures so the coordinates * match how the uv coordinates system works */ @@ -376,41 +409,32 @@ static void rtt_draw(struct ngl_node *node) const int vp[4] = {0, 0, s->width, s->height}; ngli_gctx_set_viewport(gctx, vp); - float prev_clear_color[4] = {0}; - ngli_gctx_get_clear_color(gctx, prev_clear_color); - ngli_gctx_set_clear_color(gctx, s->clear_color); - - struct rendertarget *prev_rendertarget = ctx->current_rendertarget; - int prev_clear = ctx->clear_current_rendertarget; + struct rendertarget *prev_rendertargets[2] = { + ctx->available_rendertargets[0], + ctx->available_rendertargets[1], + }; - ctx->current_rendertarget = s->rt; + ctx->available_rendertargets[0] = s->available_rendertargets[0]; + ctx->available_rendertargets[1] = s->available_rendertargets[1]; + ctx->current_rendertarget = s->available_rendertargets[0]; ctx->bind_current_rendertarget = 1; - ctx->clear_current_rendertarget = 1; ngli_node_draw(s->child); if (ctx->bind_current_rendertarget) { ngli_gctx_set_rendertarget(gctx, ctx->current_rendertarget); - if (ctx->clear_current_rendertarget) { - ngli_gctx_clear_color(gctx); - ngli_gctx_clear_depth_stencil(gctx); - } ctx->bind_current_rendertarget = 0; - ctx->clear_current_rendertarget = 0; } if (s->samples > 0) ngli_rendertarget_resolve(ctx->current_rendertarget); - if (s->invalidate_depth_stencil) - ngli_gctx_invalidate_depth_stencil(gctx); - - ctx->current_rendertarget = prev_rendertarget; + ctx->current_rendertarget = prev_rendertargets[1]; + ctx->available_rendertargets[0] = prev_rendertargets[0]; + ctx->available_rendertargets[1] = prev_rendertargets[1]; ctx->bind_current_rendertarget = 1; - ctx->clear_current_rendertarget = prev_clear; ngli_gctx_set_viewport(gctx, prev_vp); - ngli_gctx_set_clear_color(gctx, prev_clear_color); for (int i = 0; i < s->nb_color_textures; i++) { struct texture_priv *texture_priv = s->color_textures[i]->priv_data; @@ -425,6 +449,7 @@ static void rtt_release(struct ngl_node *node) struct rtt_priv *s = node->priv_data; ngli_rendertarget_freep(&s->rt); + ngli_rendertarget_freep(&s->rt_resume); ngli_texture_freep(&s->depth); for (int i = 0; i < s->nb_ms_colors; i++) diff --git a/libnodegl/node_text.c b/libnodegl/node_text.c index ea6abab970..efbcf8b33a 100644 --- a/libnodegl/node_text.c +++ b/libnodegl/node_text.c @@ -679,12 +679,7 @@ static void text_draw(struct ngl_node *node) if (ctx->bind_current_rendertarget) { struct gctx *gctx = ctx->gctx; ngli_gctx_set_rendertarget(gctx, ctx->current_rendertarget); - if (ctx->clear_current_rendertarget) { - ngli_gctx_clear_color(gctx); - ngli_gctx_clear_depth_stencil(gctx); - } ctx->bind_current_rendertarget = 0; - ctx->clear_current_rendertarget = 0; } struct pipeline_subdesc *bg_desc = &desc->bg; diff --git a/libnodegl/nodes.h b/libnodegl/nodes.h index 488a57a932..2b4bc3292b 100644 --- a/libnodegl/nodes.h +++ b/libnodegl/nodes.h @@ -83,9 +83,9 @@ struct ngl_ctx { struct rnode *rnode_pos; struct ngl_node *scene; struct ngl_config config; + struct rendertarget *available_rendertargets[2]; struct rendertarget *current_rendertarget; int bind_current_rendertarget; - int clear_current_rendertarget; struct darray modelview_matrix_stack; struct darray projection_matrix_stack; struct darray activitycheck_nodes; diff --git a/libnodegl/pass.c b/libnodegl/pass.c index a04c881995..991eda5bdb 100644 --- a/libnodegl/pass.c +++ b/libnodegl/pass.c @@ -702,12 +702,7 @@ int ngli_pass_exec(struct pass *s) if (ctx->bind_current_rendertarget) { struct gctx *gctx = ctx->gctx; ngli_gctx_set_rendertarget(gctx, ctx->current_rendertarget); - if (ctx->clear_current_rendertarget) { - ngli_gctx_clear_color(gctx); - ngli_gctx_clear_depth_stencil(gctx); - } ctx->bind_current_rendertarget = 0; - ctx->clear_current_rendertarget = 0; } if (s->indices_buffer) diff --git a/libnodegl/rendertarget.h b/libnodegl/rendertarget.h index da9cbe947b..dbbe6a3434 100644 --- a/libnodegl/rendertarget.h +++ b/libnodegl/rendertarget.h @@ -27,6 +27,17 @@ #define NGLI_MAX_COLOR_ATTACHMENTS 8 +enum { + NGLI_LOAD_OP_LOAD, + NGLI_LOAD_OP_CLEAR, + NGLI_LOAD_OP_DONT_CARE, +}; + +enum { + NGLI_STORE_OP_STORE, + NGLI_STORE_OP_DONT_CARE, +}; + struct attachment_desc { int format; int samples; @@ -44,6 +55,9 @@ struct attachment { int attachment_layer; struct texture *resolve_target; int resolve_target_layer; + int load_op; + float clear_value[4]; + int store_op; }; struct rendertarget_params { diff --git a/libnodegl/rendertarget_gl.c b/libnodegl/rendertarget_gl.c index 292193d83e..2d8e4aa068 100644 --- a/libnodegl/rendertarget_gl.c +++ b/libnodegl/rendertarget_gl.c @@ -194,6 +194,54 @@ static int require_resolve_fbo(struct rendertarget *s) return 0; } +static void clear_buffer(struct rendertarget *s) +{ + struct gctx_gl *gctx_gl = (struct gctx_gl *)s->gctx; + struct glcontext *gl = gctx_gl->glcontext; + struct rendertarget_gl *s_priv = (struct rendertarget_gl *)s; + const struct rendertarget_params *params = &s->params; + + if (params->nb_colors >= 1) { + const struct attachment *color = ¶ms->colors[0]; + const float *clear_value = color->clear_value; + ngli_glClearColor(gl, clear_value[0], clear_value[1], clear_value[2], clear_value[3]); + ngli_glClear(gl, s_priv->clear_flags); + } +} + +static void clear_buffers(struct rendertarget *s) +{ + struct gctx_gl *gctx_gl = (struct gctx_gl *)s->gctx; + struct glcontext *gl = gctx_gl->glcontext; + const struct rendertarget_params *params = &s->params; + + for (int i = 0; i < params->nb_colors; i++) { + const struct attachment *color = ¶ms->colors[i]; + if (color->load_op != NGLI_LOAD_OP_LOAD) { + ngli_glClearBufferfv(gl, GL_COLOR, i, color->clear_value); + } + } + + if (params->depth_stencil.attachment) { + const struct attachment *depth_stencil = ¶ms->depth_stencil; + if (depth_stencil->load_op != NGLI_LOAD_OP_LOAD) { + ngli_glClearBufferfi(gl, GL_DEPTH_STENCIL, 0, 1.0f, 0); + } + } +} + +static void invalidate_noop(struct rendertarget *s) +{ +} + +static void invalidate(struct rendertarget *s) +{ + struct rendertarget_gl *s_priv = (struct rendertarget_gl *)s; + struct gctx_gl *gctx_gl = (struct gctx_gl *)s->gctx; + struct glcontext *gl = gctx_gl->glcontext; + ngli_glInvalidateFramebuffer(gl, GL_FRAMEBUFFER, s_priv->nb_invalidate_attachments, s_priv->invalidate_attachments); +} + struct rendertarget *ngli_rendertarget_gl_create(struct gctx *gctx) { struct rendertarget_gl *s = ngli_calloc(1, sizeof(*s)); @@ -225,6 +273,18 @@ int ngli_rendertarget_gl_init(struct rendertarget *s, const struct rendertarget_ if (ret < 0) goto done; + if (gl->features & NGLI_FEATURE_INVALIDATE_SUBDATA) { + s_priv->invalidate = invalidate; + } else { + s_priv->invalidate = invalidate_noop; + } + + if (gl->features & NGLI_FEATURE_CLEAR_BUFFER) { + s_priv->clear = clear_buffers; + } else { + s_priv->clear = clear_buffer; + } + s_priv->resolve = resolve_no_draw_buffers; if (gl->features & NGLI_FEATURE_DRAW_BUFFERS) { if (s->nb_color_attachments > limits->max_draw_buffers) { @@ -248,6 +308,29 @@ int ngli_rendertarget_gl_init(struct rendertarget *s, const struct rendertarget_ } } + for (int i = 0; i < params->nb_colors; i++) { + const struct attachment *color = ¶ms->colors[i]; + if (color->load_op == NGLI_LOAD_OP_DONT_CARE || + color->load_op == NGLI_LOAD_OP_CLEAR) { + s_priv->clear_flags |= GL_COLOR_BUFFER_BIT; + } + if (color->store_op == NGLI_STORE_OP_DONT_CARE) { + s_priv->invalidate_attachments[s_priv->nb_invalidate_attachments++] = GL_COLOR_ATTACHMENT0 + i; + } + } + + const struct attachment *depth_stencil = ¶ms->depth_stencil; + if (depth_stencil->attachment) { + if (depth_stencil->load_op == NGLI_LOAD_OP_DONT_CARE || + depth_stencil->load_op == NGLI_LOAD_OP_CLEAR) { + s_priv->clear_flags |= (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + } + if (depth_stencil->store_op == NGLI_STORE_OP_DONT_CARE) { + s_priv->invalidate_attachments[s_priv->nb_invalidate_attachments++] = GL_DEPTH_ATTACHMENT; + s_priv->invalidate_attachments[s_priv->nb_invalidate_attachments++] = GL_STENCIL_ATTACHMENT; + } + } + done:; struct rendertarget *rt = gctx_gl->rendertarget; struct rendertarget_gl *rt_gl = (struct rendertarget_gl *)rt; @@ -279,6 +362,18 @@ void ngli_rendertarget_gl_resolve(struct rendertarget *s) ngli_glBindFramebuffer(gl, GL_FRAMEBUFFER, fbo_id); } +void ngli_rendertarget_gl_clear(struct rendertarget *s) +{ + const struct rendertarget_gl *s_priv = (struct rendertarget_gl *)s; + s_priv->clear(s); +} + +void ngli_rendertarget_gl_invalidate(struct rendertarget *s) +{ + const struct rendertarget_gl *s_priv = (struct rendertarget_gl *)s; + s_priv->invalidate(s); +} + void ngli_rendertarget_gl_read_pixels(struct rendertarget *s, uint8_t *data) { const struct rendertarget_gl *s_priv = (struct rendertarget_gl *)s; diff --git a/libnodegl/rendertarget_gl.h b/libnodegl/rendertarget_gl.h index 3799a6e586..ac667d1d39 100644 --- a/libnodegl/rendertarget_gl.h +++ b/libnodegl/rendertarget_gl.h @@ -32,12 +32,19 @@ struct rendertarget_gl { GLuint prev_id; GLenum draw_buffers[NGLI_MAX_COLOR_ATTACHMENTS]; GLenum blit_draw_buffers[NGLI_MAX_COLOR_ATTACHMENTS*(NGLI_MAX_COLOR_ATTACHMENTS+1)/2]; + GLenum clear_flags; + GLenum invalidate_attachments[NGLI_MAX_COLOR_ATTACHMENTS + 2]; // max color attachments + depth and stencil attachments + int nb_invalidate_attachments; + void (*clear)(struct rendertarget *s); + void (*invalidate)(struct rendertarget *s); void (*resolve)(struct rendertarget *s); }; struct rendertarget *ngli_rendertarget_gl_create(struct gctx *gctx); int ngli_rendertarget_gl_init(struct rendertarget *s, const struct rendertarget_params *params); void ngli_rendertarget_gl_resolve(struct rendertarget *s); +void ngli_rendertarget_gl_clear(struct rendertarget *s); +void ngli_rendertarget_gl_invalidate(struct rendertarget *s); void ngli_rendertarget_gl_read_pixels(struct rendertarget *s, uint8_t *data); void ngli_rendertarget_gl_freep(struct rendertarget **sp); From 8de319ceaa6d3ab0315b4f85449e081184681261 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 14 Oct 2020 13:06:41 +0200 Subject: [PATCH 172/388] gctx: remove ngli_gctx_invalidate_depth_stencil() This function is superseded by the new rendertarget API that allows the user to specify the store operations. --- libnodegl/gctx.c | 5 ----- libnodegl/gctx.h | 2 -- libnodegl/gctx_gl.c | 14 -------------- 3 files changed, 21 deletions(-) diff --git a/libnodegl/gctx.c b/libnodegl/gctx.c index 3c2ed025bb..eaf7b58d30 100644 --- a/libnodegl/gctx.c +++ b/libnodegl/gctx.c @@ -175,11 +175,6 @@ void ngli_gctx_clear_depth_stencil(struct gctx *s) s->class->clear_depth_stencil(s); } -void ngli_gctx_invalidate_depth_stencil(struct gctx *s) -{ - s->class->invalidate_depth_stencil(s); -} - int ngli_gctx_get_preferred_depth_format(struct gctx *s) { return s->class->get_preferred_depth_format(s); diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index cdeeb6f47a..0e9bd28ac6 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -56,7 +56,6 @@ struct gctx_class { void (*get_clear_color)(struct gctx *s, float *color); void (*clear_color)(struct gctx *s); void (*clear_depth_stencil)(struct gctx *s); - void (*invalidate_depth_stencil)(struct gctx *s); int (*get_preferred_depth_format)(struct gctx *s); int (*get_preferred_depth_stencil_format)(struct gctx *s); @@ -135,7 +134,6 @@ void ngli_gctx_get_clear_color(struct gctx *s, float *color); void ngli_gctx_clear_color(struct gctx *s); void ngli_gctx_clear_depth_stencil(struct gctx *s); -void ngli_gctx_invalidate_depth_stencil(struct gctx *s); int ngli_gctx_get_preferred_depth_format(struct gctx *s); int ngli_gctx_get_preferred_depth_stencil_format(struct gctx *s); diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 837776e461..49d4080d1e 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -516,18 +516,6 @@ static void gl_clear_depth_stencil(struct gctx *s) ngli_glEnable(gl, GL_SCISSOR_TEST); } -static void gl_invalidate_depth_stencil(struct gctx *s) -{ - struct gctx_gl *s_priv = (struct gctx_gl *)s; - struct glcontext *gl = s_priv->glcontext; - - if (!(gl->features & NGLI_FEATURE_INVALIDATE_SUBDATA)) - return; - - static const GLenum attachments[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT}; - ngli_glInvalidateFramebuffer(gl, GL_FRAMEBUFFER, NGLI_ARRAY_NB(attachments), attachments); -} - static int gl_get_preferred_depth_format(struct gctx *s) { return NGLI_FORMAT_D16_UNORM; @@ -562,7 +550,6 @@ const struct gctx_class ngli_gctx_gl = { .get_clear_color = gl_get_clear_color, .clear_color = gl_clear_color, .clear_depth_stencil = gl_clear_depth_stencil, - .invalidate_depth_stencil = gl_invalidate_depth_stencil, .get_preferred_depth_format = gl_get_preferred_depth_format, .get_preferred_depth_stencil_format = gl_get_preferred_depth_stencil_format, @@ -633,7 +620,6 @@ const struct gctx_class ngli_gctx_gles = { .get_clear_color = gl_get_clear_color, .clear_color = gl_clear_color, .clear_depth_stencil = gl_clear_depth_stencil, - .invalidate_depth_stencil = gl_invalidate_depth_stencil, .get_preferred_depth_format = gl_get_preferred_depth_format, .get_preferred_depth_stencil_format = gl_get_preferred_depth_stencil_format, From 3259577b13dc8ecc8d90174b133d0d1b8c5d0c3f Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 12 Oct 2020 14:42:05 +0200 Subject: [PATCH 173/388] gctx: remove clear functions Clear functions are superseded by the new rendertarget API that allows the user to specify the load operations. --- libnodegl/gctx.c | 20 ----------------- libnodegl/gctx.h | 10 --------- libnodegl/gctx_gl.c | 54 --------------------------------------------- libnodegl/gctx_gl.h | 1 - 4 files changed, 85 deletions(-) diff --git a/libnodegl/gctx.c b/libnodegl/gctx.c index eaf7b58d30..4ed62853dd 100644 --- a/libnodegl/gctx.c +++ b/libnodegl/gctx.c @@ -155,26 +155,6 @@ void ngli_gctx_get_scissor(struct gctx *s, int *scissor) s->class->get_scissor(s, scissor); } -void ngli_gctx_set_clear_color(struct gctx *s, const float *color) -{ - s->class->set_clear_color(s, color); -} - -void ngli_gctx_get_clear_color(struct gctx *s, float *color) -{ - s->class->get_clear_color(s, color); -} - -void ngli_gctx_clear_color(struct gctx *s) -{ - s->class->clear_color(s); -} - -void ngli_gctx_clear_depth_stencil(struct gctx *s) -{ - s->class->clear_depth_stencil(s); -} - int ngli_gctx_get_preferred_depth_format(struct gctx *s) { return s->class->get_preferred_depth_format(s); diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index 0e9bd28ac6..d104b3456a 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -52,10 +52,6 @@ struct gctx_class { void (*get_viewport)(struct gctx *s, int *viewport); void (*set_scissor)(struct gctx *s, const int *scissor); void (*get_scissor)(struct gctx *s, int *scissor); - void (*set_clear_color)(struct gctx *s, const float *color); - void (*get_clear_color)(struct gctx *s, float *color); - void (*clear_color)(struct gctx *s); - void (*clear_depth_stencil)(struct gctx *s); int (*get_preferred_depth_format)(struct gctx *s); int (*get_preferred_depth_stencil_format)(struct gctx *s); @@ -129,12 +125,6 @@ void ngli_gctx_get_viewport(struct gctx *s, int *viewport); void ngli_gctx_set_scissor(struct gctx *s, const int *scissor); void ngli_gctx_get_scissor(struct gctx *s, int *scissor); -void ngli_gctx_set_clear_color(struct gctx *s, const float *color); -void ngli_gctx_get_clear_color(struct gctx *s, float *color); - -void ngli_gctx_clear_color(struct gctx *s); -void ngli_gctx_clear_depth_stencil(struct gctx *s); - int ngli_gctx_get_preferred_depth_format(struct gctx *s); int ngli_gctx_get_preferred_depth_stencil_format(struct gctx *s); diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 49d4080d1e..80c97f3320 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -296,8 +296,6 @@ static int gl_init(struct gctx *s) const GLint scissor[] = {0, 0, gl->width, gl->height}; ngli_gctx_set_scissor(s, scissor); - ngli_gctx_set_clear_color(s, config->clear_color); - return 0; } @@ -472,50 +470,6 @@ static void gl_get_scissor(struct gctx *s, int *scissor) memcpy(scissor, &s_priv->scissor, sizeof(s_priv->scissor)); } -static void gl_set_clear_color(struct gctx *s, const float *color) -{ - struct gctx_gl *s_priv = (struct gctx_gl *)s; - struct glcontext *gl = s_priv->glcontext; - memcpy(s_priv->clear_color, color, sizeof(s_priv->clear_color)); - ngli_glClearColor(gl, color[0], color[1], color[2], color[3]); -} - -static void gl_get_clear_color(struct gctx *s, float *color) -{ - struct gctx_gl *s_priv = (struct gctx_gl *)s; - memcpy(color, &s_priv->clear_color, sizeof(s_priv->clear_color)); -} - -static void gl_clear_color(struct gctx *s) -{ - struct gctx_gl *s_priv = (struct gctx_gl *)s; - struct glcontext *gl = s_priv->glcontext; - struct glstate *glstate = &s_priv->glstate; - - const int scissor_test = glstate->scissor_test; - ngli_glDisable(gl, GL_SCISSOR_TEST); - - ngli_glClear(gl, GL_COLOR_BUFFER_BIT); - - if (scissor_test) - ngli_glEnable(gl, GL_SCISSOR_TEST); -} - -static void gl_clear_depth_stencil(struct gctx *s) -{ - struct gctx_gl *s_priv = (struct gctx_gl *)s; - struct glcontext *gl = s_priv->glcontext; - struct glstate *glstate = &s_priv->glstate; - - const int scissor_test = glstate->scissor_test; - ngli_glDisable(gl, GL_SCISSOR_TEST); - - ngli_glClear(gl, GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - - if (scissor_test) - ngli_glEnable(gl, GL_SCISSOR_TEST); -} - static int gl_get_preferred_depth_format(struct gctx *s) { return NGLI_FORMAT_D16_UNORM; @@ -546,10 +500,6 @@ const struct gctx_class ngli_gctx_gl = { .get_viewport = gl_get_viewport, .set_scissor = gl_set_scissor, .get_scissor = gl_get_scissor, - .set_clear_color = gl_set_clear_color, - .get_clear_color = gl_get_clear_color, - .clear_color = gl_clear_color, - .clear_depth_stencil = gl_clear_depth_stencil, .get_preferred_depth_format = gl_get_preferred_depth_format, .get_preferred_depth_stencil_format = gl_get_preferred_depth_stencil_format, @@ -616,10 +566,6 @@ const struct gctx_class ngli_gctx_gles = { .get_viewport = gl_get_viewport, .set_scissor = gl_set_scissor, .get_scissor = gl_get_scissor, - .set_clear_color = gl_set_clear_color, - .get_clear_color = gl_get_clear_color, - .clear_color = gl_clear_color, - .clear_depth_stencil = gl_clear_depth_stencil, .get_preferred_depth_format = gl_get_preferred_depth_format, .get_preferred_depth_stencil_format = gl_get_preferred_depth_stencil_format, diff --git a/libnodegl/gctx_gl.h b/libnodegl/gctx_gl.h index d61d3afc88..f65ecd772a 100644 --- a/libnodegl/gctx_gl.h +++ b/libnodegl/gctx_gl.h @@ -49,7 +49,6 @@ struct gctx_gl { struct rendertarget *rendertarget; int viewport[4]; int scissor[4]; - float clear_color[4]; int timer_active; /* Offscreen render target */ struct rendertarget *rt; From a7fc90b9cf75412b815889d0a2964e6c8112405a Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 12 Oct 2020 23:13:14 +0200 Subject: [PATCH 174/388] gctx: add ngli_gctx_rendertarget_{begin,end}_render_pass() Also removes ngli_gctx_set_rendertarget() and ngli_rendertarget_resolve(). --- libnodegl/gctx.c | 13 ++++++---- libnodegl/gctx.h | 9 +++++-- libnodegl/gctx_gl.c | 55 +++++++++++++++++++++++++++---------------- libnodegl/hwconv.c | 5 ++-- libnodegl/node_hud.c | 2 +- libnodegl/node_rtt.c | 10 ++++---- libnodegl/node_text.c | 2 +- libnodegl/pass.c | 9 ++++++- 8 files changed, 69 insertions(+), 36 deletions(-) diff --git a/libnodegl/gctx.c b/libnodegl/gctx.c index 4ed62853dd..b6d2822e7f 100644 --- a/libnodegl/gctx.c +++ b/libnodegl/gctx.c @@ -115,14 +115,19 @@ void ngli_gctx_transform_projection_matrix(struct gctx *s, float *dst) s->class->transform_projection_matrix(s, dst); } -void ngli_gctx_get_rendertarget_uvcoord_matrix(struct gctx *s, float *dst) +void ngli_gctx_begin_render_pass(struct gctx *s, struct rendertarget *rt) { - s->class->get_rendertarget_uvcoord_matrix(s, dst); + s->class->begin_render_pass(s, rt); } -void ngli_gctx_set_rendertarget(struct gctx *s, struct rendertarget *rt) +void ngli_gctx_end_render_pass(struct gctx *s) { - s->class->set_rendertarget(s, rt); + s->class->end_render_pass(s); +} + +void ngli_gctx_get_rendertarget_uvcoord_matrix(struct gctx *s, float *dst) +{ + s->class->get_rendertarget_uvcoord_matrix(s, dst); } struct rendertarget *ngli_gctx_get_rendertarget(struct gctx *s) diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index d104b3456a..d120f831f1 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -45,9 +45,12 @@ struct gctx_class { void (*transform_projection_matrix)(struct gctx *s, float *dst); void (*get_rendertarget_uvcoord_matrix)(struct gctx *s, float *dst); - void (*set_rendertarget)(struct gctx *s, struct rendertarget *rt); struct rendertarget *(*get_rendertarget)(struct gctx *s); const struct rendertarget_desc *(*get_default_rendertarget_desc)(struct gctx *s); + + void (*begin_render_pass)(struct gctx *s, struct rendertarget *rt); + void (*end_render_pass)(struct gctx *s); + void (*set_viewport)(struct gctx *s, const int *viewport); void (*get_viewport)(struct gctx *s, int *viewport); void (*set_scissor)(struct gctx *s, const int *scissor); @@ -116,10 +119,12 @@ int ngli_gctx_transform_cull_mode(struct gctx *s, int cull_mode); void ngli_gctx_transform_projection_matrix(struct gctx *s, float *dst); void ngli_gctx_get_rendertarget_uvcoord_matrix(struct gctx *s, float *dst); -void ngli_gctx_set_rendertarget(struct gctx *s, struct rendertarget *rt); struct rendertarget *ngli_gctx_get_rendertarget(struct gctx *s); const struct rendertarget_desc *ngli_gctx_get_default_rendertarget_desc(struct gctx *s); +void ngli_gctx_begin_render_pass(struct gctx *s, struct rendertarget *rt); +void ngli_gctx_end_render_pass(struct gctx *s); + void ngli_gctx_set_viewport(struct gctx *s, const int *viewport); void ngli_gctx_get_viewport(struct gctx *s, int *viewport); void ngli_gctx_set_scissor(struct gctx *s, const int *scissor); diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 80c97f3320..6073d25e39 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -49,7 +49,6 @@ static void capture_default(struct gctx *s) struct ngl_config *config = &s->config; struct rendertarget *rt = s_priv->rt; - ngli_rendertarget_resolve(rt); if (config->capture_buffer) ngli_rendertarget_read_pixels(rt, config->capture_buffer); } @@ -58,9 +57,7 @@ static void capture_ios(struct gctx *s) { struct gctx_gl *s_priv = (struct gctx_gl *)s; struct glcontext *gl = s_priv->glcontext; - struct rendertarget *rt = s_priv->rt; - ngli_rendertarget_resolve(rt); ngli_glFinish(gl); } @@ -193,7 +190,6 @@ static int offscreen_rendertarget_init(struct gctx *s) s_priv->capture_func = ios_capture ? capture_ios : capture_default; - ngli_gctx_set_rendertarget(s, s_priv->rt); const int vp[4] = {0, 0, config->width, config->height}; ngli_gctx_set_viewport(s, vp); @@ -328,6 +324,9 @@ static int gl_pre_draw(struct gctx *s, double t) struct gctx_gl *s_priv = (struct gctx_gl *)s; struct glcontext *gl = s_priv->glcontext; const struct ngl_config *config = &s->config; + + ngli_gctx_begin_render_pass(s, config->offscreen ? s_priv->rt : NULL); + const float *color = config->clear_color; ngli_glClearColor(gl, color[0], color[1], color[2], color[3]); ngli_glClear(gl, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); @@ -342,6 +341,8 @@ static int gl_post_draw(struct gctx *s, double t) ngli_glstate_update(s, &s_priv->default_graphicstate); + ngli_gctx_end_render_pass(s); + if (s_priv->capture_func) s_priv->capture_func(s); @@ -405,17 +406,25 @@ static void gl_get_rendertarget_uvcoord_matrix(struct gctx *s, float *dst) memcpy(dst, matrix, 4 * 4 * sizeof(float)); } -static void gl_set_rendertarget(struct gctx *s, struct rendertarget *rt) +static struct rendertarget *gl_get_rendertarget(struct gctx *s) { struct gctx_gl *s_priv = (struct gctx_gl *)s; - struct glcontext *gl = s_priv->glcontext; + const struct ngl_config *config = &s->config; + if (config->offscreen) + return s_priv->rt; + return NULL; +} - if (rt == s_priv->rendertarget) - return; +static const struct rendertarget_desc *gl_get_default_rendertarget_desc(struct gctx *s) +{ + struct gctx_gl *s_priv = (struct gctx_gl *)s; + return &s_priv->default_rendertarget_desc; +} - if (s_priv->rendertarget) { - ngli_rendertarget_gl_invalidate(s_priv->rendertarget); - } +static void gl_begin_render_pass(struct gctx *s, struct rendertarget *rt) +{ + struct gctx_gl *s_priv = (struct gctx_gl *)s; + struct glcontext *gl = s_priv->glcontext; struct rendertarget_gl *rt_gl = (struct rendertarget_gl *)rt; const GLuint fbo_id = rt_gl ? rt_gl->id : ngli_glcontext_get_default_framebuffer(gl); @@ -432,16 +441,16 @@ static void gl_set_rendertarget(struct gctx *s, struct rendertarget *rt) s_priv->rendertarget = rt; } -static struct rendertarget *gl_get_rendertarget(struct gctx *s) +static void gl_end_render_pass(struct gctx *s) { struct gctx_gl *s_priv = (struct gctx_gl *)s; - return s_priv->rendertarget; -} -static const struct rendertarget_desc *gl_get_default_rendertarget_desc(struct gctx *s) -{ - struct gctx_gl *s_priv = (struct gctx_gl *)s; - return &s_priv->default_rendertarget_desc; + if (s_priv->rendertarget) { + ngli_rendertarget_gl_resolve(s_priv->rendertarget); + ngli_rendertarget_gl_invalidate(s_priv->rendertarget); + } + + s_priv->rendertarget = NULL; } static void gl_set_viewport(struct gctx *s, const int *viewport) @@ -493,9 +502,12 @@ const struct gctx_class ngli_gctx_gl = { .transform_projection_matrix = gl_transform_projection_matrix, .get_rendertarget_uvcoord_matrix = gl_get_rendertarget_uvcoord_matrix, - .set_rendertarget = gl_set_rendertarget, .get_rendertarget = gl_get_rendertarget, .get_default_rendertarget_desc = gl_get_default_rendertarget_desc, + + .begin_render_pass = gl_begin_render_pass, + .end_render_pass = gl_end_render_pass, + .set_viewport = gl_set_viewport, .get_viewport = gl_get_viewport, .set_scissor = gl_set_scissor, @@ -559,9 +571,12 @@ const struct gctx_class ngli_gctx_gles = { .transform_projection_matrix = gl_transform_projection_matrix, .get_rendertarget_uvcoord_matrix = gl_get_rendertarget_uvcoord_matrix, - .set_rendertarget = gl_set_rendertarget, .get_rendertarget = gl_get_rendertarget, .get_default_rendertarget_desc = gl_get_default_rendertarget_desc, + + .begin_render_pass = gl_begin_render_pass, + .end_render_pass = gl_end_render_pass, + .set_viewport = gl_set_viewport, .get_viewport = gl_get_viewport, .set_scissor = gl_set_scissor, diff --git a/libnodegl/hwconv.c b/libnodegl/hwconv.c index 1bcaeeae3b..2395d2e89c 100644 --- a/libnodegl/hwconv.c +++ b/libnodegl/hwconv.c @@ -178,8 +178,7 @@ int ngli_hwconv_convert_image(struct hwconv *hwconv, const struct image *image) ngli_assert(hwconv->src_params.layout == image->params.layout); struct rendertarget *rt = hwconv->rt; - struct rendertarget *prev_rt = ngli_gctx_get_rendertarget(gctx); - ngli_gctx_set_rendertarget(gctx, rt); + ngli_gctx_begin_render_pass(gctx, rt); int prev_vp[4] = {0}; ngli_gctx_get_viewport(gctx, prev_vp); @@ -222,7 +221,7 @@ int ngli_hwconv_convert_image(struct hwconv *hwconv, const struct image *image) ngli_pipeline_draw(hwconv->pipeline, 4, 1); - ngli_gctx_set_rendertarget(gctx, prev_rt); + ngli_gctx_end_render_pass(gctx); ngli_gctx_set_viewport(gctx, prev_vp); return 0; diff --git a/libnodegl/node_hud.c b/libnodegl/node_hud.c index 8944ca5c97..2c975d34aa 100644 --- a/libnodegl/node_hud.c +++ b/libnodegl/node_hud.c @@ -1416,7 +1416,7 @@ static void hud_draw(struct ngl_node *node) if (ctx->bind_current_rendertarget) { struct gctx *gctx = ctx->gctx; - ngli_gctx_set_rendertarget(gctx, ctx->current_rendertarget); + ngli_gctx_begin_render_pass(gctx, ctx->current_rendertarget); ctx->bind_current_rendertarget = 0; } diff --git a/libnodegl/node_rtt.c b/libnodegl/node_rtt.c index 7bc3a5d5e9..341eeb6939 100644 --- a/libnodegl/node_rtt.c +++ b/libnodegl/node_rtt.c @@ -414,6 +414,10 @@ static void rtt_draw(struct ngl_node *node) ctx->available_rendertargets[1], }; + if (!ctx->bind_current_rendertarget) { + ngli_gctx_end_render_pass(gctx); + } + ctx->available_rendertargets[0] = s->available_rendertargets[0]; ctx->available_rendertargets[1] = s->available_rendertargets[1]; ctx->current_rendertarget = s->available_rendertargets[0]; @@ -422,12 +426,10 @@ static void rtt_draw(struct ngl_node *node) ngli_node_draw(s->child); if (ctx->bind_current_rendertarget) { - ngli_gctx_set_rendertarget(gctx, ctx->current_rendertarget); + ngli_gctx_begin_render_pass(gctx, ctx->current_rendertarget); ctx->bind_current_rendertarget = 0; } - - if (s->samples > 0) - ngli_rendertarget_resolve(ctx->current_rendertarget); + ngli_gctx_end_render_pass(gctx); ctx->current_rendertarget = prev_rendertargets[1]; ctx->available_rendertargets[0] = prev_rendertargets[0]; diff --git a/libnodegl/node_text.c b/libnodegl/node_text.c index efbcf8b33a..54a407d084 100644 --- a/libnodegl/node_text.c +++ b/libnodegl/node_text.c @@ -678,7 +678,7 @@ static void text_draw(struct ngl_node *node) if (ctx->bind_current_rendertarget) { struct gctx *gctx = ctx->gctx; - ngli_gctx_set_rendertarget(gctx, ctx->current_rendertarget); + ngli_gctx_begin_render_pass(gctx, ctx->current_rendertarget); ctx->bind_current_rendertarget = 0; } diff --git a/libnodegl/pass.c b/libnodegl/pass.c index 991eda5bdb..0e5378bd3e 100644 --- a/libnodegl/pass.c +++ b/libnodegl/pass.c @@ -701,7 +701,7 @@ int ngli_pass_exec(struct pass *s) if (s->pipeline_type == NGLI_PIPELINE_TYPE_GRAPHICS) { if (ctx->bind_current_rendertarget) { struct gctx *gctx = ctx->gctx; - ngli_gctx_set_rendertarget(gctx, ctx->current_rendertarget); + ngli_gctx_begin_render_pass(gctx, ctx->current_rendertarget); ctx->bind_current_rendertarget = 0; } @@ -710,6 +710,13 @@ int ngli_pass_exec(struct pass *s) else ngli_pipeline_draw(pipeline, s->nb_vertices, s->nb_instances); } else { + if (!ctx->bind_current_rendertarget) { + struct gctx *gctx = ctx->gctx; + ngli_gctx_end_render_pass(gctx); + ctx->current_rendertarget = ctx->available_rendertargets[1]; + ctx->bind_current_rendertarget = 1; + } + ngli_pipeline_dispatch(pipeline, params->nb_group_x, params->nb_group_y, params->nb_group_z); } From 8003680777b564713bbfd536149c403f253b369c Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 16 Oct 2020 13:41:02 +0200 Subject: [PATCH 175/388] gctx: rename ngli_gctx_get_rendertarget() to ngli_gctx_get_default_rendertarger() --- libnodegl/gctx.c | 6 +++--- libnodegl/gctx.h | 4 ++-- libnodegl/gctx_gl.c | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libnodegl/gctx.c b/libnodegl/gctx.c index b6d2822e7f..783deeb9c3 100644 --- a/libnodegl/gctx.c +++ b/libnodegl/gctx.c @@ -75,7 +75,7 @@ int ngli_gctx_draw(struct gctx *s, struct ngl_node *scene, double t) if (scene) { struct ngl_ctx *ctx = scene->ctx; - struct rendertarget *rt = ngli_gctx_get_rendertarget(s); + struct rendertarget *rt = ngli_gctx_get_default_rendertarget(s); ctx->available_rendertargets[0] = rt; ctx->available_rendertargets[1] = rt; ctx->current_rendertarget = rt; @@ -130,9 +130,9 @@ void ngli_gctx_get_rendertarget_uvcoord_matrix(struct gctx *s, float *dst) s->class->get_rendertarget_uvcoord_matrix(s, dst); } -struct rendertarget *ngli_gctx_get_rendertarget(struct gctx *s) +struct rendertarget *ngli_gctx_get_default_rendertarget(struct gctx *s) { - return s->class->get_rendertarget(s); + return s->class->get_default_rendertarget(s); } const struct rendertarget_desc *ngli_gctx_get_default_rendertarget_desc(struct gctx *s) diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index d120f831f1..6049661f40 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -45,7 +45,7 @@ struct gctx_class { void (*transform_projection_matrix)(struct gctx *s, float *dst); void (*get_rendertarget_uvcoord_matrix)(struct gctx *s, float *dst); - struct rendertarget *(*get_rendertarget)(struct gctx *s); + struct rendertarget *(*get_default_rendertarget)(struct gctx *s); const struct rendertarget_desc *(*get_default_rendertarget_desc)(struct gctx *s); void (*begin_render_pass)(struct gctx *s, struct rendertarget *rt); @@ -119,7 +119,7 @@ int ngli_gctx_transform_cull_mode(struct gctx *s, int cull_mode); void ngli_gctx_transform_projection_matrix(struct gctx *s, float *dst); void ngli_gctx_get_rendertarget_uvcoord_matrix(struct gctx *s, float *dst); -struct rendertarget *ngli_gctx_get_rendertarget(struct gctx *s); +struct rendertarget *ngli_gctx_get_default_rendertarget(struct gctx *s); const struct rendertarget_desc *ngli_gctx_get_default_rendertarget_desc(struct gctx *s); void ngli_gctx_begin_render_pass(struct gctx *s, struct rendertarget *rt); diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 6073d25e39..afb05bca6d 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -406,7 +406,7 @@ static void gl_get_rendertarget_uvcoord_matrix(struct gctx *s, float *dst) memcpy(dst, matrix, 4 * 4 * sizeof(float)); } -static struct rendertarget *gl_get_rendertarget(struct gctx *s) +static struct rendertarget *gl_get_default_rendertarget(struct gctx *s) { struct gctx_gl *s_priv = (struct gctx_gl *)s; const struct ngl_config *config = &s->config; @@ -502,7 +502,7 @@ const struct gctx_class ngli_gctx_gl = { .transform_projection_matrix = gl_transform_projection_matrix, .get_rendertarget_uvcoord_matrix = gl_get_rendertarget_uvcoord_matrix, - .get_rendertarget = gl_get_rendertarget, + .get_default_rendertarget = gl_get_default_rendertarget, .get_default_rendertarget_desc = gl_get_default_rendertarget_desc, .begin_render_pass = gl_begin_render_pass, @@ -571,7 +571,7 @@ const struct gctx_class ngli_gctx_gles = { .transform_projection_matrix = gl_transform_projection_matrix, .get_rendertarget_uvcoord_matrix = gl_get_rendertarget_uvcoord_matrix, - .get_rendertarget = gl_get_rendertarget, + .get_default_rendertarget = gl_get_default_rendertarget, .get_default_rendertarget_desc = gl_get_default_rendertarget_desc, .begin_render_pass = gl_begin_render_pass, From 00f21b759374b6c433ebcabc6743c97246e67574 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 15 Oct 2020 17:52:52 +0200 Subject: [PATCH 176/388] ngl_ctx: rename bind_rendertarget field to begin_render_pass --- libnodegl/gctx.c | 2 +- libnodegl/node_hud.c | 4 ++-- libnodegl/node_rtt.c | 10 +++++----- libnodegl/node_text.c | 4 ++-- libnodegl/nodes.h | 2 +- libnodegl/pass.c | 8 ++++---- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/libnodegl/gctx.c b/libnodegl/gctx.c index 783deeb9c3..ee72c78cbb 100644 --- a/libnodegl/gctx.c +++ b/libnodegl/gctx.c @@ -79,7 +79,7 @@ int ngli_gctx_draw(struct gctx *s, struct ngl_node *scene, double t) ctx->available_rendertargets[0] = rt; ctx->available_rendertargets[1] = rt; ctx->current_rendertarget = rt; - ctx->bind_current_rendertarget = 0; + ctx->begin_render_pass = 0; LOG(DEBUG, "draw scene %s @ t=%f", scene->label, t); ngli_node_draw(scene); } diff --git a/libnodegl/node_hud.c b/libnodegl/node_hud.c index 2c975d34aa..0d29dbb412 100644 --- a/libnodegl/node_hud.c +++ b/libnodegl/node_hud.c @@ -1414,10 +1414,10 @@ static void hud_draw(struct ngl_node *node) if (ret < 0) return; - if (ctx->bind_current_rendertarget) { + if (ctx->begin_render_pass) { struct gctx *gctx = ctx->gctx; ngli_gctx_begin_render_pass(gctx, ctx->current_rendertarget); - ctx->bind_current_rendertarget = 0; + ctx->begin_render_pass = 0; } const float *modelview_matrix = ngli_darray_tail(&ctx->modelview_matrix_stack); diff --git a/libnodegl/node_rtt.c b/libnodegl/node_rtt.c index 341eeb6939..efd409994f 100644 --- a/libnodegl/node_rtt.c +++ b/libnodegl/node_rtt.c @@ -414,27 +414,27 @@ static void rtt_draw(struct ngl_node *node) ctx->available_rendertargets[1], }; - if (!ctx->bind_current_rendertarget) { + if (!ctx->begin_render_pass) { ngli_gctx_end_render_pass(gctx); } ctx->available_rendertargets[0] = s->available_rendertargets[0]; ctx->available_rendertargets[1] = s->available_rendertargets[1]; ctx->current_rendertarget = s->available_rendertargets[0]; - ctx->bind_current_rendertarget = 1; + ctx->begin_render_pass = 1; ngli_node_draw(s->child); - if (ctx->bind_current_rendertarget) { + if (ctx->begin_render_pass) { ngli_gctx_begin_render_pass(gctx, ctx->current_rendertarget); - ctx->bind_current_rendertarget = 0; + ctx->begin_render_pass = 0; } ngli_gctx_end_render_pass(gctx); ctx->current_rendertarget = prev_rendertargets[1]; ctx->available_rendertargets[0] = prev_rendertargets[0]; ctx->available_rendertargets[1] = prev_rendertargets[1]; - ctx->bind_current_rendertarget = 1; + ctx->begin_render_pass = 1; ngli_gctx_set_viewport(gctx, prev_vp); diff --git a/libnodegl/node_text.c b/libnodegl/node_text.c index 54a407d084..4c374987c6 100644 --- a/libnodegl/node_text.c +++ b/libnodegl/node_text.c @@ -676,10 +676,10 @@ static void text_draw(struct ngl_node *node) struct pipeline_desc *descs = ngli_darray_data(&s->pipeline_descs); struct pipeline_desc *desc = &descs[ctx->rnode_pos->id]; - if (ctx->bind_current_rendertarget) { + if (ctx->begin_render_pass) { struct gctx *gctx = ctx->gctx; ngli_gctx_begin_render_pass(gctx, ctx->current_rendertarget); - ctx->bind_current_rendertarget = 0; + ctx->begin_render_pass = 0; } struct pipeline_subdesc *bg_desc = &desc->bg; diff --git a/libnodegl/nodes.h b/libnodegl/nodes.h index 2b4bc3292b..123c257f12 100644 --- a/libnodegl/nodes.h +++ b/libnodegl/nodes.h @@ -85,7 +85,7 @@ struct ngl_ctx { struct ngl_config config; struct rendertarget *available_rendertargets[2]; struct rendertarget *current_rendertarget; - int bind_current_rendertarget; + int begin_render_pass; struct darray modelview_matrix_stack; struct darray projection_matrix_stack; struct darray activitycheck_nodes; diff --git a/libnodegl/pass.c b/libnodegl/pass.c index 0e5378bd3e..6b7e1e72c6 100644 --- a/libnodegl/pass.c +++ b/libnodegl/pass.c @@ -699,10 +699,10 @@ int ngli_pass_exec(struct pass *s) } if (s->pipeline_type == NGLI_PIPELINE_TYPE_GRAPHICS) { - if (ctx->bind_current_rendertarget) { + if (ctx->begin_render_pass) { struct gctx *gctx = ctx->gctx; ngli_gctx_begin_render_pass(gctx, ctx->current_rendertarget); - ctx->bind_current_rendertarget = 0; + ctx->begin_render_pass = 0; } if (s->indices_buffer) @@ -710,11 +710,11 @@ int ngli_pass_exec(struct pass *s) else ngli_pipeline_draw(pipeline, s->nb_vertices, s->nb_instances); } else { - if (!ctx->bind_current_rendertarget) { + if (!ctx->begin_render_pass) { struct gctx *gctx = ctx->gctx; ngli_gctx_end_render_pass(gctx); ctx->current_rendertarget = ctx->available_rendertargets[1]; - ctx->bind_current_rendertarget = 1; + ctx->begin_render_pass = 1; } ngli_pipeline_dispatch(pipeline, params->nb_group_x, params->nb_group_y, params->nb_group_z); From 9dfa103468cf5d3a97502772baaa8229f3961323 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Sun, 18 Oct 2020 17:58:01 +0200 Subject: [PATCH 177/388] rendertarget: remove ngli_rendertarget_resolve() Forgotten in a7fc90b9cf75412b815889d0a2964e6c8112405a. --- libnodegl/gctx.h | 1 - libnodegl/gctx_gl.c | 2 -- libnodegl/rendertarget.c | 5 ----- 3 files changed, 8 deletions(-) diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index 6049661f40..6879e6e4aa 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -88,7 +88,6 @@ struct gctx_class { struct rendertarget *(*rendertarget_create)(struct gctx *ctx); int (*rendertarget_init)(struct rendertarget *s, const struct rendertarget_params *params); - void (*rendertarget_resolve)(struct rendertarget *s); void (*rendertarget_read_pixels)(struct rendertarget *s, uint8_t *data); void (*rendertarget_freep)(struct rendertarget **sp); diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index afb05bca6d..0c44836f3f 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -545,7 +545,6 @@ const struct gctx_class ngli_gctx_gl = { .rendertarget_create = ngli_rendertarget_gl_create, .rendertarget_init = ngli_rendertarget_gl_init, - .rendertarget_resolve = ngli_rendertarget_gl_resolve, .rendertarget_read_pixels = ngli_rendertarget_gl_read_pixels, .rendertarget_freep = ngli_rendertarget_gl_freep, @@ -614,7 +613,6 @@ const struct gctx_class ngli_gctx_gles = { .rendertarget_create = ngli_rendertarget_gl_create, .rendertarget_init = ngli_rendertarget_gl_init, - .rendertarget_resolve = ngli_rendertarget_gl_resolve, .rendertarget_read_pixels = ngli_rendertarget_gl_read_pixels, .rendertarget_freep = ngli_rendertarget_gl_freep, diff --git a/libnodegl/rendertarget.c b/libnodegl/rendertarget.c index d86a9c49eb..1a6347f536 100644 --- a/libnodegl/rendertarget.c +++ b/libnodegl/rendertarget.c @@ -32,11 +32,6 @@ int ngli_rendertarget_init(struct rendertarget *s, const struct rendertarget_par return s->gctx->class->rendertarget_init(s, params); } -void ngli_rendertarget_resolve(struct rendertarget *s) -{ - s->gctx->class->rendertarget_resolve(s); -} - void ngli_rendertarget_read_pixels(struct rendertarget *s, uint8_t *data) { s->gctx->class->rendertarget_read_pixels(s, data); From ccd7731f999004517b805eb9606e0a2d5f5c61ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 8 Oct 2020 22:47:21 +0200 Subject: [PATCH 178/388] render,program: hold the vert_out_vars array in the program That way, when multiple Render share a single program, we only have one array in memory. It would also simplify the usage of the Program node if another node than Render ends up using it. --- libnodegl/node_program.c | 27 +++++++++++++++++++++++++++ libnodegl/node_render.c | 23 ++--------------------- libnodegl/nodes.h | 2 ++ 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/libnodegl/node_program.c b/libnodegl/node_program.c index 8d8d4340c7..dcc062b32f 100644 --- a/libnodegl/node_program.c +++ b/libnodegl/node_program.c @@ -20,9 +20,12 @@ */ #include + +#include "darray.h" #include "log.h" #include "nodegl.h" #include "nodes.h" +#include "pgcraft.h" #define IO_NODES (const int[]){NGL_NODE_IOINT, \ NGL_NODE_IOIVEC2, \ @@ -67,13 +70,37 @@ static int program_init(struct ngl_node *node) return NGL_ERROR_INVALID_USAGE; } + ngli_darray_init(&s->vert_out_vars_array, sizeof(struct pgcraft_iovar), 0); + if (s->vert_out_vars) { + const struct hmap_entry *e = NULL; + while ((e = ngli_hmap_next(s->vert_out_vars, e))) { + const struct ngl_node *iovar_node = e->data; + const struct io_priv *iovar_priv = iovar_node->priv_data; + struct pgcraft_iovar iovar = { + .type = iovar_priv->type, + .precision_in = iovar_priv->precision_in, + .precision_out = iovar_priv->precision_out, + }; + snprintf(iovar.name, sizeof(iovar.name), "%s", e->key); + if (!ngli_darray_push(&s->vert_out_vars_array, &iovar)) + return NGL_ERROR_MEMORY; + } + } + return 0; } +static void program_uninit(struct ngl_node *node) +{ + struct program_priv *s = node->priv_data; + ngli_darray_reset(&s->vert_out_vars_array); +} + const struct node_class ngli_program_class = { .id = NGL_NODE_PROGRAM, .name = "Program", .init = program_init, + .uninit = program_uninit, .priv_size = sizeof(struct program_priv), .params = program_params, .file = __FILE__, diff --git a/libnodegl/node_render.c b/libnodegl/node_render.c index 69bddea158..09b509eda9 100644 --- a/libnodegl/node_render.c +++ b/libnodegl/node_render.c @@ -42,7 +42,6 @@ struct render_priv { int nb_instances; struct pass pass; - struct darray vert_out_vars; // pgcraft_iovar }; #define PROGRAMS_TYPES_LIST (const int[]){NGL_NODE_PROGRAM, \ @@ -157,24 +156,7 @@ static int render_init(struct ngl_node *node) return NGL_ERROR_INVALID_USAGE; } - ngli_darray_init(&s->vert_out_vars, sizeof(struct pgcraft_iovar), 0); const struct program_priv *program = s->program->priv_data; - if (program->vert_out_vars) { - const struct hmap_entry *e = NULL; - while ((e = ngli_hmap_next(program->vert_out_vars, e))) { - const struct ngl_node *iovar_node = e->data; - const struct io_priv *iovar_priv = iovar_node->priv_data; - struct pgcraft_iovar iovar = { - .type = iovar_priv->type, - .precision_in = iovar_priv->precision_in, - .precision_out = iovar_priv->precision_out, - }; - snprintf(iovar.name, sizeof(iovar.name), "%s", e->key); - if (!ngli_darray_push(&s->vert_out_vars, &iovar)) - return NGL_ERROR_MEMORY; - } - } - struct pass_params params = { .label = node->label, .geometry = s->geometry, @@ -186,8 +168,8 @@ static int render_init(struct ngl_node *node) .attributes = s->attributes, .instance_attributes = s->instance_attributes, .nb_instances = s->nb_instances, - .vert_out_vars = ngli_darray_data(&s->vert_out_vars), - .nb_vert_out_vars = ngli_darray_count(&s->vert_out_vars), + .vert_out_vars = ngli_darray_data(&program->vert_out_vars_array), + .nb_vert_out_vars = ngli_darray_count(&program->vert_out_vars_array), .nb_frag_output = program->nb_frag_output, }; return ngli_pass_init(&s->pass, ctx, ¶ms); @@ -203,7 +185,6 @@ static void render_uninit(struct ngl_node *node) { struct render_priv *s = node->priv_data; ngli_pass_uninit(&s->pass); - ngli_darray_reset(&s->vert_out_vars); } static int render_update(struct ngl_node *node, double t) diff --git a/libnodegl/nodes.h b/libnodegl/nodes.h index 123c257f12..d41d4df041 100644 --- a/libnodegl/nodes.h +++ b/libnodegl/nodes.h @@ -276,6 +276,8 @@ struct program_priv { struct hmap *properties; struct hmap *vert_out_vars; int nb_frag_output; + + struct darray vert_out_vars_array; // pgcraft_iovar }; extern const struct param_choices ngli_mipmap_filter_choices; From 943e2ac6f0404016d7de6384b1d9d5ae403ff1c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 9 Oct 2020 22:21:37 +0200 Subject: [PATCH 179/388] hmap: remove constness from ngli_hmap_next() return A user may want to alter the content of an entry; this is perfectly valid and acceptable, so there is no reason for the hmap to try to prevent that. --- libnodegl/hmap.c | 8 ++++---- libnodegl/hmap.h | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/libnodegl/hmap.c b/libnodegl/hmap.c index 3da60426af..3f5bb1666d 100644 --- a/libnodegl/hmap.c +++ b/libnodegl/hmap.c @@ -191,8 +191,8 @@ int ngli_hmap_set(struct hmap *hm, const char *key, void *data) return 0; } -static const struct hmap_entry *get_first_entry(const struct hmap *hm, - int bucket_start) +static struct hmap_entry *get_first_entry(const struct hmap *hm, + int bucket_start) { for (int i = bucket_start; i < hm->size; i++) { const struct bucket *b = &hm->buckets[i]; @@ -202,8 +202,8 @@ static const struct hmap_entry *get_first_entry(const struct hmap *hm, return NULL; } -const struct hmap_entry *ngli_hmap_next(const struct hmap *hm, - const struct hmap_entry *prev) +struct hmap_entry *ngli_hmap_next(const struct hmap *hm, + const struct hmap_entry *prev) { if (!hm->count) return NULL; diff --git a/libnodegl/hmap.h b/libnodegl/hmap.h index c2b6cff17a..99bee9ea69 100644 --- a/libnodegl/hmap.h +++ b/libnodegl/hmap.h @@ -41,8 +41,7 @@ void ngli_hmap_set_free(struct hmap *hm, user_free_func_type user_free_func, voi int ngli_hmap_count(const struct hmap *hm); int ngli_hmap_set(struct hmap *hm, const char *key, void *data); void *ngli_hmap_get(const struct hmap *hm, const char *key); -const struct hmap_entry *ngli_hmap_next(const struct hmap *hm, - const struct hmap_entry *prev); +struct hmap_entry *ngli_hmap_next(const struct hmap *hm, const struct hmap_entry *prev); void ngli_hmap_freep(struct hmap **hmp); #endif From 5c5434d089a113e4f4d16cbd86a802921c077281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 19 Oct 2020 23:08:58 +0200 Subject: [PATCH 180/388] text: simplify realloc check code The for-loop is the only thing excluded from the need_realloc scope, but does things only if need_realloc is verified. --- libnodegl/node_text.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/libnodegl/node_text.c b/libnodegl/node_text.c index 4c374987c6..2d1f869999 100644 --- a/libnodegl/node_text.c +++ b/libnodegl/node_text.c @@ -330,10 +330,8 @@ static int update_character_geometries(struct ngl_node *node) px++; } - if (nb_indices != s->nb_indices) { - const int need_realloc = nb_indices > s->nb_indices; + if (nb_indices > s->nb_indices) { // need re-alloc - if (need_realloc) { ngli_buffer_freep(&s->vertices); ngli_buffer_freep(&s->uvcoords); ngli_buffer_freep(&s->indices); @@ -350,17 +348,14 @@ static int update_character_geometries(struct ngl_node *node) (ret = ngli_buffer_init(s->uvcoords, nb_uvcoords * sizeof(*uvcoords), NGLI_BUFFER_USAGE_DYNAMIC)) < 0 || (ret = ngli_buffer_init(s->indices, nb_indices * sizeof(*indices), NGLI_BUFFER_USAGE_DYNAMIC)) < 0) goto end; - } struct pipeline_desc *descs = ngli_darray_data(&s->pipeline_descs); const int nb_descs = ngli_darray_count(&s->pipeline_descs); for (int i = 0; i < nb_descs; i++) { struct pipeline_subdesc *desc = &descs[i].fg; - if (need_realloc) { ngli_pipeline_update_attribute(desc->pipeline, 0, s->vertices); ngli_pipeline_update_attribute(desc->pipeline, 1, s->uvcoords); - } } } From dce4d8dade3d2168df21ef59fe077c26a1e45fdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 19 Oct 2020 23:09:18 +0200 Subject: [PATCH 181/388] text: re-indent after previous commit --- libnodegl/node_text.c | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/libnodegl/node_text.c b/libnodegl/node_text.c index 2d1f869999..818e94bb06 100644 --- a/libnodegl/node_text.c +++ b/libnodegl/node_text.c @@ -331,31 +331,30 @@ static int update_character_geometries(struct ngl_node *node) } if (nb_indices > s->nb_indices) { // need re-alloc + ngli_buffer_freep(&s->vertices); + ngli_buffer_freep(&s->uvcoords); + ngli_buffer_freep(&s->indices); - ngli_buffer_freep(&s->vertices); - ngli_buffer_freep(&s->uvcoords); - ngli_buffer_freep(&s->indices); - - s->vertices = ngli_buffer_create(gctx); - s->uvcoords = ngli_buffer_create(gctx); - s->indices = ngli_buffer_create(gctx); - if (!s->vertices || !s->uvcoords || !s->indices) { - ret = NGL_ERROR_MEMORY; - goto end; - } + s->vertices = ngli_buffer_create(gctx); + s->uvcoords = ngli_buffer_create(gctx); + s->indices = ngli_buffer_create(gctx); + if (!s->vertices || !s->uvcoords || !s->indices) { + ret = NGL_ERROR_MEMORY; + goto end; + } - if ((ret = ngli_buffer_init(s->vertices, nb_vertices * sizeof(*vertices), NGLI_BUFFER_USAGE_DYNAMIC)) < 0 || - (ret = ngli_buffer_init(s->uvcoords, nb_uvcoords * sizeof(*uvcoords), NGLI_BUFFER_USAGE_DYNAMIC)) < 0 || - (ret = ngli_buffer_init(s->indices, nb_indices * sizeof(*indices), NGLI_BUFFER_USAGE_DYNAMIC)) < 0) - goto end; + if ((ret = ngli_buffer_init(s->vertices, nb_vertices * sizeof(*vertices), NGLI_BUFFER_USAGE_DYNAMIC)) < 0 || + (ret = ngli_buffer_init(s->uvcoords, nb_uvcoords * sizeof(*uvcoords), NGLI_BUFFER_USAGE_DYNAMIC)) < 0 || + (ret = ngli_buffer_init(s->indices, nb_indices * sizeof(*indices), NGLI_BUFFER_USAGE_DYNAMIC)) < 0) + goto end; struct pipeline_desc *descs = ngli_darray_data(&s->pipeline_descs); const int nb_descs = ngli_darray_count(&s->pipeline_descs); for (int i = 0; i < nb_descs; i++) { struct pipeline_subdesc *desc = &descs[i].fg; - ngli_pipeline_update_attribute(desc->pipeline, 0, s->vertices); - ngli_pipeline_update_attribute(desc->pipeline, 1, s->uvcoords); + ngli_pipeline_update_attribute(desc->pipeline, 0, s->vertices); + ngli_pipeline_update_attribute(desc->pipeline, 1, s->uvcoords); } } From fe61ad4acc960d1f517d6db49f6b80187f2c43db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 15 Oct 2020 17:58:36 +0200 Subject: [PATCH 182/388] block: do not switch from SSBO type back to UBO When a block is shared between 2 pipelines, if one of the pipeline requires an SSBO type (typically if it needs to write to it in a compute pass), the block type is changed from UBO to SSBO. This behaviour was working fine to switch from UBO to SSBO, but it was unfortunately also switching back to UBO if a later pipeline didn't need SSBO. A typical scenario would be: .---------. | Group | `-.-----.-' 0 | | 1 v v .---------------. block .---------. .--------. | ResourceProps |<---------| Compute | | Render | | writable=True | `-----.---' `---.----' `---------------' | block | v v .---------------. | Block | | layout=std140 | `---------------' The Compute node would make the block type to SSBO (because of the resource property of writable) and the Render would then restore it back to UBO (because std140 layout doesn't imply writable, and there is no resource property implying such a thing). This commit fixes this by only setting the default UBO type when not set previously. --- libnodegl/block.c | 1 + libnodegl/pass.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/libnodegl/block.c b/libnodegl/block.c index c896b42996..17e5eb293a 100644 --- a/libnodegl/block.c +++ b/libnodegl/block.c @@ -31,6 +31,7 @@ void ngli_block_init(struct block *s, enum block_layout layout) { ngli_darray_init(&s->fields, sizeof(struct block_field), 0); s->layout = layout; + s->type = NGLI_TYPE_NONE; } static const int strides_map[NGLI_BLOCK_NB_LAYOUTS][NGLI_TYPE_NB] = { diff --git a/libnodegl/pass.c b/libnodegl/pass.c index 6b7e1e72c6..9e98d64b19 100644 --- a/libnodegl/pass.c +++ b/libnodegl/pass.c @@ -181,7 +181,7 @@ static int register_block(struct pass *s, const char *name, struct ngl_node *blo * Select buffer type. We prefer UBO over SSBO, but in the following * situations, UBO is not possible. */ - int type = NGLI_TYPE_UNIFORM_BUFFER; + int type = block->type == NGLI_TYPE_NONE ? NGLI_TYPE_UNIFORM_BUFFER : block->type; if (block->layout == NGLI_BLOCK_LAYOUT_STD430) { LOG(DEBUG, "block %s has a std430 layout, declaring it as SSBO", name); type = NGLI_TYPE_STORAGE_BUFFER; From b81a1ba260c3ef5e2ecea3781a4e2ba43ab295f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 15 Oct 2020 18:22:01 +0200 Subject: [PATCH 183/388] tests/compute: set writable properties to output resources This is currently working because the std430 layout forces a switch to SSBO. Before previous commit, setting them to std140 would have failed because UBOs are not writable. The switch to std140 is to make sure this doesn't break again in the future. --- tests/compute.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/compute.py b/tests/compute.py index c03d7e6599..cbbde9320e 100644 --- a/tests/compute.py +++ b/tests/compute.py @@ -84,7 +84,7 @@ def compute_particules(cfg): ], layout='std430', ) - opositions = ngl.Block(fields=[ngl.BufferVec3(count=nb_particules, label='positions')], layout='std430') + opositions = ngl.Block(fields=[ngl.BufferVec3(count=nb_particules, label='positions')], layout='std140') animkf = [ ngl.AnimKeyFrameFloat(0, 0), @@ -95,6 +95,7 @@ def compute_particules(cfg): group_size = nb_particules / local_size program = ngl.ComputeProgram(_PARTICULES_COMPUTE % dict(local_size=local_size)) + program.update_properties(odata=ngl.ResourceProps(writable=True)) compute = ngl.Compute(nb_particules, 1, 1, program) compute.update_resources(time=time, duration=duration, idata=ipositions, odata=opositions) @@ -198,7 +199,7 @@ def compute_histogram(cfg, show_dbg_points=False): texture = ngl.Texture2D(width=size, height=size, data_src=texture_buffer) texture.set_format('r32g32b32a32_sfloat') - histogram_block = ngl.Block(layout='std430', label='histogram') + histogram_block = ngl.Block(layout='std140', label='histogram') histogram_block.add_fields( ngl.BufferUInt(hsize, label='r'), ngl.BufferUInt(hsize, label='g'), @@ -223,6 +224,7 @@ def compute_histogram(cfg, show_dbg_points=False): group_size = size // local_size exec_histogram_shader = _COMPUTE_HISTOGRAM_EXEC % shader_params exec_histogram_program = ngl.ComputeProgram(exec_histogram_shader) + exec_histogram_program.update_properties(hist=ngl.ResourceProps(writable=True)) exec_histogram = ngl.Compute( group_size, group_size, @@ -280,7 +282,7 @@ def compute_animation(cfg): input_vertices = ngl.BufferVec3(data=vertices_data, label='vertices') output_vertices = ngl.BufferVec3(data=vertices_data, label='vertices') input_block = ngl.Block(fields=[input_vertices], layout='std140') - output_block = ngl.Block(fields=[output_vertices], layout='std430') + output_block = ngl.Block(fields=[output_vertices], layout='std140') rotate_animkf = [ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration, 360)] @@ -288,6 +290,7 @@ def compute_animation(cfg): transform = ngl.UniformMat4(transform=rotate) program = ngl.ComputeProgram(compute_shader) + program.update_properties(dst=ngl.ResourceProps(writable=True)) compute = ngl.Compute(nb_vertices / (local_size ** 2), 1, 1, program) compute.update_resources(transform=transform, src=input_block, dst=output_block) From 3f35f83d7a05b2372b225b552bce069dde2184e6 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 20 Oct 2020 08:25:07 +0200 Subject: [PATCH 184/388] rtt: remove vflip parameter This parameter was useful for users relying on the OpenGL backend and prepared the geometry and uv coordinates to deal with the rendertarget output being vertically flipped. Since df18098f01ff9795018b23dfc844225f325db527, we alter the rendertarget output coordinate matrix for OpenGL offscreen rendering, leaving this parameter not working as expected in this case. Moreover, this parameter won't work as expected with other graphics API. --- libnodegl/doc/libnodegl.md | 1 - libnodegl/node_rtt.c | 27 +++++++++++---------------- libnodegl/nodes.specs | 1 - 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/libnodegl/doc/libnodegl.md b/libnodegl/doc/libnodegl.md index 3ee00f3f06..d140bc99a5 100644 --- a/libnodegl/doc/libnodegl.md +++ b/libnodegl/doc/libnodegl.md @@ -457,7 +457,6 @@ Parameter | Live-chg. | Type | Description | Default `samples` | | [`int`](#parameter-types) | number of samples used for multisampling anti-aliasing | `0` `clear_color` | | [`vec4`](#parameter-types) | color used to clear the `color_texture` | (`0`,`0`,`0`,`0`) `features` | | [`framebuffer_features`](#framebuffer_features-choices) | framebuffer feature mask | `0` -`vflip` | | [`bool`](#parameter-types) | apply a vertical flip to `color_texture` and `depth_texture` transformation matrices to match the `node.gl` uv coordinates system | `1` **Source**: [node_rtt.c](/libnodegl/node_rtt.c) diff --git a/libnodegl/node_rtt.c b/libnodegl/node_rtt.c index efd409994f..56bc443f0b 100644 --- a/libnodegl/node_rtt.c +++ b/libnodegl/node_rtt.c @@ -39,7 +39,6 @@ struct rtt_priv { int samples; float clear_color[4]; int features; - int vflip; int use_rt_resume; int width; @@ -86,8 +85,6 @@ static const struct node_param rtt_params[] = { {"features", PARAM_TYPE_FLAGS, OFFSET(features), .choices=&feature_choices, .desc=NGLI_DOCSTRING("framebuffer feature mask")}, - {"vflip", PARAM_TYPE_BOOL, OFFSET(vflip), {.i64=1}, - .desc=NGLI_DOCSTRING("apply a vertical flip to `color_texture` and `depth_texture` transformation matrices to match the `node.gl` uv coordinates system")}, {NULL} }; @@ -356,20 +353,18 @@ static int rtt_prefetch(struct ngl_node *node) s->available_rendertargets[1] = s->rt_resume; } - if (s->vflip) { - /* flip vertically the color and depth textures so the coordinates - * match how the uv coordinates system works */ - for (int i = 0; i < s->nb_color_textures; i++) { - struct texture_priv *texture_priv = s->color_textures[i]->priv_data; - struct image *image = &texture_priv->image; - ngli_gctx_get_rendertarget_uvcoord_matrix(gctx, image->coordinates_matrix); - } + /* transform the color and depth textures so the coordinates + * match how the graphics context uv coordinate system works */ + for (int i = 0; i < s->nb_color_textures; i++) { + struct texture_priv *texture_priv = s->color_textures[i]->priv_data; + struct image *image = &texture_priv->image; + ngli_gctx_get_rendertarget_uvcoord_matrix(gctx, image->coordinates_matrix); + } - if (s->depth_texture) { - struct texture_priv *depth_texture_priv = s->depth_texture->priv_data; - struct image *depth_image = &depth_texture_priv->image; - ngli_gctx_get_rendertarget_uvcoord_matrix(gctx, depth_image->coordinates_matrix); - } + if (s->depth_texture) { + struct texture_priv *depth_texture_priv = s->depth_texture->priv_data; + struct image *depth_image = &depth_texture_priv->image; + ngli_gctx_get_rendertarget_uvcoord_matrix(gctx, depth_image->coordinates_matrix); } return 0; diff --git a/libnodegl/nodes.specs b/libnodegl/nodes.specs index 9e3eca2402..cb7920086b 100644 --- a/libnodegl/nodes.specs +++ b/libnodegl/nodes.specs @@ -302,7 +302,6 @@ - [samples, int] - [clear_color, vec4] - [features, flags] - - [vflip, bool] - ResourceProps: - [precision, select] From f4ca76b67cd7bd9f4cf718ce5a268e000222f56a Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 29 Sep 2020 09:41:01 +0200 Subject: [PATCH 185/388] rendertarget: move samples field from attachment_desc to rendertarget_desc All attachments must have the same samples count. --- libnodegl/gctx_gl.c | 3 +-- libnodegl/node_rtt.c | 7 +++---- libnodegl/rendertarget.h | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 0c44836f3f..078f915f60 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -270,12 +270,11 @@ static int gl_init(struct gctx *s) s->version = gl->version; s->features = gl->features; s->limits = gl->limits; + s_priv->default_rendertarget_desc.samples = gl->samples; s_priv->default_rendertarget_desc.nb_colors = 1; s_priv->default_rendertarget_desc.colors[0].format = NGLI_FORMAT_R8G8B8A8_UNORM; - s_priv->default_rendertarget_desc.colors[0].samples = gl->samples; s_priv->default_rendertarget_desc.colors[0].resolve = gl->samples > 1; s_priv->default_rendertarget_desc.depth_stencil.format = NGLI_FORMAT_D24_UNORM_S8_UINT; - s_priv->default_rendertarget_desc.depth_stencil.samples = gl->samples; s_priv->default_rendertarget_desc.depth_stencil.resolve = gl->samples > 1; ngli_glstate_probe(gl, &s_priv->glstate); diff --git a/libnodegl/node_rtt.c b/libnodegl/node_rtt.c index 56bc443f0b..698a0b8f90 100644 --- a/libnodegl/node_rtt.c +++ b/libnodegl/node_rtt.c @@ -148,14 +148,15 @@ static int rtt_prepare(struct ngl_node *node) s->use_rt_resume = 1; } - struct rendertarget_desc desc = {0}; + struct rendertarget_desc desc = { + .samples = s->samples, + }; for (int i = 0; i < s->nb_color_textures; i++) { const struct texture_priv *texture_priv = s->color_textures[i]->priv_data; const struct texture_params *params = &texture_priv->params; const int faces = params->type == NGLI_TEXTURE_TYPE_CUBE ? 6 : 1; for (int j = 0; j < faces; j++) { desc.colors[desc.nb_colors].format = params->format; - desc.colors[desc.nb_colors].samples = s->samples; desc.colors[desc.nb_colors].resolve = s->samples > 1; desc.nb_colors++; } @@ -164,7 +165,6 @@ static int rtt_prepare(struct ngl_node *node) const struct texture_priv *depth_texture_priv = s->depth_texture->priv_data; const struct texture_params *depth_texture_params = &depth_texture_priv->params; desc.depth_stencil.format = depth_texture_params->format; - desc.depth_stencil.samples = s->samples; desc.depth_stencil.resolve = s->samples > 1; } else { int depth_format = NGLI_FORMAT_UNDEFINED; @@ -173,7 +173,6 @@ static int rtt_prepare(struct ngl_node *node) else if (s->features & FEATURE_DEPTH) depth_format = ngli_gctx_get_preferred_depth_format(gctx); desc.depth_stencil.format = depth_format; - desc.depth_stencil.samples = s->samples; } rnode->rendertarget_desc = desc; diff --git a/libnodegl/rendertarget.h b/libnodegl/rendertarget.h index dbbe6a3434..a7e476f94e 100644 --- a/libnodegl/rendertarget.h +++ b/libnodegl/rendertarget.h @@ -40,11 +40,11 @@ enum { struct attachment_desc { int format; - int samples; int resolve; }; struct rendertarget_desc { + int samples; int nb_colors; struct attachment_desc colors[NGLI_MAX_COLOR_ATTACHMENTS]; struct attachment_desc depth_stencil; From bbddaed20d49fde4f3d6efd72dd3480850832293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 20 Oct 2020 17:06:46 +0200 Subject: [PATCH 186/388] internal: rename features.h to feature.h This may conflict with the system features.h. An easy way to reproduce the compilation failure is: CFLAGS=-I. make nodegl-install This will cause system headers to include features.h from our tree in priority over their own (/usr/include/features.h typically). This means we can not move our headers in a dedicated directory (because it would imply adding a -I. that would take over the system directory, even with < > includes). --- libnodegl/{features.h => feature.h} | 4 ++-- libnodegl/gctx.h | 2 +- libnodegl/glcontext.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename libnodegl/{features.h => feature.h} (98%) diff --git a/libnodegl/features.h b/libnodegl/feature.h similarity index 98% rename from libnodegl/features.h rename to libnodegl/feature.h index 5599839685..de5a0f2103 100644 --- a/libnodegl/features.h +++ b/libnodegl/feature.h @@ -19,8 +19,8 @@ * under the License. */ -#ifndef FEATURES_H -#define FEATURES_H +#ifndef FEATURE_H +#define FEATURE_H #define NGLI_FEATURE_VERTEX_ARRAY_OBJECT (1 << 0) #define NGLI_FEATURE_TEXTURE_3D (1 << 1) diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index 6879e6e4aa..08c734b7ae 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -23,7 +23,7 @@ #define GCTX_H #include "buffer.h" -#include "features.h" +#include "feature.h" #include "gtimer.h" #include "limits.h" #include "nodegl.h" diff --git a/libnodegl/glcontext.h b/libnodegl/glcontext.h index 18aa4efd20..29de2e85c6 100644 --- a/libnodegl/glcontext.h +++ b/libnodegl/glcontext.h @@ -24,7 +24,7 @@ #include -#include "features.h" +#include "feature.h" #include "glfunctions.h" #include "limits.h" #include "nodegl.h" From a07a3326141be77a2f8e4905496a313a29ed6c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 20 Oct 2020 17:20:47 +0200 Subject: [PATCH 187/388] internal: rename limits.h to limit.h See previous commit for the rationale. --- libnodegl/gctx.h | 2 +- libnodegl/glcontext.c | 2 +- libnodegl/glcontext.h | 2 +- libnodegl/{limits.h => limit.h} | 4 ++-- libnodegl/node_compute.c | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename libnodegl/{limits.h => limit.h} (96%) diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index 08c734b7ae..e40d78d0ac 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -25,7 +25,7 @@ #include "buffer.h" #include "feature.h" #include "gtimer.h" -#include "limits.h" +#include "limit.h" #include "nodegl.h" #include "pipeline.h" #include "rendertarget.h" diff --git a/libnodegl/glcontext.c b/libnodegl/glcontext.c index a082e7779d..791cdc79cc 100644 --- a/libnodegl/glcontext.c +++ b/libnodegl/glcontext.c @@ -25,7 +25,7 @@ #include "bstr.h" #include "glcontext.h" -#include "limits.h" +#include "limit.h" #include "log.h" #include "memory.h" #include "nodegl.h" diff --git a/libnodegl/glcontext.h b/libnodegl/glcontext.h index 29de2e85c6..cbd02edd47 100644 --- a/libnodegl/glcontext.h +++ b/libnodegl/glcontext.h @@ -26,7 +26,7 @@ #include "feature.h" #include "glfunctions.h" -#include "limits.h" +#include "limit.h" #include "nodegl.h" struct glcontext_class; diff --git a/libnodegl/limits.h b/libnodegl/limit.h similarity index 96% rename from libnodegl/limits.h rename to libnodegl/limit.h index 53ec855464..0f46e80ac1 100644 --- a/libnodegl/limits.h +++ b/libnodegl/limit.h @@ -19,8 +19,8 @@ * under the License. */ -#ifndef LIMITS_H -#define LIMITS_H +#ifndef LIMIT_H +#define LIMIT_H struct limits { int max_texture_image_units; diff --git a/libnodegl/node_compute.c b/libnodegl/node_compute.c index 8257d68bba..03c2637e8e 100644 --- a/libnodegl/node_compute.c +++ b/libnodegl/node_compute.c @@ -26,7 +26,7 @@ #include "gctx.h" #include "hmap.h" -#include "limits.h" +#include "limit.h" #include "log.h" #include "nodegl.h" #include "nodes.h" From 2afbc312546fa0687f00d57ddab2cf50b80026c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 20 Oct 2020 17:23:16 +0200 Subject: [PATCH 188/388] internal: remove unused limits.h includes These includes appeared to be necessary because of the use of INT_MAX in the past, but it is not used anymore. --- libnodegl/node_compute.c | 1 - libnodegl/node_render.c | 1 - libnodegl/pass.c | 1 - 3 files changed, 3 deletions(-) diff --git a/libnodegl/node_compute.c b/libnodegl/node_compute.c index 03c2637e8e..256a02ce0d 100644 --- a/libnodegl/node_compute.c +++ b/libnodegl/node_compute.c @@ -22,7 +22,6 @@ #include #include #include -#include #include "gctx.h" #include "hmap.h" diff --git a/libnodegl/node_render.c b/libnodegl/node_render.c index 09b509eda9..e60fc36d5b 100644 --- a/libnodegl/node_render.c +++ b/libnodegl/node_render.c @@ -22,7 +22,6 @@ #include #include #include -#include #include "hmap.h" #include "log.h" diff --git a/libnodegl/pass.c b/libnodegl/pass.c index 9e98d64b19..8fc67d8efe 100644 --- a/libnodegl/pass.c +++ b/libnodegl/pass.c @@ -22,7 +22,6 @@ #include #include #include -#include #include #include From 7ac2c553ed9e32f041f96ff0b9db4ab12f9a3d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 22 Oct 2020 12:30:42 +0200 Subject: [PATCH 189/388] tools/common: define _POSIX_C_SOURCE for clock_gettime() This is needed for clock_gettime() and its associated timespec struct. The reason it currently works is because of -D_REENTRANT being added by the SDL C flags, which implies a recent _POSIX_C_SOURCE. Not all tools are built with SDL, but we are lucky the first one compiling common.c does it with the SDL flags. This depends on the undefined behaviour of evaluation of the prerequisite orders, and we should not rely on that. --- ngl-tools/common.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ngl-tools/common.c b/ngl-tools/common.c index ca46c41ce3..a89a75c1c2 100644 --- a/ngl-tools/common.c +++ b/ngl-tools/common.c @@ -19,6 +19,8 @@ * under the License. */ +#define _POSIX_C_SOURCE 199309L // clock_gettime() + #include #include #include From 8b7750d48dab9aa32af46be69aea59c7d4c7c676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 22 Oct 2020 12:40:19 +0200 Subject: [PATCH 190/388] gl: set wrapper generator output files as argument The build system is responsible for tracking compilation files, so it should be made aware of it instead of relying on code generation tools. --- libnodegl/Makefile | 2 +- libnodegl/gen-gl-wrappers.py | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/libnodegl/Makefile b/libnodegl/Makefile index 2806f851f6..2facd00ad4 100644 --- a/libnodegl/Makefile +++ b/libnodegl/Makefile @@ -339,7 +339,7 @@ updatedoc: gen_doc$(EXESUF) # OpenGL function wrappers # gen_gl_wrappers: gl.xml - $(PYTHON) gen-gl-wrappers.py $^ + $(PYTHON) gen-gl-wrappers.py $^ glfunctions.h gldefinitions_data.h glwrappers.h gl.xml: $(CURL) https://raw.githubusercontent.com/KhronosGroup/OpenGL-Registry/master/xml/$@ -o $@ diff --git a/libnodegl/gen-gl-wrappers.py b/libnodegl/gen-gl-wrappers.py index b32b250ce8..2bcce77a5b 100644 --- a/libnodegl/gen-gl-wrappers.py +++ b/libnodegl/gen-gl-wrappers.py @@ -264,7 +264,7 @@ def get_proto_elems(xml_node): elems.append(text) return elems -def gen(gl_xml): +def gen(gl_xml, func_file, def_file, wrap_file): do_not_edit = '/* DO NOT EDIT - This file is autogenerated */\n' @@ -370,13 +370,15 @@ def gen(gl_xml): glfunctions += '};\n\n#endif\n' gldefinitions += '};\n' - open('glfunctions.h', 'w').write(glfunctions) - open('gldefinitions_data.h', 'w').write(gldefinitions) - open('glwrappers.h', 'w').write(glwrappers) - + with open(func_file, 'w') as f: + f.write(glfunctions) + with open(def_file, 'w') as f: + f.write(gldefinitions) + with open(wrap_file, 'w') as f: + f.write(glwrappers) if __name__ == '__main__': - if len(sys.argv) != 2: - print('Usage: %s gl.xml' % sys.argv[0]) + if len(sys.argv) != 5: + print('Usage: %s ' % sys.argv[0]) sys.exit(0) - gen(sys.argv[1]) + gen(*sys.argv[1:]) From 5356d628971566d8ba4468adbda049d290b75654 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 21 Oct 2020 11:33:04 +0200 Subject: [PATCH 191/388] gctx: rename pre_draw()/post_draw() to begin_draw()/end_draw() --- libnodegl/gctx.c | 4 ++-- libnodegl/gctx.h | 4 ++-- libnodegl/gctx_gl.c | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/libnodegl/gctx.c b/libnodegl/gctx.c index ee72c78cbb..5a989ca2d4 100644 --- a/libnodegl/gctx.c +++ b/libnodegl/gctx.c @@ -69,7 +69,7 @@ int ngli_gctx_draw(struct gctx *s, struct ngl_node *scene, double t) { const struct gctx_class *class = s->class; - int ret = class->pre_draw(s, t); + int ret = class->begin_draw(s, t); if (ret < 0) goto end; @@ -85,7 +85,7 @@ int ngli_gctx_draw(struct gctx *s, struct ngl_node *scene, double t) } end:; - int end_ret = class->post_draw(s, t); + int end_ret = class->end_draw(s, t); if (end_ret < 0) return end_ret; diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index e40d78d0ac..74e27ea3a9 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -37,8 +37,8 @@ struct gctx_class { struct gctx *(*create)(const struct ngl_config *config); int (*init)(struct gctx *s); int (*resize)(struct gctx *s, int width, int height, const int *viewport); - int (*pre_draw)(struct gctx *s, double t); - int (*post_draw)(struct gctx *s, double t); + int (*begin_draw)(struct gctx *s, double t); + int (*end_draw)(struct gctx *s, double t); void (*destroy)(struct gctx *s); int (*transform_cull_mode)(struct gctx *s, int cull_mode); diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 078f915f60..2d14f2e316 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -318,7 +318,7 @@ static int gl_resize(struct gctx *s, int width, int height, const int *viewport) return 0; } -static int gl_pre_draw(struct gctx *s, double t) +static int gl_begin_draw(struct gctx *s, double t) { struct gctx_gl *s_priv = (struct gctx_gl *)s; struct glcontext *gl = s_priv->glcontext; @@ -332,7 +332,7 @@ static int gl_pre_draw(struct gctx *s, double t) return 0; } -static int gl_post_draw(struct gctx *s, double t) +static int gl_end_draw(struct gctx *s, double t) { struct gctx_gl *s_priv = (struct gctx_gl *)s; struct glcontext *gl = s_priv->glcontext; @@ -493,8 +493,8 @@ const struct gctx_class ngli_gctx_gl = { .create = gl_create, .init = gl_init, .resize = gl_resize, - .pre_draw = gl_pre_draw, - .post_draw = gl_post_draw, + .begin_draw = gl_begin_draw, + .end_draw = gl_end_draw, .destroy = gl_destroy, .transform_cull_mode = gl_transform_cull_mode, @@ -561,8 +561,8 @@ const struct gctx_class ngli_gctx_gles = { .create = gl_create, .init = gl_init, .resize = gl_resize, - .pre_draw = gl_pre_draw, - .post_draw = gl_post_draw, + .begin_draw = gl_begin_draw, + .end_draw = gl_end_draw, .destroy = gl_destroy, .transform_cull_mode = gl_transform_cull_mode, From 51c9f6db42f3619c48556aa790e46602b26e523c Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 22 Oct 2020 11:15:34 +0200 Subject: [PATCH 192/388] rendertarget_gl: remove unused prev_id field --- libnodegl/rendertarget_gl.h | 1 - 1 file changed, 1 deletion(-) diff --git a/libnodegl/rendertarget_gl.h b/libnodegl/rendertarget_gl.h index ac667d1d39..f3748861ca 100644 --- a/libnodegl/rendertarget_gl.h +++ b/libnodegl/rendertarget_gl.h @@ -29,7 +29,6 @@ struct rendertarget_gl { struct rendertarget parent; GLuint id; GLuint resolve_id; - GLuint prev_id; GLenum draw_buffers[NGLI_MAX_COLOR_ATTACHMENTS]; GLenum blit_draw_buffers[NGLI_MAX_COLOR_ATTACHMENTS*(NGLI_MAX_COLOR_ATTACHMENTS+1)/2]; GLenum clear_flags; From 86bdcb897d64736956d91df1a6edcef0de893db6 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 22 Oct 2020 11:20:14 +0200 Subject: [PATCH 193/388] rendertarget: remove nb_resolve_color_attachments field This field is only written once and never read back. --- libnodegl/rendertarget.h | 1 - libnodegl/rendertarget_gl.c | 1 - 2 files changed, 2 deletions(-) diff --git a/libnodegl/rendertarget.h b/libnodegl/rendertarget.h index a7e476f94e..3dc2d1f691 100644 --- a/libnodegl/rendertarget.h +++ b/libnodegl/rendertarget.h @@ -74,7 +74,6 @@ struct rendertarget { int width; int height; int nb_color_attachments; - int nb_resolve_color_attachments; }; struct rendertarget *ngli_rendertarget_create(struct gctx *gctx); diff --git a/libnodegl/rendertarget_gl.c b/libnodegl/rendertarget_gl.c index 2d8e4aa068..80bd310851 100644 --- a/libnodegl/rendertarget_gl.c +++ b/libnodegl/rendertarget_gl.c @@ -170,7 +170,6 @@ static int create_fbo(struct rendertarget *s, int resolve) if (resolve) { s_priv->resolve_id = id; - s->nb_resolve_color_attachments = nb_color_attachments; } else { s_priv->id = id; s->nb_color_attachments = nb_color_attachments; From 5b8a7a6905cd4c8768805374ed4152c7338376c7 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 22 Oct 2020 11:26:29 +0200 Subject: [PATCH 194/388] rendertarget_gl: remove nb_color_attachment field And uses rendertarget_params.nb_colors instead. --- libnodegl/rendertarget.h | 1 - libnodegl/rendertarget_gl.c | 15 +++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/libnodegl/rendertarget.h b/libnodegl/rendertarget.h index 3dc2d1f691..c9b9f66aee 100644 --- a/libnodegl/rendertarget.h +++ b/libnodegl/rendertarget.h @@ -73,7 +73,6 @@ struct rendertarget { struct rendertarget_params params; int width; int height; - int nb_color_attachments; }; struct rendertarget *ngli_rendertarget_create(struct gctx *gctx); diff --git a/libnodegl/rendertarget_gl.c b/libnodegl/rendertarget_gl.c index 80bd310851..891ddc6fae 100644 --- a/libnodegl/rendertarget_gl.c +++ b/libnodegl/rendertarget_gl.c @@ -87,7 +87,7 @@ static void resolve_draw_buffers(struct rendertarget *s) ngli_glBlitFramebuffer(gl, 0, 0, s->width, s->height, 0, 0, s->width, s->height, flags, GL_NEAREST); } ngli_glReadBuffer(gl, GL_COLOR_ATTACHMENT0); - ngli_glDrawBuffers(gl, s->nb_color_attachments, s_priv->draw_buffers); + ngli_glDrawBuffers(gl, params->nb_colors, s_priv->draw_buffers); } static int create_fbo(struct rendertarget *s, int resolve) @@ -172,7 +172,6 @@ static int create_fbo(struct rendertarget *s, int resolve) s_priv->resolve_id = id; } else { s_priv->id = id; - s->nb_color_attachments = nb_color_attachments; } return 0; @@ -286,19 +285,19 @@ int ngli_rendertarget_gl_init(struct rendertarget *s, const struct rendertarget_ s_priv->resolve = resolve_no_draw_buffers; if (gl->features & NGLI_FEATURE_DRAW_BUFFERS) { - if (s->nb_color_attachments > limits->max_draw_buffers) { + if (params->nb_colors > limits->max_draw_buffers) { LOG(ERROR, "draw buffer count (%d) exceeds driver limit (%d)", - s->nb_color_attachments, limits->max_draw_buffers); + params->nb_colors, limits->max_draw_buffers); ret = NGL_ERROR_UNSUPPORTED; goto done; } - if (s->nb_color_attachments > 1) { - for (int i = 0; i < s->nb_color_attachments; i++) + if (params->nb_colors > 1) { + for (int i = 0; i < params->nb_colors; i++) s_priv->draw_buffers[i] = GL_COLOR_ATTACHMENT0 + i; - ngli_glDrawBuffers(gl, s->nb_color_attachments, s_priv->draw_buffers); + ngli_glDrawBuffers(gl, params->nb_colors, s_priv->draw_buffers); GLenum *draw_buffers = s_priv->blit_draw_buffers; - for (int i = 0; i < s->nb_color_attachments; i++) { + for (int i = 0; i < params->nb_colors; i++) { draw_buffers += i + 1; draw_buffers[-1] = GL_COLOR_ATTACHMENT0 + i; } From 7475884a3aa78a9926ec127d6cd624569ee1f421 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 22 Oct 2020 12:02:59 +0200 Subject: [PATCH 195/388] rendertarget_gl: simplify resolve draw buffers logic --- libnodegl/rendertarget_gl.c | 12 ++---------- libnodegl/rendertarget_gl.h | 1 - 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/libnodegl/rendertarget_gl.c b/libnodegl/rendertarget_gl.c index 891ddc6fae..98f708f4ce 100644 --- a/libnodegl/rendertarget_gl.c +++ b/libnodegl/rendertarget_gl.c @@ -68,8 +68,6 @@ static void resolve_draw_buffers(struct rendertarget *s) struct glcontext *gl = gctx_gl->glcontext; struct rendertarget_params *params = &s->params; - const GLenum *draw_buffers = s_priv->blit_draw_buffers; - for (int i = 0; i < params->nb_colors; i++) { const struct attachment *attachment = ¶ms->colors[i]; if (!attachment->resolve_target) @@ -81,9 +79,10 @@ static void resolve_draw_buffers(struct rendertarget *s) #if defined(TARGET_DARWIN) ngli_glDrawBuffer(gl, GL_COLOR_ATTACHMENT0 + i); #else + GLenum draw_buffers[NGLI_MAX_COLOR_ATTACHMENTS] = {0}; + draw_buffers[i] = GL_COLOR_ATTACHMENT0 + i; ngli_glDrawBuffers(gl, i + 1, draw_buffers); #endif - draw_buffers += i + 1; ngli_glBlitFramebuffer(gl, 0, 0, s->width, s->height, 0, 0, s->width, s->height, flags, GL_NEAREST); } ngli_glReadBuffer(gl, GL_COLOR_ATTACHMENT0); @@ -295,13 +294,6 @@ int ngli_rendertarget_gl_init(struct rendertarget *s, const struct rendertarget_ for (int i = 0; i < params->nb_colors; i++) s_priv->draw_buffers[i] = GL_COLOR_ATTACHMENT0 + i; ngli_glDrawBuffers(gl, params->nb_colors, s_priv->draw_buffers); - - GLenum *draw_buffers = s_priv->blit_draw_buffers; - for (int i = 0; i < params->nb_colors; i++) { - draw_buffers += i + 1; - draw_buffers[-1] = GL_COLOR_ATTACHMENT0 + i; - } - s_priv->resolve = resolve_draw_buffers; } } diff --git a/libnodegl/rendertarget_gl.h b/libnodegl/rendertarget_gl.h index f3748861ca..68c9e17bdc 100644 --- a/libnodegl/rendertarget_gl.h +++ b/libnodegl/rendertarget_gl.h @@ -30,7 +30,6 @@ struct rendertarget_gl { GLuint id; GLuint resolve_id; GLenum draw_buffers[NGLI_MAX_COLOR_ATTACHMENTS]; - GLenum blit_draw_buffers[NGLI_MAX_COLOR_ATTACHMENTS*(NGLI_MAX_COLOR_ATTACHMENTS+1)/2]; GLenum clear_flags; GLenum invalidate_attachments[NGLI_MAX_COLOR_ATTACHMENTS + 2]; // max color attachments + depth and stencil attachments int nb_invalidate_attachments; From 0f799469b069a5d6b90724ae6cb96c02bdb1dc41 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 22 Oct 2020 11:41:32 +0200 Subject: [PATCH 196/388] rendertarget_gl: add ngli_default_rendertarget_gl_init() --- libnodegl/rendertarget_gl.c | 52 +++++++++++++++++++++++++++++++++++++ libnodegl/rendertarget_gl.h | 2 ++ 2 files changed, 54 insertions(+) diff --git a/libnodegl/rendertarget_gl.c b/libnodegl/rendertarget_gl.c index 98f708f4ce..5cc3256428 100644 --- a/libnodegl/rendertarget_gl.c +++ b/libnodegl/rendertarget_gl.c @@ -397,3 +397,55 @@ void ngli_rendertarget_gl_freep(struct rendertarget **sp) ngli_freep(sp); } + +int ngli_default_rendertarget_gl_init(struct rendertarget *s, const struct rendertarget_params *params) +{ + struct rendertarget_gl *s_priv = (struct rendertarget_gl *)s; + struct gctx_gl *gctx_gl = (struct gctx_gl *)s->gctx; + struct glcontext *gl = gctx_gl->glcontext; + + ngli_assert(params->nb_colors == 1); + + s->params = *params; + s->width = params->width; + s->height = params->height; + + s_priv->id = ngli_glcontext_get_default_framebuffer(gl); + + if (gl->features & NGLI_FEATURE_INVALIDATE_SUBDATA) { + s_priv->invalidate = invalidate; + } else { + s_priv->invalidate = invalidate_noop; + } + + if (gl->features & NGLI_FEATURE_CLEAR_BUFFER) { + s_priv->clear = clear_buffers; + } else { + s_priv->clear = clear_buffer; + } + + s_priv->resolve = resolve_no_draw_buffers; + + const struct attachment *color = ¶ms->colors[0]; + if (color->load_op == NGLI_LOAD_OP_DONT_CARE || + color->load_op == NGLI_LOAD_OP_CLEAR) { + s_priv->clear_flags |= GL_COLOR_BUFFER_BIT; + } + if (color->store_op == NGLI_STORE_OP_DONT_CARE) { + s_priv->invalidate_attachments[s_priv->nb_invalidate_attachments++] = GL_COLOR; + } + + const struct attachment *depth_stencil = ¶ms->depth_stencil; + if (depth_stencil->attachment) { + if (depth_stencil->load_op == NGLI_LOAD_OP_DONT_CARE || + depth_stencil->load_op == NGLI_LOAD_OP_CLEAR) { + s_priv->clear_flags |= (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + } + if (depth_stencil->store_op == NGLI_STORE_OP_DONT_CARE) { + s_priv->invalidate_attachments[s_priv->nb_invalidate_attachments++] = GL_DEPTH; + s_priv->invalidate_attachments[s_priv->nb_invalidate_attachments++] = GL_STENCIL; + } + } + + return 0; +} diff --git a/libnodegl/rendertarget_gl.h b/libnodegl/rendertarget_gl.h index 68c9e17bdc..729cf8a68d 100644 --- a/libnodegl/rendertarget_gl.h +++ b/libnodegl/rendertarget_gl.h @@ -46,4 +46,6 @@ void ngli_rendertarget_gl_invalidate(struct rendertarget *s); void ngli_rendertarget_gl_read_pixels(struct rendertarget *s, uint8_t *data); void ngli_rendertarget_gl_freep(struct rendertarget **sp); +int ngli_default_rendertarget_gl_init(struct rendertarget *s, const struct rendertarget_params *params); + #endif From 427e7a6d18b95537b1ea2202911e9f04cba8fa24 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 22 Oct 2020 11:45:15 +0200 Subject: [PATCH 197/388] gctx_gl: improve offscreen capture callback and resources comment --- libnodegl/gctx_gl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libnodegl/gctx_gl.h b/libnodegl/gctx_gl.h index f65ecd772a..ce3c59301d 100644 --- a/libnodegl/gctx_gl.h +++ b/libnodegl/gctx_gl.h @@ -55,7 +55,7 @@ struct gctx_gl { struct texture *color; struct texture *ms_color; struct texture *depth; - /* Capture offscreen render target */ + /* Offscreen capture callback and resources */ capture_func_type capture_func; #if defined(TARGET_IPHONE) CVPixelBufferRef capture_cvbuffer; From cd00bf286701f5b39296f7ee570226ddf31762f9 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 22 Oct 2020 11:52:25 +0200 Subject: [PATCH 198/388] gctx_gl: use ngli_default_rendertarget_gl_init() --- libnodegl/gctx_gl.c | 80 ++++++++++++++++++++++++++++++++------------- libnodegl/gctx_gl.h | 2 +- 2 files changed, 59 insertions(+), 23 deletions(-) diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 2d14f2e316..3b8a1f9880 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -196,7 +196,40 @@ static int offscreen_rendertarget_init(struct gctx *s) return 0; } -static void offscreen_rendertarget_reset(struct gctx *s) +static int onscreen_rendertarget_init(struct gctx *s) +{ + struct gctx_gl *s_priv = (struct gctx_gl *)s; + const struct ngl_config *config = &s->config; + + const struct rendertarget_params rt_params = { + .width = config->width, + .height = config->height, + .nb_colors = 1, + .colors[0] = { + .attachment = NULL, + .resolve_target = NULL, + .load_op = NGLI_LOAD_OP_LOAD, + .clear_value[0] = config->clear_color[0], + .clear_value[1] = config->clear_color[1], + .clear_value[2] = config->clear_color[2], + .clear_value[3] = config->clear_color[3], + .store_op = NGLI_STORE_OP_STORE, + }, + .depth_stencil = { + .attachment = NULL, + .load_op = NGLI_LOAD_OP_LOAD, + .store_op = NGLI_STORE_OP_STORE, + }, + }; + + s_priv->rt = ngli_rendertarget_create(s); + if (!s_priv->rt) + return NGL_ERROR_MEMORY; + + return ngli_default_rendertarget_gl_init(s_priv->rt, &rt_params); +} + +static void rendertarget_reset(struct gctx *s) { struct gctx_gl *s_priv = (struct gctx_gl *)s; ngli_rendertarget_freep(&s_priv->rt); @@ -261,11 +294,9 @@ static int gl_init(struct gctx *s) } #endif - if (gl->offscreen) { - ret = offscreen_rendertarget_init(s); - if (ret < 0) - return ret; - } + ret = gl->offscreen ? offscreen_rendertarget_init(s) : onscreen_rendertarget_init(s); + if (ret < 0) + return ret; s->version = gl->version; s->features = gl->features; @@ -305,6 +336,16 @@ static int gl_resize(struct gctx *s, int width, int height, const int *viewport) if (ret < 0) return ret; + s_priv->rt->width = gl->width; + s_priv->rt->height = gl->height; + + /* + * The default framebuffer id can change after a resize operation on EAGL, + * thus we need to update the rendertarget wrapping the default framebuffer + */ + struct rendertarget_gl *rt_gl = (struct rendertarget_gl *)s_priv->rt; + rt_gl->id = ngli_glcontext_get_default_framebuffer(gl); + if (viewport && viewport[2] > 0 && viewport[3] > 0) { ngli_gctx_set_viewport(s, viewport); } else { @@ -324,7 +365,7 @@ static int gl_begin_draw(struct gctx *s, double t) struct glcontext *gl = s_priv->glcontext; const struct ngl_config *config = &s->config; - ngli_gctx_begin_render_pass(s, config->offscreen ? s_priv->rt : NULL); + ngli_gctx_begin_render_pass(s, s_priv->rt); const float *color = config->clear_color; ngli_glClearColor(gl, color[0], color[1], color[2], color[3]); @@ -360,7 +401,7 @@ static int gl_end_draw(struct gctx *s, double t) static void gl_destroy(struct gctx *s) { struct gctx_gl *s_priv = (struct gctx_gl *)s; - offscreen_rendertarget_reset(s); + rendertarget_reset(s); ngli_glcontext_freep(&s_priv->glcontext); } @@ -408,10 +449,7 @@ static void gl_get_rendertarget_uvcoord_matrix(struct gctx *s, float *dst) static struct rendertarget *gl_get_default_rendertarget(struct gctx *s) { struct gctx_gl *s_priv = (struct gctx_gl *)s; - const struct ngl_config *config = &s->config; - if (config->offscreen) - return s_priv->rt; - return NULL; + return s_priv->rt; } static const struct rendertarget_desc *gl_get_default_rendertarget_desc(struct gctx *s) @@ -425,17 +463,15 @@ static void gl_begin_render_pass(struct gctx *s, struct rendertarget *rt) struct gctx_gl *s_priv = (struct gctx_gl *)s; struct glcontext *gl = s_priv->glcontext; + ngli_assert(rt); struct rendertarget_gl *rt_gl = (struct rendertarget_gl *)rt; - const GLuint fbo_id = rt_gl ? rt_gl->id : ngli_glcontext_get_default_framebuffer(gl); - ngli_glBindFramebuffer(gl, GL_FRAMEBUFFER, fbo_id); - - if (rt) { - const int scissor_test = s_priv->glstate.scissor_test; - ngli_glDisable(gl, GL_SCISSOR_TEST); - ngli_rendertarget_gl_clear(rt); - if (scissor_test) - ngli_glEnable(gl, GL_SCISSOR_TEST); - } + ngli_glBindFramebuffer(gl, GL_FRAMEBUFFER, rt_gl->id); + + const int scissor_test = s_priv->glstate.scissor_test; + ngli_glDisable(gl, GL_SCISSOR_TEST); + ngli_rendertarget_gl_clear(rt); + if (scissor_test) + ngli_glEnable(gl, GL_SCISSOR_TEST); s_priv->rendertarget = rt; } diff --git a/libnodegl/gctx_gl.h b/libnodegl/gctx_gl.h index ce3c59301d..262154d246 100644 --- a/libnodegl/gctx_gl.h +++ b/libnodegl/gctx_gl.h @@ -50,8 +50,8 @@ struct gctx_gl { int viewport[4]; int scissor[4]; int timer_active; - /* Offscreen render target */ struct rendertarget *rt; + /* Offscreen render target resources */ struct texture *color; struct texture *ms_color; struct texture *depth; From e9d29c1f29bb0d33ef872015764f79892d187737 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 22 Oct 2020 13:51:22 +0200 Subject: [PATCH 199/388] rtt: cosmetics --- libnodegl/node_rtt.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libnodegl/node_rtt.c b/libnodegl/node_rtt.c index 698a0b8f90..9775a83197 100644 --- a/libnodegl/node_rtt.c +++ b/libnodegl/node_rtt.c @@ -330,6 +330,7 @@ static int rtt_prefetch(struct ngl_node *node) s->rt = ngli_rendertarget_create(gctx); if (!s->rt) return NGL_ERROR_MEMORY; + ret = ngli_rendertarget_init(s->rt, &rt_params); if (ret < 0) return ret; @@ -346,6 +347,7 @@ static int rtt_prefetch(struct ngl_node *node) s->rt_resume = ngli_rendertarget_create(gctx); if (!s->rt_resume) return NGL_ERROR_MEMORY; + ret = ngli_rendertarget_init(s->rt_resume, &rt_params); if (ret < 0) return ret; From 555d0152e4e966f93f294da028181ef023fef042 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 22 Oct 2020 13:51:37 +0200 Subject: [PATCH 200/388] rtt: set the proper rendertarget at the end of rtt_draw() If the render pass associated with the current rendertarget (LOAD_OP=CLEAR) was not started, we must make this rendertarget current again at the end of rtt_draw() otherwise the clear operation will never be executed. The commit fixes the following scenario: (RTT 1) | | .---------(Group)------. | | | | | | | | | (TimeRangeFilter) (RTT 2) (Render 2) / | / | (TimeRangeNoop=0) (Render 1) Where: - the RTT 1 node detects that it needs to allocate two rendertargets, one with LOAD_OP=CLEAR and one with LOAD_OP=LOAD because there can be two separate render passes (one for each render) - the RTT 1 node makes current the rendertarget with LOAD_OP=CLEAR - the TimeRangeFilter node prevents the execution of Render 1 according to its time ranges preventing the start of the render pass associated with the current rendertarget - the RTT 2 node sets its own rendertarget current - the RTT 2 node calls ngli_draw_node() - the RTT 2 node restores the previous rendertarget to the second one available (LOAD_OP=LOAD) which prevents the clear operation needed by RTT 1 to be executed Fixes a regression introduced by 7f8a6f888fb0e6f464a179f4c42c755b60da5656. --- libnodegl/node_rtt.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libnodegl/node_rtt.c b/libnodegl/node_rtt.c index 9775a83197..faa09e7039 100644 --- a/libnodegl/node_rtt.c +++ b/libnodegl/node_rtt.c @@ -410,8 +410,10 @@ static void rtt_draw(struct ngl_node *node) ctx->available_rendertargets[1], }; + int current_rendertarget_index = 0; if (!ctx->begin_render_pass) { ngli_gctx_end_render_pass(gctx); + current_rendertarget_index = 1; } ctx->available_rendertargets[0] = s->available_rendertargets[0]; @@ -427,7 +429,7 @@ static void rtt_draw(struct ngl_node *node) } ngli_gctx_end_render_pass(gctx); - ctx->current_rendertarget = prev_rendertargets[1]; + ctx->current_rendertarget = prev_rendertargets[current_rendertarget_index]; ctx->available_rendertargets[0] = prev_rendertargets[0]; ctx->available_rendertargets[1] = prev_rendertargets[1]; ctx->begin_render_pass = 1; From 0e650df05e8411785f71ef03566c41b12ac769ad Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 22 Oct 2020 13:48:33 +0200 Subject: [PATCH 201/388] tests/rtt: add rtt_clear_attachment_with_timeranges test --- .../rtt_clear_attachment_with_timeranges.ref | 10 +++++ tests/rtt.mak | 1 + tests/rtt.py | 43 +++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 tests/refs/rtt_clear_attachment_with_timeranges.ref diff --git a/tests/refs/rtt_clear_attachment_with_timeranges.ref b/tests/refs/rtt_clear_attachment_with_timeranges.ref new file mode 100644 index 0000000000..01d23df9d5 --- /dev/null +++ b/tests/refs/rtt_clear_attachment_with_timeranges.ref @@ -0,0 +1,10 @@ +20004554200220022002200220020554 2000455429D22C8629D2688228820554 00000000000000000000000000000000 200045546DD628022DD6280228820554 +31C1CC1C00033000C00000033000870C 11C18F5CEF153C87E13028C33A018308 00000000000000000000000000000000 31C1DF5CEF173CC5E330A8C33A00870C +205457000002C0030000200020002014 28541754ADD6A887E9D4688028802814 00000000000000000000000000000000 285457D4BFC6E007E9D5280028002814 +05C06015200030000000C00200020700 05C06D552DD43C80ADD0A882A8820700 00000000000000000000000000000000 05C06D556C553C00BDD0E002AA820780 +3071C70C30000003C000300000038C1C 1851875C7F143C87E13038C2BA018A18 00000000000000000000000000000000 3871C75D3F14BCC7E33038C0AA038E1C +20004554200220022002200220020554 2000455429D22C8629D2688228820554 00000000000000000000000000000000 200045546DD628022DD6280228820554 +31C1CC1C00033000C00000033000870C 11C18F5CEF153C87E13028C33A018308 00000000000000000000000000000000 31C1DF5CEF173CC5E330A8C33A00870C +205457000002C0030000200020002014 28541754ADD6A887E9D4688028802814 00000000000000000000000000000000 285457D4BFC6E007E9D5280028002814 +05C06015200030000000C00200020700 05C06D552DD43C80ADD0A882A8820700 00000000000000000000000000000000 05C06D556C553C00BDD0E002AA820780 +3071C70C30000003C000300000038C1C 1851875C7F143C87E13038C2BA018A18 00000000000000000000000000000000 3871C75D3F14BCC7E33038C0AA038E1C diff --git a/tests/rtt.mak b/tests/rtt.mak index 6ad89713d4..432f99a11d 100644 --- a/tests/rtt.mak +++ b/tests/rtt.mak @@ -26,6 +26,7 @@ RTT_TEST_NAMES = \ mipmap \ texture_depth \ texture_depth_stencil \ + clear_attachment_with_timeranges \ ifneq ($(DISABLE_TESTS_SAMPLES),yes) RTT_TEST_NAMES += \ diff --git a/tests/rtt.py b/tests/rtt.py index 500fea068e..46e34f824f 100644 --- a/tests/rtt.py +++ b/tests/rtt.py @@ -200,3 +200,46 @@ def rtt_load_attachment(cfg): foreground.update_frag_resources(tex0=texture) return ngl.Group(children=(background, rtt, foreground)) + + +@test_fingerprint(width=512, height=512, nb_keyframes=10) +@scene() +def rtt_clear_attachment_with_timeranges(cfg): + cfg.aspect_ratio = (1, 1) + + # Time-disabled full screen white quad + quad = ngl.Quad((-1, -1, 0), (2, 0, 0), (0, 2, 0)) + program = ngl.Program(vertex=cfg.get_vert('color'), fragment=cfg.get_frag('color')) + program.update_vert_out_vars(var_tex0_coord=ngl.IOVec2(), var_uvcoord=ngl.IOVec2()) + render = ngl.Render(quad, program) + render.update_frag_resources(color=ngl.UniformVec4(value=COLORS['white'])) + time_range_filter = ngl.TimeRangeFilter(render) + time_range_filter.add_ranges(ngl.TimeRangeModeNoop(0)) + + # Intermediate no-op RTT to force the use of a different render pass internally + texture = ngl.Texture2D(width=32, height=32) + rtt_noop = ngl.RenderToTexture(ngl.Identity(), [texture]) + + # Centered rotating quad + quad = ngl.Quad((-0.5, -0.5, 0), (1, 0, 0), (0, 1, 0)) + program = ngl.Program(vertex=cfg.get_vert('color'), fragment=cfg.get_frag('color')) + program.update_vert_out_vars(var_tex0_coord=ngl.IOVec2(), var_uvcoord=ngl.IOVec2()) + render = ngl.Render(quad, program) + render.update_frag_resources(color=ngl.UniformVec4(value=COLORS['orange'])) + animkf = [ngl.AnimKeyFrameFloat(0, 0), ngl.AnimKeyFrameFloat(cfg.duration, -360)] + render = ngl.Rotate(render, anim=ngl.AnimatedFloat(animkf)) + + group = ngl.Group(children=(time_range_filter, rtt_noop, render)) + + # Root RTT + texture = ngl.Texture2D(width=512, height=512) + rtt = ngl.RenderToTexture(group, [texture]) + + # Full screen render of the root RTT result + quad = ngl.Quad((-1, -1, 0), (2, 0, 0), (0, 2, 0)) + program = ngl.Program(vertex=cfg.get_vert('texture'), fragment=cfg.get_frag('texture')) + program.update_vert_out_vars(var_tex0_coord=ngl.IOVec2(), var_uvcoord=ngl.IOVec2()) + render = ngl.Render(quad, program) + render.update_frag_resources(tex0=texture) + + return ngl.Group(children=(rtt, render)) From b794ab5486b314893117aedaf9e815b0ed9264de Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 23 Oct 2020 10:38:34 +0200 Subject: [PATCH 202/388] rendertarget_gl: do not try to delete the default framebuffer While this won't be an issue with the OpenGL default framebuffer as rendertarget_gl.id is set to 0 (glDeleteFramebuffers() silently ignores 0), it is on the EAGL platform as the default framebuffer managed by glcontext_eagl is a regular framebuffer object. Also properly sets the invalidate attachments depending on whether we are dealing with the OpenGL default framebuffer or a framebuffer object. Forgotten in 0f799469b069a5d6b90724ae6cb96c02bdb1dc41. --- libnodegl/rendertarget_gl.c | 16 +++++++++++----- libnodegl/rendertarget_gl.h | 1 + 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/libnodegl/rendertarget_gl.c b/libnodegl/rendertarget_gl.c index 5cc3256428..6117a8a003 100644 --- a/libnodegl/rendertarget_gl.c +++ b/libnodegl/rendertarget_gl.c @@ -259,6 +259,8 @@ int ngli_rendertarget_gl_init(struct rendertarget *s, const struct rendertarget_ s->width = params->width; s->height = params->height; + s_priv->wrapped = 0; + int ret; if (require_resolve_fbo(s)) { ret = create_fbo(s, 1); @@ -392,8 +394,11 @@ void ngli_rendertarget_gl_freep(struct rendertarget **sp) struct gctx_gl *gctx_gl = (struct gctx_gl *)s->gctx; struct glcontext *gl = gctx_gl->glcontext; struct rendertarget_gl *s_priv = (struct rendertarget_gl *)s; - ngli_glDeleteFramebuffers(gl, 1, &s_priv->id); - ngli_glDeleteFramebuffers(gl, 1, &s_priv->resolve_id); + + if (!s_priv->wrapped) { + ngli_glDeleteFramebuffers(gl, 1, &s_priv->id); + ngli_glDeleteFramebuffers(gl, 1, &s_priv->resolve_id); + } ngli_freep(sp); } @@ -410,6 +415,7 @@ int ngli_default_rendertarget_gl_init(struct rendertarget *s, const struct rende s->width = params->width; s->height = params->height; + s_priv->wrapped = 1; s_priv->id = ngli_glcontext_get_default_framebuffer(gl); if (gl->features & NGLI_FEATURE_INVALIDATE_SUBDATA) { @@ -432,7 +438,7 @@ int ngli_default_rendertarget_gl_init(struct rendertarget *s, const struct rende s_priv->clear_flags |= GL_COLOR_BUFFER_BIT; } if (color->store_op == NGLI_STORE_OP_DONT_CARE) { - s_priv->invalidate_attachments[s_priv->nb_invalidate_attachments++] = GL_COLOR; + s_priv->invalidate_attachments[s_priv->nb_invalidate_attachments++] = s_priv->id ? GL_COLOR_ATTACHMENT0 : GL_COLOR; } const struct attachment *depth_stencil = ¶ms->depth_stencil; @@ -442,8 +448,8 @@ int ngli_default_rendertarget_gl_init(struct rendertarget *s, const struct rende s_priv->clear_flags |= (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } if (depth_stencil->store_op == NGLI_STORE_OP_DONT_CARE) { - s_priv->invalidate_attachments[s_priv->nb_invalidate_attachments++] = GL_DEPTH; - s_priv->invalidate_attachments[s_priv->nb_invalidate_attachments++] = GL_STENCIL; + s_priv->invalidate_attachments[s_priv->nb_invalidate_attachments++] = s_priv->id ? GL_DEPTH_ATTACHMENT : GL_DEPTH; + s_priv->invalidate_attachments[s_priv->nb_invalidate_attachments++] = s_priv->id ? GL_STENCIL_ATTACHMENT : GL_STENCIL; } } diff --git a/libnodegl/rendertarget_gl.h b/libnodegl/rendertarget_gl.h index 729cf8a68d..b62bb00c84 100644 --- a/libnodegl/rendertarget_gl.h +++ b/libnodegl/rendertarget_gl.h @@ -27,6 +27,7 @@ struct rendertarget_gl { struct rendertarget parent; + int wrapped; GLuint id; GLuint resolve_id; GLenum draw_buffers[NGLI_MAX_COLOR_ATTACHMENTS]; From 0941bd415faed0b2e1feaa7debca92f0026cfc38 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 23 Oct 2020 15:14:02 +0200 Subject: [PATCH 203/388] texture: move access field to pipeline_texture_desc Texture access is pipeline specific. --- libnodegl/doc/libnodegl.md | 10 ---------- libnodegl/hwupload_vaapi_gl.c | 1 - libnodegl/node_media.c | 1 - libnodegl/node_texture.c | 15 --------------- libnodegl/pgcraft.c | 1 + libnodegl/pipeline.h | 1 + libnodegl/pipeline_gl.c | 15 ++++++++++++--- libnodegl/texture.h | 2 -- libnodegl/texture_gl.c | 11 ----------- libnodegl/texture_gl.h | 1 - 10 files changed, 14 insertions(+), 44 deletions(-) diff --git a/libnodegl/doc/libnodegl.md b/libnodegl/doc/libnodegl.md index d140bc99a5..c537107b4d 100644 --- a/libnodegl/doc/libnodegl.md +++ b/libnodegl/doc/libnodegl.md @@ -547,7 +547,6 @@ Parameter | Live-chg. | Type | Description | Default `mipmap_filter` | | [`mipmap_filter`](#mipmap_filter-choices) | texture minifying mipmap function | `none` `wrap_s` | | [`wrap`](#wrap-choices) | wrap parameter for the texture on the s dimension (horizontal) | `clamp_to_edge` `wrap_t` | | [`wrap`](#wrap-choices) | wrap parameter for the texture on the t dimension (vertical) | `clamp_to_edge` -`access` | | [`access`](#access-choices) | texture access (only honored by the `Compute` node) | `read+write` `data_src` | | [`Node`](#parameter-types) ([Media](#media), [AnimatedBufferFloat](#animatedbuffer), [AnimatedBufferVec2](#animatedbuffer), [AnimatedBufferVec4](#animatedbuffer), [BufferByte](#buffer), [BufferBVec2](#buffer), [BufferBVec4](#buffer), [BufferInt](#buffer), [BufferIVec2](#buffer), [BufferIVec4](#buffer), [BufferShort](#buffer), [BufferSVec2](#buffer), [BufferSVec4](#buffer), [BufferUByte](#buffer), [BufferUBVec2](#buffer), [BufferUBVec4](#buffer), [BufferUInt](#buffer), [BufferUIVec2](#buffer), [BufferUIVec4](#buffer), [BufferUShort](#buffer), [BufferUSVec2](#buffer), [BufferUSVec4](#buffer), [BufferFloat](#buffer), [BufferVec2](#buffer), [BufferVec4](#buffer)) | data source | `direct_rendering` | | [`bool`](#parameter-types) | whether direct rendering is allowed or not for media playback | `1` @@ -569,7 +568,6 @@ Parameter | Live-chg. | Type | Description | Default `wrap_s` | | [`wrap`](#wrap-choices) | wrap parameter for the texture on the s dimension (horizontal) | `clamp_to_edge` `wrap_t` | | [`wrap`](#wrap-choices) | wrap parameter for the texture on the t dimension (vertical) | `clamp_to_edge` `wrap_r` | | [`wrap`](#wrap-choices) | wrap parameter for the texture on the r dimension (depth) | `clamp_to_edge` -`access` | | [`access`](#access-choices) | texture access (only honored by the `Compute` node) | `read+write` `data_src` | | [`Node`](#parameter-types) ([AnimatedBufferFloat](#animatedbuffer), [AnimatedBufferVec2](#animatedbuffer), [AnimatedBufferVec4](#animatedbuffer), [BufferByte](#buffer), [BufferBVec2](#buffer), [BufferBVec4](#buffer), [BufferInt](#buffer), [BufferIVec2](#buffer), [BufferIVec4](#buffer), [BufferShort](#buffer), [BufferSVec2](#buffer), [BufferSVec4](#buffer), [BufferUByte](#buffer), [BufferUBVec2](#buffer), [BufferUBVec4](#buffer), [BufferUInt](#buffer), [BufferUIVec2](#buffer), [BufferUIVec4](#buffer), [BufferUShort](#buffer), [BufferUSVec2](#buffer), [BufferUSVec4](#buffer), [BufferFloat](#buffer), [BufferVec2](#buffer), [BufferVec4](#buffer)) | data source | @@ -588,7 +586,6 @@ Parameter | Live-chg. | Type | Description | Default `wrap_s` | | [`wrap`](#wrap-choices) | wrap parameter for the texture on the s dimension (horizontal) | `clamp_to_edge` `wrap_t` | | [`wrap`](#wrap-choices) | wrap parameter for the texture on the t dimension (vertical) | `clamp_to_edge` `wrap_r` | | [`wrap`](#wrap-choices) | wrap parameter for the texture on the r dimension (depth) | `clamp_to_edge` -`access` | | [`access`](#access-choices) | texture access (only honored by the `Compute` node) | `read+write` `data_src` | | [`Node`](#parameter-types) ([AnimatedBufferFloat](#animatedbuffer), [AnimatedBufferVec2](#animatedbuffer), [AnimatedBufferVec4](#animatedbuffer), [BufferByte](#buffer), [BufferBVec2](#buffer), [BufferBVec4](#buffer), [BufferInt](#buffer), [BufferIVec2](#buffer), [BufferIVec4](#buffer), [BufferShort](#buffer), [BufferSVec2](#buffer), [BufferSVec4](#buffer), [BufferUByte](#buffer), [BufferUBVec2](#buffer), [BufferUBVec4](#buffer), [BufferUInt](#buffer), [BufferUIVec2](#buffer), [BufferUIVec4](#buffer), [BufferUShort](#buffer), [BufferUSVec2](#buffer), [BufferUSVec4](#buffer), [BufferFloat](#buffer), [BufferVec2](#buffer), [BufferVec4](#buffer)) | data source | @@ -1483,10 +1480,3 @@ Constant | Description `clamp_to_edge` | clamp to edge wrapping `mirrored_repeat` | mirrored repeat wrapping `repeat` | repeat pattern wrapping - -## access choices - -Constant | Description --------- | ----------- -`read` | read -`write` | write diff --git a/libnodegl/hwupload_vaapi_gl.c b/libnodegl/hwupload_vaapi_gl.c index 0a34e67d47..7a61f36472 100644 --- a/libnodegl/hwupload_vaapi_gl.c +++ b/libnodegl/hwupload_vaapi_gl.c @@ -96,7 +96,6 @@ static int vaapi_init(struct ngl_node *node, struct sxplayer_frame *frame) .wrap_s = params->wrap_s, .wrap_t = params->wrap_t, .wrap_r = params->wrap_r, - .access = params->access, .external_storage = 1, }; diff --git a/libnodegl/node_media.c b/libnodegl/node_media.c index fffa6f179c..8b14e60562 100644 --- a/libnodegl/node_media.c +++ b/libnodegl/node_media.c @@ -162,7 +162,6 @@ static int media_init(struct ngl_node *node) .wrap_s = NGLI_WRAP_CLAMP_TO_EDGE, .wrap_t = NGLI_WRAP_CLAMP_TO_EDGE, .wrap_r = NGLI_WRAP_CLAMP_TO_EDGE, - .access = NGLI_ACCESS_READ_WRITE, .external_oes = 1, }; diff --git a/libnodegl/node_texture.c b/libnodegl/node_texture.c index 5a14d08240..1f021db7a0 100644 --- a/libnodegl/node_texture.c +++ b/libnodegl/node_texture.c @@ -63,15 +63,6 @@ static const struct param_choices wrap_choices = { } }; -static const struct param_choices access_choices = { - .name = "access", - .consts = { - {"read", NGLI_ACCESS_READ_BIT, .desc=NGLI_DOCSTRING("read")}, - {"write", NGLI_ACCESS_WRITE_BIT, .desc=NGLI_DOCSTRING("write")}, - {NULL} - } -}; - /* These formats are not in format.h because they do not represent a native GPU format */ #define NGLI_FORMAT_AUTO_DEPTH (NGLI_FORMAT_NB + 1) #define NGLI_FORMAT_AUTO_DEPTH_STENCIL (NGLI_FORMAT_NB + 2) @@ -192,8 +183,6 @@ static const struct node_param texture2d_params[] = { .desc=NGLI_DOCSTRING("wrap parameter for the texture on the s dimension (horizontal)")}, {"wrap_t", PARAM_TYPE_SELECT, OFFSET(params.wrap_t), {.i64=NGLI_WRAP_CLAMP_TO_EDGE}, .choices=&wrap_choices, .desc=NGLI_DOCSTRING("wrap parameter for the texture on the t dimension (vertical)")}, - {"access", PARAM_TYPE_FLAGS, OFFSET(params.access), {.i64=NGLI_ACCESS_READ_WRITE}, .choices=&access_choices, - .desc=NGLI_DOCSTRING("texture access (only honored by the `Compute` node)")}, {"data_src", PARAM_TYPE_NODE, OFFSET(data_src), .node_types=DATA_SRC_TYPES_LIST_2D, .desc=NGLI_DOCSTRING("data source")}, {"direct_rendering", PARAM_TYPE_BOOL, OFFSET(direct_rendering), {.i64=1}, @@ -223,8 +212,6 @@ static const struct node_param texture3d_params[] = { .desc=NGLI_DOCSTRING("wrap parameter for the texture on the t dimension (vertical)")}, {"wrap_r", PARAM_TYPE_SELECT, OFFSET(params.wrap_r), {.i64=NGLI_WRAP_CLAMP_TO_EDGE}, .choices=&wrap_choices, .desc=NGLI_DOCSTRING("wrap parameter for the texture on the r dimension (depth)")}, - {"access", PARAM_TYPE_FLAGS, OFFSET(params.access), {.i64=NGLI_ACCESS_READ_WRITE}, .choices=&access_choices, - .desc=NGLI_DOCSTRING("texture access (only honored by the `Compute` node)")}, {"data_src", PARAM_TYPE_NODE, OFFSET(data_src), .node_types=DATA_SRC_TYPES_LIST_3D, .desc=NGLI_DOCSTRING("data source")}, {NULL} @@ -248,8 +235,6 @@ static const struct node_param texturecube_params[] = { .desc=NGLI_DOCSTRING("wrap parameter for the texture on the t dimension (vertical)")}, {"wrap_r", PARAM_TYPE_SELECT, OFFSET(params.wrap_r), {.i64=NGLI_WRAP_CLAMP_TO_EDGE}, .choices=&wrap_choices, .desc=NGLI_DOCSTRING("wrap parameter for the texture on the r dimension (depth)")}, - {"access", PARAM_TYPE_FLAGS, OFFSET(params.access), {.i64=NGLI_ACCESS_READ_WRITE}, .choices=&access_choices, - .desc=NGLI_DOCSTRING("texture access (only honored by the `Compute` node)")}, {"data_src", PARAM_TYPE_NODE, OFFSET(data_src), .node_types=DATA_SRC_TYPES_LIST_3D, .desc=NGLI_DOCSTRING("data source")}, {NULL} diff --git a/libnodegl/pgcraft.c b/libnodegl/pgcraft.c index 0661f9337a..10d0a871d2 100644 --- a/libnodegl/pgcraft.c +++ b/libnodegl/pgcraft.c @@ -310,6 +310,7 @@ static int inject_texture_info(struct pgcraft *s, struct pgcraft_texture_info *i .type = field->type, .location = -1, .binding = -1, + .access = info->writable ? NGLI_ACCESS_WRITE_BIT : NGLI_ACCESS_READ_BIT, }; snprintf(pl_texture_desc.name, sizeof(pl_texture_desc.name), "%s", field->name); diff --git a/libnodegl/pipeline.h b/libnodegl/pipeline.h index 0bba00cc97..91c6fef91e 100644 --- a/libnodegl/pipeline.h +++ b/libnodegl/pipeline.h @@ -42,6 +42,7 @@ struct pipeline_texture_desc { int type; int location; int binding; + int access; }; struct pipeline_buffer_desc { diff --git a/libnodegl/pipeline_gl.c b/libnodegl/pipeline_gl.c index 471a6f4463..8ae2c4177f 100644 --- a/libnodegl/pipeline_gl.c +++ b/libnodegl/pipeline_gl.c @@ -245,6 +245,17 @@ static int acquire_next_available_texture_unit(uint64_t *texture_units) return NGL_ERROR_LIMIT_EXCEEDED; } +static const GLenum gl_access_map[NGLI_ACCESS_NB] = { + [NGLI_ACCESS_READ_BIT] = GL_READ_ONLY, + [NGLI_ACCESS_WRITE_BIT] = GL_WRITE_ONLY, + [NGLI_ACCESS_READ_WRITE] = GL_READ_WRITE, +}; + +static GLenum get_gl_access(int access) +{ + return gl_access_map[access]; +} + static void set_textures(struct pipeline *s, struct glcontext *gl) { struct pipeline_gl *s_priv = (struct pipeline_gl *)s; @@ -257,12 +268,10 @@ static void set_textures(struct pipeline *s, struct glcontext *gl) if (texture_binding->desc.type == NGLI_TYPE_IMAGE_2D) { GLuint texture_id = 0; - GLenum access = GL_READ_WRITE; + const GLenum access = get_gl_access(texture_binding->desc.access); GLenum internal_format = GL_RGBA8; if (texture) { - const struct texture_params *params = &texture->params; texture_id = texture_gl->id; - access = ngli_texture_get_gl_access(params->access); internal_format = texture_gl->internal_format; } ngli_glBindImageTexture(gl, texture_binding->desc.binding, texture_id, 0, GL_FALSE, 0, access, internal_format); diff --git a/libnodegl/texture.h b/libnodegl/texture.h index d30b32e584..43f4533077 100644 --- a/libnodegl/texture.h +++ b/libnodegl/texture.h @@ -65,7 +65,6 @@ NGLI_STATIC_ASSERT(texture_access, (NGLI_ACCESS_READ_BIT | NGLI_ACCESS_WRITE_BIT .wrap_s = NGLI_WRAP_CLAMP_TO_EDGE, \ .wrap_t = NGLI_WRAP_CLAMP_TO_EDGE, \ .wrap_r = NGLI_WRAP_CLAMP_TO_EDGE, \ - .access = NGLI_ACCESS_READ_WRITE \ } #define NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY (1 << 0) @@ -89,7 +88,6 @@ struct texture_params { int wrap_s; int wrap_t; int wrap_r; - int access; int immutable; int usage; int external_storage; diff --git a/libnodegl/texture_gl.c b/libnodegl/texture_gl.c index c025d70cca..ad70b9c47c 100644 --- a/libnodegl/texture_gl.c +++ b/libnodegl/texture_gl.c @@ -65,17 +65,6 @@ GLint ngli_texture_get_gl_wrap(int wrap) return gl_wrap_map[wrap]; } -static const GLenum gl_access_map[NGLI_ACCESS_NB] = { - [NGLI_ACCESS_READ_BIT] = GL_READ_ONLY, - [NGLI_ACCESS_WRITE_BIT] = GL_WRITE_ONLY, - [NGLI_ACCESS_READ_WRITE] = GL_READ_WRITE, -}; - -GLenum ngli_texture_get_gl_access(int access) -{ - return gl_access_map[access]; -} - static void texture_set_image(struct texture *s, const uint8_t *data) { struct texture_gl *s_priv = (struct texture_gl *)s; diff --git a/libnodegl/texture_gl.h b/libnodegl/texture_gl.h index ef2b3696a0..793011af23 100644 --- a/libnodegl/texture_gl.h +++ b/libnodegl/texture_gl.h @@ -28,7 +28,6 @@ GLint ngli_texture_get_gl_min_filter(int min_filter, int mipmap_filter); GLint ngli_texture_get_gl_mag_filter(int mag_filter); GLint ngli_texture_get_gl_wrap(int wrap); -GLenum ngli_texture_get_gl_access(int access); struct texture_gl { struct texture parent; From bad8be3906a9131d7be38902446f27e2e26b12a4 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 26 Oct 2020 12:31:28 +0100 Subject: [PATCH 204/388] texture: move NGLI_ACCESS_* definitions to pipeline.h --- libnodegl/pipeline.h | 10 ++++++++++ libnodegl/texture.h | 10 ---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/libnodegl/pipeline.h b/libnodegl/pipeline.h index 91c6fef91e..9e936e5542 100644 --- a/libnodegl/pipeline.h +++ b/libnodegl/pipeline.h @@ -31,6 +31,16 @@ struct gctx; +enum { + NGLI_ACCESS_UNDEFINED, + NGLI_ACCESS_READ_BIT, + NGLI_ACCESS_WRITE_BIT, + NGLI_ACCESS_READ_WRITE, + NGLI_ACCESS_NB +}; + +NGLI_STATIC_ASSERT(texture_access, (NGLI_ACCESS_READ_BIT | NGLI_ACCESS_WRITE_BIT) == NGLI_ACCESS_READ_WRITE); + struct pipeline_uniform_desc { char name[MAX_ID_LEN]; int type; diff --git a/libnodegl/texture.h b/libnodegl/texture.h index 43f4533077..3de9e3fe2f 100644 --- a/libnodegl/texture.h +++ b/libnodegl/texture.h @@ -46,16 +46,6 @@ enum { NGLI_NB_WRAP }; -enum { - NGLI_ACCESS_UNDEFINED, - NGLI_ACCESS_READ_BIT, - NGLI_ACCESS_WRITE_BIT, - NGLI_ACCESS_READ_WRITE, - NGLI_ACCESS_NB -}; - -NGLI_STATIC_ASSERT(texture_access, (NGLI_ACCESS_READ_BIT | NGLI_ACCESS_WRITE_BIT) == NGLI_ACCESS_READ_WRITE); - #define NGLI_TEXTURE_PARAM_DEFAULTS { \ .type = NGLI_TEXTURE_TYPE_2D, \ .format = NGLI_FORMAT_UNDEFINED, \ From 30ad3f4599d8d91c9c72d3daddb7407092618fa0 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 23 Oct 2020 15:59:18 +0200 Subject: [PATCH 205/388] texture: remove NGLI_TEXTURE_PARAM_DEFAULTS macro --- libnodegl/gctx_gl.c | 50 ++++++++++++--------- libnodegl/hwupload_videotoolbox_darwin_gl.c | 10 +++-- libnodegl/node_hud.c | 16 ++++--- libnodegl/node_rtt.c | 42 +++++++++-------- libnodegl/node_text.c | 16 ++++--- libnodegl/texture.h | 16 +++---- 6 files changed, 82 insertions(+), 68 deletions(-) diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 3b8a1f9880..5a4ce50e93 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -107,10 +107,12 @@ static int offscreen_rendertarget_init(struct gctx *s) ngli_glTexParameteri(gl, GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); ngli_glBindTexture(gl, GL_TEXTURE_2D, 0); - struct texture_params attachment_params = NGLI_TEXTURE_PARAM_DEFAULTS; - attachment_params.format = NGLI_FORMAT_B8G8R8A8_UNORM; - attachment_params.width = width; - attachment_params.height = height; + struct texture_params attachment_params = { + .type = NGLI_TEXTURE_TYPE_2D, + .format = NGLI_FORMAT_B8G8R8A8_UNORM, + .width = width, + .height = height, + }; s_priv->color = ngli_texture_create(s); if (!s_priv->color) return NGL_ERROR_MEMORY; @@ -119,11 +121,13 @@ static int offscreen_rendertarget_init(struct gctx *s) return ret; #endif } else { - struct texture_params params = NGLI_TEXTURE_PARAM_DEFAULTS; - params.format = NGLI_FORMAT_R8G8B8A8_UNORM; - params.width = config->width; - params.height = config->height; - params.usage = NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY; + struct texture_params params = { + .type = NGLI_TEXTURE_TYPE_2D, + .format = NGLI_FORMAT_R8G8B8A8_UNORM, + .width = config->width, + .height = config->height, + .usage = NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY, + }; s_priv->color = ngli_texture_create(s); if (!s_priv->color) return NGL_ERROR_MEMORY; @@ -133,12 +137,14 @@ static int offscreen_rendertarget_init(struct gctx *s) } if (config->samples) { - struct texture_params params = NGLI_TEXTURE_PARAM_DEFAULTS; - params.format = NGLI_FORMAT_R8G8B8A8_UNORM; - params.width = config->width; - params.height = config->height; - params.usage = NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY; - params.samples = config->samples; + struct texture_params params = { + .type = NGLI_TEXTURE_TYPE_2D, + .format = NGLI_FORMAT_R8G8B8A8_UNORM, + .width = config->width, + .height = config->height, + .usage = NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY, + .samples = config->samples, + }; s_priv->ms_color = ngli_texture_create(s); if (!s_priv->ms_color) return NGL_ERROR_MEMORY; @@ -147,12 +153,14 @@ static int offscreen_rendertarget_init(struct gctx *s) return ret; } - struct texture_params attachment_params = NGLI_TEXTURE_PARAM_DEFAULTS; - attachment_params.format = NGLI_FORMAT_D24_UNORM_S8_UINT; - attachment_params.width = config->width; - attachment_params.height = config->height; - attachment_params.samples = config->samples; - attachment_params.usage = NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY; + struct texture_params attachment_params = { + .type = NGLI_TEXTURE_TYPE_2D, + .format = NGLI_FORMAT_D24_UNORM_S8_UINT, + .width = config->width, + .height = config->height, + .samples = config->samples, + .usage = NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY, + }; s_priv->depth = ngli_texture_create(s); if (!s_priv->depth) return NGL_ERROR_MEMORY; diff --git a/libnodegl/hwupload_videotoolbox_darwin_gl.c b/libnodegl/hwupload_videotoolbox_darwin_gl.c index 36a77a943d..ecd1fde7ed 100644 --- a/libnodegl/hwupload_videotoolbox_darwin_gl.c +++ b/libnodegl/hwupload_videotoolbox_darwin_gl.c @@ -116,10 +116,12 @@ static int vt_darwin_init(struct ngl_node *node, struct sxplayer_frame * frame) struct hwupload_vt_darwin *vt = hwupload->hwmap_priv_data; for (int i = 0; i < 2; i++) { - struct texture_params plane_params = NGLI_TEXTURE_PARAM_DEFAULTS; - plane_params.format = i == 0 ? NGLI_FORMAT_R8_UNORM : NGLI_FORMAT_R8G8_UNORM; - plane_params.rectangle = 1; - plane_params.external_storage = 1; + struct texture_params plane_params = { + .type = NGLI_TEXTURE_TYPE_2D, + .format = i == 0 ? NGLI_FORMAT_R8_UNORM : NGLI_FORMAT_R8G8_UNORM, + .rectangle = 1, + .external_storage = 1, + }; vt->planes[i] = ngli_texture_create(gctx); if (!vt->planes[i]) diff --git a/libnodegl/node_hud.c b/libnodegl/node_hud.c index 0d29dbb412..365f55f3eb 100644 --- a/libnodegl/node_hud.c +++ b/libnodegl/node_hud.c @@ -1269,13 +1269,15 @@ static int hud_init(struct ngl_node *node) if (ret < 0) return ret; - struct texture_params tex_params = NGLI_TEXTURE_PARAM_DEFAULTS; - tex_params.width = s->canvas.w; - tex_params.height = s->canvas.h; - tex_params.format = NGLI_FORMAT_R8G8B8A8_UNORM; - tex_params.min_filter = NGLI_FILTER_LINEAR; - tex_params.mag_filter = NGLI_FILTER_NEAREST; - tex_params.mipmap_filter = NGLI_MIPMAP_FILTER_LINEAR; + struct texture_params tex_params = { + .type = NGLI_TEXTURE_TYPE_2D, + .format = NGLI_FORMAT_R8G8B8A8_UNORM, + .width = s->canvas.w, + .height = s->canvas.h, + .min_filter = NGLI_FILTER_LINEAR, + .mag_filter = NGLI_FILTER_NEAREST, + .mipmap_filter = NGLI_MIPMAP_FILTER_LINEAR, + }; s->texture = ngli_texture_create(gctx); if (!s->texture) return NGL_ERROR_MEMORY; diff --git a/libnodegl/node_rtt.c b/libnodegl/node_rtt.c index faa09e7039..a585a0cebd 100644 --- a/libnodegl/node_rtt.c +++ b/libnodegl/node_rtt.c @@ -243,12 +243,14 @@ static int rtt_prefetch(struct ngl_node *node) if (!ms_texture) return NGL_ERROR_MEMORY; s->ms_colors[s->nb_ms_colors++] = ms_texture; - struct texture_params attachment_params = NGLI_TEXTURE_PARAM_DEFAULTS; - attachment_params.format = params->format; - attachment_params.width = s->width; - attachment_params.height = s->height; - attachment_params.samples = s->samples; - attachment_params.usage = NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY; + struct texture_params attachment_params = { + .type = NGLI_TEXTURE_TYPE_2D, + .format = params->format, + .width = s->width, + .height = s->height, + .samples = s->samples, + .usage = NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY, + }; ret = ngli_texture_init(ms_texture, &attachment_params); if (ret < 0) return ret; @@ -283,12 +285,14 @@ static int rtt_prefetch(struct ngl_node *node) if (!ms_texture) return NGL_ERROR_MEMORY; s->ms_depth = ms_texture; - struct texture_params attachment_params = NGLI_TEXTURE_PARAM_DEFAULTS; - attachment_params.format = params->format; - attachment_params.width = s->width; - attachment_params.height = s->height; - attachment_params.samples = s->samples; - attachment_params.usage = NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY; + struct texture_params attachment_params = { + .type = NGLI_TEXTURE_TYPE_2D, + .format = params->format, + .width = s->width, + .height = s->height, + .samples = s->samples, + .usage = NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY, + }; ret = ngli_texture_init(ms_texture, &attachment_params); if (ret < 0) return ret; @@ -312,12 +316,14 @@ static int rtt_prefetch(struct ngl_node *node) if (!depth) return NGL_ERROR_MEMORY; s->depth = depth; - struct texture_params attachment_params = NGLI_TEXTURE_PARAM_DEFAULTS; - attachment_params.format = depth_format; - attachment_params.width = s->width; - attachment_params.height = s->height; - attachment_params.samples = s->samples; - attachment_params.usage = NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY; + struct texture_params attachment_params = { + .type = NGLI_TEXTURE_TYPE_2D, + .format = depth_format, + .width = s->width, + .height = s->height, + .samples = s->samples, + .usage = NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY, + }; ret = ngli_texture_init(depth, &attachment_params); if (ret < 0) return ret; diff --git a/libnodegl/node_text.c b/libnodegl/node_text.c index 818e94bb06..1b1ffb75dd 100644 --- a/libnodegl/node_text.c +++ b/libnodegl/node_text.c @@ -418,13 +418,15 @@ static int atlas_create(struct ngl_node *node) if (ret < 0) goto end; - struct texture_params tex_params = NGLI_TEXTURE_PARAM_DEFAULTS; - tex_params.width = canvas.w; - tex_params.height = canvas.h; - tex_params.format = NGLI_FORMAT_R8_UNORM; - tex_params.min_filter = NGLI_FILTER_LINEAR; - tex_params.mag_filter = NGLI_FILTER_NEAREST; - tex_params.mipmap_filter = NGLI_MIPMAP_FILTER_LINEAR; + struct texture_params tex_params = { + .type = NGLI_TEXTURE_TYPE_2D, + .width = canvas.w, + .height = canvas.h, + .format = NGLI_FORMAT_R8_UNORM, + .min_filter = NGLI_FILTER_LINEAR, + .mag_filter = NGLI_FILTER_NEAREST, + .mipmap_filter = NGLI_MIPMAP_FILTER_LINEAR, + }; ctx->font_atlas = ngli_texture_create(gctx); // freed at context reconfiguration/destruction if (!ctx->font_atlas) { diff --git a/libnodegl/texture.h b/libnodegl/texture.h index 3de9e3fe2f..ee63cf6566 100644 --- a/libnodegl/texture.h +++ b/libnodegl/texture.h @@ -46,17 +46,6 @@ enum { NGLI_NB_WRAP }; -#define NGLI_TEXTURE_PARAM_DEFAULTS { \ - .type = NGLI_TEXTURE_TYPE_2D, \ - .format = NGLI_FORMAT_UNDEFINED, \ - .min_filter = NGLI_FILTER_NEAREST, \ - .mag_filter = NGLI_FILTER_NEAREST, \ - .mipmap_filter = NGLI_MIPMAP_FILTER_NONE, \ - .wrap_s = NGLI_WRAP_CLAMP_TO_EDGE, \ - .wrap_t = NGLI_WRAP_CLAMP_TO_EDGE, \ - .wrap_r = NGLI_WRAP_CLAMP_TO_EDGE, \ -} - #define NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY (1 << 0) enum texture_type { @@ -65,6 +54,11 @@ enum texture_type { NGLI_TEXTURE_TYPE_CUBE, }; +NGLI_STATIC_ASSERT(texture_params_type_default, NGLI_TEXTURE_TYPE_2D == 0); +NGLI_STATIC_ASSERT(texture_params_filter_default, NGLI_FILTER_NEAREST == 0); +NGLI_STATIC_ASSERT(texture_params_mipmap_filter_default, NGLI_MIPMAP_FILTER_NONE == 0); +NGLI_STATIC_ASSERT(texture_params_wrap_default, NGLI_WRAP_CLAMP_TO_EDGE == 0); + struct texture_params { enum texture_type type; int format; From 5cc8e20730086e70234224882edb0cf75be6b6ee Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 23 Oct 2020 16:48:20 +0200 Subject: [PATCH 206/388] media: cosmetics --- libnodegl/node_media.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/libnodegl/node_media.c b/libnodegl/node_media.c index 8b14e60562..f3582573b4 100644 --- a/libnodegl/node_media.c +++ b/libnodegl/node_media.c @@ -155,13 +155,13 @@ static int media_init(struct ngl_node *node) if (config->backend == NGL_BACKEND_OPENGLES) { struct texture_params params = { - .type = NGLI_TEXTURE_TYPE_2D, - .format = NGLI_FORMAT_UNDEFINED, - .min_filter = NGLI_FILTER_NEAREST, - .mag_filter = NGLI_FILTER_NEAREST, - .wrap_s = NGLI_WRAP_CLAMP_TO_EDGE, - .wrap_t = NGLI_WRAP_CLAMP_TO_EDGE, - .wrap_r = NGLI_WRAP_CLAMP_TO_EDGE, + .type = NGLI_TEXTURE_TYPE_2D, + .format = NGLI_FORMAT_UNDEFINED, + .min_filter = NGLI_FILTER_NEAREST, + .mag_filter = NGLI_FILTER_NEAREST, + .wrap_s = NGLI_WRAP_CLAMP_TO_EDGE, + .wrap_t = NGLI_WRAP_CLAMP_TO_EDGE, + .wrap_r = NGLI_WRAP_CLAMP_TO_EDGE, .external_oes = 1, }; From e8749f4b3b0eb5bf64b6493924e029bcc0e85ad9 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 23 Oct 2020 16:48:26 +0200 Subject: [PATCH 207/388] hwupload_vaapi_gl: cosmetics --- libnodegl/hwupload_vaapi_gl.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/libnodegl/hwupload_vaapi_gl.c b/libnodegl/hwupload_vaapi_gl.c index 7a61f36472..59fd17733e 100644 --- a/libnodegl/hwupload_vaapi_gl.c +++ b/libnodegl/hwupload_vaapi_gl.c @@ -88,14 +88,14 @@ static int vaapi_init(struct ngl_node *node, struct sxplayer_frame *frame) int format = i == 0 ? NGLI_FORMAT_R8_UNORM : NGLI_FORMAT_R8G8_UNORM; const struct texture_params plane_params = { - .type = NGLI_TEXTURE_TYPE_2D, - .format = format, - .min_filter = params->min_filter, - .mag_filter = params->mag_filter, - .mipmap_filter = NGLI_MIPMAP_FILTER_NONE, - .wrap_s = params->wrap_s, - .wrap_t = params->wrap_t, - .wrap_r = params->wrap_r, + .type = NGLI_TEXTURE_TYPE_2D, + .format = format, + .min_filter = params->min_filter, + .mag_filter = params->mag_filter, + .mipmap_filter = NGLI_MIPMAP_FILTER_NONE, + .wrap_s = params->wrap_s, + .wrap_t = params->wrap_t, + .wrap_r = params->wrap_r, .external_storage = 1, }; From 62ab2fdce6693b0ddc43b2fbbe6d04816671e3b8 Mon Sep 17 00:00:00 2001 From: mrobertseidowsky Date: Fri, 23 Oct 2020 16:40:12 +0200 Subject: [PATCH 208/388] ci: Add Github action workflow ci_win.yml file --- .github/workflows/ci_win.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/ci_win.yml diff --git a/.github/workflows/ci_win.yml b/.github/workflows/ci_win.yml new file mode 100644 index 0000000000..62ba891c6a --- /dev/null +++ b/.github/workflows/ci_win.yml @@ -0,0 +1,30 @@ +name: 'tests Windows' + +on: + push: + branches: + - 'master' + pull_request: + +jobs: + build_libs: + + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + + - uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 # Start a 64 bit Mingw environment + update: true + + - name: Install dependencies + run: | + C:\msys64\usr\bin\bash -lc "pacman -S --noconfirm --needed git make" + C:\msys64\usr\bin\bash -lc "pacman -S --noconfirm --needed mingw-w64-x86_64-{toolchain,ffmpeg,python}" + + - name: Build + run: | + $env:CHERE_INVOKING = 'yes' # Preserve the current working directory + C:\msys64\usr\bin\bash -lc "make -j$(($(nproc)+1)) TARGET_OS=MinGW-w64" From aca3053b7e74f2a6f0fa7f36c56d3b17579db017 Mon Sep 17 00:00:00 2001 From: mrobertseidowsky Date: Mon, 26 Oct 2020 17:08:57 +0100 Subject: [PATCH 209/388] README: add Windows test badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 55201020d1..67551b0ed2 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ and API can change at any time. ![tests Linux](https://github.com/gopro/gopro-lib-node.gl/workflows/tests%20Linux/badge.svg) ![tests Mac](https://github.com/gopro/gopro-lib-node.gl/workflows/tests%20Mac/badge.svg) +![tests Windows](https://github.com/gopro/gopro-lib-node.gl/workflows/tests%20Windows/badge.svg) [![coverage](https://codecov.io/gh/gopro/gopro-lib-node.gl/branch/master/graph/badge.svg)](https://codecov.io/gh/gopro/gopro-lib-node.gl) [gopro]: https://gopro.com/ From 4e9dc1b0aba157f25a6795e2fec182b892ea4e00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 27 Oct 2020 23:41:33 +0100 Subject: [PATCH 210/388] specs: remove access flags Forgotten in 0941bd415faed0b2e1feaa7debca92f0026cfc38. --- libnodegl/nodes.specs | 3 --- 1 file changed, 3 deletions(-) diff --git a/libnodegl/nodes.specs b/libnodegl/nodes.specs index cb7920086b..2c54e258aa 100644 --- a/libnodegl/nodes.specs +++ b/libnodegl/nodes.specs @@ -350,7 +350,6 @@ - [mipmap_filter, select] - [wrap_s, select] - [wrap_t, select] - - [access, flags] - [data_src, Node] - [direct_rendering, bool] @@ -365,7 +364,6 @@ - [wrap_s, select] - [wrap_t, select] - [wrap_r, select] - - [access, flags] - [data_src, Node] - TextureCube: @@ -377,7 +375,6 @@ - [wrap_s, select] - [wrap_t, select] - [wrap_r, select] - - [access, flags] - [data_src, Node] - Time: From bf11a133b4b70a9b8b23fb221caec03138a110ce Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 29 Oct 2020 10:13:55 +0100 Subject: [PATCH 211/388] tools/desktop: workaround a SDL issue on macOS --- ngl-tools/ngl-desktop.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ngl-tools/ngl-desktop.c b/ngl-tools/ngl-desktop.c index c59622f8aa..222bed0f39 100644 --- a/ngl-tools/ngl-desktop.c +++ b/ngl-tools/ngl-desktop.c @@ -19,6 +19,17 @@ * under the License. */ +/* + * To workaround a SDL issue¹ on macOS, we need to include before + * defining _POSIX_C_SOURCE to ensure that memset_pattern4() is defined. This + * is mandatory because SDL_stdinc.h (included by SDL.h) uses memset_pattern4() + * unconditionally on Apple systems. + * [1]: https://bugzilla.libsdl.org/show_bug.cgi?id=5107 + */ +#ifdef __APPLE__ +#include +#endif + #define _POSIX_C_SOURCE 200112L // for struct addrinfo with glibc #include From 65db76fe8658d8e47f058f7fd9d2036747999499 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 27 Oct 2020 13:32:53 +0100 Subject: [PATCH 212/388] test/compute: mark hist block as writable in the clear_histogram program The clear_histogram program clears the hist block data. --- tests/compute.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/compute.py b/tests/compute.py index cbbde9320e..32b0d15136 100644 --- a/tests/compute.py +++ b/tests/compute.py @@ -212,6 +212,7 @@ def compute_histogram(cfg, show_dbg_points=False): group_size = hsize // local_size clear_histogram_shader = _COMPUTE_HISTOGRAM_CLEAR % shader_params clear_histogram_program = ngl.ComputeProgram(clear_histogram_shader) + clear_histogram_program.update_properties(hist=ngl.ResourceProps(writable=True)) clear_histogram = ngl.Compute( group_size, 1, From 173726ab8e08b892979f270f1c0f4406bd9c9589 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 27 Oct 2020 10:40:41 +0100 Subject: [PATCH 213/388] pgcraft: allow the shader to read and write if the image is set to writable The writable parameter now implies that the shader can read and write from the image. This is useful if the image is used with atomic operations which require read and write operations. --- libnodegl/pgcraft.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libnodegl/pgcraft.c b/libnodegl/pgcraft.c index 10d0a871d2..5d3f378e0b 100644 --- a/libnodegl/pgcraft.c +++ b/libnodegl/pgcraft.c @@ -310,7 +310,7 @@ static int inject_texture_info(struct pgcraft *s, struct pgcraft_texture_info *i .type = field->type, .location = -1, .binding = -1, - .access = info->writable ? NGLI_ACCESS_WRITE_BIT : NGLI_ACCESS_READ_BIT, + .access = info->writable ? NGLI_ACCESS_READ_WRITE : NGLI_ACCESS_READ_BIT, }; snprintf(pl_texture_desc.name, sizeof(pl_texture_desc.name), "%s", field->name); @@ -332,7 +332,7 @@ static int inject_texture_info(struct pgcraft *s, struct pgcraft_texture_info *i ngli_bstr_printf(b, "layout(%s", format); if (pl_texture_desc.binding != -1) ngli_bstr_printf(b, ", binding=%d", pl_texture_desc.binding); - ngli_bstr_printf(b, ") %s ", info->writable ? "writeonly" : "readonly"); + ngli_bstr_printf(b, ") %s ", info->writable ? "" : "readonly"); } else if (pl_texture_desc.binding != -1) { ngli_bstr_printf(b, "layout(binding=%d) ", pl_texture_desc.binding); } From 48224b168d9e1a1f42903594c26b97cdd9f253a9 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 27 Oct 2020 10:37:52 +0100 Subject: [PATCH 214/388] pgcraft: specify buffer memory qualifiers Fixes #105. --- libnodegl/pass.c | 19 ++++++++++--------- libnodegl/pgcraft.c | 3 +++ libnodegl/pgcraft.h | 1 + 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/libnodegl/pass.c b/libnodegl/pass.c index 8fc67d8efe..75eb03b639 100644 --- a/libnodegl/pass.c +++ b/libnodegl/pass.c @@ -188,15 +188,16 @@ static int register_block(struct pass *s, const char *name, struct ngl_node *blo LOG(DEBUG, "block %s is larger than the max UBO size (%d > %d), declaring it as SSBO", name, block->size, limits->max_uniform_block_size); type = NGLI_TYPE_STORAGE_BUFFER; - } else { - const struct pass_params *params = &s->params; - if (params->properties) { - const struct ngl_node *resprops_node = ngli_hmap_get(params->properties, name); - if (resprops_node) { - const struct resourceprops_priv *resprops = resprops_node->priv_data; - if (resprops->variadic || resprops->writable) - type = NGLI_TYPE_STORAGE_BUFFER; - } + } + + const struct pass_params *params = &s->params; + if (params->properties) { + const struct ngl_node *resprops_node = ngli_hmap_get(params->properties, name); + if (resprops_node) { + const struct resourceprops_priv *resprops = resprops_node->priv_data; + if (resprops->variadic || resprops->writable) + type = NGLI_TYPE_STORAGE_BUFFER; + crafter_block.writable = resprops->writable; } } diff --git a/libnodegl/pgcraft.c b/libnodegl/pgcraft.c index 5d3f378e0b..87f81e5538 100644 --- a/libnodegl/pgcraft.c +++ b/libnodegl/pgcraft.c @@ -405,6 +405,9 @@ static int inject_block(struct pgcraft *s, struct bstr *b, ngli_bstr_printf(b, "layout(%s)", layout); } + if (block->type == NGLI_TYPE_STORAGE_BUFFER && !named_block->writable) + ngli_bstr_print(b, " readonly"); + const char *keyword = get_glsl_type(block->type); ngli_bstr_printf(b, " %s %s_block {\n", keyword, named_block->name); const struct block_field *field_info = ngli_darray_data(&block->fields); diff --git a/libnodegl/pgcraft.h b/libnodegl/pgcraft.h index ffadbf0edd..32c246789c 100644 --- a/libnodegl/pgcraft.h +++ b/libnodegl/pgcraft.h @@ -65,6 +65,7 @@ struct pgcraft_block { const char *instance_name; int stage; int variadic; + int writable; const struct block *block; struct buffer *buffer; }; From 050aafdc47edfd5d5f699de6e00bcbae74db1d0f Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 27 Oct 2020 11:19:17 +0100 Subject: [PATCH 215/388] pipeline: add access field to pipeline_buffer_desc This will be used later on to determine which kind of barrier we need to insert after a draw or compute pipeline. --- libnodegl/pgcraft.c | 1 + libnodegl/pipeline.h | 1 + 2 files changed, 2 insertions(+) diff --git a/libnodegl/pgcraft.c b/libnodegl/pgcraft.c index 87f81e5538..5d1bf86ea3 100644 --- a/libnodegl/pgcraft.c +++ b/libnodegl/pgcraft.c @@ -388,6 +388,7 @@ static int inject_block(struct pgcraft *s, struct bstr *b, struct pipeline_buffer_desc pl_buffer_desc = { .type = block->type, .binding = -1, + .access = named_block->writable ? NGLI_ACCESS_READ_WRITE : NGLI_ACCESS_READ_BIT, }; int len = snprintf(pl_buffer_desc.name, sizeof(pl_buffer_desc.name), "%s_block", named_block->name); if (len >= sizeof(pl_buffer_desc.name)) { diff --git a/libnodegl/pipeline.h b/libnodegl/pipeline.h index 9e936e5542..d1079e057a 100644 --- a/libnodegl/pipeline.h +++ b/libnodegl/pipeline.h @@ -59,6 +59,7 @@ struct pipeline_buffer_desc { char name[MAX_ID_LEN]; int type; int binding; + int access; }; struct pipeline_attribute_desc { From a8a7b6ac745261fc0026fb3da1916c2c0f7f4bc5 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 27 Oct 2020 11:24:51 +0100 Subject: [PATCH 216/388] glfeatures: declare glMemoryBarrier as part of GL_ARB_shader_image_load_store glMemoryBarrier() is introduced by GL_ARB_shader_image_load_store and is available in OpenGL version 4.2, not GL_ARB_compute_shader and OpenGL 4.3. The base OpenGLES version requirement is still accurate and left unchanged. --- libnodegl/glfeatures_data.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libnodegl/glfeatures_data.h b/libnodegl/glfeatures_data.h index 211eaf35d9..589f85a18f 100644 --- a/libnodegl/glfeatures_data.h +++ b/libnodegl/glfeatures_data.h @@ -68,7 +68,6 @@ static const struct glfeature { .es_version = 310, .extensions = (const char*[]){"GL_ARB_compute_shader", NULL}, .funcs_offsets = (const size_t[]){OFFSET(DispatchCompute), - OFFSET(MemoryBarrier), -1} }, { .name = "program_interface_query", @@ -89,6 +88,7 @@ static const struct glfeature { .es_version = 310, .extensions = (const char*[]){"GL_ARB_shader_image_load_store", NULL}, .funcs_offsets = (const size_t[]){OFFSET(BindImageTexture), + OFFSET(MemoryBarrier), -1} }, { .name = "shader_storage_buffer_object", From 75ea726fc1a9ac9744bd9430a6103a85228df6a6 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 27 Oct 2020 09:30:37 +0100 Subject: [PATCH 217/388] pipeline_gl: only insert memory barriers after glDispatchCompute() It is up to the previous pipeline executions to insert the relevant memory barriers (which is already the case for the compute pipelines). --- libnodegl/pipeline_gl.c | 1 - 1 file changed, 1 deletion(-) diff --git a/libnodegl/pipeline_gl.c b/libnodegl/pipeline_gl.c index 8ae2c4177f..7cc493b8a2 100644 --- a/libnodegl/pipeline_gl.c +++ b/libnodegl/pipeline_gl.c @@ -731,7 +731,6 @@ void ngli_pipeline_gl_dispatch(struct pipeline *s, int nb_group_x, int nb_group_ set_buffers(s, gl); set_textures(s, gl); - ngli_glMemoryBarrier(gl, GL_ALL_BARRIER_BITS); ngli_glDispatchCompute(gl, nb_group_x, nb_group_y, nb_group_z); ngli_glMemoryBarrier(gl, GL_ALL_BARRIER_BITS); } From b842c04b12920791a9dd976e16559fd07de4db91 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 27 Oct 2020 11:59:21 +0100 Subject: [PATCH 218/388] pipeline_gl: add missing feature checks --- libnodegl/pipeline_gl.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/libnodegl/pipeline_gl.c b/libnodegl/pipeline_gl.c index 7cc493b8a2..431c02416e 100644 --- a/libnodegl/pipeline_gl.c +++ b/libnodegl/pipeline_gl.c @@ -211,6 +211,11 @@ static int build_texture_bindings(struct pipeline *s, const struct pipeline_para struct glcontext *gl = gctx_gl->glcontext; const struct limits *limits = &gl->limits; + if (!(gl->features & NGLI_FEATURE_SHADER_IMAGE_LOAD_STORE)) { + LOG(ERROR, "context does not support shader image load store operations"); + return NGL_ERROR_UNSUPPORTED; + } + int max_nb_textures = NGLI_MIN(limits->max_texture_image_units, sizeof(s_priv->used_texture_units) * 8); if (texture_desc->binding >= max_nb_textures) { LOG(ERROR, "maximum number (%d) of texture unit reached", max_nb_textures); @@ -310,10 +315,24 @@ static void set_buffers(struct pipeline *s, struct glcontext *gl) static int build_buffer_bindings(struct pipeline *s, const struct pipeline_params *params) { struct pipeline_gl *s_priv = (struct pipeline_gl *)s; + struct gctx_gl *gctx_gl = (struct gctx_gl *)s->gctx; + struct glcontext *gl = gctx_gl->glcontext; for (int i = 0; i < params->nb_buffers; i++) { const struct pipeline_buffer_desc *pipeline_buffer_desc = ¶ms->buffers_desc[i]; + if (pipeline_buffer_desc->type == NGLI_TYPE_UNIFORM_BUFFER && + !(gl->features & NGLI_FEATURE_UNIFORM_BUFFER_OBJECT)) { + LOG(ERROR, "context does not support uniform buffer objects"); + return NGL_ERROR_UNSUPPORTED; + } + + if (pipeline_buffer_desc->type == NGLI_TYPE_STORAGE_BUFFER && + !(gl->features & NGLI_FEATURE_SHADER_STORAGE_BUFFER_OBJECT)) { + LOG(ERROR, "context does not support shader storage buffer objects"); + return NGL_ERROR_UNSUPPORTED; + } + struct buffer_binding binding = { .type = ngli_type_get_gl_type(pipeline_buffer_desc->type), .desc = *pipeline_buffer_desc, From a8f26b98e009bca81894e3e5d46fa66b0bc8d321 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 27 Oct 2020 12:00:07 +0100 Subject: [PATCH 219/388] pipeline_gl: insert memory barriers after draw calls Previously, memory barriers were only inserted for the compute pipelines as they must use incoherent memory accesses to write to an output. We also need to do that for graphics pipelines if they perform such accesses (write to a buffer or an image). --- libnodegl/pipeline_gl.c | 30 +++++++++++++++++++++++++++++- libnodegl/pipeline_gl.h | 2 ++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/libnodegl/pipeline_gl.c b/libnodegl/pipeline_gl.c index 431c02416e..e0436ca516 100644 --- a/libnodegl/pipeline_gl.c +++ b/libnodegl/pipeline_gl.c @@ -226,6 +226,9 @@ static int build_texture_bindings(struct pipeline *s, const struct pipeline_para return NGL_ERROR_INVALID_DATA; } s_priv->used_texture_units |= 1ULL << texture_desc->binding; + + if (texture_desc->access & NGLI_ACCESS_WRITE_BIT) + s_priv->barriers |= GL_ALL_BARRIER_BITS; } struct texture_binding binding = { @@ -333,6 +336,9 @@ static int build_buffer_bindings(struct pipeline *s, const struct pipeline_param return NGL_ERROR_UNSUPPORTED; } + if (pipeline_buffer_desc->access & NGLI_ACCESS_WRITE_BIT) + s_priv->barriers |= GL_ALL_BARRIER_BITS; + struct buffer_binding binding = { .type = ngli_type_get_gl_type(pipeline_buffer_desc->type), .desc = *pipeline_buffer_desc, @@ -478,6 +484,18 @@ static int pipeline_compute_init(struct pipeline *s) return 0; } +static void insert_memory_barriers_noop(struct pipeline *s) +{ +} + +static void insert_memory_barriers(struct pipeline *s) +{ + struct pipeline_gl *s_priv = (struct pipeline_gl *)s; + struct gctx_gl *gctx_gl = (struct gctx_gl *)s->gctx; + struct glcontext *gl = gctx_gl->glcontext; + ngli_glMemoryBarrier(gl, s_priv->barriers); +} + struct pipeline *ngli_pipeline_gl_create(struct gctx *gctx) { struct pipeline_gl *s = ngli_calloc(1, sizeof(*s)); @@ -518,6 +536,10 @@ int ngli_pipeline_gl_init(struct pipeline *s, const struct pipeline_params *para ngli_assert(0); } + s_priv->insert_memory_barriers = s_priv->barriers + ? insert_memory_barriers + : insert_memory_barriers_noop; + return 0; } @@ -695,6 +717,8 @@ void ngli_pipeline_gl_draw(struct pipeline *s, int nb_vertices, int nb_instances ngli_glDrawArrays(gl, gl_topology, 0, nb_vertices); unbind_vertex_attribs(s, gl); + + s_priv->insert_memory_barriers(s); } void ngli_pipeline_gl_draw_indexed(struct pipeline *s, struct buffer *indices, int indices_format, int nb_indices, int nb_instances) @@ -736,6 +760,8 @@ void ngli_pipeline_gl_draw_indexed(struct pipeline *s, struct buffer *indices, i ngli_glDrawElements(gl, gl_topology, nb_indices, gl_indices_type, 0); unbind_vertex_attribs(s, gl); + + s_priv->insert_memory_barriers(s); } void ngli_pipeline_gl_dispatch(struct pipeline *s, int nb_group_x, int nb_group_y, int nb_group_z) @@ -751,7 +777,9 @@ void ngli_pipeline_gl_dispatch(struct pipeline *s, int nb_group_x, int nb_group_ set_textures(s, gl); ngli_glDispatchCompute(gl, nb_group_x, nb_group_y, nb_group_z); - ngli_glMemoryBarrier(gl, GL_ALL_BARRIER_BITS); + + struct pipeline_gl *s_priv = (struct pipeline_gl *)s; + s_priv->insert_memory_barriers(s); } void ngli_pipeline_gl_freep(struct pipeline **sp) diff --git a/libnodegl/pipeline_gl.h b/libnodegl/pipeline_gl.h index 0c1c95912d..cd39114440 100644 --- a/libnodegl/pipeline_gl.h +++ b/libnodegl/pipeline_gl.h @@ -39,6 +39,8 @@ struct pipeline_gl { uint64_t used_texture_units; GLuint vao_id; + GLenum barriers; + void (*insert_memory_barriers)(struct pipeline *s); }; struct pipeline *ngli_pipeline_gl_create(struct gctx *gctx); From bc4d84a372f7a5c5ca763fcefaf43759f9a21a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 30 Oct 2020 14:23:12 +0100 Subject: [PATCH 220/388] tools/desktop: simplify macOS SDL issue workaround This uses the same pattern used in SDL upstream to address the issue. --- ngl-tools/ngl-desktop.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ngl-tools/ngl-desktop.c b/ngl-tools/ngl-desktop.c index 222bed0f39..7a396d5018 100644 --- a/ngl-tools/ngl-desktop.c +++ b/ngl-tools/ngl-desktop.c @@ -20,14 +20,14 @@ */ /* - * To workaround a SDL issue¹ on macOS, we need to include before - * defining _POSIX_C_SOURCE to ensure that memset_pattern4() is defined. This + * To workaround a SDL issue¹ on macOS, we need to define _DARWIN_C_SOURCE when + * _POSIX_C_SOURCE is also set to ensure that memset_pattern4() is defined. This * is mandatory because SDL_stdinc.h (included by SDL.h) uses memset_pattern4() * unconditionally on Apple systems. * [1]: https://bugzilla.libsdl.org/show_bug.cgi?id=5107 */ #ifdef __APPLE__ -#include +#define _DARWIN_C_SOURCE #endif #define _POSIX_C_SOURCE 200112L // for struct addrinfo with glibc From ac7b809188696b1beff2c90e09c4ea16504a77b5 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 3 Nov 2020 15:45:06 +0100 Subject: [PATCH 221/388] glincludes: add missing GL_COLOR, GL_DEPTH and GL_STENCIL definitions Forgotten in b794ab5486b314893117aedaf9e815b0ed9264de. Fixes build on Android. --- libnodegl/glincludes.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libnodegl/glincludes.h b/libnodegl/glincludes.h index 6c8fca6204..2e69db2483 100644 --- a/libnodegl/glincludes.h +++ b/libnodegl/glincludes.h @@ -109,6 +109,9 @@ typedef void (NGLI_GL_APIENTRY *GLDEBUGPROC)(GLenum source, GLenum type, GLuint #endif #if NGL_GLES2_COMPAT_INCLUDES +# define GL_COLOR 0x1800 +# define GL_DEPTH 0x1801 +# define GL_STENCIL 0x1802 # define GL_UNPACK_ROW_LENGTH 0x0CF2 # define GL_MAX_DRAW_BUFFERS 0x8824 # define GL_MAX_SAMPLES 0x8D57 From 33603da2e182b87a50f98c2ae812e51cdba2d721 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 5 Nov 2020 09:18:43 +0100 Subject: [PATCH 222/388] rendertarget: remove ngli_rendertarget_resolve() declaration Forgotten in 9dfa103468cf5d3a97502772baaa8229f3961323. --- libnodegl/rendertarget.h | 1 - 1 file changed, 1 deletion(-) diff --git a/libnodegl/rendertarget.h b/libnodegl/rendertarget.h index c9b9f66aee..dfe3f89e1a 100644 --- a/libnodegl/rendertarget.h +++ b/libnodegl/rendertarget.h @@ -77,7 +77,6 @@ struct rendertarget { struct rendertarget *ngli_rendertarget_create(struct gctx *gctx); int ngli_rendertarget_init(struct rendertarget *s, const struct rendertarget_params *params); -void ngli_rendertarget_resolve(struct rendertarget *s); void ngli_rendertarget_read_pixels(struct rendertarget *s, uint8_t *data); void ngli_rendertarget_freep(struct rendertarget **sp); From 5bd9ee29993096e5ce96ea48bd6674c7f6eecc05 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 4 Nov 2020 16:33:09 +0100 Subject: [PATCH 223/388] pgcraft: use GLSL version to check if explicit bindings are supported --- libnodegl/pgcraft.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libnodegl/pgcraft.c b/libnodegl/pgcraft.c index 5d1bf86ea3..6fcb565ce2 100644 --- a/libnodegl/pgcraft.c +++ b/libnodegl/pgcraft.c @@ -952,8 +952,6 @@ static int probe_pipeline_elems(struct pgcraft *s) return 0; } -#define IS_GL_ES_MIN(min) (config->backend == NGL_BACKEND_OPENGLES && gctx->version >= (min)) -#define IS_GL_MIN(min) (config->backend == NGL_BACKEND_OPENGL && gctx->version >= (min)) #define IS_GLSL_ES_MIN(min) (config->backend == NGL_BACKEND_OPENGLES && s->glsl_version >= (min)) #define IS_GLSL_MIN(min) (config->backend == NGL_BACKEND_OPENGL && s->glsl_version >= (min)) @@ -995,7 +993,7 @@ static void setup_glsl_info_gl(struct pgcraft *s) s->has_precision_qualifiers = IS_GLSL_ES_MIN(100); s->has_modern_texture_picking = IS_GLSL_ES_MIN(300) || IS_GLSL_MIN(330); - const int has_buffer_bindings = IS_GL_ES_MIN(310) || IS_GL_MIN(420); + const int has_buffer_bindings = IS_GLSL_ES_MIN(310) || IS_GLSL_MIN(420); if (has_buffer_bindings) { /* Bindings are unique across stages and types */ for (int i = 0; i < NB_BINDINGS; i++) From 15c0a5ef5ff26cc27884b1763e3bb61171dd0726 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 4 Nov 2020 16:34:28 +0100 Subject: [PATCH 224/388] pgcraft: rename has_buffer_bindings to has_explicit_bindings --- libnodegl/pgcraft.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libnodegl/pgcraft.c b/libnodegl/pgcraft.c index 6fcb565ce2..b34d4d0615 100644 --- a/libnodegl/pgcraft.c +++ b/libnodegl/pgcraft.c @@ -993,8 +993,8 @@ static void setup_glsl_info_gl(struct pgcraft *s) s->has_precision_qualifiers = IS_GLSL_ES_MIN(100); s->has_modern_texture_picking = IS_GLSL_ES_MIN(300) || IS_GLSL_MIN(330); - const int has_buffer_bindings = IS_GLSL_ES_MIN(310) || IS_GLSL_MIN(420); - if (has_buffer_bindings) { + const int has_explicit_bindings = IS_GLSL_ES_MIN(310) || IS_GLSL_MIN(420); + if (has_explicit_bindings) { /* Bindings are unique across stages and types */ for (int i = 0; i < NB_BINDINGS; i++) s->next_bindings[i] = &s->bindings[i]; From 8f4f46464b7978c0ac7a147c6fb50adcb5a84e4c Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 4 Nov 2020 17:40:01 +0100 Subject: [PATCH 225/388] gctx: add language version field Exposes the shading language version of the backend. --- libnodegl/gctx.h | 1 + libnodegl/gctx_gl.c | 1 + libnodegl/glcontext.c | 30 ++++++++++++++++++++++++++++++ libnodegl/glcontext.h | 3 +++ 4 files changed, 35 insertions(+) diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index 74e27ea3a9..3295fffc17 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -104,6 +104,7 @@ struct gctx { struct ngl_config config; const struct gctx_class *class; int version; + int language_version; uint64_t features; struct limits limits; }; diff --git a/libnodegl/gctx_gl.c b/libnodegl/gctx_gl.c index 5a4ce50e93..b2488908ba 100644 --- a/libnodegl/gctx_gl.c +++ b/libnodegl/gctx_gl.c @@ -307,6 +307,7 @@ static int gl_init(struct gctx *s) return ret; s->version = gl->version; + s->language_version = gl->glsl_version; s->features = gl->features; s->limits = gl->limits; s_priv->default_rendertarget_desc.samples = gl->samples; diff --git a/libnodegl/glcontext.c b/libnodegl/glcontext.c index 791cdc79cc..fad3089fdf 100644 --- a/libnodegl/glcontext.c +++ b/libnodegl/glcontext.c @@ -169,6 +169,32 @@ static int glcontext_probe_version(struct glcontext *glcontext) return 0; } +static int glcontext_probe_glsl_version(struct glcontext *glcontext) +{ + if (glcontext->backend == NGL_BACKEND_OPENGL) { + const char *glsl_version = (const char *)ngli_glGetString(glcontext, GL_SHADING_LANGUAGE_VERSION); + if (!glsl_version) { + LOG(ERROR, "could not get GLSL version"); + return NGL_ERROR_BUG; + } + + int major_version; + int minor_version; + int ret = sscanf(glsl_version, "%d.%d", &major_version, &minor_version); + if (ret != 2) { + LOG(ERROR, "could not parse GLSL version (%s)", glsl_version); + return NGL_ERROR_BUG; + } + glcontext->glsl_version = major_version * 100 + minor_version; + } else if (glcontext->backend == NGL_BACKEND_OPENGLES) { + glcontext->glsl_version = glcontext->version >= 300 ? glcontext->version : 100; + } else { + ngli_assert(0); + } + + return 0; +} + static int glcontext_check_extension(const char *extension, const struct glcontext *glcontext) { @@ -326,6 +352,10 @@ static int glcontext_load_extensions(struct glcontext *glcontext) if (ret < 0) return ret; + ret = glcontext_probe_glsl_version(glcontext); + if (ret < 0) + return ret; + ret = glcontext_probe_extensions(glcontext); if (ret < 0) return ret; diff --git a/libnodegl/glcontext.h b/libnodegl/glcontext.h index cbd02edd47..734c71d0ba 100644 --- a/libnodegl/glcontext.h +++ b/libnodegl/glcontext.h @@ -47,6 +47,9 @@ struct glcontext { /* GL api */ int version; + /* GLSL version */ + int glsl_version; + /* GL features */ uint64_t features; From 621446311aa487656c97c215e8af7efdc9ebc80c Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 4 Nov 2020 17:46:19 +0100 Subject: [PATCH 226/388] pgcraft: use gctx language_version field --- libnodegl/pgcraft.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/libnodegl/pgcraft.c b/libnodegl/pgcraft.c index b34d4d0615..52e7b6dfa0 100644 --- a/libnodegl/pgcraft.c +++ b/libnodegl/pgcraft.c @@ -964,19 +964,12 @@ static void setup_glsl_info_gl(struct pgcraft *s) s->sym_vertex_index = "gl_VertexID"; s->sym_instance_index = "gl_InstanceID"; - if (config->backend == NGL_BACKEND_OPENGL) { - switch (gctx->version) { - case 300: s->glsl_version = 130; break; - case 310: s->glsl_version = 140; break; - case 320: s->glsl_version = 150; break; - default: s->glsl_version = gctx->version; break; - } - } else if (config->backend == NGL_BACKEND_OPENGLES) { + s->glsl_version = gctx->language_version; + + if (config->backend == NGL_BACKEND_OPENGLES) { if (gctx->version >= 300) { - s->glsl_version = gctx->version; s->glsl_version_suffix = " es"; } else { - s->glsl_version = 100; s->rg = "ra"; } #if defined(TARGET_ANDROID) @@ -984,8 +977,6 @@ static void setup_glsl_info_gl(struct pgcraft *s) static const char * const img_ext3[] = {"GL_OES_EGL_image_external_essl3", NULL}; s->required_tex_exts = gctx->version < 300 ? img_ext : img_ext3; #endif - } else { - ngli_assert(0); } s->has_in_out_qualifiers = IS_GLSL_ES_MIN(300) || IS_GLSL_MIN(150); From 41b15eb0ac170c00133d95c164bd7c5a1248b1da Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 4 Nov 2020 22:09:01 +0100 Subject: [PATCH 227/388] glfeatures: add NGLI_FEATURE_SHADER_IMAGE_SIZE feature --- libnodegl/feature.h | 2 ++ libnodegl/glfeatures_data.h | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/libnodegl/feature.h b/libnodegl/feature.h index de5a0f2103..e973e811aa 100644 --- a/libnodegl/feature.h +++ b/libnodegl/feature.h @@ -55,10 +55,12 @@ #define NGLI_FEATURE_EGL_ANDROID_GET_IMAGE_NATIVE_CLIENT_BUFFER (1 << 30) #define NGLI_FEATURE_KHR_DEBUG (1ULL << 31) #define NGLI_FEATURE_CLEAR_BUFFER (1ULL << 32) +#define NGLI_FEATURE_SHADER_IMAGE_SIZE (1ULL << 33) #define NGLI_FEATURE_COMPUTE_SHADER_ALL (NGLI_FEATURE_COMPUTE_SHADER | \ NGLI_FEATURE_PROGRAM_INTERFACE_QUERY | \ NGLI_FEATURE_SHADER_IMAGE_LOAD_STORE | \ + NGLI_FEATURE_SHADER_IMAGE_SIZE | \ NGLI_FEATURE_SHADER_STORAGE_BUFFER_OBJECT) #endif diff --git a/libnodegl/glfeatures_data.h b/libnodegl/glfeatures_data.h index 589f85a18f..bace59f5db 100644 --- a/libnodegl/glfeatures_data.h +++ b/libnodegl/glfeatures_data.h @@ -266,5 +266,11 @@ static const struct glfeature { .funcs_offsets = (const size_t[]){OFFSET(ClearBufferfv), OFFSET(ClearBufferfi), -1} + }, { + .name = "shader_image_size", + .flag = NGLI_FEATURE_SHADER_IMAGE_SIZE, + .version = 430, + .es_version = 310, + .extensions = (const char*[]){"GL_ARB_shader_image_size", NULL}, } }; From a85553702b3520969c61a53a4d7ab7f953a3b94e Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 4 Nov 2020 22:39:42 +0100 Subject: [PATCH 228/388] glfeatures: add NGLI_FEATURE_SHADING_LANGUAGE_420PACK feature --- libnodegl/feature.h | 1 + libnodegl/glfeatures_data.h | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/libnodegl/feature.h b/libnodegl/feature.h index e973e811aa..1e9292855a 100644 --- a/libnodegl/feature.h +++ b/libnodegl/feature.h @@ -56,6 +56,7 @@ #define NGLI_FEATURE_KHR_DEBUG (1ULL << 31) #define NGLI_FEATURE_CLEAR_BUFFER (1ULL << 32) #define NGLI_FEATURE_SHADER_IMAGE_SIZE (1ULL << 33) +#define NGLI_FEATURE_SHADING_LANGUAGE_420PACK (1ULL << 34) #define NGLI_FEATURE_COMPUTE_SHADER_ALL (NGLI_FEATURE_COMPUTE_SHADER | \ NGLI_FEATURE_PROGRAM_INTERFACE_QUERY | \ diff --git a/libnodegl/glfeatures_data.h b/libnodegl/glfeatures_data.h index bace59f5db..a024e780ff 100644 --- a/libnodegl/glfeatures_data.h +++ b/libnodegl/glfeatures_data.h @@ -272,5 +272,10 @@ static const struct glfeature { .version = 430, .es_version = 310, .extensions = (const char*[]){"GL_ARB_shader_image_size", NULL}, + }, { + .name = "shading_language_420pack", + .flag = NGLI_FEATURE_SHADING_LANGUAGE_420PACK, + .version = 420, + .extensions = (const char*[]){"GL_ARB_shading_language_420pack", NULL}, } }; From d66783a6283bce56acff086b6d0e383fa96ea534 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 4 Nov 2020 22:40:40 +0100 Subject: [PATCH 229/388] pgcraft: enable explicit bindings if NGLI_FEATURE_SHADING_LANGUAGE_420PACK is supported --- libnodegl/pgcraft.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libnodegl/pgcraft.c b/libnodegl/pgcraft.c index 52e7b6dfa0..04cb398121 100644 --- a/libnodegl/pgcraft.c +++ b/libnodegl/pgcraft.c @@ -984,7 +984,8 @@ static void setup_glsl_info_gl(struct pgcraft *s) s->has_precision_qualifiers = IS_GLSL_ES_MIN(100); s->has_modern_texture_picking = IS_GLSL_ES_MIN(300) || IS_GLSL_MIN(330); - const int has_explicit_bindings = IS_GLSL_ES_MIN(310) || IS_GLSL_MIN(420); + const int has_explicit_bindings = IS_GLSL_ES_MIN(310) || IS_GLSL_MIN(420) || + (gctx->features & NGLI_FEATURE_SHADING_LANGUAGE_420PACK); if (has_explicit_bindings) { /* Bindings are unique across stages and types */ for (int i = 0; i < NB_BINDINGS; i++) From 572223c25fb1354e3b71f97af370265cdf8ea9bf Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 4 Nov 2020 22:10:02 +0100 Subject: [PATCH 230/388] pgcraft: add support for GLSL extensions Allows pgcraft to craft shaders compatible with OpenGL contexts that support features through extensions. --- libnodegl/pgcraft.c | 88 ++++++++++++++++++++++++++++++++++++--------- libnodegl/pgcraft.h | 2 +- 2 files changed, 73 insertions(+), 17 deletions(-) diff --git a/libnodegl/pgcraft.c b/libnodegl/pgcraft.c index 04cb398121..ab8087ed68 100644 --- a/libnodegl/pgcraft.c +++ b/libnodegl/pgcraft.c @@ -19,6 +19,7 @@ * under the License. */ +#include #include #include @@ -489,15 +490,75 @@ const char *ublock_names[] = { [NGLI_PROGRAM_SHADER_COMP] = "comp", }; -static void set_glsl_header(struct pgcraft *s, struct bstr *b) +static int params_have_ssbos(struct pgcraft *s, const struct pgcraft_params *params, int stage) { + for (int i = 0; i < params->nb_blocks; i++) { + const struct pgcraft_block *pgcraft_block = ¶ms->blocks[i]; + const struct block *block = pgcraft_block->block; + if (pgcraft_block->stage == stage && block->type == NGLI_TYPE_STORAGE_BUFFER) + return 1; + } + return 0; +} + +static int params_have_images(struct pgcraft *s, const struct pgcraft_params *params, int stage) +{ + const struct darray *texture_infos_array = &s->texture_infos; + const struct pgcraft_texture_info *texture_infos = ngli_darray_data(texture_infos_array); + for (int i = 0; i < ngli_darray_count(texture_infos_array); i++) { + const struct pgcraft_texture_info *info = &texture_infos[i]; + for (int j = 0; j < NGLI_INFO_FIELD_NB; j++) { + const struct pgcraft_texture_info_field *field = &info->fields[j]; + if (field->stage == stage && field->type == NGLI_TYPE_IMAGE_2D) + return 1; + } + } + return 0; +} + +static void set_glsl_header(struct pgcraft *s, struct bstr *b, const struct pgcraft_params *params, int stage) +{ + struct ngl_ctx *ctx = s->ctx; + struct gctx *gctx = ctx->gctx; + const struct ngl_config *config = &gctx->config; + ngli_bstr_printf(b, "#version %d%s\n", s->glsl_version, s->glsl_version_suffix); - if (ngli_darray_data(&s->texture_infos)) { - if (s->required_tex_exts) - for (int i = 0; s->required_tex_exts[i]; i++) - ngli_bstr_printf(b, "#extension %s : require\n", s->required_tex_exts[i]); + const int require_ssbo_feature = params_have_ssbos(s, params, stage); + const int require_image_feature = params_have_images(s, params, stage); +#if defined(TARGET_ANDROID) + const int require_image_external_feature = ngli_darray_count(&s->texture_infos) > 0 && s->glsl_version < 300; + const int require_image_external_essl3_feature = ngli_darray_count(&s->texture_infos) > 0 && s->glsl_version >= 300; +#endif + const struct { + int backend; + const char *extension; + int glsl_version; + int required; + } features[] = { + /* OpenGL */ + {NGL_BACKEND_OPENGL, "GL_ARB_shading_language_420pack", 420, s->has_explicit_bindings}, + {NGL_BACKEND_OPENGL, "GL_ARB_shader_image_load_store", 420, require_image_feature}, + {NGL_BACKEND_OPENGL, "GL_ARB_shader_image_size", 430, require_image_feature}, + {NGL_BACKEND_OPENGL, "GL_ARB_shader_storage_buffer_object", 430, require_ssbo_feature}, + {NGL_BACKEND_OPENGL, "GL_ARB_compute_shader", 430, stage == NGLI_PROGRAM_SHADER_COMP}, + + /* OpenGLES */ +#if defined(TARGET_ANDROID) + {NGL_BACKEND_OPENGLES, "GL_OES_EGL_image_external", INT_MAX, require_image_external_feature}, + {NGL_BACKEND_OPENGLES, "GL_OES_EGL_image_external_essl3", INT_MAX, require_image_external_essl3_feature}, +#endif + }; + + for (int i = 0; i < NGLI_ARRAY_NB(features); i++) { + if (features[i].backend == config->backend && + features[i].glsl_version > s->glsl_version && + features[i].required) + ngli_bstr_printf(b, "#extension %s : require\n", features[i].extension); + } + + if (ngli_darray_data(&s->texture_infos)) { if (s->has_modern_texture_picking) ngli_bstr_print(b, "#define ngl_tex2d texture\n" "#define ngl_tex3d texture\n" @@ -730,7 +791,7 @@ static int craft_vert(struct pgcraft *s, const struct pgcraft_params *params) { struct bstr *b = s->shaders[NGLI_PROGRAM_SHADER_VERT]; - set_glsl_header(s, b); + set_glsl_header(s, b, params, NGLI_PROGRAM_SHADER_VERT); ngli_bstr_printf(b, "#define ngl_out_pos gl_Position\n" "#define ngl_vertex_index %s\n" @@ -754,7 +815,7 @@ static int craft_frag(struct pgcraft *s, const struct pgcraft_params *params) { struct bstr *b = s->shaders[NGLI_PROGRAM_SHADER_FRAG]; - set_glsl_header(s, b); + set_glsl_header(s, b, params, NGLI_PROGRAM_SHADER_FRAG); if (s->has_precision_qualifiers) ngli_bstr_print(b, "#if GL_FRAGMENT_PRECISION_HIGH\n" @@ -798,7 +859,7 @@ static int craft_comp(struct pgcraft *s, const struct pgcraft_params *params) { struct bstr *b = s->shaders[NGLI_PROGRAM_SHADER_COMP]; - set_glsl_header(s, b); + set_glsl_header(s, b, params, NGLI_PROGRAM_SHADER_COMP); int ret; if ((ret = inject_uniforms(s, b, params, NGLI_PROGRAM_SHADER_COMP)) < 0 || @@ -972,11 +1033,6 @@ static void setup_glsl_info_gl(struct pgcraft *s) } else { s->rg = "ra"; } -#if defined(TARGET_ANDROID) - static const char * const img_ext[] = {"GL_OES_EGL_image_external", NULL}; - static const char * const img_ext3[] = {"GL_OES_EGL_image_external_essl3", NULL}; - s->required_tex_exts = gctx->version < 300 ? img_ext : img_ext3; -#endif } s->has_in_out_qualifiers = IS_GLSL_ES_MIN(300) || IS_GLSL_MIN(150); @@ -984,9 +1040,9 @@ static void setup_glsl_info_gl(struct pgcraft *s) s->has_precision_qualifiers = IS_GLSL_ES_MIN(100); s->has_modern_texture_picking = IS_GLSL_ES_MIN(300) || IS_GLSL_MIN(330); - const int has_explicit_bindings = IS_GLSL_ES_MIN(310) || IS_GLSL_MIN(420) || - (gctx->features & NGLI_FEATURE_SHADING_LANGUAGE_420PACK); - if (has_explicit_bindings) { + s->has_explicit_bindings = IS_GLSL_ES_MIN(310) || IS_GLSL_MIN(420) || + (gctx->features & NGLI_FEATURE_SHADING_LANGUAGE_420PACK); + if (s->has_explicit_bindings) { /* Bindings are unique across stages and types */ for (int i = 0; i < NB_BINDINGS; i++) s->next_bindings[i] = &s->bindings[i]; diff --git a/libnodegl/pgcraft.h b/libnodegl/pgcraft.h index 32c246789c..abb9a7ed53 100644 --- a/libnodegl/pgcraft.h +++ b/libnodegl/pgcraft.h @@ -194,7 +194,7 @@ struct pgcraft { int has_in_out_layout_qualifiers; int has_precision_qualifiers; int has_modern_texture_picking; - const char * const *required_tex_exts; + int has_explicit_bindings; }; struct pgcraft *ngli_pgcraft_create(struct ngl_ctx *ctx); From 576eec34c8c12539c5cd44ddf6ec212d752b2121 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 6 Nov 2020 18:04:02 +0100 Subject: [PATCH 231/388] pgcraft: use ngli_darray_count() where appropriate --- libnodegl/pgcraft.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libnodegl/pgcraft.c b/libnodegl/pgcraft.c index ab8087ed68..8467c80544 100644 --- a/libnodegl/pgcraft.c +++ b/libnodegl/pgcraft.c @@ -558,7 +558,7 @@ static void set_glsl_header(struct pgcraft *s, struct bstr *b, const struct pgcr ngli_bstr_printf(b, "#extension %s : require\n", features[i].extension); } - if (ngli_darray_data(&s->texture_infos)) { + if (ngli_darray_count(&s->texture_infos) > 0) { if (s->has_modern_texture_picking) ngli_bstr_print(b, "#define ngl_tex2d texture\n" "#define ngl_tex3d texture\n" From 2bf0741a1736496e266258d38263b5b3c1022ac0 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 15 Sep 2020 09:43:14 +0200 Subject: [PATCH 232/388] pynodegl: move ngl_config initialization to a dedicated function --- pynodegl/pynodegl.pyx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pynodegl/pynodegl.pyx b/pynodegl/pynodegl.pyx index b8f61ecf14..21ecf6d786 100644 --- a/pynodegl/pynodegl.pyx +++ b/pynodegl/pynodegl.pyx @@ -178,9 +178,9 @@ cdef class Context: if self.ctx is NULL: raise MemoryError() - def configure(self, **kwargs): - cdef ngl_config config - memset(&config, 0, sizeof(config)); + @staticmethod + cdef void _init_ngl_config_from_dict(ngl_config *config, kwargs): + memset(config, 0, sizeof(ngl_config)); config.platform = kwargs.get('platform', PLATFORM_AUTO) config.backend = kwargs.get('backend', BACKEND_AUTO) config.display = kwargs.get('display', 0) @@ -198,9 +198,14 @@ cdef class Context: clear_color = kwargs.get('clear_color', (0.0, 0.0, 0.0, 1.0)) for i in range(4): config.clear_color[i] = clear_color[i] + capture_buffer = kwargs.get('capture_buffer') + if capture_buffer is not None: + config.capture_buffer = capture_buffer + + def configure(self, **kwargs): self.capture_buffer = kwargs.get('capture_buffer') - if self.capture_buffer is not None: - config.capture_buffer = self.capture_buffer + cdef ngl_config config + Context._init_ngl_config_from_dict(&config, kwargs) return ngl_configure(self.ctx, &config) def resize(self, width, height, viewport=None): From 8ffde4f3dc07f437680cc6a1d2bf37ab49c03884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 3 Nov 2020 10:02:02 +0100 Subject: [PATCH 233/388] log: add NGL_LOG_QUIET NGL_LOG_QUIET is set to a large enough value so that no future log level shall be larger. This is also the reason no trailing comma is added at the end (it must be the last log level). This log level can be useful in scripting scenario where the output is parsed and we don't want logging to interfere. --- libnodegl/nodegl.h | 1 + ngl-tools/opts.c | 1 + pynodegl-utils/pynodegl_utils/config.py | 1 + pynodegl/pynodegl.pyx | 2 ++ 4 files changed, 5 insertions(+) diff --git a/libnodegl/nodegl.h b/libnodegl/nodegl.h index cc12283b9f..a65444aaf1 100644 --- a/libnodegl/nodegl.h +++ b/libnodegl/nodegl.h @@ -44,6 +44,7 @@ enum { NGL_LOG_INFO, NGL_LOG_WARNING, NGL_LOG_ERROR, + NGL_LOG_QUIET = 1 << 8 }; /** diff --git a/ngl-tools/opts.c b/ngl-tools/opts.c index 62777d3739..01b00282cc 100644 --- a/ngl-tools/opts.c +++ b/ngl-tools/opts.c @@ -77,6 +77,7 @@ static int opt_loglevel(const char *arg, void *dst) {"info", NGL_LOG_INFO}, {"warning", NGL_LOG_WARNING}, {"error", NGL_LOG_ERROR}, + {"quiet", NGL_LOG_QUIET}, }; const int lvl = s2i(loglevel_map, ARRAY_NB(loglevel_map), arg); if (lvl < 0) { diff --git a/pynodegl-utils/pynodegl_utils/config.py b/pynodegl-utils/pynodegl_utils/config.py index 876cfbc512..5692078c6a 100644 --- a/pynodegl-utils/pynodegl_utils/config.py +++ b/pynodegl-utils/pynodegl_utils/config.py @@ -52,6 +52,7 @@ class Config(QtCore.QObject): 'info', 'warning', 'error', + 'quiet', ], 'backend': [ 'gl', diff --git a/pynodegl/pynodegl.pyx b/pynodegl/pynodegl.pyx index 21ecf6d786..43563577ee 100644 --- a/pynodegl/pynodegl.pyx +++ b/pynodegl/pynodegl.pyx @@ -31,6 +31,7 @@ cdef extern from "nodegl.h": cdef int NGL_LOG_INFO cdef int NGL_LOG_WARNING cdef int NGL_LOG_ERROR + cdef int NGL_LOG_QUIET void ngl_log_set_min_level(int level) @@ -107,6 +108,7 @@ LOG_DEBUG = NGL_LOG_DEBUG LOG_INFO = NGL_LOG_INFO LOG_WARNING = NGL_LOG_WARNING LOG_ERROR = NGL_LOG_ERROR +LOG_QUIET = NGL_LOG_QUIET cdef _ret_pystr(char *s): try: From 4c48eb70f5eb317eeedb66a3786d8778f6099d75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 3 Nov 2020 18:45:56 +0100 Subject: [PATCH 234/388] utils: do not hard fail when the configuration keys change --- pynodegl-utils/pynodegl_utils/config.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/pynodegl-utils/pynodegl_utils/config.py b/pynodegl-utils/pynodegl_utils/config.py index 5692078c6a..b7c57a27c9 100644 --- a/pynodegl-utils/pynodegl_utils/config.py +++ b/pynodegl-utils/pynodegl_utils/config.py @@ -90,7 +90,7 @@ def __init__(self, module_pkgname): config_filepath = self._get_config_filepath() if op.exists(config_filepath): with open(config_filepath) as f: - self._cfg.update(json.load(f)) + self._cfg.update(self._sanitized_config(json.load(f))) else: self._needs_saving = True @@ -99,6 +99,18 @@ def __init__(self, module_pkgname): self._config_timer.timeout.connect(self._check_config) self._config_timer.start() + def _sanitized_config(self, cfg): + out_cfg = {} + for key, value in cfg.items(): + allowed_values = self.CHOICES.get(key) + if isinstance(value, list): + value = tuple(value) + if allowed_values is None or value in allowed_values: + out_cfg[key] = value + else: + print(f'warning: {value} not allowed for {key}') + return out_cfg + def _get_config_filepath(self): config_basedir = os.environ.get('XDG_DATA_HOME', op.expanduser('~/.local/share')) config_dir = op.join(config_basedir, 'node.gl') From db9941461e556ffbd196326bf210e7f249a0a512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 3 Nov 2020 18:46:44 +0100 Subject: [PATCH 235/388] utils,tools: harmonize backend string identifiers --- .github/workflows/ci_coverage.yml | 4 ++-- .github/workflows/ci_linux.yml | 4 ++-- doc/ref/pynodegl-utils.md | 6 +++--- ngl-tools/ngl-desktop.c | 4 ++-- pynodegl-utils/pynodegl_utils/config.py | 6 +++--- pynodegl-utils/pynodegl_utils/misc.py | 6 +++--- pynodegl-utils/pynodegl_utils/ui/hooks_view.py | 7 +++---- pynodegl-utils/pynodegl_utils/ui/toolbar.py | 4 ++-- 8 files changed, 20 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci_coverage.yml b/.github/workflows/ci_coverage.yml index 05c4e8d273..0d686f34cd 100644 --- a/.github/workflows/ci_coverage.yml +++ b/.github/workflows/ci_coverage.yml @@ -21,10 +21,10 @@ jobs: - name: Run tests with GL backend run: | export CFLAGS=-fPIE - DISABLE_TESTS_SAMPLES=yes DISABLE_TESTS_STD430=yes BACKEND=gl DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests COVERAGE=yes + DISABLE_TESTS_SAMPLES=yes DISABLE_TESTS_STD430=yes BACKEND=opengl DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests COVERAGE=yes - name: Run tests with GLES backend run: | - DISABLE_TESTS_SAMPLES=yes DISABLE_TESTS_STD430=yes BACKEND=gles DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests COVERAGE=yes + DISABLE_TESTS_SAMPLES=yes DISABLE_TESTS_STD430=yes BACKEND=opengles DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests COVERAGE=yes - name: Get coverage run: | gcovr -r libnodegl -s -x -o coverage.xml diff --git a/.github/workflows/ci_linux.yml b/.github/workflows/ci_linux.yml index bfca20fcfb..e8ae910c83 100644 --- a/.github/workflows/ci_linux.yml +++ b/.github/workflows/ci_linux.yml @@ -33,7 +33,7 @@ jobs: make -j$(($(nproc)+1)) - name: Run tests with GL backend run: | - DISABLE_TESTS_SAMPLES=yes DISABLE_TESTS_STD430=yes BACKEND=gl DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests + DISABLE_TESTS_SAMPLES=yes DISABLE_TESTS_STD430=yes BACKEND=opengl DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests - name: Run tests with GLES backend run: | - DISABLE_TESTS_SAMPLES=yes DISABLE_TESTS_STD430=yes BACKEND=gles DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests + DISABLE_TESTS_SAMPLES=yes DISABLE_TESTS_STD430=yes BACKEND=opengles DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests diff --git a/doc/ref/pynodegl-utils.md b/doc/ref/pynodegl-utils.md index 6ed4c666c8..191fd29e0c 100644 --- a/doc/ref/pynodegl-utils.md +++ b/doc/ref/pynodegl-utils.md @@ -221,8 +221,8 @@ Y5fd953df Smartphone 9000 GEN X Accepted values for the `backend`: -- `gl` -- `gles` +- `opengl` +- `opengles` Accepted values for the `system`: @@ -235,7 +235,7 @@ Accepted values for the `system`: ```shell $ ./hook.get_session_info X2fca1f2c -backend=gles +backend=opengles system=Linux ``` diff --git a/ngl-tools/ngl-desktop.c b/ngl-tools/ngl-desktop.c index 7a396d5018..751d99c8bf 100644 --- a/ngl-tools/ngl-desktop.c +++ b/ngl-tools/ngl-desktop.c @@ -285,8 +285,8 @@ static int handle_tag_info(const uint8_t *data, int size, int fd, struct ctx *s) if (size != 0) return NGL_ERROR_INVALID_DATA; static const char * const backend_map[] = { - [NGL_BACKEND_OPENGL] = "gl", - [NGL_BACKEND_OPENGLES] = "gles", + [NGL_BACKEND_OPENGL] = "opengl", + [NGL_BACKEND_OPENGLES] = "opengles", }; /* Note: we use the player ngl_config and not the local config because the * former contains the backend in use after the configure call */ diff --git a/pynodegl-utils/pynodegl_utils/config.py b/pynodegl-utils/pynodegl_utils/config.py index b7c57a27c9..abd5cab6b0 100644 --- a/pynodegl-utils/pynodegl_utils/config.py +++ b/pynodegl-utils/pynodegl_utils/config.py @@ -55,8 +55,8 @@ class Config(QtCore.QObject): 'quiet', ], 'backend': [ - 'gl', - 'gles', + 'opengl', + 'opengles', ], } @@ -71,7 +71,7 @@ def __init__(self, module_pkgname): 'clear_color': (0.0, 0.0, 0.0, 1.0), 'enable_hud': False, 'hud_scale': 1, - 'backend': 'gl', + 'backend': 'opengl', # Export 'export_width': 1280, diff --git a/pynodegl-utils/pynodegl_utils/misc.py b/pynodegl-utils/pynodegl_utils/misc.py index c56618046a..30f63f2efa 100644 --- a/pynodegl-utils/pynodegl_utils/misc.py +++ b/pynodegl-utils/pynodegl_utils/misc.py @@ -139,7 +139,7 @@ class SceneCfg: 'aspect_ratio': (16, 9), 'duration': 30.0, 'framerate': (60, 1), - 'backend': 'gl', + 'backend': 'opengl', 'samples': 0, 'system': platform.system(), 'files': [], @@ -203,7 +203,7 @@ def get_viewport(width, height, aspect_ratio): def get_backend(backend): backend_map = { - 'gl': ngl.BACKEND_OPENGL, - 'gles': ngl.BACKEND_OPENGLES, + 'opengl': ngl.BACKEND_OPENGL, + 'opengles': ngl.BACKEND_OPENGLES, } return backend_map[backend] diff --git a/pynodegl-utils/pynodegl_utils/ui/hooks_view.py b/pynodegl-utils/pynodegl_utils/ui/hooks_view.py index 020c4d1d41..a5969fce23 100644 --- a/pynodegl-utils/pynodegl_utils/ui/hooks_view.py +++ b/pynodegl-utils/pynodegl_utils/ui/hooks_view.py @@ -44,8 +44,8 @@ def __init__(self, config): loglevel_hbox.addWidget(self._loglevel_cbbox) backend_names = { - 'gl': 'OpenGL', - 'gles': 'OpenGL ES', + 'opengl': 'OpenGL', + 'opengles': 'OpenGL ES', } all_backends = config.CHOICES['backend'] default_backend = config.get('backend') @@ -94,8 +94,7 @@ def __init__(self, config): @QtCore.Slot() def _spawn(self): loglevel = self._config.CHOICES['log_level'][self._loglevel_cbbox.currentIndex()] - backend_remap = dict(gl='opengl', gles='opengles') - backend = backend_remap[self._config.CHOICES['backend'][self._backend_cbbox.currentIndex()]] + backend = self._config.CHOICES['backend'][self._backend_cbbox.currentIndex()] listen = self._listen_text.text() port = self._port_spin.value() subprocess.Popen(['ngl-desktop', '--host', listen, '--backend', backend, '--loglevel', loglevel, '--port', str(port)]) diff --git a/pynodegl-utils/pynodegl_utils/ui/toolbar.py b/pynodegl-utils/pynodegl_utils/ui/toolbar.py index c1d83c2b90..ac640be8db 100644 --- a/pynodegl-utils/pynodegl_utils/ui/toolbar.py +++ b/pynodegl-utils/pynodegl_utils/ui/toolbar.py @@ -116,8 +116,8 @@ def __init__(self, config): loglevel_hbox.addWidget(self._loglevel_cbbox) backend_names = { - 'gl': 'OpenGL', - 'gles': 'OpenGL ES', + 'opengl': 'OpenGL', + 'opengles': 'OpenGL ES', } all_backends = config.CHOICES['backend'] default_backend = config.get('backend') From df61b216323a176455823cca623a038662429cc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 3 Nov 2020 17:47:44 +0100 Subject: [PATCH 236/388] gctx: include a backend string identifier This makes the errors more user-friendly when node.gl is not built with a given backend. These unique string identifiers are also meant be exposed to the user during the probing. --- libnodegl/gctx.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/libnodegl/gctx.c b/libnodegl/gctx.c index 5a989ca2d4..220220bfcb 100644 --- a/libnodegl/gctx.c +++ b/libnodegl/gctx.c @@ -30,22 +30,37 @@ extern const struct gctx_class ngli_gctx_gl; extern const struct gctx_class ngli_gctx_gles; -static const struct gctx_class *backend_map[] = { +static const struct { + const char *string_id; + const struct gctx_class *cls; +} backend_map[] = { + [NGL_BACKEND_OPENGL] = { + .string_id = "opengl", #ifdef BACKEND_GL - [NGL_BACKEND_OPENGL] = &ngli_gctx_gl, - [NGL_BACKEND_OPENGLES] = &ngli_gctx_gles, + .cls = &ngli_gctx_gl, #endif + }, + [NGL_BACKEND_OPENGLES] = { + .string_id = "opengles", +#ifdef BACKEND_GL + .cls = &ngli_gctx_gles, +#endif + }, }; struct gctx *ngli_gctx_create(const struct ngl_config *config) { if (config->backend < 0 || - config->backend >= NGLI_ARRAY_NB(backend_map) || - !backend_map[config->backend]) { + config->backend >= NGLI_ARRAY_NB(backend_map)) { LOG(ERROR, "unknown backend %d", config->backend); return NULL; } - const struct gctx_class *class = backend_map[config->backend]; + if (!backend_map[config->backend].cls) { + LOG(ERROR, "backend \"%s\" not available with this build", + backend_map[config->backend].string_id); + return NULL; + } + const struct gctx_class *class = backend_map[config->backend].cls; struct gctx *s = class->create(config); if (!s) return NULL; From 6f46628b1e05e5f88a521700b2e2a449a1aebfb9 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 15 Sep 2020 09:45:13 +0200 Subject: [PATCH 237/388] api: add ngl_backends_{probe,freep}() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Clément Bœsch --- libnodegl/api.c | 71 +++++++++++++++++++++++++++++++++++++++++++ libnodegl/nodegl.h | 27 ++++++++++++++++ pynodegl/pynodegl.pyx | 30 ++++++++++++++++++ 3 files changed, 128 insertions(+) diff --git a/libnodegl/api.c b/libnodegl/api.c index 70514acf96..12cd81b2a3 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -324,6 +324,77 @@ static void stop_thread(struct ngl_ctx *s) pthread_mutex_destroy(&s->lock); } +static int backend_probe(struct ngl_backend *backend, const struct ngl_config *config) +{ + struct gctx *gctx = ngli_gctx_create(config); + if (!gctx) + return NGL_ERROR_MEMORY; + + int ret = ngli_gctx_init(gctx); + if (ret < 0) + goto end; + + backend->id = config->backend; + backend->name = gctx->class->name; + +end: + ngli_gctx_freep(&gctx); + return ret; +} + +static const int backend_ids[] = { +#ifdef BACKEND_GL + NGL_BACKEND_OPENGL, + NGL_BACKEND_OPENGLES, +#endif +}; + +int ngl_backends_probe(const struct ngl_config *user_config, int *nb_backendsp, struct ngl_backend **backendsp) +{ + static const struct ngl_config default_config = { + .width = 1, + .height = 1, + .offscreen = 1, + }; + + if (!user_config) + user_config = &default_config; + + const int platform = user_config->platform == NGL_PLATFORM_AUTO ? get_default_platform() : user_config->platform; + + struct ngl_backend *backends = ngli_calloc(NGLI_ARRAY_NB(backend_ids), sizeof(*backends)); + if (!backends) + return NGL_ERROR_MEMORY; + int nb_backends = 0; + + for (int i = 0; i < NGLI_ARRAY_NB(backend_ids); i++) { + if (user_config->backend != NGL_BACKEND_AUTO && user_config->backend != backend_ids[i]) + continue; + struct ngl_config config = *user_config; + config.backend = backend_ids[i]; + config.platform = platform; + + int ret = backend_probe(&backends[nb_backends], &config); + if (ret < 0) + continue; + backends[nb_backends].is_default = backend_ids[i] == DEFAULT_BACKEND; + + nb_backends++; + } + + if (!nb_backends) + ngl_backends_freep(&backends); + + *backendsp = backends; + *nb_backendsp = nb_backends; + return 0; +} + +void ngl_backends_freep(struct ngl_backend **backendsp) +{ + ngli_freep(backendsp); +} + struct ngl_ctx *ngl_create(void) { struct ngl_ctx *s = ngli_calloc(1, sizeof(*s)); diff --git a/libnodegl/nodegl.h b/libnodegl/nodegl.h index a65444aaf1..94c3233012 100644 --- a/libnodegl/nodegl.h +++ b/libnodegl/nodegl.h @@ -404,6 +404,33 @@ struct ngl_config { bytes. */ }; +struct ngl_backend { + int id; /* any of NGL_BACKEND_* */ + const char *name; + int is_default; +}; + +/** + * Returns the available backends for a given configuration + * + * @param user_config a ngl_config structure used to intialize the different + * backends. If ngl_config.backend is set to NGL_BACKEND_AUTO, + * ngl_backends_probe() will probe every supported backends, otherwise, it will + * only probe the specified backend + * + * @param nb_backends a pointer to an integer set to the number of backends + * available + * + * @param backends a pointer to an array of ngl_backend structures. The array + * is allocated by ngl_backends_probe() and has a size of nb_backends. Must be + * freed by the user using ngl_backends_freep() + * + * @return 0 on success, NGL_ERROR_* (< 0) on error + */ +int ngl_backends_probe(const struct ngl_config *user_config, int *nb_backendsp, struct ngl_backend **backendsp); + +void ngl_backends_freep(struct ngl_backend **backendsp); + /** * Opaque structure identifying a node.gl context */ diff --git a/pynodegl/pynodegl.pyx b/pynodegl/pynodegl.pyx index 43563577ee..7454b14d98 100644 --- a/pynodegl/pynodegl.pyx +++ b/pynodegl/pynodegl.pyx @@ -61,6 +61,11 @@ cdef extern from "nodegl.h": cdef int NGL_BACKEND_OPENGL cdef int NGL_BACKEND_OPENGLES + cdef struct ngl_backend: + int id + const char *name + int is_default + cdef struct ngl_ctx cdef struct ngl_config: @@ -80,6 +85,8 @@ cdef extern from "nodegl.h": uint8_t *capture_buffer ngl_ctx *ngl_create() + int ngl_backends_probe(const ngl_config *user_config, int *nb_backendsp, ngl_backend **backendsp) + void ngl_backends_freep(ngl_backend **backendsp) int ngl_configure(ngl_ctx *s, ngl_config *config) int ngl_resize(ngl_ctx *s, int width, int height, const int *viewport); int ngl_set_scene(ngl_ctx *s, ngl_node *scene) @@ -170,6 +177,29 @@ cdef _set_node_ctx(_Node node, int type): if node.ctx is NULL: raise MemoryError() +def probe_backends(**kwargs): + cdef ngl_config config + cdef ngl_config *configp = NULL; + if kwargs: + configp = &config + Context._init_ngl_config_from_dict(configp, kwargs) + cdef int nb_backends = 0 + cdef ngl_backend *backend = NULL + cdef ngl_backend *backends = NULL + cdef int ret = ngl_backends_probe(configp, &nb_backends, &backends) + if ret < 0: + raise Exception("Error probing backends") + backend_set = [] + for i in range(nb_backends): + backend = &backends[i] + backend_set.append(dict( + id=backend.id, + name=backend.name, + is_default=True if backend.is_default else False, + )) + ngl_backends_freep(&backends) + return backend_set + cdef class Context: cdef ngl_ctx *ctx From f3a53354917b90f107602291ee34fe69cb5bbc17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 3 Nov 2020 17:55:39 +0100 Subject: [PATCH 238/388] api: add unique backend string identifier to ngl_backend structure --- libnodegl/api.c | 1 + libnodegl/gctx.c | 1 + libnodegl/gctx.h | 1 + libnodegl/nodegl.h | 1 + pynodegl/pynodegl.pyx | 2 ++ 5 files changed, 6 insertions(+) diff --git a/libnodegl/api.c b/libnodegl/api.c index 12cd81b2a3..5841ebdc2f 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -335,6 +335,7 @@ static int backend_probe(struct ngl_backend *backend, const struct ngl_config *c goto end; backend->id = config->backend; + backend->string_id = gctx->backend_str; backend->name = gctx->class->name; end: diff --git a/libnodegl/gctx.c b/libnodegl/gctx.c index 220220bfcb..f3be101f77 100644 --- a/libnodegl/gctx.c +++ b/libnodegl/gctx.c @@ -65,6 +65,7 @@ struct gctx *ngli_gctx_create(const struct ngl_config *config) if (!s) return NULL; s->config = *config; + s->backend_str = backend_map[config->backend].string_id; s->class = class; return s; } diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index 3295fffc17..b92967bb99 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -102,6 +102,7 @@ struct gctx_class { struct gctx { struct ngl_config config; + const char *backend_str; const struct gctx_class *class; int version; int language_version; diff --git a/libnodegl/nodegl.h b/libnodegl/nodegl.h index 94c3233012..78ba7de405 100644 --- a/libnodegl/nodegl.h +++ b/libnodegl/nodegl.h @@ -406,6 +406,7 @@ struct ngl_config { struct ngl_backend { int id; /* any of NGL_BACKEND_* */ + const char *string_id; /* unique backend identifier string */ const char *name; int is_default; }; diff --git a/pynodegl/pynodegl.pyx b/pynodegl/pynodegl.pyx index 7454b14d98..cc1bd8a10f 100644 --- a/pynodegl/pynodegl.pyx +++ b/pynodegl/pynodegl.pyx @@ -63,6 +63,7 @@ cdef extern from "nodegl.h": cdef struct ngl_backend: int id + const char *string_id const char *name int is_default @@ -194,6 +195,7 @@ def probe_backends(**kwargs): backend = &backends[i] backend_set.append(dict( id=backend.id, + string_id=backend.string_id, name=backend.name, is_default=True if backend.is_default else False, )) From c05e0eb56d84681122581f524094ee86f3f8c9c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 9 Nov 2020 14:12:28 +0100 Subject: [PATCH 239/388] api: add capabilities to ngl_backend structure --- libnodegl/api.c | 61 +++++++++++++++++++++++++++++++++++++++++++ libnodegl/feature.h | 3 +++ libnodegl/nodegl.h | 19 ++++++++++++++ pynodegl/pynodegl.pyx | 39 +++++++++++++++++++++++++++ 4 files changed, 122 insertions(+) diff --git a/libnodegl/api.c b/libnodegl/api.c index 5841ebdc2f..860ad9d66b 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -22,6 +22,7 @@ #include #include #include +#include #if defined(TARGET_ANDROID) #include @@ -324,6 +325,59 @@ static void stop_thread(struct ngl_ctx *s) pthread_mutex_destroy(&s->lock); } +static const char *get_cap_string_id(unsigned cap_id) +{ + switch (cap_id) { + case NGL_CAP_BLOCK: return "block"; + case NGL_CAP_COMPUTE: return "compute"; + case NGL_CAP_INSTANCED_DRAW: return "instanced_draw"; + case NGL_CAP_MAX_COMPUTE_GROUP_COUNT_X: return "max_compute_group_count_x"; + case NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Y: return "max_compute_group_count_y"; + case NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Z: return "max_compute_group_count_z"; + case NGL_CAP_MAX_SAMPLES: return "max_samples"; + case NGL_CAP_NPOT_TEXTURE: return "npot_texture"; + case NGL_CAP_TEXTURE_3D: return "texture_3d"; + case NGL_CAP_TEXTURE_CUBE: return "texture_cube"; + } + ngli_assert(0); +} + +#define CAP(cap_id, value) {cap_id, get_cap_string_id(cap_id), value} +#define ALL_FEATURES(features, mask) ((features & (mask)) == mask) +#define ANY_FEATURES(features, mask) ((features & (mask)) != 0) + +static int load_caps(struct ngl_backend *backend, const struct gctx *gctx) +{ + const int has_block = ANY_FEATURES(gctx->features, NGLI_FEATURE_BUFFER_OBJECTS_ALL); + const int has_compute = ALL_FEATURES(gctx->features, NGLI_FEATURE_COMPUTE_SHADER_ALL); + const int has_instanced_draw = ALL_FEATURES(gctx->features, NGLI_FEATURE_INSTANCED_ARRAY); + const int has_npot_texture = ALL_FEATURES(gctx->features, NGLI_FEATURE_TEXTURE_NPOT); + const int has_texture_3d = ALL_FEATURES(gctx->features, NGLI_FEATURE_TEXTURE_3D); + const int has_texture_cube = ALL_FEATURES(gctx->features, NGLI_FEATURE_TEXTURE_CUBE_MAP); + + const struct limits *limits = &gctx->limits; + const struct ngl_cap caps[] = { + CAP(NGL_CAP_BLOCK, has_block), + CAP(NGL_CAP_COMPUTE, has_compute), + CAP(NGL_CAP_MAX_COMPUTE_GROUP_COUNT_X, limits->max_compute_work_group_counts[0]), + CAP(NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Y, limits->max_compute_work_group_counts[1]), + CAP(NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Z, limits->max_compute_work_group_counts[2]), + CAP(NGL_CAP_INSTANCED_DRAW, has_instanced_draw), + CAP(NGL_CAP_MAX_SAMPLES, limits->max_samples), + CAP(NGL_CAP_NPOT_TEXTURE, has_npot_texture), + CAP(NGL_CAP_TEXTURE_3D, has_texture_3d), + CAP(NGL_CAP_TEXTURE_CUBE, has_texture_cube), + }; + + backend->nb_caps = NGLI_ARRAY_NB(caps); + backend->caps = ngli_calloc(backend->nb_caps, sizeof(*backend->caps)); + if (!backend->caps) + return NGL_ERROR_MEMORY; + memcpy(backend->caps, caps, sizeof(caps)); + + return 0; +} + static int backend_probe(struct ngl_backend *backend, const struct ngl_config *config) { struct gctx *gctx = ngli_gctx_create(config); @@ -338,6 +392,10 @@ static int backend_probe(struct ngl_backend *backend, const struct ngl_config *c backend->string_id = gctx->backend_str; backend->name = gctx->class->name; + ret = load_caps(backend, gctx); + if (ret < 0) + goto end; + end: ngli_gctx_freep(&gctx); return ret; @@ -393,6 +451,9 @@ int ngl_backends_probe(const struct ngl_config *user_config, int *nb_backendsp, void ngl_backends_freep(struct ngl_backend **backendsp) { + struct ngl_backend *backends = *backendsp; + for (int i = 0; i < NGLI_ARRAY_NB(backend_ids); i++) + ngli_free(backends[i].caps); ngli_freep(backendsp); } diff --git a/libnodegl/feature.h b/libnodegl/feature.h index 1e9292855a..e4ff24fa5d 100644 --- a/libnodegl/feature.h +++ b/libnodegl/feature.h @@ -64,4 +64,7 @@ NGLI_FEATURE_SHADER_IMAGE_SIZE | \ NGLI_FEATURE_SHADER_STORAGE_BUFFER_OBJECT) +#define NGLI_FEATURE_BUFFER_OBJECTS_ALL (NGLI_FEATURE_UNIFORM_BUFFER_OBJECT | \ + NGLI_FEATURE_SHADER_STORAGE_BUFFER_OBJECT) + #endif diff --git a/libnodegl/nodegl.h b/libnodegl/nodegl.h index 78ba7de405..7b87b37ab9 100644 --- a/libnodegl/nodegl.h +++ b/libnodegl/nodegl.h @@ -404,11 +404,30 @@ struct ngl_config { bytes. */ }; +#define NGL_CAP_BLOCK NGL_NODE_BLOCK +#define NGL_CAP_COMPUTE NGL_NODE_COMPUTE +#define NGL_CAP_INSTANCED_DRAW NGLI_FOURCC('I','D','r','w') +#define NGL_CAP_MAX_COMPUTE_GROUP_COUNT_X NGLI_FOURCC('C','G','c','x') +#define NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Y NGLI_FOURCC('C','G','c','y') +#define NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Z NGLI_FOURCC('C','G','c','z') +#define NGL_CAP_MAX_SAMPLES NGLI_FOURCC('M','S','A','A') +#define NGL_CAP_NPOT_TEXTURE NGLI_FOURCC('N','P','O','T') +#define NGL_CAP_TEXTURE_3D NGL_NODE_TEXTURE3D +#define NGL_CAP_TEXTURE_CUBE NGL_NODE_TEXTURECUBE + +struct ngl_cap { + unsigned id; /* any of NGL_CAP_* */ + const char *string_id; /* unique capability identifier string */ + int value; +}; + struct ngl_backend { int id; /* any of NGL_BACKEND_* */ const char *string_id; /* unique backend identifier string */ const char *name; int is_default; + struct ngl_cap *caps; + int nb_caps; }; /** diff --git a/pynodegl/pynodegl.pyx b/pynodegl/pynodegl.pyx index cc1bd8a10f..32b4c06b69 100644 --- a/pynodegl/pynodegl.pyx +++ b/pynodegl/pynodegl.pyx @@ -61,11 +61,29 @@ cdef extern from "nodegl.h": cdef int NGL_BACKEND_OPENGL cdef int NGL_BACKEND_OPENGLES + cdef int NGL_CAP_BLOCK + cdef int NGL_CAP_COMPUTE + cdef int NGL_CAP_INSTANCED_DRAW + cdef int NGL_CAP_MAX_COMPUTE_GROUP_COUNT_X + cdef int NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Y + cdef int NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Z + cdef int NGL_CAP_MAX_SAMPLES + cdef int NGL_CAP_NPOT_TEXTURE + cdef int NGL_CAP_TEXTURE_3D + cdef int NGL_CAP_TEXTURE_CUBE + + cdef struct ngl_cap: + unsigned id + const char *string_id + int value + cdef struct ngl_backend: int id const char *string_id const char *name int is_default + ngl_cap *caps + int nb_caps cdef struct ngl_ctx @@ -111,6 +129,17 @@ BACKEND_AUTO = NGL_BACKEND_AUTO BACKEND_OPENGL = NGL_BACKEND_OPENGL BACKEND_OPENGLES = NGL_BACKEND_OPENGLES +CAP_BLOCK = NGL_CAP_BLOCK +CAP_COMPUTE = NGL_CAP_COMPUTE +CAP_INSTANCED_DRAW = NGL_CAP_INSTANCED_DRAW +CAP_MAX_COMPUTE_GROUP_COUNT_X = NGL_CAP_MAX_COMPUTE_GROUP_COUNT_X +CAP_MAX_COMPUTE_GROUP_COUNT_Y = NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Y +CAP_MAX_COMPUTE_GROUP_COUNT_Z = NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Z +CAP_MAX_SAMPLES = NGL_CAP_MAX_SAMPLES +CAP_NPOT_TEXTURE = NGL_CAP_NPOT_TEXTURE +CAP_TEXTURE_3D = NGL_CAP_TEXTURE_3D +CAP_TEXTURE_CUBE = NGL_CAP_TEXTURE_CUBE + LOG_VERBOSE = NGL_LOG_VERBOSE LOG_DEBUG = NGL_LOG_DEBUG LOG_INFO = NGL_LOG_INFO @@ -187,17 +216,27 @@ def probe_backends(**kwargs): cdef int nb_backends = 0 cdef ngl_backend *backend = NULL cdef ngl_backend *backends = NULL + cdef ngl_cap *cap = NULL cdef int ret = ngl_backends_probe(configp, &nb_backends, &backends) if ret < 0: raise Exception("Error probing backends") backend_set = [] for i in range(nb_backends): backend = &backends[i] + caps = [] + for j in range(backend.nb_caps): + cap = &backend.caps[j]; + caps.append(dict( + id=cap.id, + string_id=cap.string_id, + value=cap.value, + )) backend_set.append(dict( id=backend.id, string_id=backend.string_id, name=backend.name, is_default=True if backend.is_default else False, + caps=caps, )) ngl_backends_freep(&backends) return backend_set From a40466130ba87f18a1939c37e71940cf98964db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 2 Nov 2020 17:31:57 +0100 Subject: [PATCH 240/388] tools: add ngl-probe --- doc/ref/ngl-tools.md | 11 ++++ ngl-tools/.gitignore | 1 + ngl-tools/Makefile | 6 +- ngl-tools/ngl-probe.c | 139 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 ngl-tools/ngl-probe.c diff --git a/doc/ref/ngl-tools.md b/doc/ref/ngl-tools.md index 20a022fa0f..8f5a4a9bf5 100644 --- a/doc/ref/ngl-tools.md +++ b/doc/ref/ngl-tools.md @@ -101,6 +101,17 @@ The detail of available options can be obtained with `ngl-ipc -h`. **Example**: `ngl-serialize pynodegl_utils.examples.misc fibo - | ngl-ipc -p 2000 -f - -t 5` +## ngl-probe + +`ngl-probe` is a backend capabilities probing tool. + +By default, `ngl-probe` prints the capabilities of every available backends, +following a YAML output layout. If the `--cap` option is specified, it will +query the specified backend if set (`-b`/`--backend`) or query the default one. + +The detail of available options can be obtained with `ngl-probe -h`. + + ## Player keyboard controls `ngl-player` and `ngl-python` are both scene players supporting the following diff --git a/ngl-tools/.gitignore b/ngl-tools/.gitignore index 80660c0ad4..d4dd94199b 100644 --- a/ngl-tools/.gitignore +++ b/ngl-tools/.gitignore @@ -1,6 +1,7 @@ /ngl-desktop /ngl-ipc /ngl-player +/ngl-probe /ngl-python /ngl-render /ngl-serialize diff --git a/ngl-tools/Makefile b/ngl-tools/Makefile index de66c85994..27951f17a5 100644 --- a/ngl-tools/Makefile +++ b/ngl-tools/Makefile @@ -45,7 +45,7 @@ endif # "pkg-config --exists python" never will, so an explicit version is needed. HAS_PYTHON := $(if $(shell pkg-config --exists python$(PYTHON_MAJOR) && echo 1),yes,no) -TOOLS = desktop ipc player render +TOOLS = desktop ipc player probe render ifeq ($(HAS_PYTHON),yes) PYTHON_CFLAGS := $(shell python$(PYTHON_MAJOR)-config --cflags) # @@ -127,6 +127,10 @@ ngl-player$(EXESUF): CFLAGS = $(PROJECT_CFLAGS) $(TOOLS_CFLAGS) $(SDL_CFLAGS) $( ngl-player$(EXESUF): LDLIBS = $(PROJECT_LDLIBS) $(TOOLS_LDLIBS) $(SDL_LDLIBS) $(SXPLAYER_LDLIBS) ngl-player$(EXESUF): ngl-player.o player.o opts.o $(WSI_OBJS) +ngl-probe$(EXESUF): CFLAGS = $(PROJECT_CFLAGS) $(TOOLS_CFLAGS) +ngl-probe$(EXESUF): LDLIBS = $(PROJECT_LDLIBS) $(TOOLS_LDLIBS) +ngl-probe$(EXESUF): ngl-probe.o opts.o + ngl-render$(EXESUF): CFLAGS = $(PROJECT_CFLAGS) $(TOOLS_CFLAGS) $(SDL_CFLAGS) ngl-render$(EXESUF): LDLIBS = $(PROJECT_LDLIBS) $(TOOLS_LDLIBS) $(SDL_LDLIBS) ngl-render$(EXESUF): ngl-render.o opts.o $(WSI_OBJS) diff --git a/ngl-tools/ngl-probe.c b/ngl-tools/ngl-probe.c new file mode 100644 index 0000000000..0792e439b1 --- /dev/null +++ b/ngl-tools/ngl-probe.c @@ -0,0 +1,139 @@ +/* + * Copyright 2020 GoPro Inc. + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include +#include +#include + +#include + +#include "common.h" +#include "opts.h" + +struct ctx { + /* options */ + int log_level; + struct ngl_config cfg; + const char *cap; +}; + +#define OFFSET(x) offsetof(struct ctx, x) +static const struct opt options[] = { + {"-l", "--loglevel", OPT_TYPE_LOGLEVEL, .offset=OFFSET(log_level)}, + {"-b", "--backend", OPT_TYPE_BACKEND, .offset=OFFSET(cfg.backend)}, + {"-c", "--cap", OPT_TYPE_STR, .offset=OFFSET(cap)}, +}; + +static const struct ngl_cap *get_cap(const struct ngl_backend *backend, const char *key) +{ + char *endptr; + long int id = strtol(key, &endptr, 0); + const int use_id = !*endptr; // user input is an int, so we will use that as id + + for (int i = 0; i < backend->nb_caps; i++) { + const struct ngl_cap *cap = &backend->caps[i]; + if (use_id && cap->id == id) + return cap; + if (!use_id && !strcmp(cap->string_id, key)) + return cap; + } + return NULL; +} + +static const struct ngl_backend *select_default_backend(const struct ngl_backend *backends, int nb_backends) +{ + for (int i = 0; i < nb_backends; i++) { + const struct ngl_backend *backend = &backends[i]; + if (backend->is_default) + return backend; + } + return NULL; +} + +int main(int argc, char *argv[]) +{ + struct ctx s = { + .log_level = NGL_LOG_WARNING, + .cfg.backend = NGL_BACKEND_AUTO, + .cfg.width = 1, + .cfg.height = 1, + .cfg.offscreen = 1, + }; + + int ret = opts_parse(argc, argc, argv, options, ARRAY_NB(options), &s); + if (ret < 0 || ret == OPT_HELP) { + opts_print_usage(argv[0], options, ARRAY_NB(options), NULL); + return ret == OPT_HELP ? 0 : EXIT_FAILURE; + } + + ngl_log_set_min_level(s.log_level); + + const int specified_backend = s.cfg.backend != NGL_BACKEND_AUTO; + const struct ngl_config *config = specified_backend ? &s.cfg : NULL; + + struct ngl_backend *backends; + int nb_backends; + ret = ngl_backends_probe(config, &nb_backends, &backends); + if (ret < 0) + return EXIT_FAILURE; + + if (s.cap) { + if (!nb_backends) { + fprintf(stderr, "no backend to query\n"); + ret = EXIT_FAILURE; + goto end; + } + + const struct ngl_backend *backend = &backends[0]; + if (!specified_backend) { + backend = select_default_backend(backends, nb_backends); + if (!backend) { + fprintf(stderr, "unable to get the default backend\n"); + ret = EXIT_FAILURE; + goto end; + } + } + + const struct ngl_cap *cap = get_cap(backend, s.cap); + if (!cap) { + fprintf(stderr, "cap %s not found\n", s.cap); + ret = EXIT_FAILURE; + goto end; + } + printf("%d\n", cap->value); + } else { + for (int i = 0; i < nb_backends; i++) { + const struct ngl_backend *backend = &backends[i]; + printf("- %s:\n", backend->string_id); + printf(" name: %s\n", backend->name); + printf(" is_default: %s\n", backend->is_default ? "yes" : "no"); + printf(" caps:\n"); + for (int j = 0; j < backend->nb_caps; j++) { + const struct ngl_cap *cap = &backends->caps[j]; + printf(" %s: %d\n", cap->string_id, cap->value); + } + } + } + +end: + ngl_backends_freep(&backends); + return ret; +} From ccfec34e288e119fc327cd710248d0c234ce920c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 2 Nov 2020 17:32:35 +0100 Subject: [PATCH 241/388] tests: use ngl-probe to detect features instead of hardcoding them --- .github/workflows/ci_coverage.yml | 4 ++-- .github/workflows/ci_linux.yml | 4 ++-- tests/Makefile | 8 ++++---- tests/compute.mak | 2 +- tests/data.mak | 2 +- tests/live.mak | 2 +- tests/rtt.mak | 2 +- tests/shape.mak | 2 +- tests/texture.mak | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci_coverage.yml b/.github/workflows/ci_coverage.yml index 0d686f34cd..4e2adbd2b8 100644 --- a/.github/workflows/ci_coverage.yml +++ b/.github/workflows/ci_coverage.yml @@ -21,10 +21,10 @@ jobs: - name: Run tests with GL backend run: | export CFLAGS=-fPIE - DISABLE_TESTS_SAMPLES=yes DISABLE_TESTS_STD430=yes BACKEND=opengl DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests COVERAGE=yes + BACKEND=opengl DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests COVERAGE=yes - name: Run tests with GLES backend run: | - DISABLE_TESTS_SAMPLES=yes DISABLE_TESTS_STD430=yes BACKEND=opengles DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests COVERAGE=yes + BACKEND=opengles DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests COVERAGE=yes - name: Get coverage run: | gcovr -r libnodegl -s -x -o coverage.xml diff --git a/.github/workflows/ci_linux.yml b/.github/workflows/ci_linux.yml index e8ae910c83..4c5ace57ab 100644 --- a/.github/workflows/ci_linux.yml +++ b/.github/workflows/ci_linux.yml @@ -33,7 +33,7 @@ jobs: make -j$(($(nproc)+1)) - name: Run tests with GL backend run: | - DISABLE_TESTS_SAMPLES=yes DISABLE_TESTS_STD430=yes BACKEND=opengl DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests + BACKEND=opengl DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests - name: Run tests with GLES backend run: | - DISABLE_TESTS_SAMPLES=yes DISABLE_TESTS_STD430=yes BACKEND=opengles DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests + BACKEND=opengles DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests diff --git a/tests/Makefile b/tests/Makefile index b4a25f29f3..0b113c52ec 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -21,6 +21,10 @@ include ../common.mak +BACKEND ?= auto +HAS_COMPUTE := $(or $(shell ngl-probe -l quiet -b $(BACKEND) --cap compute | tail -n1),0) +MAX_SAMPLES := $(or $(shell ngl-probe -l quiet -b $(BACKEND) --cap max_samples | tail -n1),0) + ifeq ($(V),) Q := @ OUT := > /dev/null @@ -56,10 +60,6 @@ define DECLARE_REF_TESTS $(call DECLARE_TESTS,REF_TESTS,$(1),$(2)) endef -ifeq ($(TARGET_OS),Darwin) -DISABLE_TESTS_STD430 = yes -endif - include anim.mak include api.mak include blending.mak diff --git a/tests/compute.mak b/tests/compute.mak index abbe49bb0e..90bace9f67 100644 --- a/tests/compute.mak +++ b/tests/compute.mak @@ -21,7 +21,7 @@ COMPUTE_TEST_NAMES = -ifneq ($(DISABLE_TESTS_STD430),yes) +ifeq ($(HAS_COMPUTE),1) COMPUTE_TEST_NAMES += \ animation \ histogram \ diff --git a/tests/data.mak b/tests/data.mak index 6ba62871d4..8390d35c08 100644 --- a/tests/data.mak +++ b/tests/data.mak @@ -58,7 +58,7 @@ DATA_TEST_BLOCK_NAMES = \ DATA_TEST_NAMES += $(addsuffix _uniform,$(DATA_TEST_UNIFORM_NAMES)) DATA_TEST_NAMES += $(addsuffix _std140,$(DATA_TEST_BLOCK_NAMES)) -ifneq ($(DISABLE_TESTS_STD430),yes) +ifeq ($(HAS_COMPUTE),1) DATA_TEST_NAMES += $(addsuffix _std430,$(DATA_TEST_BLOCK_NAMES)) endif diff --git a/tests/live.mak b/tests/live.mak index 81bc2e8781..f32abbd4cb 100644 --- a/tests/live.mak +++ b/tests/live.mak @@ -34,7 +34,7 @@ LIVE_TEST_BASE_NAMES = \ LIVE_TEST_NAMES += $(addsuffix _uniform,$(LIVE_TEST_BASE_NAMES)) LIVE_TEST_NAMES += $(addsuffix _std140,$(LIVE_TEST_BASE_NAMES)) -ifneq ($(DISABLE_TESTS_STD430),yes) +ifeq ($(HAS_COMPUTE),1) LIVE_TEST_NAMES += $(addsuffix _std430,$(LIVE_TEST_BASE_NAMES)) endif diff --git a/tests/rtt.mak b/tests/rtt.mak index 432f99a11d..93483e65b3 100644 --- a/tests/rtt.mak +++ b/tests/rtt.mak @@ -28,7 +28,7 @@ RTT_TEST_NAMES = \ texture_depth_stencil \ clear_attachment_with_timeranges \ -ifneq ($(DISABLE_TESTS_SAMPLES),yes) +ifneq ($(MAX_SAMPLES),$(filter $(MAX_SAMPLES),0 1)) RTT_TEST_NAMES += \ feature_depth_msaa \ feature_depth_stencil_msaa \ diff --git a/tests/shape.mak b/tests/shape.mak index a526e5ddcb..38df7aac08 100644 --- a/tests/shape.mak +++ b/tests/shape.mak @@ -42,7 +42,7 @@ SHAPE_TEST_NAMES = \ cropboard \ cropboard_indices \ -ifneq ($(DISABLE_TESTS_SAMPLES),yes) +ifneq ($(MAX_SAMPLES),$(filter $(MAX_SAMPLES),0 1)) SHAPE_TEST_NAMES += geometry_rtt_samples \ triangle_msaa \ diff --git a/tests/texture.mak b/tests/texture.mak index e7c511470c..ae26d68a84 100644 --- a/tests/texture.mak +++ b/tests/texture.mak @@ -30,7 +30,7 @@ TEXTURE_TEST_NAMES = \ mipmap \ scissor \ -ifneq ($(DISABLE_TESTS_SAMPLES),yes) +ifneq ($(MAX_SAMPLES),$(filter $(MAX_SAMPLES),0 1)) TEXTURE_TEST_NAMES += \ cubemap_from_mrt_msaa \ From 4fa7f6592229f2c604c8237521de132677150155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 2 Nov 2020 17:54:12 +0100 Subject: [PATCH 242/388] tools/opts: use backend probing API to parse the backend --- ngl-tools/opts.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/ngl-tools/opts.c b/ngl-tools/opts.c index 01b00282cc..a2cd3a5e94 100644 --- a/ngl-tools/opts.c +++ b/ngl-tools/opts.c @@ -88,14 +88,25 @@ static int opt_loglevel(const char *arg, void *dst) return 0; } +static int get_backend(const char *id) +{ + if (!strcmp(id, "auto")) + return NGL_BACKEND_AUTO; + + int nb_backends; + struct ngl_backend *backends; + int ret = ngl_backends_probe(NULL, &nb_backends, &backends); + if (ret < 0) + return ret; + for (int i = 0; i < nb_backends; i++) + if (!strcmp(backends[i].string_id, id)) + return backends[i].id; + return NGL_ERROR_NOT_FOUND; +} + static int opt_backend(const char *arg, void *dst) { - static const struct s2i backend_map[] = { - {"auto", NGL_BACKEND_AUTO}, - {"opengl", NGL_BACKEND_OPENGL}, - {"opengles", NGL_BACKEND_OPENGLES}, - }; - const int backend = s2i(backend_map, ARRAY_NB(backend_map), arg); + const int backend = get_backend(arg); if (backend < 0) { fprintf(stderr, "invalid backend \"%s\"\n", arg); return backend; From 6346c4f8369df8a52dfb675fa16286353f4a9f4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 3 Nov 2020 10:18:49 +0100 Subject: [PATCH 243/388] log: set default min level to warning This prevents the probing API from logging a lot of confusing information by default. The tools still log as info, but only after the probing, which makes the whole experience more user friendly. --- libnodegl/log.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libnodegl/log.c b/libnodegl/log.c index 8b43597f29..5c4cb70e2c 100644 --- a/libnodegl/log.c +++ b/libnodegl/log.c @@ -67,7 +67,7 @@ static struct { int min_level; } log_ctx = { .callback = default_callback, - .min_level = NGL_LOG_INFO, + .min_level = NGL_LOG_WARNING, }; void ngl_log_set_callback(void *arg, ngl_log_callback_type callback) From f33ae86c675e1139314025beb979d13b7bb11889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 3 Nov 2020 22:47:02 +0100 Subject: [PATCH 244/388] tests: dump probe info at the beginning of the tests This is useful in particular in the CI situation. --- tests/Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/Makefile b/tests/Makefile index 0b113c52ec..93a2d3e747 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -85,7 +85,11 @@ TESTS = $(SIMPLE_TESTS) $(REF_TESTS) tests: $(TESTS) +$(TESTS): tests-info +tests-info: + $(Q)ngl-probe -l info -b $(BACKEND) + tests-list: @for test in $(TESTS); do echo $$test; done -.PHONY: all tests tests-list $(TESTS) +.PHONY: all tests tests-info tests-list $(TESTS) From e2f2cd6b9e98e6621cb1d01eb7f6eb7808789d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Sat, 7 Nov 2020 10:07:16 +0100 Subject: [PATCH 245/388] ci/coverage: remove unneeded -fPIE CFLAGS Forgotten in 023e9c3ca6d0a8f7c124a9652afb052a798eb508. --- .github/workflows/ci_coverage.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci_coverage.yml b/.github/workflows/ci_coverage.yml index 4e2adbd2b8..ff5f02846a 100644 --- a/.github/workflows/ci_coverage.yml +++ b/.github/workflows/ci_coverage.yml @@ -20,7 +20,6 @@ jobs: - name: Run tests with GL backend run: | - export CFLAGS=-fPIE BACKEND=opengl DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests COVERAGE=yes - name: Run tests with GLES backend run: | From 7ccf711ef5a866348805aaecb9113d70c5795acd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Sat, 7 Nov 2020 10:09:25 +0100 Subject: [PATCH 246/388] ci: give build_libs jobs a more meaningful name --- .github/workflows/ci_coverage.yml | 2 +- .github/workflows/ci_linux.yml | 2 +- .github/workflows/ci_mac.yml | 2 +- .github/workflows/ci_win.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci_coverage.yml b/.github/workflows/ci_coverage.yml index ff5f02846a..0f3b721f47 100644 --- a/.github/workflows/ci_coverage.yml +++ b/.github/workflows/ci_coverage.yml @@ -6,7 +6,7 @@ on: - 'master' jobs: - build_libs: + coverage: runs-on: ubuntu-20.04 steps: diff --git a/.github/workflows/ci_linux.yml b/.github/workflows/ci_linux.yml index 4c5ace57ab..7196985491 100644 --- a/.github/workflows/ci_linux.yml +++ b/.github/workflows/ci_linux.yml @@ -7,7 +7,7 @@ on: pull_request: jobs: - build_libs: + linux: runs-on: ubuntu-20.04 diff --git a/.github/workflows/ci_mac.yml b/.github/workflows/ci_mac.yml index 1c7cb3b803..8847c1ab7a 100644 --- a/.github/workflows/ci_mac.yml +++ b/.github/workflows/ci_mac.yml @@ -7,7 +7,7 @@ on: pull_request: jobs: - build_libs: + macos: runs-on: macos-latest diff --git a/.github/workflows/ci_win.yml b/.github/workflows/ci_win.yml index 62ba891c6a..b6105d6676 100644 --- a/.github/workflows/ci_win.yml +++ b/.github/workflows/ci_win.yml @@ -7,7 +7,7 @@ on: pull_request: jobs: - build_libs: + mingw: runs-on: windows-latest From 340b0b29a089c9080b61d1ad4ed681f1a0a6442d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 5 Nov 2020 09:51:11 +0100 Subject: [PATCH 247/388] tests: make ngl-test work without a reference file This has the nice side effect of removing the direct dependency to Python within the test suite Makefile. --- pynodegl-utils/pynodegl_utils/tests/__init__.py | 14 ++++++++++---- tests/Makefile | 4 +--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/pynodegl-utils/pynodegl_utils/tests/__init__.py b/pynodegl-utils/pynodegl_utils/tests/__init__.py index bc4f56fb7a..4dfe31a222 100644 --- a/pynodegl-utils/pynodegl_utils/tests/__init__.py +++ b/pynodegl-utils/pynodegl_utils/tests/__init__.py @@ -104,8 +104,8 @@ def run(): sys.stderr.write('GEN environment variable must be any of {}\n'.format(', '.join(allowed_gen_opt))) sys.exit(1) - if len(sys.argv) != 4: - sys.stderr.write('''Usage: [GEN={}] {} + if len(sys.argv) not in (3, 4): + sys.stderr.write('''Usage: [GEN={}] {} [] GEN=yes Create the reference file if not present GEN=update Same as "yes" and update the reference if the test fails @@ -115,11 +115,17 @@ def run(): '''.format('|'.join(allowed_gen_opt), op.basename(sys.argv[0]))) sys.exit(1) - script_path, func_name, ref_filepath = sys.argv[1:4] + if len(sys.argv) == 3: + script_path, func_name, ref_filepath = sys.argv[1], sys.argv[2], None + else: + script_path, func_name, ref_filepath = sys.argv[1:4] module = load_script(script_path) func = getattr(module, func_name) - tester = func.tester + if ref_filepath is None: + sys.exit(func()) + + tester = func.tester test_func = _gen_map.get(gen_opt, _run_test_default) err = test_func(func_name, tester, ref_filepath) if err: diff --git a/tests/Makefile b/tests/Makefile index 93a2d3e747..4e77f9e4e8 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -19,8 +19,6 @@ # under the License. # -include ../common.mak - BACKEND ?= auto HAS_COMPUTE := $(or $(shell ngl-probe -l quiet -b $(BACKEND) --cap compute | tail -n1),0) MAX_SAMPLES := $(or $(shell ngl-probe -l quiet -b $(BACKEND) --cap max_samples | tail -n1),0) @@ -79,7 +77,7 @@ $(REF_TESTS): $(SIMPLE_TESTS): @echo $@ - $(Q)$(PYTHON) -c 'from $(MODULE) import $(FUNC_NAME); $(FUNC_NAME)()' $(OUT) + $(Q)ngl-test $(MODULE).py $(FUNC_NAME) $(OUT) TESTS = $(SIMPLE_TESTS) $(REF_TESTS) From ae804b88c1c82e942d88cca0c1da016ccc9aa5f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 5 Nov 2020 15:22:29 +0100 Subject: [PATCH 248/388] tools/player: use bilinear filtering to improve quality when scaled --- ngl-tools/ngl-player.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ngl-tools/ngl-player.c b/ngl-tools/ngl-player.c index dff1b620b4..1be5ab8bac 100644 --- a/ngl-tools/ngl-player.c +++ b/ngl-tools/ngl-player.c @@ -84,6 +84,8 @@ static struct ngl_node *get_scene(const char *filename, int direct_rendering) ngl_node_param_set(quad, "height", height); ngl_node_param_set(texture, "data_src", media); + ngl_node_param_set(texture, "min_filter", "linear"); + ngl_node_param_set(texture, "mag_filter", "linear"); if (direct_rendering != -1) ngl_node_param_set(texture, "direct_rendering", direct_rendering); From a71374ef5ec1f8859d525b21c9dab00817b3b93f Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 9 Nov 2020 19:42:08 +0100 Subject: [PATCH 249/388] api: add max_compute_group_{invocations,size} capabilities --- libnodegl/api.c | 8 ++++++++ libnodegl/glcontext.c | 8 ++++++++ libnodegl/limit.h | 2 ++ libnodegl/nodegl.h | 4 ++++ 4 files changed, 22 insertions(+) diff --git a/libnodegl/api.c b/libnodegl/api.c index 860ad9d66b..8f3b55a04c 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -334,6 +334,10 @@ static const char *get_cap_string_id(unsigned cap_id) case NGL_CAP_MAX_COMPUTE_GROUP_COUNT_X: return "max_compute_group_count_x"; case NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Y: return "max_compute_group_count_y"; case NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Z: return "max_compute_group_count_z"; + case NGL_CAP_MAX_COMPUTE_GROUP_INVOCATIONS: return "max_compute_group_invocations"; + case NGL_CAP_MAX_COMPUTE_GROUP_SIZE_X: return "max_compute_group_size_x"; + case NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Y: return "max_compute_group_size_y"; + case NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Z: return "max_compute_group_size_z"; case NGL_CAP_MAX_SAMPLES: return "max_samples"; case NGL_CAP_NPOT_TEXTURE: return "npot_texture"; case NGL_CAP_TEXTURE_3D: return "texture_3d"; @@ -362,6 +366,10 @@ static int load_caps(struct ngl_backend *backend, const struct gctx *gctx) CAP(NGL_CAP_MAX_COMPUTE_GROUP_COUNT_X, limits->max_compute_work_group_counts[0]), CAP(NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Y, limits->max_compute_work_group_counts[1]), CAP(NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Z, limits->max_compute_work_group_counts[2]), + CAP(NGL_CAP_MAX_COMPUTE_GROUP_INVOCATIONS, limits->max_compute_work_group_invocations), + CAP(NGL_CAP_MAX_COMPUTE_GROUP_SIZE_X, limits->max_compute_work_group_sizes[0]), + CAP(NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Y, limits->max_compute_work_group_sizes[1]), + CAP(NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Z, limits->max_compute_work_group_sizes[2]), CAP(NGL_CAP_INSTANCED_DRAW, has_instanced_draw), CAP(NGL_CAP_MAX_SAMPLES, limits->max_samples), CAP(NGL_CAP_NPOT_TEXTURE, has_npot_texture), diff --git a/libnodegl/glcontext.c b/libnodegl/glcontext.c index fad3089fdf..ba6b546257 100644 --- a/libnodegl/glcontext.c +++ b/libnodegl/glcontext.c @@ -332,6 +332,14 @@ static int glcontext_probe_settings(struct glcontext *glcontext) ngli_glGetIntegeri_v(glcontext, GL_MAX_COMPUTE_WORK_GROUP_COUNT, i, &limits->max_compute_work_group_counts[i]); } + + ngli_glGetIntegerv(glcontext, GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS, + &limits->max_compute_work_group_invocations); + + for (int i = 0; i < NGLI_ARRAY_NB(limits->max_compute_work_group_sizes); i++) { + ngli_glGetIntegeri_v(glcontext, GL_MAX_COMPUTE_WORK_GROUP_SIZE, + i, &limits->max_compute_work_group_sizes[i]); + } } limits->max_draw_buffers = 1; diff --git a/libnodegl/limit.h b/libnodegl/limit.h index 0f46e80ac1..d036cc833c 100644 --- a/libnodegl/limit.h +++ b/libnodegl/limit.h @@ -25,6 +25,8 @@ struct limits { int max_texture_image_units; int max_compute_work_group_counts[3]; + int max_compute_work_group_invocations; + int max_compute_work_group_sizes[3]; int max_uniform_block_size; int max_samples; int max_color_attachments; diff --git a/libnodegl/nodegl.h b/libnodegl/nodegl.h index 7b87b37ab9..567b5b8956 100644 --- a/libnodegl/nodegl.h +++ b/libnodegl/nodegl.h @@ -410,6 +410,10 @@ struct ngl_config { #define NGL_CAP_MAX_COMPUTE_GROUP_COUNT_X NGLI_FOURCC('C','G','c','x') #define NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Y NGLI_FOURCC('C','G','c','y') #define NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Z NGLI_FOURCC('C','G','c','z') +#define NGL_CAP_MAX_COMPUTE_GROUP_INVOCATIONS NGLI_FOURCC('C','G','i','v') +#define NGL_CAP_MAX_COMPUTE_GROUP_SIZE_X NGLI_FOURCC('C','G','s','x') +#define NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Y NGLI_FOURCC('C','G','s','y') +#define NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Z NGLI_FOURCC('C','G','s','z') #define NGL_CAP_MAX_SAMPLES NGLI_FOURCC('M','S','A','A') #define NGL_CAP_NPOT_TEXTURE NGLI_FOURCC('N','P','O','T') #define NGL_CAP_TEXTURE_3D NGL_NODE_TEXTURE3D From 3288863186757ab6032daa9416cce9c571464a5f Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 9 Nov 2020 20:25:21 +0100 Subject: [PATCH 250/388] api: re-indent after previous commit --- libnodegl/api.c | 52 +++++++++++++++++++++++----------------------- libnodegl/nodegl.h | 26 +++++++++++------------ 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/libnodegl/api.c b/libnodegl/api.c index 8f3b55a04c..12920cb195 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -328,20 +328,20 @@ static void stop_thread(struct ngl_ctx *s) static const char *get_cap_string_id(unsigned cap_id) { switch (cap_id) { - case NGL_CAP_BLOCK: return "block"; - case NGL_CAP_COMPUTE: return "compute"; - case NGL_CAP_INSTANCED_DRAW: return "instanced_draw"; - case NGL_CAP_MAX_COMPUTE_GROUP_COUNT_X: return "max_compute_group_count_x"; - case NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Y: return "max_compute_group_count_y"; - case NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Z: return "max_compute_group_count_z"; + case NGL_CAP_BLOCK: return "block"; + case NGL_CAP_COMPUTE: return "compute"; + case NGL_CAP_INSTANCED_DRAW: return "instanced_draw"; + case NGL_CAP_MAX_COMPUTE_GROUP_COUNT_X: return "max_compute_group_count_x"; + case NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Y: return "max_compute_group_count_y"; + case NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Z: return "max_compute_group_count_z"; case NGL_CAP_MAX_COMPUTE_GROUP_INVOCATIONS: return "max_compute_group_invocations"; - case NGL_CAP_MAX_COMPUTE_GROUP_SIZE_X: return "max_compute_group_size_x"; - case NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Y: return "max_compute_group_size_y"; - case NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Z: return "max_compute_group_size_z"; - case NGL_CAP_MAX_SAMPLES: return "max_samples"; - case NGL_CAP_NPOT_TEXTURE: return "npot_texture"; - case NGL_CAP_TEXTURE_3D: return "texture_3d"; - case NGL_CAP_TEXTURE_CUBE: return "texture_cube"; + case NGL_CAP_MAX_COMPUTE_GROUP_SIZE_X: return "max_compute_group_size_x"; + case NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Y: return "max_compute_group_size_y"; + case NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Z: return "max_compute_group_size_z"; + case NGL_CAP_MAX_SAMPLES: return "max_samples"; + case NGL_CAP_NPOT_TEXTURE: return "npot_texture"; + case NGL_CAP_TEXTURE_3D: return "texture_3d"; + case NGL_CAP_TEXTURE_CUBE: return "texture_cube"; } ngli_assert(0); } @@ -361,20 +361,20 @@ static int load_caps(struct ngl_backend *backend, const struct gctx *gctx) const struct limits *limits = &gctx->limits; const struct ngl_cap caps[] = { - CAP(NGL_CAP_BLOCK, has_block), - CAP(NGL_CAP_COMPUTE, has_compute), - CAP(NGL_CAP_MAX_COMPUTE_GROUP_COUNT_X, limits->max_compute_work_group_counts[0]), - CAP(NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Y, limits->max_compute_work_group_counts[1]), - CAP(NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Z, limits->max_compute_work_group_counts[2]), + CAP(NGL_CAP_BLOCK, has_block), + CAP(NGL_CAP_COMPUTE, has_compute), + CAP(NGL_CAP_MAX_COMPUTE_GROUP_COUNT_X, limits->max_compute_work_group_counts[0]), + CAP(NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Y, limits->max_compute_work_group_counts[1]), + CAP(NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Z, limits->max_compute_work_group_counts[2]), CAP(NGL_CAP_MAX_COMPUTE_GROUP_INVOCATIONS, limits->max_compute_work_group_invocations), - CAP(NGL_CAP_MAX_COMPUTE_GROUP_SIZE_X, limits->max_compute_work_group_sizes[0]), - CAP(NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Y, limits->max_compute_work_group_sizes[1]), - CAP(NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Z, limits->max_compute_work_group_sizes[2]), - CAP(NGL_CAP_INSTANCED_DRAW, has_instanced_draw), - CAP(NGL_CAP_MAX_SAMPLES, limits->max_samples), - CAP(NGL_CAP_NPOT_TEXTURE, has_npot_texture), - CAP(NGL_CAP_TEXTURE_3D, has_texture_3d), - CAP(NGL_CAP_TEXTURE_CUBE, has_texture_cube), + CAP(NGL_CAP_MAX_COMPUTE_GROUP_SIZE_X, limits->max_compute_work_group_sizes[0]), + CAP(NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Y, limits->max_compute_work_group_sizes[1]), + CAP(NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Z, limits->max_compute_work_group_sizes[2]), + CAP(NGL_CAP_INSTANCED_DRAW, has_instanced_draw), + CAP(NGL_CAP_MAX_SAMPLES, limits->max_samples), + CAP(NGL_CAP_NPOT_TEXTURE, has_npot_texture), + CAP(NGL_CAP_TEXTURE_3D, has_texture_3d), + CAP(NGL_CAP_TEXTURE_CUBE, has_texture_cube), }; backend->nb_caps = NGLI_ARRAY_NB(caps); diff --git a/libnodegl/nodegl.h b/libnodegl/nodegl.h index 567b5b8956..0d5bbf9938 100644 --- a/libnodegl/nodegl.h +++ b/libnodegl/nodegl.h @@ -404,20 +404,20 @@ struct ngl_config { bytes. */ }; -#define NGL_CAP_BLOCK NGL_NODE_BLOCK -#define NGL_CAP_COMPUTE NGL_NODE_COMPUTE -#define NGL_CAP_INSTANCED_DRAW NGLI_FOURCC('I','D','r','w') -#define NGL_CAP_MAX_COMPUTE_GROUP_COUNT_X NGLI_FOURCC('C','G','c','x') -#define NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Y NGLI_FOURCC('C','G','c','y') -#define NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Z NGLI_FOURCC('C','G','c','z') +#define NGL_CAP_BLOCK NGL_NODE_BLOCK +#define NGL_CAP_COMPUTE NGL_NODE_COMPUTE +#define NGL_CAP_INSTANCED_DRAW NGLI_FOURCC('I','D','r','w') +#define NGL_CAP_MAX_COMPUTE_GROUP_COUNT_X NGLI_FOURCC('C','G','c','x') +#define NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Y NGLI_FOURCC('C','G','c','y') +#define NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Z NGLI_FOURCC('C','G','c','z') #define NGL_CAP_MAX_COMPUTE_GROUP_INVOCATIONS NGLI_FOURCC('C','G','i','v') -#define NGL_CAP_MAX_COMPUTE_GROUP_SIZE_X NGLI_FOURCC('C','G','s','x') -#define NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Y NGLI_FOURCC('C','G','s','y') -#define NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Z NGLI_FOURCC('C','G','s','z') -#define NGL_CAP_MAX_SAMPLES NGLI_FOURCC('M','S','A','A') -#define NGL_CAP_NPOT_TEXTURE NGLI_FOURCC('N','P','O','T') -#define NGL_CAP_TEXTURE_3D NGL_NODE_TEXTURE3D -#define NGL_CAP_TEXTURE_CUBE NGL_NODE_TEXTURECUBE +#define NGL_CAP_MAX_COMPUTE_GROUP_SIZE_X NGLI_FOURCC('C','G','s','x') +#define NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Y NGLI_FOURCC('C','G','s','y') +#define NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Z NGLI_FOURCC('C','G','s','z') +#define NGL_CAP_MAX_SAMPLES NGLI_FOURCC('M','S','A','A') +#define NGL_CAP_NPOT_TEXTURE NGLI_FOURCC('N','P','O','T') +#define NGL_CAP_TEXTURE_3D NGL_NODE_TEXTURE3D +#define NGL_CAP_TEXTURE_CUBE NGL_NODE_TEXTURECUBE struct ngl_cap { unsigned id; /* any of NGL_CAP_* */ From e263ef591c6c93aef8a2015b518fdc32ed3f9898 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Tue, 3 Nov 2020 15:12:01 -0800 Subject: [PATCH 251/388] tests: add TEST_OPTIONS=dump to dump tests outputs to the filesystem Signed-off-by: Matthieu Bouron --- .../pynodegl_utils/tests/__init__.py | 32 ++++++++++++------- pynodegl-utils/pynodegl_utils/tests/cmp.py | 18 ++++++++++- .../pynodegl_utils/tests/cmp_cuepoints.py | 15 +++++++-- .../pynodegl_utils/tests/cmp_fingerprint.py | 10 ++++-- .../pynodegl_utils/tests/cmp_floats.py | 2 +- .../pynodegl_utils/tests/cmp_resources.py | 2 +- 6 files changed, 60 insertions(+), 19 deletions(-) diff --git a/pynodegl-utils/pynodegl_utils/tests/__init__.py b/pynodegl-utils/pynodegl_utils/tests/__init__.py index 4dfe31a222..f57d20382b 100644 --- a/pynodegl-utils/pynodegl_utils/tests/__init__.py +++ b/pynodegl-utils/pynodegl_utils/tests/__init__.py @@ -46,17 +46,17 @@ def _get_ref_data(tester, ref_filepath): return tester.deserialize(serialized_data) -def _run_test_default(func_name, tester, ref_filepath): +def _run_test_default(func_name, tester, ref_filepath, dump=False): if not op.exists(ref_filepath): sys.stderr.write('{}: reference file {} not found, use GEN=yes to create it\n'.format(func_name, ref_filepath)) sys.exit(1) ref_data = _get_ref_data(tester, ref_filepath) - out_data = tester.get_out_data() + out_data = tester.get_out_data(dump, func_name) return _run_test(func_name, tester, ref_data, out_data) -def _run_test_gen_yes(func_name, tester, ref_filepath): - out_data = tester.get_out_data() +def _run_test_gen_yes(func_name, tester, ref_filepath, dump=False): + out_data = tester.get_out_data(dump, func_name) if not op.exists(ref_filepath): sys.stderr.write('{}: creating {}\n'.format(func_name, ref_filepath)) _set_ref_data(tester, ref_filepath, out_data) @@ -65,8 +65,8 @@ def _run_test_gen_yes(func_name, tester, ref_filepath): return _run_test(func_name, tester, ref_data, out_data) -def _run_test_gen_update(func_name, tester, ref_filepath): - out_data = tester.get_out_data() +def _run_test_gen_update(func_name, tester, ref_filepath, dump=False): + out_data = tester.get_out_data(dump, func_name) if not op.exists(ref_filepath): sys.stderr.write('{}: creating {}\n'.format(func_name, ref_filepath)) _set_ref_data(tester, ref_filepath, out_data) @@ -79,12 +79,12 @@ def _run_test_gen_update(func_name, tester, ref_filepath): return None -def _run_test_gen_force(func_name, tester, ref_filepath): +def _run_test_gen_force(func_name, tester, ref_filepath, dump=False): if not op.exists(ref_filepath): sys.stderr.write('{}: creating {}\n'.format(func_name, ref_filepath)) else: sys.stderr.write('{}: re-generating {}\n'.format(func_name, ref_filepath)) - out_data = tester.get_out_data() + out_data = tester.get_out_data(dump, func_name) _set_ref_data(tester, ref_filepath, out_data) return [] @@ -104,15 +104,25 @@ def run(): sys.stderr.write('GEN environment variable must be any of {}\n'.format(', '.join(allowed_gen_opt))) sys.exit(1) + tests_opts = os.environ.get('TESTS_OPTIONS') + + allowed_tests_opts = ('dump',) + if tests_opts is not None and tests_opts not in allowed_tests_opts: + sys.stderr.write('TESTS_OPTIONS environment variable must be any of {}\n'.format(', '.join(allowed_tests_opts))) + sys.exit(1) + dump = tests_opts == 'dump' + if len(sys.argv) not in (3, 4): - sys.stderr.write('''Usage: [GEN={}] {} [] + sys.stderr.write('''Usage: [TESTS_OPTIONS={} GEN={}] {} [] GEN=yes Create the reference file if not present GEN=update Same as "yes" and update the reference if the test fails GEN=force Same as "yes" and always replace the reference file. This may change the references even if the tests are passing due to the threshold/tolerance mechanism. -'''.format('|'.join(allowed_gen_opt), op.basename(sys.argv[0]))) + + TESTS_OPTIONS=dump Dump tests outputs to the filesystem (in /nodegl/tests) +'''.format('|'.join(allowed_tests_opts), '|'.join(allowed_gen_opt), op.basename(sys.argv[0]))) sys.exit(1) if len(sys.argv) == 3: @@ -127,7 +137,7 @@ def run(): tester = func.tester test_func = _gen_map.get(gen_opt, _run_test_default) - err = test_func(func_name, tester, ref_filepath) + err = test_func(func_name, tester, ref_filepath, dump) if err: sys.stderr.write('\n'.join(err) + '\n') sys.exit(1) diff --git a/pynodegl-utils/pynodegl_utils/tests/cmp.py b/pynodegl-utils/pynodegl_utils/tests/cmp.py index de0affa02b..6e67bb2909 100644 --- a/pynodegl-utils/pynodegl_utils/tests/cmp.py +++ b/pynodegl-utils/pynodegl_utils/tests/cmp.py @@ -21,9 +21,11 @@ # import os +import os.path as op import difflib import pynodegl as ngl from pynodegl_utils.misc import get_backend +import tempfile class CompareBase: @@ -36,7 +38,7 @@ def serialize(data): def deserialize(data): return data - def get_out_data(self): + def get_out_data(self, debug=False, debug_func=None): raise NotImplementedError def compare_data(self, test_name, ref_data, out_data): @@ -51,6 +53,13 @@ def compare_data(self, test_name, ref_data, out_data): err.append('{} fail:\n{}'.format(test_name, diff)) return err + @staticmethod + def dump_image(img, dump_index, func_name=None): + filename = op.join(get_temp_dir(), f'{func_name}_{dump_index:03}.png') + print(f'Dumping output image to {filename}') + img.save(filename) + dump_index += 1 + class CompareSceneBase(CompareBase): @@ -133,3 +142,10 @@ def test_decorator(user_func): return user_func return test_decorator return test_func + + +def get_temp_dir(): + dir = op.join(tempfile.gettempdir(), 'nodegl/tests') + if not op.exists(dir): + os.makedirs(dir) + return dir diff --git a/pynodegl-utils/pynodegl_utils/tests/cmp_cuepoints.py b/pynodegl-utils/pynodegl_utils/tests/cmp_cuepoints.py index 2774e1f97c..b2cbec2b3d 100644 --- a/pynodegl-utils/pynodegl_utils/tests/cmp_cuepoints.py +++ b/pynodegl-utils/pynodegl_utils/tests/cmp_cuepoints.py @@ -20,7 +20,13 @@ # under the License. # -from .cmp import CompareSceneBase, get_test_decorator +import os.path as op +from PIL import Image + +from .cmp import CompareBase, CompareSceneBase, get_test_decorator, get_temp_dir + +_MODE = 'RGBA' + class _CompareCuePoints(CompareSceneBase): @@ -56,9 +62,14 @@ def _pos_to_px(pos, width, height): y = min(max(y, 0), height - 1) return [x, y] - def get_out_data(self): + def get_out_data(self, dump=False, func_name=None): cpoints = [] + dump_index = 0 for (width, height, capture_buffer) in self.render_frames(): + if dump: + img = Image.frombuffer(_MODE, (width, height), capture_buffer, 'raw', _MODE, 0, 1) + CompareBase.dump_image(img, dump_index, func_name) + dump_index += 1 frame_cpoints = {} for point_name, (x, y) in self._points.items(): pix_x, pix_y = self._pos_to_px((x, y), width, height) diff --git a/pynodegl-utils/pynodegl_utils/tests/cmp_fingerprint.py b/pynodegl-utils/pynodegl_utils/tests/cmp_fingerprint.py index b4a774eace..1c616b65f1 100644 --- a/pynodegl-utils/pynodegl_utils/tests/cmp_fingerprint.py +++ b/pynodegl-utils/pynodegl_utils/tests/cmp_fingerprint.py @@ -20,9 +20,10 @@ # under the License. # +import os.path as op from PIL import Image -from .cmp import CompareSceneBase, get_test_decorator +from .cmp import CompareBase, CompareSceneBase, get_test_decorator, get_temp_dir _HSIZE = 8 @@ -73,11 +74,14 @@ def _get_plane_hashes(buf): hashes.append(comp_hash) return hashes - def get_out_data(self): + def get_out_data(self, dump=False, func_name=None): hashes = [] + dump_index = 0 for (width, height, capture_buffer) in self.render_frames(): - # TODO: png output for debug? img = Image.frombuffer(_MODE, (width, height), capture_buffer, 'raw', _MODE, 0, 1) + if dump: + CompareBase.dump_image(img, dump_index, func_name) + dump_index += 1 img = img.resize((_HSIZE + 1, _HSIZE + 1), resample=Image.LANCZOS) data = img.tobytes() frame_hashes = self._get_plane_hashes(data) diff --git a/pynodegl-utils/pynodegl_utils/tests/cmp_floats.py b/pynodegl-utils/pynodegl_utils/tests/cmp_floats.py index e53a3e7f3e..b5df54e507 100644 --- a/pynodegl-utils/pynodegl_utils/tests/cmp_floats.py +++ b/pynodegl-utils/pynodegl_utils/tests/cmp_floats.py @@ -45,7 +45,7 @@ def deserialize(data): ret.append([name] + [float(f) for f in floats.split()]) return ret - def get_out_data(self): + def get_out_data(self, debug=False, debug_func=None): return self._func(**self._func_kwargs) def compare_data(self, test_name, ref_data, out_data): diff --git a/pynodegl-utils/pynodegl_utils/tests/cmp_resources.py b/pynodegl-utils/pynodegl_utils/tests/cmp_resources.py index 90057bcdd0..baad3230b7 100644 --- a/pynodegl-utils/pynodegl_utils/tests/cmp_resources.py +++ b/pynodegl-utils/pynodegl_utils/tests/cmp_resources.py @@ -59,7 +59,7 @@ def _scene_wrap(self, scene): os.close(fd) return ngl.HUD(scene, export_filename=self._csvfile) - def get_out_data(self): + def get_out_data(self, debug=False, debug_func=None): for frame in self.render_frames(): pass From f9f5dac1c4c916e4faace86c485e498130785dd4 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Tue, 3 Nov 2020 17:24:35 -0800 Subject: [PATCH 252/388] log: use pretty function in log messages This is useful to de-mangle the function name when the LOG() macro is called from C++ code. Signed-off-by: Matthieu Bouron --- libnodegl/log.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libnodegl/log.h b/libnodegl/log.h index 09f646181f..780dd71c6e 100644 --- a/libnodegl/log.h +++ b/libnodegl/log.h @@ -25,7 +25,11 @@ #include "nodegl.h" #include "utils.h" -#define LOG(log_level, ...) ngli_log_print(NGL_LOG_##log_level, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) +#ifndef __PRETTY_FUNCTION__ +#define __PRETTY_FUNCTION__ __FUNCTION__ +#endif + +#define LOG(log_level, ...) ngli_log_print(NGL_LOG_##log_level, __FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__) #ifdef LOGTRACE # define TRACE(...) LOG(VERBOSE, __VA_ARGS__) From a5b7ac7cd01f41e69fcdb0b0019017d2f335e4a9 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Tue, 3 Nov 2020 17:09:10 -0800 Subject: [PATCH 253/388] darray: use assertion to verify that index is valid in ngli_darray_get() Signed-off-by: Matthieu Bouron --- libnodegl/darray.c | 3 +-- libnodegl/deserialize.c | 6 +++++- libnodegl/pipeline_gl.c | 7 ------- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/libnodegl/darray.c b/libnodegl/darray.c index c49a47a68d..111185cd51 100644 --- a/libnodegl/darray.c +++ b/libnodegl/darray.c @@ -103,8 +103,7 @@ void *ngli_darray_pop(struct darray *darray) void *ngli_darray_get(const struct darray *darray, int index) { - if (index < 0 || index >= darray->count) - return NULL; + ngli_assert(index >= 0 && index < darray->count); return darray->data + index * darray->element_size; } diff --git a/libnodegl/deserialize.c b/libnodegl/deserialize.c index d3c43d15e0..b8d1eaea3f 100644 --- a/libnodegl/deserialize.c +++ b/libnodegl/deserialize.c @@ -223,7 +223,11 @@ static int parse_kvs(const char *s, int *nb_kvsp, char ***keysp, int **valsp) static struct ngl_node **get_abs_node(struct darray *nodes_array, int id) { - return ngli_darray_get(nodes_array, ngli_darray_count(nodes_array) - id - 1); + const int index = ngli_darray_count(nodes_array) - id - 1; + if (index < 0 || index >= ngli_darray_count(nodes_array)) + return NULL; + struct ngl_node **nodes = ngli_darray_data(nodes_array); + return &nodes[index]; } static const uint8_t hexm[256] = { diff --git a/libnodegl/pipeline_gl.c b/libnodegl/pipeline_gl.c index e0436ca516..8f7cba05d8 100644 --- a/libnodegl/pipeline_gl.c +++ b/libnodegl/pipeline_gl.c @@ -571,7 +571,6 @@ int ngli_pipeline_gl_set_resources(struct pipeline *s, const struct pipeline_res ngli_assert(ngli_darray_count(&s_priv->uniform_bindings) == data_params->nb_uniforms); for (int i = 0; i < data_params->nb_uniforms; i++) { struct uniform_binding *uniform_binding = ngli_darray_get(&s_priv->uniform_bindings, i); - ngli_assert(uniform_binding); const void *uniform_data = data_params->uniforms[i]; uniform_binding->data = uniform_data; } @@ -592,8 +591,6 @@ int ngli_pipeline_gl_update_attribute(struct pipeline *s, int index, struct buff ngli_assert(s->type == NGLI_PIPELINE_TYPE_GRAPHICS); struct attribute_binding *attribute_binding = ngli_darray_get(&s_priv->attribute_bindings, index); - ngli_assert(attribute_binding); - const struct buffer *current_buffer = attribute_binding->buffer; if (!current_buffer && buffer) s_priv->nb_unbound_attributes--; @@ -626,7 +623,6 @@ int ngli_pipeline_gl_update_uniform(struct pipeline *s, int index, const void *d return NGL_ERROR_NOT_FOUND; struct uniform_binding *uniform_binding = ngli_darray_get(&s_priv->uniform_bindings, index); - ngli_assert(uniform_binding); if (data) { struct gctx *gctx = s->gctx; @@ -649,8 +645,6 @@ int ngli_pipeline_gl_update_texture(struct pipeline *s, int index, struct textur return NGL_ERROR_NOT_FOUND; struct texture_binding *texture_binding = ngli_darray_get(&s_priv->texture_bindings, index); - ngli_assert(texture_binding); - texture_binding->texture = texture; return 0; @@ -664,7 +658,6 @@ int ngli_pipeline_gl_update_buffer(struct pipeline *s, int index, struct buffer return NGL_ERROR_NOT_FOUND; struct buffer_binding *buffer_binding = ngli_darray_get(&s_priv->buffer_bindings, index); - ngli_assert(buffer_binding); if (buffer) { struct gctx_gl *gctx_gl = (struct gctx_gl *)s->gctx; From 3e4d900ddab32be05a78d68d4dcc949d21eb359d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Sat, 7 Nov 2020 10:50:00 +0100 Subject: [PATCH 254/388] build: bump and build sxplayer with meson Starting v9.6.0, sxplayer uses meson as build system. --- .gitignore | 1 + Makefile | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index d9fb6bda29..326a58c43e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.d *.exe *.o +builddir nodegl-env/ sxplayer* *.gcda diff --git a/Makefile b/Makefile index 47c26dd88b..d945eeb4a7 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ PREFIX ?= $(PWD)/nodegl-env include common.mak -SXPLAYER_VERSION ?= 9.5.1 +SXPLAYER_VERSION ?= 9.6.0 # Prevent headers from being rewritten, which would cause unecessary # recompilations between `make` calls. @@ -34,7 +34,18 @@ ACTIVATE = $(PREFIX)/bin/activate RPATH_LDFLAGS ?= -Wl,-rpath,$(PREFIX)/lib ifeq ($(TARGET_OS),Darwin) LIBNODEGL_EXTRA_LDFLAGS = -Wl,-install_name,@rpath/libnodegl.dylib - LIBSXPLAYER_EXTRA_LDFLAGS = -Wl,-install_name,@rpath/libsxplayer.dylib +endif + +MESON_SETUP = meson setup --prefix=$(PREFIX) --pkg-config-path=$(PREFIX)/lib/pkgconfig -Drpath=true +MESON_COMPILE = meson compile +MESON_INSTALL = meson install +ifeq ($(DEBUG),yes) +MESON_SETUP += --buildtype=debugoptimized +else +MESON_SETUP += --buildtype=release +endif +ifneq ($(V),) +MESON_COMPILE += -v endif all: ngl-tools-install pynodegl-utils-install @@ -81,7 +92,7 @@ nodegl-install: sxplayer-install PKG_CONFIG_PATH=$(PREFIX)/lib/pkgconfig LDFLAGS="$(RPATH_LDFLAGS) $(LIBNODEGL_EXTRA_LDFLAGS)" $(MAKE) -C libnodegl install PREFIX=$(PREFIX) DEBUG=$(DEBUG) SHARED=yes INSTALL="$(INSTALL)" sxplayer-install: sxplayer $(PREFIX) - PKG_CONFIG_PATH=$(PREFIX)/lib/pkgconfig LDFLAGS="$(RPATH_LDFLAGS) $(LIBSXPLAYER_EXTRA_LDFLAGS)" $(MAKE) -C sxplayer install PREFIX=$(PREFIX) DEBUG=$(DEBUG) SHARED=yes INSTALL="$(INSTALL)" + (. $(ACTIVATE) && $(MESON_SETUP) sxplayer builddir/sxplayer && $(MESON_COMPILE) -C builddir/sxplayer && $(MESON_INSTALL) -C builddir/sxplayer) # Note for developers: in order to customize the sxplayer you're building # against, you can use your own sources post-install: @@ -104,6 +115,7 @@ sxplayer-$(SXPLAYER_VERSION).tar.gz: $(PREFIX): $(PYTHON) -m venv $(PREFIX) + (. $(ACTIVATE) && pip install meson ninja) tests: ngl-tools-install pynodegl-utils-install nodegl-tests (. $(ACTIVATE) && $(MAKE) -C tests) @@ -126,6 +138,7 @@ clean_gcx: $(RM) ngl-tools/*.gcda ngl-tools/*.gcno clean: clean_gcx clean_py + $(RM) -r builddir/sxplayer PKG_CONFIG_PATH=$(PREFIX)/lib/pkgconfig $(MAKE) -C libnodegl clean PKG_CONFIG_PATH=$(PREFIX)/lib/pkgconfig $(MAKE) -C ngl-tools clean From 825c7e87dd792be82e4f6077334e7cbb602ee973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 22 Oct 2020 12:45:17 +0200 Subject: [PATCH 255/388] build: switch libnodegl and ngl-tools to meson This change is mainly motivated by the need for Microsoft Visual Studio integration. While it currently works with MinGW for our case, it will cause integration issues with native C++ libraries such as D3D (because of the different mangling). Note that MSVC is still not supported, but this is a first step. The root Makefile is still present to bootstrap the virtual environment, but the native code submodules (libnodegl and ngl-tools) are ported to this new build system. The existing feature list should be completely ported: - build on Linux/Mac/Windows/Android/iOS - debug vs release builds - generate static and shared libraries (using the default_library {static,shared} builtin) - various control options such as external libraries and log tracing - detect optional external dependencies (including Apple frameworks) - exporting a restricted symbols list - generating a correct pkg-config file according to the configuration (typically in shared vs static scenarios), with custom variables - coverage integration - reduced symbol visibility to only the public interfaces - special rules to generate the documentation, the OpenGL wrappers and the node specifications - unit test programs Additionally, we gain a bunch of useful build features that can't be exhaustively listed here. But to name a few: - we now have a smarter behavior and more control with regards to external mandatory and optional dependencies - some operations like generating a pkg-config are now less clumsy - the builtin capability probing support might be convenient for future features - the portability concerns are now abstracted away for the most part - out of tree build is now supported - the detection/config and build are now 2 separated phases, so the settings are now memorized (DEBUG, CC), preventing at the same time the risk of partial state --- .github/workflows/ci_coverage.yml | 5 +- .github/workflows/ci_win.yml | 2 + .gitignore | 6 - Makefile | 61 ++-- common.mak | 38 -- doc/expl/techchoices.md | 7 - doc/howto/c-api.md | 2 +- doc/howto/installation.md | 31 +- doc/howto/write-new-node.md | 10 +- libnodegl/.gitignore | 14 - libnodegl/Makefile | 423 --------------------- libnodegl/libnodegl.darwin.symexport | 1 + libnodegl/libnodegl.pc.tpl | 12 - libnodegl/libnodegl.symexport | 4 + libnodegl/meson.build | 526 +++++++++++++++++++++++++++ libnodegl/meson_options.txt | 40 ++ libnodegl/{nodegl.h => nodegl.h.in} | 6 +- ngl-tools/.gitignore | 7 - ngl-tools/Makefile | 158 -------- ngl-tools/meson.build | 133 +++++++ ngl-tools/meson_options.txt | 28 ++ 21 files changed, 796 insertions(+), 718 deletions(-) delete mode 100644 libnodegl/.gitignore delete mode 100644 libnodegl/Makefile create mode 100644 libnodegl/libnodegl.darwin.symexport delete mode 100644 libnodegl/libnodegl.pc.tpl create mode 100644 libnodegl/libnodegl.symexport create mode 100644 libnodegl/meson.build create mode 100644 libnodegl/meson_options.txt rename libnodegl/{nodegl.h => nodegl.h.in} (99%) delete mode 100644 ngl-tools/.gitignore delete mode 100644 ngl-tools/Makefile create mode 100644 ngl-tools/meson.build create mode 100644 ngl-tools/meson_options.txt diff --git a/.github/workflows/ci_coverage.yml b/.github/workflows/ci_coverage.yml index 0f3b721f47..71b279b1ca 100644 --- a/.github/workflows/ci_coverage.yml +++ b/.github/workflows/ci_coverage.yml @@ -26,9 +26,8 @@ jobs: BACKEND=opengles DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests COVERAGE=yes - name: Get coverage run: | - gcovr -r libnodegl -s -x -o coverage.xml - make clean + make coverage-xml - name: Upload coverage to Codecov uses: codecov/codecov-action@v1 with: - file: coverage.xml + file: builddir/libnodegl/meson-logs/coverage.xml diff --git a/.github/workflows/ci_win.yml b/.github/workflows/ci_win.yml index b6105d6676..55dad5a9ad 100644 --- a/.github/workflows/ci_win.yml +++ b/.github/workflows/ci_win.yml @@ -23,6 +23,8 @@ jobs: run: | C:\msys64\usr\bin\bash -lc "pacman -S --noconfirm --needed git make" C:\msys64\usr\bin\bash -lc "pacman -S --noconfirm --needed mingw-w64-x86_64-{toolchain,ffmpeg,python}" + C:\msys64\usr\bin\bash -lc "pacman -S --noconfirm --needed mingw-w64-x86_64-python3-pip" + C:\msys64\usr\bin\bash -lc "pacman -S --noconfirm --needed mingw-w64-x86_64-meson" - name: Build run: | diff --git a/.gitignore b/.gitignore index 326a58c43e..27859dbe6f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,3 @@ -*.d -*.exe -*.o builddir nodegl-env/ sxplayer* -*.gcda -*.gcno -ngl-cov/ diff --git a/Makefile b/Makefile index d945eeb4a7..c4f83342e5 100644 --- a/Makefile +++ b/Makefile @@ -25,20 +25,19 @@ include common.mak SXPLAYER_VERSION ?= 9.6.0 -# Prevent headers from being rewritten, which would cause unecessary -# recompilations between `make` calls. -INSTALL = install -C - ACTIVATE = $(PREFIX)/bin/activate RPATH_LDFLAGS ?= -Wl,-rpath,$(PREFIX)/lib -ifeq ($(TARGET_OS),Darwin) - LIBNODEGL_EXTRA_LDFLAGS = -Wl,-install_name,@rpath/libnodegl.dylib -endif MESON_SETUP = meson setup --prefix=$(PREFIX) --pkg-config-path=$(PREFIX)/lib/pkgconfig -Drpath=true -MESON_COMPILE = meson compile +# MAKEFLAGS= is a workaround for the issue described here: +# https://github.com/ninja-build/ninja/issues/1139#issuecomment-724061270 +MESON_COMPILE = MAKEFLAGS= meson compile MESON_INSTALL = meson install +ifeq ($(COVERAGE),yes) +MESON_SETUP += -Db_coverage=true +DEBUG = yes +endif ifeq ($(DEBUG),yes) MESON_SETUP += --buildtype=debugoptimized else @@ -48,6 +47,14 @@ ifneq ($(V),) MESON_COMPILE += -v endif +# Workaround Debian/Ubuntu bug; see https://github.com/mesonbuild/meson/issues/5925 +ifeq ($(TARGET_OS),Linux) +DISTRIB_ID := $(or $(shell lsb_release -si 2>/dev/null),none) +ifeq ($(DISTRIB_ID),$(filter $(DISTRIB_ID),Ubuntu Debian)) +MESON_SETUP += --libdir lib +endif +endif + all: ngl-tools-install pynodegl-utils-install @echo @echo " Install completed." @@ -57,7 +64,7 @@ all: ngl-tools-install pynodegl-utils-install @echo ngl-tools-install: nodegl-install - PKG_CONFIG_PATH=$(PREFIX)/lib/pkgconfig LDFLAGS=$(RPATH_LDFLAGS) $(MAKE) -C ngl-tools install PREFIX=$(PREFIX) DEBUG=$(DEBUG) + (. $(ACTIVATE) && $(MESON_SETUP) ngl-tools builddir/ngl-tools && $(MESON_COMPILE) -C builddir/ngl-tools && $(MESON_INSTALL) -C builddir/ngl-tools) pynodegl-utils-install: pynodegl-utils-deps-install (. $(ACTIVATE) && pip -v install -e ./pynodegl-utils) @@ -89,7 +96,7 @@ pynodegl-deps-install: $(PREFIX) nodegl-install (. $(ACTIVATE) && pip install -r ./pynodegl/requirements.txt) nodegl-install: sxplayer-install - PKG_CONFIG_PATH=$(PREFIX)/lib/pkgconfig LDFLAGS="$(RPATH_LDFLAGS) $(LIBNODEGL_EXTRA_LDFLAGS)" $(MAKE) -C libnodegl install PREFIX=$(PREFIX) DEBUG=$(DEBUG) SHARED=yes INSTALL="$(INSTALL)" + (. $(ACTIVATE) && $(MESON_SETUP) libnodegl builddir/libnodegl && $(MESON_COMPILE) -C builddir/libnodegl && $(MESON_INSTALL) -C builddir/libnodegl) sxplayer-install: sxplayer $(PREFIX) (. $(ACTIVATE) && $(MESON_SETUP) sxplayer builddir/sxplayer && $(MESON_COMPILE) -C builddir/sxplayer && $(MESON_INSTALL) -C builddir/sxplayer) @@ -113,15 +120,24 @@ sxplayer-$(SXPLAYER_VERSION): sxplayer-$(SXPLAYER_VERSION).tar.gz sxplayer-$(SXPLAYER_VERSION).tar.gz: $(CURL) -L https://github.com/Stupeflix/sxplayer/archive/v$(SXPLAYER_VERSION).tar.gz -o $@ +# +# We do not pull meson from pip on Windows for the same reasons we don't pull +# Pillow and PySide2. We require the users to have it on their system. +# $(PREFIX): $(PYTHON) -m venv $(PREFIX) +ifneq ($(TARGET_OS),MinGW-w64) (. $(ACTIVATE) && pip install meson ninja) +endif tests: ngl-tools-install pynodegl-utils-install nodegl-tests (. $(ACTIVATE) && $(MAKE) -C tests) +nodegl-tests: nodegl-install + (. $(ACTIVATE) && meson test -C builddir/libnodegl) + nodegl-%: nodegl-install - (. $(ACTIVATE) && PKG_CONFIG_PATH=$(PREFIX)/lib/pkgconfig LDFLAGS=$(RPATH_LDFLAGS) $(MAKE) -C libnodegl $(subst nodegl-,,$@) DEBUG=$(DEBUG)) + (. $(ACTIVATE) && $(MESON_COMPILE) -C builddir/libnodegl $(subst nodegl-,,$@)) clean_py: $(RM) pynodegl/nodes_def.pyx @@ -133,20 +149,19 @@ clean_py: $(RM) -r pynodegl-utils/pynodegl_utils.egg-info $(RM) -r pynodegl-utils/.eggs -clean_gcx: - $(RM) libnodegl/*.gcda libnodegl/*.gcno - $(RM) ngl-tools/*.gcda ngl-tools/*.gcno - -clean: clean_gcx clean_py +clean: clean_py $(RM) -r builddir/sxplayer - PKG_CONFIG_PATH=$(PREFIX)/lib/pkgconfig $(MAKE) -C libnodegl clean - PKG_CONFIG_PATH=$(PREFIX)/lib/pkgconfig $(MAKE) -C ngl-tools clean + $(RM) -r builddir/libnodegl + $(RM) -r builddir/ngl-tools # You need to build and run with COVERAGE set to generate data. # For example: `make clean && make -j8 tests COVERAGE=yes` -coverage: - mkdir -p ngl-cov - gcovr -r libnodegl --html-details --html-title "node.gl coverage" --print-summary -o ngl-cov/index.html +# We don't use `meson coverage` here because of +# https://github.com/mesonbuild/meson/issues/7895 +coverage-html: + (. $(ACTIVATE) && ninja -C builddir/libnodegl coverage-html) +coverage-xml: + (. $(ACTIVATE) && ninja -C builddir/libnodegl coverage-xml) .PHONY: all .PHONY: ngl-tools-install @@ -155,5 +170,5 @@ coverage: .PHONY: nodegl-install .PHONY: sxplayer-install .PHONY: tests -.PHONY: clean clean_gcx clean_py -.PHONY: coverage +.PHONY: clean clean_py +.PHONY: coverage-html coverage-xml diff --git a/common.mak b/common.mak index d926b11d41..e5b2932ec6 100644 --- a/common.mak +++ b/common.mak @@ -24,51 +24,13 @@ PYTHON_MAJOR = 3 # # User configuration # -SHARED ?= no DEBUG ?= no -SMALL ?= no COVERAGE ?= no CURL ?= curl -INSTALL ?= install -PKG_CONFIG ?= pkg-config PYTHON ?= python$(if $(shell which python$(PYTHON_MAJOR) 2> /dev/null),$(PYTHON_MAJOR),) TAR ?= tar TARGET_OS ?= $(shell uname -s) -ARCH ?= $(shell uname -m) ifneq ($(shell $(PYTHON) -c "import sys;print(sys.version_info.major)"),$(PYTHON_MAJOR)) $(error "Python $(PYTHON_MAJOR) not found") endif - -ifeq ($(OS),Windows_NT) - TARGET_OS = MinGW-w64 - PREFIX ?= C:/msys64/usr/local -else - PREFIX ?= /usr/local -endif - -define capitalize -$(shell echo $1 | tr a-z- A-Z_) -endef - -PROJECT_CFLAGS := $(CFLAGS) -Wall -O2 -Werror=missing-prototypes \ - -std=c99 \ - -DTARGET_$(call capitalize,$(TARGET_OS)) \ - -DARCH_$(call capitalize,$(ARCH)) -PROJECT_LDLIBS := $(LDLIBS) - -ifeq ($(COVERAGE),yes) - PROJECT_CFLAGS += --coverage - PROJECT_LDLIBS += --coverage - DEBUG = yes -endif -ifeq ($(DEBUG),yes) - PROJECT_CFLAGS += -g -endif -ifeq ($(SMALL),yes) - PROJECT_CFLAGS += -DCONFIG_SMALL -endif - -ifeq ($(TARGET_OS),MinGW-w64) - EXESUF = .exe -endif # MinGW diff --git a/doc/expl/techchoices.md b/doc/expl/techchoices.md index 7d4b17a479..5898dae380 100644 --- a/doc/expl/techchoices.md +++ b/doc/expl/techchoices.md @@ -26,10 +26,3 @@ option when targeting desktop and mobile. It is very fast and simple to script in Python. Bindings in other languages could be added. - -## Makefile - -There are tons of build system available out there, and the most popular ones -(autotools, CMake, ...) are the worse. Until a good, portable and wildly -distributed build system appears, a simple `Makefile` with control variables -will do the trick just fine. diff --git a/doc/howto/c-api.md b/doc/howto/c-api.md index 1252fc3c93..765148fecc 100644 --- a/doc/howto/c-api.md +++ b/doc/howto/c-api.md @@ -14,7 +14,7 @@ The `ngli_*` symbols share this meaning and correspond to symbols shared inside the project but never exposed to the user (you will never see them in the public header). -[nodegl-header]: /libnodegl/nodegl.h +[nodegl-header]: /libnodegl/nodegl.h.in ## Compilation and linking with node.gl diff --git a/doc/howto/installation.md b/doc/howto/installation.md index fb72e28cad..193790e801 100644 --- a/doc/howto/installation.md +++ b/doc/howto/installation.md @@ -49,6 +49,8 @@ On Windows, the bootstrap is slightly more complex: pacman -Syuu # and restart the shell pacman -S git make pacman -S mingw-w64-x86_64-{toolchain,ffmpeg,python} +pacman -S mingw-w64-x86_64-python3-pip +pacman -S mingw-w64-x86_64-meson make TARGET_OS=MinGW-w64 ``` @@ -56,27 +58,20 @@ Then you should be able to enter the environment and run the tools. ## Installation of `libnodegl` (the core library) -### Build +`libnodegl` uses [Meson][meson] for its build system. Its compilation and +installation usually looks like the following: -`make` is enough to build `libnodegl.a`. - -If you prefer a dynamic library, you can use the variable `SHARED`, such as -`make SHARED=yes`. - -If you need symbol debugging, you can use `make DEBUG=yes`. - -Make allow options to be combinable, so `make SHARED=yes DEBUG=yes` is valid. - -Additionally, `PYTHON` and `PKG_CONFIG` which respectively allows to customize -`python` and `pkg-config` executable paths. - -### Installation +```sh +meson setup builddir +meson compile -C builddir +meson install -C builddir +``` -`make install` will install the library in `PREFIX`, which you can override, -for example using `make install PREFIX=/tmp/local`. +`meson configure` can be used to list the available options. See the [Meson +documentation][meson-doc] for more information. -You can check the installed version of `libnodegl` using `pkg-config ---modversion libnodegl` +[meson]: https://mesonbuild.com/ +[meson-doc]: https://mesonbuild.com/Quick-guide.html#compiling-a-meson-project ## Installation of `ngl-tools` diff --git a/doc/howto/write-new-node.md b/doc/howto/write-new-node.md index ab64ac27a8..251a189006 100644 --- a/doc/howto/write-new-node.md +++ b/doc/howto/write-new-node.md @@ -9,14 +9,14 @@ prototypes required to implement a new node. In order to add a node, you need to: -- in `nodegl.h`: declare a new `NGL_NODE_*` with an arbitrary unique FourCC +- in `nodegl.h.in`: declare a new `NGL_NODE_*` with an arbitrary unique FourCC - create a `node_*.c` file declaring a `const struct node_class`. Don't forget the Copyright header. See other node files, such as `node_identity.c` - in `nodes_register.h`: register the new node -- in `Makefile`: add the object name associated with your `.c` to `LIB_OBJS` -- run `make updatespecs` to update `nodes.specs` every time you update the - parameter of the node -- similarly, run `make updatedoc` to update the [reference +- reference the source file node in `libnodegl/meson.build` +- run `make nodegl-updatespecs` (from the top-level Makefile) to update + `nodes.specs` every time you update the parameters of the node +- similarly, run `make nodegl-updatedoc` to update the [reference documentation][libnodegl-ref] after every change to the parameters - refer to [nodes.h][nodes-h] for the available callbacks to implement in your class map diff --git a/libnodegl/.gitignore b/libnodegl/.gitignore deleted file mode 100644 index 167a3d888e..0000000000 --- a/libnodegl/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -/gen_doc -/gen_specs -/gl.xml -/libnodegl.a -/libnodegl.pc -/libnodegl.so -/libnodegl.dylib -/libnodegl.symexport -/test_asm -/test_colorconv -/test_darray -/test_draw -/test_hmap -/test_utils diff --git a/libnodegl/Makefile b/libnodegl/Makefile deleted file mode 100644 index 2facd00ad4..0000000000 --- a/libnodegl/Makefile +++ /dev/null @@ -1,423 +0,0 @@ -# -# Copyright 2016 GoPro Inc. -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -include ../common.mak - -DEBUG_GL ?= no -LOGTRACE ?= no - -BACKEND_GL ?= yes - -ifeq ($(DEBUG_GL),yes) - PROJECT_CFLAGS += -DDEBUG_GL -endif - -ifeq ($(LOGTRACE),yes) - PROJECT_CFLAGS += -DLOGTRACE -endif - -ifeq ($(DEBUG_MEM),yes) - PROJECT_CFLAGS += -DDEBUG_MEM -endif - -ifeq ($(DEBUG_SCENE),yes) - PROJECT_CFLAGS += -DDEBUG_SCENE -endif - -LD_SYM_FILE = $(LIB_BASENAME).symexport -LD_SYM_OPTION = --version-script -LD_SYM_DATA = "{\n\tglobal: ngl_*;\n\tlocal: *;\n};\n" -DYLIBSUFFIX = so -ifeq ($(TARGET_OS),$(filter $(TARGET_OS),Darwin iPhone)) - DYLIBSUFFIX = dylib - LD_SYM_OPTION = -exported_symbols_list - LD_SYM_DATA = "_ngl_*\n" -else -ifeq ($(TARGET_OS),MinGW-w64) - DYLIBSUFFIX = dll -endif # MinGW -endif # Darwin/iPhone - -ifeq ($(SHARED),yes) - LIBSUFFIX = $(DYLIBSUFFIX) -else - LIBSUFFIX = a -endif -LIB_BASENAME = libnodegl -LIB_NAME = $(LIB_BASENAME).$(LIBSUFFIX) -LIB_PCNAME = $(LIB_BASENAME).pc - -LIB_OBJS = animation.o \ - api.o \ - block.o \ - bstr.o \ - buffer.o \ - colorconv.o \ - darray.o \ - deserialize.o \ - dot.o \ - drawutils.o \ - format.o \ - gctx.o \ - gtimer.o \ - hmap.o \ - hwconv.o \ - hwupload.o \ - hwupload_common.o \ - image.o \ - log.o \ - math_utils.o \ - memory.o \ - node_animatedbuffer.o \ - node_animated.o \ - node_animkeyframe.o \ - node_block.o \ - node_buffer.o \ - node_camera.o \ - node_circle.o \ - node_compute.o \ - node_computeprogram.o \ - node_geometry.o \ - node_graphicconfig.o \ - node_group.o \ - node_hud.o \ - node_identity.o \ - node_io.o \ - node_media.o \ - node_program.o \ - node_quad.o \ - node_render.o \ - node_resourceprops.o \ - node_rotate.o \ - node_rotatequat.o \ - node_rtt.o \ - node_scale.o \ - node_streamed.o \ - node_streamedbuffer.o \ - node_text.o \ - node_texture.o \ - node_time.o \ - node_timerangefilter.o \ - node_timerangemodes.o \ - node_transform.o \ - node_translate.o \ - node_triangle.o \ - node_uniform.o \ - node_userswitch.o \ - nodes.o \ - params.o \ - pass.o \ - pgcache.o \ - pgcraft.o \ - pipeline.o \ - precision.o \ - program.o \ - rendertarget.o \ - rnode.o \ - serialize.o \ - texture.o \ - transforms.o \ - utils.o \ - -LIB_OBJS_ARCH_aarch64 = asm_aarch64.o - -LIB_OBJS += $(LIB_OBJS_ARCH_$(ARCH)) - -LIB_CFLAGS = -fPIC -LIB_LDLIBS = -lm -lpthread - -LIB_EXTRA_OBJS_Linux = -LIB_EXTRA_OBJS_Darwin = -LIB_EXTRA_OBJS_Android = jni_utils.o android_ctx.o android_utils.o android_looper.o android_surface.o android_handler.o android_handlerthread.o android_imagereader.o -LIB_EXTRA_OBJS_iPhone = -LIB_EXTRA_OBJS_MinGW-w64 = - -LIB_EXTRA_CFLAGS_Linux = -LIB_EXTRA_CFLAGS_Darwin = -LIB_EXTRA_CFLAGS_Android = -LIB_EXTRA_CFLAGS_iPhone = -LIB_EXTRA_CFLAGS_MinGW-w64 = - -LIB_EXTRA_LDLIBS_Linux = -LIB_EXTRA_LDLIBS_Darwin = -framework CoreVideo -framework CoreFoundation -framework AppKit -framework IOSurface -LIB_EXTRA_LDLIBS_Android = -landroid -LIB_EXTRA_LDLIBS_iPhone = -framework CoreMedia -LIB_EXTRA_LDLIBS_MinGW-w64 = - -LIB_PKG_CONFIG_LIBS = "libsxplayer >= 9.4.0" -LIB_EXTRA_PKG_CONFIG_LIBS_Linux = x11 -LIB_EXTRA_PKG_CONFIG_LIBS_Darwin = -LIB_EXTRA_PKG_CONFIG_LIBS_Android = libavcodec -LIB_EXTRA_PKG_CONFIG_LIBS_iPhone = - -# -# Backends -# -ifeq ($(BACKEND_GL),yes) -LIB_OBJS_GL = buffer_gl.o \ - format_gl.o \ - gctx_gl.o \ - glcontext.o \ - glstate.o \ - gtimer_gl.o \ - pipeline_gl.o \ - program_gl.o \ - rendertarget_gl.o \ - texture_gl.o \ - topology_gl.o \ - type_gl.o \ - -LIB_OBJS += $(LIB_OBJS_GL) -LIB_CFLAGS += -DBACKEND_GL -LIB_EXTRA_OBJS_Linux += glcontext_egl.o -LIB_EXTRA_OBJS_Darwin += glcontext_nsgl.o hwupload_videotoolbox_darwin_gl.o -LIB_EXTRA_OBJS_Android += glcontext_egl.o hwupload_mediacodec_gl.o -LIB_EXTRA_OBJS_iPhone += glcontext_eagl.o hwupload_videotoolbox_ios_gl.o -LIB_EXTRA_OBJS_MinGW-w64 += glcontext_wgl.o -LIB_EXTRA_CFLAGS_Linux += -DHAVE_GLPLATFORM_EGL -LIB_EXTRA_CFLAGS_Darwin += -DHAVE_GLPLATFORM_NSGL -LIB_EXTRA_CFLAGS_Android += -DHAVE_GLPLATFORM_EGL -LIB_EXTRA_CFLAGS_iPhone += -DHAVE_GLPLATFORM_EAGL -LIB_EXTRA_CFLAGS_MinGW-w64 += -DHAVE_GLPLATFORM_WGL -LIB_EXTRA_LDLIBS_Darwin += -framework OpenGL -LIB_EXTRA_LDLIBS_Android += -legl -LIB_EXTRA_LDLIBS_MinGW-w64 += -lopengl32 -lgdi32 -LIB_EXTRA_PKG_CONFIG_LIBS_Linux += gl egl -endif - -WAYLAND_PKG_CONFIG_LIBS = "wayland-client wayland-egl" - -ifeq ($(TARGET_OS),Linux) - ENABLE_WAYLAND ?= $(shell $(PKG_CONFIG) --exists $(WAYLAND_PKG_CONFIG_LIBS) && echo yes || echo no) -else - ENABLE_WAYLAND = no -endif - -ifeq ($(ENABLE_WAYLAND),yes) - PROJECT_CFLAGS += -DHAVE_WAYLAND - LIB_PKG_CONFIG_LIBS += $(WAYLAND_PKG_CONFIG_LIBS) -endif - -VAAPI_X11_PKG_CONFIG_LIBS = "libva-x11 >= 1.1.0" "libva-drm >= 1.1.0" - -ifeq ($(TARGET_OS),Linux) - ENABLE_VAAPI_X11 ?= $(shell $(PKG_CONFIG) --exists $(VAAPI_X11_PKG_CONFIG_LIBS) && echo yes || echo no) -else - ENABLE_VAAPI_X11 = no -endif - -ifeq ($(ENABLE_VAAPI_X11),yes) - ENABLE_VAAPI = yes - PROJECT_CFLAGS += -DHAVE_VAAPI_X11 - LIB_PKG_CONFIG_LIBS += $(VAAPI_X11_PKG_CONFIG_LIBS) -endif - -VAAPI_WAYLAND_PKG_CONFIG_LIBS = "libva-wayland >= 1.1.0" "libva-drm >= 1.1.0" - -ifeq ($(TARGET_OS),Linux) - ENABLE_VAAPI_WAYLAND ?= $(shell $(PKG_CONFIG) --exists $(VAAPI_WAYLAND_PKG_CONFIG_LIBS) && echo yes || echo no) -else - ENABLE_VAAPI_WAYLAND = no -endif - -ifeq ($(ENABLE_VAAPI_WAYLAND),yes) - ENABLE_VAAPI = yes - PROJECT_CFLAGS += -DHAVE_VAAPI_WAYLAND - LIB_PKG_CONFIG_LIBS += $(VAAPI_WAYLAND_PKG_CONFIG_LIBS) -endif - -ifeq ($(ENABLE_VAAPI),yes) - PROJECT_CFLAGS += -DHAVE_VAAPI - LIB_EXTRA_OBJS_Linux += vaapi.o -ifeq ($(BACKEND_GL),yes) - LIB_EXTRA_OBJS_Linux += hwupload_vaapi_gl.o -endif -endif - -LIB_OBJS += $(LIB_EXTRA_OBJS_$(TARGET_OS)) -LIB_CFLAGS += $(LIB_EXTRA_CFLAGS_$(TARGET_OS)) -LIB_LDLIBS += $(LIB_EXTRA_LDLIBS_$(TARGET_OS)) -LIB_CFLAGS += $(shell $(PKG_CONFIG) --cflags $(LIB_PKG_CONFIG_LIBS) $(LIB_EXTRA_PKG_CONFIG_LIBS_$(TARGET_OS))) -LIB_LDLIBS += $(shell $(PKG_CONFIG) --libs $(LIB_PKG_CONFIG_LIBS) $(LIB_EXTRA_PKG_CONFIG_LIBS_$(TARGET_OS))) - -LIB_DEPS = $(LIB_OBJS:.o=.d) - - -all: $(LIB_PCNAME) $(LIB_NAME) - - -# -# Library -# -$(LIB_NAME): CFLAGS = $(PROJECT_CFLAGS) $(LIB_CFLAGS) -$(LIB_NAME): LDLIBS = $(PROJECT_LDLIBS) $(LIB_LDLIBS) -$(LIB_NAME): LDFLAGS += -Wl,$(LD_SYM_OPTION),$(LD_SYM_FILE) -$(LIB_NAME): CPPFLAGS += -MMD -MP -$(LIB_NAME): $(LD_SYM_FILE) $(LIB_OBJS) -ifeq ($(SHARED),yes) - $(CC) $(LDFLAGS) $(LIB_OBJS) -shared -o $@ $(LDLIBS) -else - $(AR) rcs $@ $(LIB_OBJS) -endif - - -# -# Symbols -# -$(LD_SYM_FILE): - $(shell printf $(LD_SYM_DATA) > $(LD_SYM_FILE)) - - -# -# pkg-config -# -define headver -$(shell sed -nE 's/^\#define NODEGL_VERSION_$(1)[^0-9]*([0-9]*)/\1/p' nodegl.h) -endef - -$(LIB_PCNAME): VERSION = $(call headver,MAJOR).$(call headver,MINOR).$(call headver,MICRO) -$(LIB_PCNAME): LDLIBS = $(PROJECT_LDLIBS) $(LIB_LDLIBS) -ifeq ($(SHARED),yes) -$(LIB_PCNAME): DEP_PRIVATE_LIBS = $(LDLIBS) -else -$(LIB_PCNAME): DEP_LIBS = $(LDLIBS) -endif -$(LIB_PCNAME): $(LIB_PCNAME).tpl - sed -e "s#PREFIX#$(PREFIX)#" \ - -e "s#DEP_LIBS#$(DEP_LIBS)#" \ - -e "s#DEP_PRIVATE_LIBS#$(DEP_PRIVATE_LIBS)#" \ - -e "s#VERSION#$(VERSION)#" \ - $^ > $@ - - -# -# Specifications -# -SPECS_FILE = nodes.specs -gen_specs$(EXESUF): CFLAGS = $(PROJECT_CFLAGS) $(LIB_CFLAGS) -gen_specs$(EXESUF): LDLIBS = $(PROJECT_LDLIBS) $(LIB_LDLIBS) -gen_specs$(EXESUF): gen_specs.o $(LIB_OBJS) -gen_specs$(EXESUF): - $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@ - -updatespecs: gen_specs$(EXESUF) - ./gen_specs$(EXESUF) > $(SPECS_FILE) - - -# -# Doc generation -# -gen_doc$(EXESUF): CFLAGS = $(PROJECT_CFLAGS) $(LIB_CFLAGS) -gen_doc$(EXESUF): LDLIBS = $(PROJECT_LDLIBS) $(LIB_LDLIBS) -gen_doc$(EXESUF): gen_doc.o $(LIB_OBJS) -gen_doc$(EXESUF): - $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@ - -updatedoc: gen_doc$(EXESUF) - ./gen_doc$(EXESUF) > doc/libnodegl.md - - -# -# OpenGL function wrappers -# -gen_gl_wrappers: gl.xml - $(PYTHON) gen-gl-wrappers.py $^ glfunctions.h gldefinitions_data.h glwrappers.h -gl.xml: - $(CURL) https://raw.githubusercontent.com/KhronosGroup/OpenGL-Registry/master/xml/$@ -o $@ - - -# -# Tests -# -TESTS = asm \ - colorconv \ - darray \ - draw \ - hmap \ - utils \ - -TESTPROGS = $(addprefix test_,$(TESTS)) -$(TESTPROGS): CFLAGS = $(PROJECT_CFLAGS) $(LIB_CFLAGS) -$(TESTPROGS): LDLIBS = $(PROJECT_LDLIBS) $(LIB_LDLIBS) - -testprogs: $(TESTPROGS) - -test_asm: LDLIBS = $(PROJECT_LDLIBS) -lm -test_asm: test_asm.o math_utils.o $(LIB_OBJS_ARCH_$(ARCH)) -test_colorconv: LDLIBS = $(PROJECT_LDLIBS) -lm -test_colorconv: test_colorconv.o colorconv.o log.o -test_darray: test_darray.o darray.o memory.o -test_draw: test_draw.o drawutils.o memory.o -test_hmap: test_hmap.o utils.o memory.o -test_utils: test_utils.o utils.o memory.o - -run_test_draw: test_draw - ./$< /tmp/ngl-test.ppm -run_test_%: test_% - ./$< - -tests: $(addprefix run_test_,$(TESTS)) - - -# -# Misc/general -# -clean: - $(RM) $(LIB_BASENAME).so $(LIB_BASENAME).dylib $(LIB_BASENAME).a $(LIB_BASENAME).dll - $(RM) $(LIB_OBJS) $(LIB_DEPS) - $(RM) gen_specs.o gen_specs$(EXESUF) - $(RM) gen_doc.o gen_doc$(EXESUF) - $(RM) $(LIB_PCNAME) - $(RM) $(LD_SYM_FILE) - $(RM) $(TESTPROGS) - $(RM) $(addsuffix .o,$(TESTPROGS)) - -install: $(LIB_NAME) $(LIB_PCNAME) - $(INSTALL) -d $(DESTDIR)$(PREFIX)/lib - $(INSTALL) -d $(DESTDIR)$(PREFIX)/lib/pkgconfig - $(INSTALL) -d $(DESTDIR)$(PREFIX)/include - $(INSTALL) -d $(DESTDIR)$(PREFIX)/share - $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/nodegl - $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/nodegl/java - $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/nodegl/java/org - $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/nodegl/java/org/nodegl -ifeq ($(TARGET_OS),MinGW-w64) -ifeq ($(SHARED),yes) - $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin - $(INSTALL) -m 644 $(LIB_NAME) $(DESTDIR)$(PREFIX)/bin -endif # shared -endif # mingw-w64 - $(INSTALL) -m 644 $(LIB_NAME) $(DESTDIR)$(PREFIX)/lib - $(INSTALL) -m 644 $(LIB_PCNAME) $(DESTDIR)$(PREFIX)/lib/pkgconfig - $(INSTALL) -m 644 nodegl.h $(DESTDIR)$(PREFIX)/include/nodegl.h - $(INSTALL) -m 644 $(SPECS_FILE) $(DESTDIR)$(PREFIX)/share/nodegl - $(INSTALL) -m 644 android/java/OnFrameAvailableListener.java $(DESTDIR)$(PREFIX)/share/nodegl/java/org/nodegl - -uninstall: - $(RM) $(DESTDIR)$(PREFIX)/lib/$(LIB_NAME) - $(RM) $(DESTDIR)$(PREFIX)/bin/$(LIB_NAME) - $(RM) $(DESTDIR)$(PREFIX)/lib/pkgconfig/$(LIB_PCNAME) - $(RM) $(DESTDIR)$(PREFIX)/include/nodegl.h - $(RM) -r $(DESTDIR)$(PREFIX)/share/nodegl - -.PHONY: all updatespecs clean install uninstall gen_gl_wrappers testprogs tests - --include $(LIB_DEPS) diff --git a/libnodegl/libnodegl.darwin.symexport b/libnodegl/libnodegl.darwin.symexport new file mode 100644 index 0000000000..7613caf828 --- /dev/null +++ b/libnodegl/libnodegl.darwin.symexport @@ -0,0 +1 @@ +_ngl_* diff --git a/libnodegl/libnodegl.pc.tpl b/libnodegl/libnodegl.pc.tpl deleted file mode 100644 index fae2806251..0000000000 --- a/libnodegl/libnodegl.pc.tpl +++ /dev/null @@ -1,12 +0,0 @@ -prefix=PREFIX -exec_prefix=${prefix} -includedir=${prefix}/include -libdir=${exec_prefix}/lib -datarootdir=${prefix}/share - -Name: node.gl -Description: Node/Graph based OpenGL engine -Version: VERSION -Cflags: -I${includedir} -Libs: -L${libdir} -lnodegl DEP_LIBS -Libs.private: DEP_PRIVATE_LIBS diff --git a/libnodegl/libnodegl.symexport b/libnodegl/libnodegl.symexport new file mode 100644 index 0000000000..f52b7ae01f --- /dev/null +++ b/libnodegl/libnodegl.symexport @@ -0,0 +1,4 @@ +LIBNODEGL { + global: ngl_*; + local: *; +}; diff --git a/libnodegl/meson.build b/libnodegl/meson.build new file mode 100644 index 0000000000..debc535b36 --- /dev/null +++ b/libnodegl/meson.build @@ -0,0 +1,526 @@ +# +# Copyright 2020 GoPro Inc. +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +project( + 'libnodegl', + 'c', + default_options: ['c_std=c99'], + license: 'Apache', + meson_version: '>= 0.54.0', + version: '0.0.0', +) + +conf_data = configuration_data() +version_array = meson.project_version().split('.') +conf_data.set('version_major', version_array[0]) +conf_data.set('version_minor', version_array[1]) +conf_data.set('version_micro', version_array[2]) + +host_system = host_machine.system() +cpu_family = host_machine.cpu_family() +cc = meson.get_compiler('c') + +# meson currently (0.56.0) doesn't distinguish between macOS and iOS (the +# "darwin" identifier is shared), so we need to test explicitely. +# We are also checking for IPHONE and not IOS but since the latter is actually +# more specific (doesn't include tvOS and watchOS) we may want to consider +# switching to it at some point. +# See https://github.com/mesonbuild/meson/issues/7944 for more information +iphone_check = '''#include +#if !TARGET_OS_IPHONE +#error not iphone +#endif''' +if host_system == 'darwin' and cc.compiles(iphone_check, name: 'iPhone target') + host_system = 'iphone' +endif + +install_rpath = get_option('rpath') ? get_option('prefix') / get_option('libdir') : '' + +debug_opts = get_option('debug_opts') +conf_data_dict = { + 'TARGET_' + host_system.to_upper(): true, + 'ARCH_' + cpu_family.to_upper(): true, + 'CONFIG_SMALL': get_option('small'), + 'DEBUG_GL': 'gl' in debug_opts, + 'DEBUG_MEM': 'mem' in debug_opts, + 'DEBUG_SCENE': 'scene' in debug_opts, +} +if host_system == 'windows' and cc.get_id() != 'msvc' + conf_data_dict += {'TARGET_MINGW_W64': true} +endif + +# This trim prefix is used to make __FILE__ starts from the source dir with +# out-of-tree builds. +# Adjusted from https://github.com/mesonbuild/meson/issues/7485 +trim_prefix = run_command([ + find_program('python'), '-c', 'import sys,os;print(os.path.relpath(*sys.argv[1:3]))', + meson.current_source_dir(), + meson.build_root(), +]).stdout().strip() + +project_args = cc.get_supported_arguments([ + '-Werror=missing-prototypes', + '-fmacro-prefix-map=@0@/='.format(trim_prefix), +]) +add_project_arguments(project_args, language: 'c') + +# Also apply to local generator targets such as gen_doc +# See https://github.com/mesonbuild/meson/issues/7940 +add_project_arguments(project_args, language: 'c', native: true) + + +# +# Library main configuration +# + +lib_version = '0.0.0' +lib_src = files( + 'animation.c', + 'api.c', + 'block.c', + 'bstr.c', + 'buffer.c', + 'colorconv.c', + 'darray.c', + 'deserialize.c', + 'dot.c', + 'drawutils.c', + 'format.c', + 'gctx.c', + 'gtimer.c', + 'hmap.c', + 'hwconv.c', + 'hwupload.c', + 'hwupload_common.c', + 'image.c', + 'log.c', + 'math_utils.c', + 'memory.c', + 'node_animatedbuffer.c', + 'node_animated.c', + 'node_animkeyframe.c', + 'node_block.c', + 'node_buffer.c', + 'node_camera.c', + 'node_circle.c', + 'node_compute.c', + 'node_computeprogram.c', + 'node_geometry.c', + 'node_graphicconfig.c', + 'node_group.c', + 'node_hud.c', + 'node_identity.c', + 'node_io.c', + 'node_media.c', + 'node_program.c', + 'node_quad.c', + 'node_render.c', + 'node_resourceprops.c', + 'node_rotate.c', + 'node_rotatequat.c', + 'node_rtt.c', + 'node_scale.c', + 'node_streamed.c', + 'node_streamedbuffer.c', + 'node_text.c', + 'node_texture.c', + 'node_time.c', + 'node_timerangefilter.c', + 'node_timerangemodes.c', + 'node_transform.c', + 'node_translate.c', + 'node_triangle.c', + 'node_uniform.c', + 'node_userswitch.c', + 'nodes.c', + 'params.c', + 'pass.c', + 'pgcache.c', + 'pgcraft.c', + 'pipeline.c', + 'precision.c', + 'program.c', + 'rendertarget.c', + 'rnode.c', + 'serialize.c', + 'texture.c', + 'transforms.c', + 'utils.c', +) + +if host_machine.cpu_family() == 'aarch64' + lib_src += files('asm_aarch64.S') +endif + +hosts_cfg = { + 'linux': { + 'deps': ['x11'], + }, + 'darwin': { + 'frameworks': ['CoreVideo', 'CoreFoundation', 'AppKit', 'IOSurface'], + }, + 'android': { + 'deps': ['libavcodec'], + 'libs': ['android'], + 'src': files( + 'jni_utils.c', + 'android_ctx.c', + 'android_utils.c', + 'android_looper.c', + 'android_surface.c', + 'android_handler.c', + 'android_handlerthread.c', + 'android_imagereader.c', + ), + }, + 'iphone': { + 'frameworks': ['CoreMedia'], + }, +} + +lib_deps = [ + cc.find_library('m', required: false), + dependency('libsxplayer', version: '>= 9.4.0'), + dependency('threads'), +] + +host_cfg = hosts_cfg.get(host_system, {}) +foreach dep : host_cfg.get('deps', []) + lib_deps += dependency(dep) +endforeach +foreach dep : host_cfg.get('libs', []) + lib_deps += cc.find_library(dep) +endforeach +if 'frameworks' in host_cfg + lib_deps += dependency('appleframeworks', modules: host_cfg.get('frameworks')) +endif +lib_src += host_cfg.get('src', []) + + +# +# VAAPI dependency +# + +libva_version = '>= 1.1.0' +vaapi_enabled = false + +opt_wayland = get_option('wayland') +dep_wayland_client = dependency('wayland-client', required: opt_wayland) +dep_wayland_egl = dependency('wayland-egl', required: opt_wayland) +if dep_wayland_client.found() and dep_wayland_egl.found() + lib_deps += [dep_wayland_client, dep_wayland_egl] + conf_data_dict += {'HAVE_WAYLAND': true} +endif + +opt_vaapi_x11 = get_option('vaapi-x11') +dep_libva_x11 = dependency('libva-x11', version: libva_version, required: opt_vaapi_x11) +dep_libva_x11_drm = dependency('libva-drm', version: libva_version, required: opt_vaapi_x11) +if dep_libva_x11.found() and dep_libva_x11_drm.found() + vaapi_enabled = true + lib_deps += [dep_libva_x11, dep_libva_x11_drm] + conf_data_dict += {'HAVE_VAAPI_X11': true} +endif + +opt_vaapi_wayland = get_option('vaapi-wayland') +dep_libva_wayland = dependency('libva-wayland', version: libva_version, required: opt_vaapi_wayland) +dep_libva_wayland_drm = dependency('libva-drm', version: libva_version, required: opt_vaapi_wayland) +if dep_libva_wayland.found() and dep_libva_wayland_drm.found() + vaapi_enabled = true + lib_deps += [dep_libva_wayland, dep_libva_wayland_drm] + conf_data_dict += {'HAVE_VAAPI_WAYLAND': true} +endif + +if vaapi_enabled + lib_src += files('vaapi.c') + conf_data_dict += {'HAVE_VAAPI': true} +endif + + +# +# Graphic backend dependencies +# + +gbackends_cfg = { + 'gl': { + 'src': files( + 'buffer_gl.c', + 'format_gl.c', + 'gctx_gl.c', + 'glcontext.c', + 'glstate.c', + 'gtimer_gl.c', + 'pipeline_gl.c', + 'program_gl.c', + 'rendertarget_gl.c', + 'texture_gl.c', + 'topology_gl.c', + 'type_gl.c', + ), + 'cfg': 'BACKEND_GL', + 'hosts_cfg': { + 'linux': { + 'src': files('glcontext_egl.c'), + 'cfg': 'HAVE_GLPLATFORM_EGL', + 'deps': ['gl', 'egl'], + }, + 'darwin': { + 'src': files('glcontext_nsgl.m', 'hwupload_videotoolbox_darwin_gl.c'), + 'cfg': 'HAVE_GLPLATFORM_NSGL', + 'frameworks': ['OpenGL'], + }, + 'android': { + 'src': files('glcontext_egl.c', 'hwupload_mediacodec_gl.c'), + 'cfg': 'HAVE_GLPLATFORM_EGL', + 'libs': ['EGL'], + }, + 'iphone': { + 'src': files('glcontext_eagl.m', 'hwupload_videotoolbox_ios_gl.c'), + 'cfg': 'HAVE_GLPLATFORM_EAGL', + }, + 'windows': { + 'src': files('glcontext_wgl.c'), + 'cfg': 'HAVE_GLPLATFORM_WGL', + 'libs': ['opengl32', 'gdi32'], + }, + }, + }, +} + +if host_system in ['darwin', 'iphone'] + add_languages('objc') +endif + +foreach gbackend_name, gbackend_cfg : gbackends_cfg + opt_gbackend = get_option('gbackend-' + gbackend_name) + + host_backend_cfg = gbackend_cfg.get('hosts_cfg').get(host_system, {}) + gbackend_deps = [] + foreach dep : host_backend_cfg.get('deps', []) + gbackend_deps += dependency(dep, required: opt_gbackend) + endforeach + foreach dep : host_backend_cfg.get('libs', []) + gbackend_deps += cc.find_library(dep, required: opt_gbackend) + endforeach + if 'frameworks' in host_backend_cfg + gbackend_deps += dependency('appleframeworks', modules: host_backend_cfg.get('frameworks'), required: opt_gbackend) + endif + + all_dep_found = true + foreach dep : gbackend_deps + all_dep_found = all_dep_found and dep.found() + endforeach + + if all_dep_found + + if gbackend_name == 'gl' and vaapi_enabled + lib_src += files('hwupload_vaapi_gl.c') + endif + + lib_src += gbackend_cfg.get('src') + lib_src += host_backend_cfg.get('src') + lib_deps += gbackend_deps + conf_data_dict += {gbackend_cfg.get('cfg'): true} + conf_data_dict += {host_backend_cfg.get('cfg') : true} + endif +endforeach + + +# +# Library +# + +summary(conf_data_dict) + +c_args = [] +foreach conf_key, conf_value : conf_data_dict + if conf_value + c_args += ['-D' + conf_key] + endif +endforeach +add_project_arguments(c_args, language: 'c') + +# See https://github.com/mesonbuild/meson/pull/4747 to follow advancement on +# the native support for symbol visibility +lib_link_args = cc.get_supported_link_arguments([ + '-Wl,--version-script,@0@'.format(meson.current_source_dir() / 'libnodegl.symexport'), # GNU ld + '-Wl,-exported_symbols_list,@0@'.format(meson.current_source_dir() / 'libnodegl.darwin.symexport'), # Darwin +]) + +libnodegl = library( + 'nodegl', + lib_src, + dependencies: lib_deps, + install: true, + install_rpath: install_rpath, + version: meson.project_version(), + link_args: lib_link_args, +) +lib_header = configure_file( + input: files('nodegl.h.in'), + output: 'nodegl.h', + configuration: conf_data +) +install_headers(lib_header) + +pkg = import('pkgconfig') +pkg.generate( + libnodegl, + name: 'libnodegl', # not specifying the name would fallback on "nodegl.pc" instead of "libnodegl.pc" + description: 'Node/Graph based OpenGL engine', + variables: ['datarootdir=${prefix}/share'], +) + + +# +# Specifications +# + +cp = find_program('cp') +specs_filename = 'nodes.specs' +dest_datadir = get_option('datadir') / 'nodegl' +install_data(files(specs_filename), install_dir: dest_datadir) +install_data(files('android/java/OnFrameAvailableListener.java'), install_dir: dest_datadir / 'java/org/nodegl') + +# XXX: should we use an intermediate static_library() to share the objects +# between the library and these tools? +# https://git.archlinux.org/pacman.git/tree/meson.build?id=4533c6a8e0f39c7707e671b7f9687607b46f1417#n310 +# seem to imply some extract_all_objects(recursive: true) +gen_specs = executable( + 'gen_specs', + lib_src + files('gen_specs.c'), + dependencies: lib_deps, + build_by_default: false, + native: true, +) +specs_file = custom_target( + specs_filename, + command: gen_specs, + capture: true, + output: specs_filename, +) +run_target( + 'updatespecs', + command: [cp, specs_file, meson.current_source_dir()], +) + + +# +# Doc +# + +gen_doc = executable( + 'gen_doc', + lib_src + files('gen_doc.c'), + dependencies: lib_deps, + build_by_default: false, + native: true, +) +doc_file = custom_target( + 'libnodegl.md', + command: gen_doc, + capture: true, + output: 'libnodegl.md', +) +run_target( + 'updatedoc', + command: [cp, doc_file, meson.current_source_dir() / 'doc'], +) + + +# +# OpenGL specifications +# + +gl_xml = custom_target( + 'gl.xml', + command: [find_program('curl'), 'https://raw.githubusercontent.com/KhronosGroup/OpenGL-Registry/master/xml/gl.xml', '-o', '@OUTPUT@'], + capture: false, + output: 'gl.xml', +) +gl_generated_files = custom_target( + 'gl_generated_files', + command: [find_program('gen-gl-wrappers.py'), '@INPUT@', '@OUTPUT@'], + capture: false, + input: gl_xml, + output: ['glfunctions.h', 'gldefinitions_data.h', 'glwrappers.h'], +) +run_target( + 'updateglwrappers', + command: [ + cp, + # FIXME: In 0.56.0, we get "WARNING: custom_target 'gl_generated_files' has + # more than one output! Using the first one.", so we need to do this + # instead of just using gl_generated_files. It also requires the "depends:" + # entry below. + # See https://github.com/mesonbuild/meson/issues/7891 for more information + gl_generated_files[0].full_path(), + gl_generated_files[1].full_path(), + gl_generated_files[2].full_path(), + meson.current_source_dir() + ], + depends: gl_generated_files, +) + + +# +# Tests +# + +test_progs = { + 'Assembly': { + 'exe': 'test_asm', + 'src': files('test_asm.c', 'math_utils.c'), + }, + 'Color convertion': { + 'exe': 'test_colorconv', + 'src': files('test_colorconv.c', 'colorconv.c', 'log.c'), + }, + 'Dynamic array': { + 'exe': 'test_darray', + 'src': files('test_darray.c', 'darray.c', 'memory.c'), + }, + 'Draw utils': { + 'exe': 'test_draw', + 'src': files('test_draw.c', 'drawutils.c', 'memory.c'), + 'args': ['ngl-test.ppm'] + }, + 'Hash map': { + 'exe': 'test_hmap', + 'src': files('test_hmap.c', 'utils.c', 'memory.c'), + }, + 'Utils': { + 'exe': 'test_utils', + 'src': files('test_utils.c', 'utils.c', 'memory.c'), + }, +} + +if get_option('tests') + foreach test_key, test_data : test_progs + exe = executable( + test_data.get('exe'), + test_data.get('src'), + dependencies: lib_deps, + build_by_default: false, + install: false, + ) + test(test_key, exe, args: test_data.get('args', [])) + endforeach +endif diff --git a/libnodegl/meson_options.txt b/libnodegl/meson_options.txt new file mode 100644 index 0000000000..4745a0b451 --- /dev/null +++ b/libnodegl/meson_options.txt @@ -0,0 +1,40 @@ +# +# Copyright 2020 GoPro Inc. +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +option('gbackend-gl', type: 'feature', value: 'auto') + +option('wayland', type: 'feature', value: 'auto', + description: 'Wayland support') +option('vaapi-x11', type: 'feature', value: 'auto', + description: 'VAAPI support for X11') +option('vaapi-wayland', type: 'feature', value: 'auto', + description: 'VAAPI support for Wayland') + +option('rpath', type: 'boolean', value: false, + description: 'install with rpath') +option('small', type: 'boolean', value: false, + description: 'exclude all the documentation strings from the binary') +option('tests', type: 'boolean', value: true) + +option('logtrace', type: 'boolean', value: false, + description: 'log tracing (slow and verbose)') +option('debug_opts', type: 'array', choices: ['gl', 'mem', 'scene'], value: [], + description: 'debugging options for developers') diff --git a/libnodegl/nodegl.h b/libnodegl/nodegl.h.in similarity index 99% rename from libnodegl/nodegl.h rename to libnodegl/nodegl.h.in index 0d5bbf9938..65aa5009c8 100644 --- a/libnodegl/nodegl.h +++ b/libnodegl/nodegl.h.in @@ -22,9 +22,9 @@ #ifndef NODEGL_H #define NODEGL_H -#define NODEGL_VERSION_MAJOR 0 -#define NODEGL_VERSION_MINOR 0 -#define NODEGL_VERSION_MICRO 0 +#define NODEGL_VERSION_MAJOR @version_major@ +#define NODEGL_VERSION_MINOR @version_minor@ +#define NODEGL_VERSION_MICRO @version_micro@ #define NODEGL_GET_VERSION(major, minor, micro) ((major)<<16 | (minor)<<8 | (micro)) diff --git a/ngl-tools/.gitignore b/ngl-tools/.gitignore deleted file mode 100644 index d4dd94199b..0000000000 --- a/ngl-tools/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -/ngl-desktop -/ngl-ipc -/ngl-player -/ngl-probe -/ngl-python -/ngl-render -/ngl-serialize diff --git a/ngl-tools/Makefile b/ngl-tools/Makefile deleted file mode 100644 index 27951f17a5..0000000000 --- a/ngl-tools/Makefile +++ /dev/null @@ -1,158 +0,0 @@ -# -# Copyright 2017 GoPro Inc. -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -include ../common.mak - -NETWORK_CFLAGS := -NETWORK_LDLIBS := - -SDL_CFLAGS := $(shell $(PKG_CONFIG) --cflags sdl2) -SDL_LDLIBS := $(shell $(PKG_CONFIG) --libs sdl2) - -SXPLAYER_CFLAGS := $(shell $(PKG_CONFIG) --cflags libsxplayer) -SXPLAYER_LDLIBS := $(shell $(PKG_CONFIG) --libs libsxplayer) - -TOOLS_CFLAGS := $(shell $(PKG_CONFIG) --cflags libnodegl) -TOOLS_LDLIBS := $(shell $(PKG_CONFIG) --libs libnodegl) -lm - -ifeq ($(TARGET_OS),Darwin) -TOOLS_LDLIBS += -framework AppKit -endif - -ifeq ($(TARGET_OS),MinGW-w64) -NETWORK_LDLIBS += -lws2_32 -endif - -# Warning: while all 3 "python{,2,3}-config" should work as expected, -# "pkg-config --exists python" never will, so an explicit version is needed. -HAS_PYTHON := $(if $(shell pkg-config --exists python$(PYTHON_MAJOR) && echo 1),yes,no) - -TOOLS = desktop ipc player probe render -ifeq ($(HAS_PYTHON),yes) -PYTHON_CFLAGS := $(shell python$(PYTHON_MAJOR)-config --cflags) -# -# After the 2-to-3 migration fiasco, the Python team did a lot of retrospective -# on why it failed. Lesson learned: now instead of bumping major, they do major -# incompatible breakages in minor revisions: -# -# To support both 3.8 and older, try python3-config --libs --embed first and -# fallback to python3-config --libs (without --embed) if the previous command -# fails. -# -# Source: https://docs.python.org/3/whatsnew/3.8.html -# -# Note that we need something more complex than `python3-config ... --embed || -# python3-config ...` because obviously on failure the tool prints on stdout. -# -# And because all of this wasn't enough, we use --ldflags instead of -# --libs because it seems to be the real equivalent of pkg-config --libs. -# Indeed, at least on MacOS --libs doesn't raise the -L flag needed for the -# link to succeed. Note that since they decided that --ldflags was also -# including the libs (when it shouldn't), there is no need to have an -# additional redundant --libs. -# -PYTHON_LDLIBS_EMBED-no = $(shell python$(PYTHON_MAJOR)-config --ldflags) -PYTHON_LDLIBS_EMBED-yes = $(shell python$(PYTHON_MAJOR)-config --ldflags --embed) -PYTHON_HAS_EMBED := $(shell python$(PYTHON_MAJOR)-config --embed >/dev/null && echo yes || echo no) -PYTHON_LDLIBS := $(PYTHON_LDLIBS_EMBED-$(PYTHON_HAS_EMBED)) -TOOLS += python serialize - -# -# This is a workaround for the following link issue: -# relocation R_X86_64_32 against `.rodata.str1.8' can not be used -# when making a PIE object; recompile with -fPIE -# -# To summarize the issue: -# -# - On Python side, `python3-config` is a script generated from a -# template using a global CFLAGS. But at the same time, the script -# uses LIBS and SYSLIBS to construct the LDFLAGS (and not LDFLAGS for -# some moronic reason). -# - On Debian/Ubuntu side, they decided to have the following injected: -# CFLAGS="-specs=/usr/share/dpkg/no-pie-compile.specs" -# LDFLAGS="-specs=/usr/share/dpkg/no-pie-link.specs" -# And unfortunately, the former can not go without the latter -# (otherwise there is a link error). -# -# As a result, `python3-config --cflags` will return the `-specs=...` -# flags, but `python3-config --ldflags` will not return the -# complementary `-specs=...`. -# -DISTRIB_ID := $(or $(shell lsb_release -si 2>/dev/null),none) -ifeq ($(DISTRIB_ID),$(filter $(DISTRIB_ID),Ubuntu Debian)) -PYTHON_LDLIBS += -specs=/usr/share/dpkg/no-pie-link.specs -endif -endif - -TOOLS_BINS = $(addprefix ngl-, $(addsuffix $(EXESUF), $(TOOLS))) - -all: $(TOOLS_BINS) - -WSI_OBJS_Linux = wsi_linux.o -WSI_OBJS_Darwin = wsi_cocoa.o -WSI_OBJS_MinGW-w64 = wsi_windows.o -WSI_OBJS = wsi.o $(WSI_OBJS_$(TARGET_OS)) - -ngl-desktop$(EXESUF): CFLAGS = $(PROJECT_CFLAGS) $(SDL_CFLAGS) $(TOOLS_CFLAGS) $(NETWORK_CFLAGS) -ngl-desktop$(EXESUF): LDLIBS = $(PROJECT_LDLIBS) $(SDL_LDLIBS) $(TOOLS_LDLIBS) $(NETWORK_LDLIBS) -lpthread -ngl-desktop$(EXESUF): ngl-desktop.o ipc.o player.o opts.o $(WSI_OBJS) - -ngl-ipc$(EXESUF): CFLAGS = $(PROJECT_CFLAGS) $(TOOLS_CFLAGS) $(NETWORK_CFLAGS) -ngl-ipc$(EXESUF): LDLIBS = $(PROJECT_LDLIBS) $(TOOLS_LDLIBS) $(NETWORK_LDLIBS) -ngl-ipc$(EXESUF): ngl-ipc.o ipc.o opts.o - -ngl-serialize$(EXESUF): CFLAGS = $(PROJECT_CFLAGS) $(TOOLS_CFLAGS) $(PYTHON_CFLAGS) -ngl-serialize$(EXESUF): LDLIBS = $(PROJECT_LDLIBS) $(TOOLS_LDLIBS) $(PYTHON_LDLIBS) -ngl-serialize$(EXESUF): ngl-serialize.o python_utils.o - -ngl-player$(EXESUF): CFLAGS = $(PROJECT_CFLAGS) $(TOOLS_CFLAGS) $(SDL_CFLAGS) $(SXPLAYER_CFLAGS) -ngl-player$(EXESUF): LDLIBS = $(PROJECT_LDLIBS) $(TOOLS_LDLIBS) $(SDL_LDLIBS) $(SXPLAYER_LDLIBS) -ngl-player$(EXESUF): ngl-player.o player.o opts.o $(WSI_OBJS) - -ngl-probe$(EXESUF): CFLAGS = $(PROJECT_CFLAGS) $(TOOLS_CFLAGS) -ngl-probe$(EXESUF): LDLIBS = $(PROJECT_LDLIBS) $(TOOLS_LDLIBS) -ngl-probe$(EXESUF): ngl-probe.o opts.o - -ngl-render$(EXESUF): CFLAGS = $(PROJECT_CFLAGS) $(TOOLS_CFLAGS) $(SDL_CFLAGS) -ngl-render$(EXESUF): LDLIBS = $(PROJECT_LDLIBS) $(TOOLS_LDLIBS) $(SDL_LDLIBS) -ngl-render$(EXESUF): ngl-render.o opts.o $(WSI_OBJS) - -ngl-python$(EXESUF): CFLAGS = $(PROJECT_CFLAGS) $(TOOLS_CFLAGS) $(SDL_CFLAGS) $(PYTHON_CFLAGS) -ngl-python$(EXESUF): LDLIBS = $(PROJECT_LDLIBS) $(TOOLS_LDLIBS) $(SDL_LDLIBS) $(PYTHON_LDLIBS) -ngl-python$(EXESUF): ngl-python.o player.o python_utils.o opts.o $(WSI_OBJS) - -TOOLS_OBJS = common.o - -$(TOOLS_BINS): $(TOOLS_OBJS) - $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@ - -clean: - $(RM) $(TOOLS_BINS) - $(RM) ngl-*.o common.o player.o python_utils.o wsi*.o - -install: $(TOOLS_BINS) - $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin - $(INSTALL) -m 755 $(TOOLS_BINS) $(DESTDIR)$(PREFIX)/bin - -uninstall: - $(RM) $(addprefix $(DESTDIR)$(PREFIX)/bin/, $(TOOLS_BINS)) - -.PHONY: all clean install uninstall diff --git a/ngl-tools/meson.build b/ngl-tools/meson.build new file mode 100644 index 0000000000..8292da9fa3 --- /dev/null +++ b/ngl-tools/meson.build @@ -0,0 +1,133 @@ +# +# Copyright 2020 GoPro Inc. +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +project( + 'ngl-tools', + 'c', + default_options: ['c_std=c99'], + license: 'Apache', + meson_version: '>= 0.53.0', +) + +host_system = host_machine.system() +cc = meson.get_compiler('c') + +add_project_arguments( + cc.get_supported_arguments([ + '-Werror=missing-prototypes', + ]), + language: 'c', +) + + +# +# Main dependencies +# +tool_deps = [ + cc.find_library('m', required: false), + dependency('libnodegl'), +] +sxplayer_dep = dependency('libsxplayer') # for media probing +threads_dep = dependency('threads', required: false) + +python_mod = import('python') +python_ins = python_mod.find_installation('python3') +python_dep = python_ins.dependency(embed: true, required: get_option('python')) + + +# +# Player based dependencies +# +wsi_src_system = { + 'linux': files('wsi_linux.c'), + 'darwin': files('wsi_cocoa.m'), + 'windows': files('wsi_windows.c'), +} +wsi_src = files('wsi.c') + wsi_src_system.get(host_system, []) +sdl_dep = dependency('sdl2', required: get_option('sdl')) +wsi_deps = [sdl_dep] +if host_system == 'darwin' + wsi_deps += [dependency('appleframeworks', modules: 'AppKit', required: false)] +endif +if host_system == 'darwin' + add_languages('objc') +endif + + +# +# Network dependencies +# +net_deps = [] +if host_system == 'windows' + net_deps += [cc.find_library('ws2_32')] +endif + + +# +# Tools specifications +# +tools_specs = { + 'ngl-desktop': { + 'src': files('ngl-desktop.c', 'ipc.c', 'player.c', 'opts.c') + wsi_src, + 'deps': tool_deps + net_deps + wsi_deps + [threads_dep], + }, + 'ngl-ipc': { + 'src': files('ngl-ipc.c', 'ipc.c', 'opts.c'), + 'deps': tool_deps + net_deps, + }, + 'ngl-player': { + 'src': files('ngl-player.c', 'player.c', 'opts.c') + wsi_src, + 'deps': tool_deps + wsi_deps + [sxplayer_dep], + }, + 'ngl-probe': { + 'src': files('ngl-probe.c', 'opts.c'), + 'deps': tool_deps, + }, + 'ngl-python': { + 'src': files('ngl-python.c', 'player.c', 'python_utils.c', 'opts.c') + wsi_src, + 'deps': tool_deps + wsi_deps + [python_dep], + }, + 'ngl-render': { + 'src': files('ngl-render.c', 'opts.c') + wsi_src, + 'deps': tool_deps + wsi_deps, + }, + 'ngl-serialize': { + 'src': files('ngl-serialize.c', 'python_utils.c'), + 'deps': tool_deps + [python_dep], + }, +} + +foreach tool_name, tool_info : tools_specs + all_dep_found = true + foreach dep : tool_info.get('deps') + all_dep_found = all_dep_found and dep.found() + endforeach + + if all_dep_found + exe = executable( + tool_name, + tool_info.get('src') + files('common.c'), + dependencies: tool_info.get('deps'), + install: true, + install_rpath: get_option('rpath') ? get_option('prefix') / 'lib' : '', + ) + endif +endforeach diff --git a/ngl-tools/meson_options.txt b/ngl-tools/meson_options.txt new file mode 100644 index 0000000000..de20b7adc0 --- /dev/null +++ b/ngl-tools/meson_options.txt @@ -0,0 +1,28 @@ +# +# Copyright 2020 GoPro Inc. +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +option('python', type: 'feature', value: 'auto', + description: 'Python support') +option('sdl', type: 'feature', value: 'auto', + description: 'SDL support') + +option('rpath', type: 'boolean', value: false, + description: 'install with rpath') From 3b3c4ec64776537f6d1e29b36c58a267935987c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 10 Nov 2020 12:53:06 +0100 Subject: [PATCH 256/388] build: inline common.mak in the root Makefile This file is not shared anymore. --- Makefile | 16 +++++++++++++++- common.mak | 36 ------------------------------------ 2 files changed, 15 insertions(+), 37 deletions(-) delete mode 100644 common.mak diff --git a/Makefile b/Makefile index c4f83342e5..64021ff090 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,21 @@ PREFIX ?= $(PWD)/nodegl-env -include common.mak +PYTHON_MAJOR = 3 + +# +# User configuration +# +DEBUG ?= no +COVERAGE ?= no +CURL ?= curl +PYTHON ?= python$(if $(shell which python$(PYTHON_MAJOR) 2> /dev/null),$(PYTHON_MAJOR),) +TAR ?= tar +TARGET_OS ?= $(shell uname -s) + +ifneq ($(shell $(PYTHON) -c "import sys;print(sys.version_info.major)"),$(PYTHON_MAJOR)) +$(error "Python $(PYTHON_MAJOR) not found") +endif SXPLAYER_VERSION ?= 9.6.0 diff --git a/common.mak b/common.mak deleted file mode 100644 index e5b2932ec6..0000000000 --- a/common.mak +++ /dev/null @@ -1,36 +0,0 @@ -# -# Copyright 2017 GoPro Inc. -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -PYTHON_MAJOR = 3 - -# -# User configuration -# -DEBUG ?= no -COVERAGE ?= no -CURL ?= curl -PYTHON ?= python$(if $(shell which python$(PYTHON_MAJOR) 2> /dev/null),$(PYTHON_MAJOR),) -TAR ?= tar -TARGET_OS ?= $(shell uname -s) - -ifneq ($(shell $(PYTHON) -c "import sys;print(sys.version_info.major)"),$(PYTHON_MAJOR)) -$(error "Python $(PYTHON_MAJOR) not found") -endif From 0df8d8b12331e130fb486a0f0ce790ae69c8d7a3 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 12 Nov 2020 14:19:33 +0100 Subject: [PATCH 257/388] build: use Pillow from system site packages on MinGW --- .github/workflows/ci_win.yml | 2 +- Makefile | 7 +++++-- doc/howto/installation.md | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci_win.yml b/.github/workflows/ci_win.yml index 55dad5a9ad..c753aa3355 100644 --- a/.github/workflows/ci_win.yml +++ b/.github/workflows/ci_win.yml @@ -23,7 +23,7 @@ jobs: run: | C:\msys64\usr\bin\bash -lc "pacman -S --noconfirm --needed git make" C:\msys64\usr\bin\bash -lc "pacman -S --noconfirm --needed mingw-w64-x86_64-{toolchain,ffmpeg,python}" - C:\msys64\usr\bin\bash -lc "pacman -S --noconfirm --needed mingw-w64-x86_64-python3-pip" + C:\msys64\usr\bin\bash -lc "pacman -S --noconfirm --needed mingw-w64-x86_64-python3-{pillow,pip}" C:\msys64\usr\bin\bash -lc "pacman -S --noconfirm --needed mingw-w64-x86_64-meson" - name: Build diff --git a/Makefile b/Makefile index 64021ff090..60d94b4d42 100644 --- a/Makefile +++ b/Makefile @@ -92,7 +92,8 @@ pynodegl-utils-install: pynodegl-utils-deps-install # # We do not pull the requirements on Windows because of various issues: # - PySide2 can't be pulled -# - Pillow fails to find zlib +# - Pillow fails to find zlib (required to be installed by the user outside the +# Python virtualenv) # - ngl-control can not currently work because of temporary files handling # # Still, we want the module to be installed so we can access the scene() @@ -139,8 +140,10 @@ sxplayer-$(SXPLAYER_VERSION).tar.gz: # Pillow and PySide2. We require the users to have it on their system. # $(PREFIX): +ifeq ($(TARGET_OS),MinGW-w64) + $(PYTHON) -m venv --system-site-packages $(PREFIX) +else $(PYTHON) -m venv $(PREFIX) -ifneq ($(TARGET_OS),MinGW-w64) (. $(ACTIVATE) && pip install meson ninja) endif diff --git a/doc/howto/installation.md b/doc/howto/installation.md index 193790e801..90c6c8703f 100644 --- a/doc/howto/installation.md +++ b/doc/howto/installation.md @@ -49,7 +49,7 @@ On Windows, the bootstrap is slightly more complex: pacman -Syuu # and restart the shell pacman -S git make pacman -S mingw-w64-x86_64-{toolchain,ffmpeg,python} -pacman -S mingw-w64-x86_64-python3-pip +pacman -S mingw-w64-x86_64-python3-{pillow,pip} pacman -S mingw-w64-x86_64-meson make TARGET_OS=MinGW-w64 ``` From 14748e5cb6455f57407877e34cb9d612fb428fad Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 12 Nov 2020 16:01:13 +0100 Subject: [PATCH 258/388] tests/data: fix pts data array type Uses signed long long (which guarantees a size of at least 8) instead of signed long. Fixes data_streamed_buffer_vec4 and data_streamed_buffer_vec4_time_anim tests on MingGW. --- tests/data.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/data.py b/tests/data.py index 198c7ba9f5..8cdd7b9b6c 100644 --- a/tests/data.py +++ b/tests/data.py @@ -272,7 +272,9 @@ def _get_data_streamed_buffer_vec4_scene(cfg, scale, show_dbg_points): ] time_anim = ngl.AnimatedTime(kfs) - pts_data = array.array('l') + pts_data = array.array('q') + assert pts_data.itemsize == 8 + for i in range(duration): offset = 10000 if i == 0 else 0 pts_data.extend([i * 1000000 + offset]) From be549dace91800f2497f0a31dd89e5b4e16baec3 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 12 Nov 2020 16:12:07 +0100 Subject: [PATCH 259/388] glcontext_wgl: do not try to enable multisampling when offscreen Offscreen rendering is managed by a dedicated framebuffer object. Trying to enable multisampling when offscreen at the context level does not make sense and can cause issues. Fixes running tests that require multisampling on the offscreen framebuffer on MinGW. --- libnodegl/glcontext_wgl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libnodegl/glcontext_wgl.c b/libnodegl/glcontext_wgl.c index 14010bb485..e88e71cf33 100644 --- a/libnodegl/glcontext_wgl.c +++ b/libnodegl/glcontext_wgl.c @@ -142,8 +142,8 @@ static int wgl_init(struct glcontext *ctx, uintptr_t display, uintptr_t window, WGL_DEPTH_BITS_ARB, 24, WGL_STENCIL_BITS_ARB, 8, WGL_DOUBLE_BUFFER_ARB, GL_TRUE, - WGL_SAMPLE_BUFFERS_ARB, ctx->samples > 0 ? GL_TRUE : GL_FALSE, - WGL_SAMPLES_ARB, ctx->samples, + WGL_SAMPLE_BUFFERS_ARB, ctx->offscreen ? 0 : (ctx->samples > 0), + WGL_SAMPLES_ARB, ctx->offscreen ? 0 : ctx->samples, 0 }; From 90e9a537ac82553cf84e30d626f9be32468d4a9e Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 12 Nov 2020 16:25:22 +0100 Subject: [PATCH 260/388] build: do not rely on symbolic links on MinGW Symbolic links are not supported on MinGW. --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 60d94b4d42..18157973cf 100644 --- a/Makefile +++ b/Makefile @@ -127,7 +127,11 @@ sxplayer-install: sxplayer $(PREFIX) # than the prerequisite directory of the sxplayer rule. If this isn't true, the # symlink will be re-recreated on the next `make` call sxplayer: sxplayer-$(SXPLAYER_VERSION) +ifneq ($(TARGET_OS),MinGW-w64) ln -snf $< $@ +else + cp -r $< $@ +endif sxplayer-$(SXPLAYER_VERSION): sxplayer-$(SXPLAYER_VERSION).tar.gz $(TAR) xf $< From 9fa4e790a268b88cb7fcefbba669df079575721a Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 12 Nov 2020 15:33:40 +0100 Subject: [PATCH 261/388] internal: workaround a critical gcc 10 issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Workarounds a critical issue¹ that has not been fixed within the last 6 months. [1]: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95189 [1]: http://r6.ca/blog/20200929T023701Z.html --- libnodegl/node_camera.c | 2 +- libnodegl/node_rotate.c | 2 +- libnodegl/node_rotatequat.c | 2 +- libnodegl/node_scale.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libnodegl/node_camera.c b/libnodegl/node_camera.c index 25e8842318..b21a0655bf 100644 --- a/libnodegl/node_camera.c +++ b/libnodegl/node_camera.c @@ -114,7 +114,7 @@ static int camera_init(struct ngl_node *node) return NGL_ERROR_INVALID_ARG; } - static const float zvec[4] = {0}; + static const float zvec[4]; s->use_perspective = memcmp(s->perspective, zvec, sizeof(s->perspective)); s->use_orthographic = memcmp(s->orthographic, zvec, sizeof(s->orthographic)); diff --git a/libnodegl/node_rotate.c b/libnodegl/node_rotate.c index f8bb47c087..3e82970653 100644 --- a/libnodegl/node_rotate.c +++ b/libnodegl/node_rotate.c @@ -61,7 +61,7 @@ static void update_trf_matrix(struct ngl_node *node, float deg_angle) static int rotate_init(struct ngl_node *node) { struct rotate_priv *s = node->priv_data; - static const float zvec[3] = {0}; + static const float zvec[3]; if (!memcmp(s->axis, zvec, sizeof(s->axis))) { LOG(ERROR, "(0.0, 0.0, 0.0) is not a valid axis"); return NGL_ERROR_INVALID_ARG; diff --git a/libnodegl/node_rotatequat.c b/libnodegl/node_rotatequat.c index 702ec3f102..100f953b5d 100644 --- a/libnodegl/node_rotatequat.c +++ b/libnodegl/node_rotatequat.c @@ -58,7 +58,7 @@ static void update_trf_matrix(struct ngl_node *node, const float *quat) static int rotatequat_init(struct ngl_node *node) { struct rotatequat_priv *s = node->priv_data; - static const float zvec[3] = {0}; + static const float zvec[3]; s->use_anchor = memcmp(s->anchor, zvec, sizeof(zvec)); if (!s->anim) update_trf_matrix(node, s->quat); diff --git a/libnodegl/node_scale.c b/libnodegl/node_scale.c index 92740ef2b7..045f8c7047 100644 --- a/libnodegl/node_scale.c +++ b/libnodegl/node_scale.c @@ -57,7 +57,7 @@ static void update_trf_matrix(struct ngl_node *node, const float *f) static int scale_init(struct ngl_node *node) { struct scale_priv *s = node->priv_data; - static const float zero_anchor[3] = {0}; + static const float zero_anchor[3]; s->use_anchor = memcmp(s->anchor, zero_anchor, sizeof(s->anchor)); if (!s->anim) update_trf_matrix(node, s->factors); From 457af3f9e9243847328c9d0a692144f4bc03d964 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 12 Nov 2020 23:26:10 +0100 Subject: [PATCH 262/388] tests/rtt: add rtt_sample_depth test Also removes the shape_geometry_rtt_depth test as this new test supersedes it. This test also cares to only sample the values from the first component. --- tests/refs/rtt_sample_depth.ref | 10 ++++++++++ tests/refs/shape_geometry_rtt_depth.ref | 1 - tests/rtt.mak | 1 + tests/rtt.py | 26 ++++++++++++++++++++----- tests/shape.mak | 1 - tests/shape.py | 6 ------ 6 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 tests/refs/rtt_sample_depth.ref delete mode 100644 tests/refs/shape_geometry_rtt_depth.ref diff --git a/tests/refs/rtt_sample_depth.ref b/tests/refs/rtt_sample_depth.ref new file mode 100644 index 0000000000..fc7cb8d333 --- /dev/null +++ b/tests/refs/rtt_sample_depth.ref @@ -0,0 +1,10 @@ +055C00008228877C8228877CC77C0000 055C00008228877C8228877CC77C0000 055C00008228877C8228877CC77C0000 00000000000000000000000000000000 +0070000002A081FAC24FC76C157C007C 0070000002A081FAC24FC76C157C007C 0070000002A081FAC24FC76C157C007C 00000000000000000000000000000000 +0C0C00A300A8078F15AF15FC05F331CC 0C0C00A300A8078F15AF15FC05F331CC 0C0C00A300A8078F15AF15FC05F331CC 00000000000000000000000000000000 +000C00A380A8C0BA14AF15FF017C0C3C 000C00A380A8C0BA14AF15FF017C0C3C 000C00A380A8C0BA14AF15FF017C0C3C 00000000000000000000000000000000 +0C0C00A300E8162A171FC5FC31F30C0C 0C0C00A300E8162A171FC5FC31F30C0C 0C0C00A300E8162A171FC5FC31F30C0C 00000000000000000000000000000000 +055C00008228877C8228877CC77C0000 055C00008228877C8228877CC77C0000 055C00008228877C8228877CC77C0000 00000000000000000000000000000000 +000C028083ABC22C1E2C15FC01700000 000C028083ABC22C1E2C15FC01700000 000C028083ABC22C1E2C15FC01700000 00000000000000000000000000000000 +03000CA330A8C1EA05EB157FC5FF31F0 03000CA330A8C1EA05EB157FC5FF31F0 03000CA330A8C1EA05EB157FC5FF31F0 00000000000000000000000000000000 +0070020CC2A001FA14FF15FCC5FC31F0 0070020CC2A001FA14FF15FCC5FC31F0 0070020CC2A001FA14FF15FCC5FC31F0 00000000000000000000000000000000 +03000C0C30A3C0E81F7A173FC5FC31C0 03000C0C30A3C0E81F7A173FC5FC31C0 03000C0C30A3C0E81F7A173FC5FC31C0 00000000000000000000000000000000 diff --git a/tests/refs/shape_geometry_rtt_depth.ref b/tests/refs/shape_geometry_rtt_depth.ref deleted file mode 100644 index bb6d7321ee..0000000000 --- a/tests/refs/shape_geometry_rtt_depth.ref +++ /dev/null @@ -1 +0,0 @@ -308000EA07FA7F4A380A1C071F3FC5FC 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 diff --git a/tests/rtt.mak b/tests/rtt.mak index 93483e65b3..8099227af3 100644 --- a/tests/rtt.mak +++ b/tests/rtt.mak @@ -24,6 +24,7 @@ RTT_TEST_NAMES = \ feature_depth \ feature_depth_stencil \ mipmap \ + sample_depth \ texture_depth \ texture_depth_stencil \ clear_attachment_with_timeranges \ diff --git a/tests/rtt.py b/tests/rtt.py index 46e34f824f..265d86f1ca 100644 --- a/tests/rtt.py +++ b/tests/rtt.py @@ -95,7 +95,16 @@ def _get_cube(): ''' -def _get_rtt_scene(cfg, features='depth', texture_ds_format=None, samples=0, mipmap_filter='none'): +_RENDER_DEPTH = ''' +void main() +{ + float depth = ngl_texvideo(tex0, var_tex0_coord).r; + ngl_out_color = vec4(depth, depth, depth, 1.0); +} +''' + + +def _get_rtt_scene(cfg, features='depth', texture_ds_format=None, samples=0, mipmap_filter='none', sample_depth=False): cfg.duration = 10 cfg.aspect_ratio = (1, 1) cube = _get_cube() @@ -144,10 +153,16 @@ def _get_rtt_scene(cfg, features='depth', texture_ds_format=None, samples=0, mip ) quad = ngl.Quad((-1, -1, 0), (2, 0, 0), (0, 2, 0)) - program = ngl.Program(vertex=cfg.get_vert('texture'), fragment=cfg.get_frag('texture')) - program.update_vert_out_vars(var_tex0_coord=ngl.IOVec2(), var_uvcoord=ngl.IOVec2()) - render = ngl.Render(quad, program) - render.update_frag_resources(tex0=texture) + if sample_depth: + program = ngl.Program(vertex=cfg.get_vert('texture'), fragment=_RENDER_DEPTH) + program.update_vert_out_vars(var_tex0_coord=ngl.IOVec2(), var_uvcoord=ngl.IOVec2()) + render = ngl.Render(quad, program) + render.update_frag_resources(tex0=texture_depth) + else: + program = ngl.Program(vertex=cfg.get_vert('texture'), fragment=cfg.get_frag('texture')) + program.update_vert_out_vars(var_tex0_coord=ngl.IOVec2(), var_uvcoord=ngl.IOVec2()) + render = ngl.Render(quad, program) + render.update_frag_resources(tex0=texture) return ngl.Group(children=(rtt, render)) @@ -165,6 +180,7 @@ def rtt_function(cfg): feature_depth_msaa=dict(features='depth', samples=4), feature_depth_stencil_msaa=dict(features='depth+stencil', samples=4), mipmap=dict(features='depth', mipmap_filter='linear'), + sample_depth=dict(texture_ds_format='auto_depth', sample_depth=True), texture_depth=dict(texture_ds_format='auto_depth'), texture_depth_stencil=dict(texture_ds_format='auto_depth_stencil'), texture_depth_msaa=dict(texture_ds_format='auto_depth', samples=4), diff --git a/tests/shape.mak b/tests/shape.mak index 38df7aac08..a5644787bc 100644 --- a/tests/shape.mak +++ b/tests/shape.mak @@ -37,7 +37,6 @@ SHAPE_TEST_NAMES = \ geometry_indices \ geometry_normals_indices \ geometry_rtt \ - geometry_rtt_depth \ morphing \ cropboard \ cropboard_indices \ diff --git a/tests/shape.py b/tests/shape.py index 0ab3e34344..941117b1d3 100644 --- a/tests/shape.py +++ b/tests/shape.py @@ -225,12 +225,6 @@ def shape_geometry_rtt(cfg): return _shape_geometry_rtt(cfg) -@test_fingerprint() -@scene() -def shape_geometry_rtt_depth(cfg): - return _shape_geometry_rtt(cfg, depth=True) - - @test_fingerprint() @scene() def shape_geometry_rtt_samples(cfg): From 7d0b97d204ca7fec87a410f4c3f1af98191a4444 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 12 Nov 2020 23:30:35 +0100 Subject: [PATCH 263/388] tests/shape: remove geometry_rtt_* tests These tests are already covered by the rtt test suite. --- tests/refs/shape_geometry_rtt.ref | 1 - tests/refs/shape_geometry_rtt_samples.ref | 1 - tests/shape.mak | 4 +- tests/shape.py | 48 ----------------------- 4 files changed, 1 insertion(+), 53 deletions(-) delete mode 100644 tests/refs/shape_geometry_rtt.ref delete mode 100644 tests/refs/shape_geometry_rtt_samples.ref diff --git a/tests/refs/shape_geometry_rtt.ref b/tests/refs/shape_geometry_rtt.ref deleted file mode 100644 index 2bfc69b05c..0000000000 --- a/tests/refs/shape_geometry_rtt.ref +++ /dev/null @@ -1 +0,0 @@ -3883C0A901FC5FF31A0C1E308790C7CC 308C00E0030A680E187A168F157F55FC 87557F55FCD5A3F1AA25AA30AA802A01 00000000000000000000000000000000 diff --git a/tests/refs/shape_geometry_rtt_samples.ref b/tests/refs/shape_geometry_rtt_samples.ref deleted file mode 100644 index 2bfc69b05c..0000000000 --- a/tests/refs/shape_geometry_rtt_samples.ref +++ /dev/null @@ -1 +0,0 @@ -3883C0A901FC5FF31A0C1E308790C7CC 308C00E0030A680E187A168F157F55FC 87557F55FCD5A3F1AA25AA30AA802A01 00000000000000000000000000000000 diff --git a/tests/shape.mak b/tests/shape.mak index a5644787bc..9fc78b519d 100644 --- a/tests/shape.mak +++ b/tests/shape.mak @@ -36,14 +36,12 @@ SHAPE_TEST_NAMES = \ geometry_normals \ geometry_indices \ geometry_normals_indices \ - geometry_rtt \ morphing \ cropboard \ cropboard_indices \ ifneq ($(MAX_SAMPLES),$(filter $(MAX_SAMPLES),0 1)) -SHAPE_TEST_NAMES += geometry_rtt_samples \ - triangle_msaa \ +SHAPE_TEST_NAMES += triangle_msaa \ endif diff --git a/tests/shape.py b/tests/shape.py index 941117b1d3..a0074fec22 100644 --- a/tests/shape.py +++ b/tests/shape.py @@ -183,54 +183,6 @@ def shape_diamond_colormask(cfg): return autogrid_simple(scenes) -def _shape_geometry_rtt(cfg, depth=False, samples=0): - w, h = 640, 480 - - scene = _shape_geometry(cfg, set_normals=True) - - if depth: - scene = ngl.GraphicConfig(scene, depth_test=True) - - texture = ngl.Texture2D() - texture.set_width(w) - texture.set_height(h) - - rtt = ngl.RenderToTexture(scene) - rtt.add_color_textures(texture) - - if depth: - texture = ngl.Texture2D() - texture.set_format('auto_depth') - texture.set_width(w) - texture.set_height(h) - rtt.set_depth_texture(texture) - else: - rtt.set_clear_color(*COLORS['cgreen']) - - if samples: - rtt.set_samples(samples) - - quad = ngl.Quad((-1, -1, 0), (2, 0, 0), (0, 2, 0)) - program = ngl.Program(vertex=cfg.get_vert('texture'), fragment=cfg.get_frag('texture')) - program.update_vert_out_vars(var_tex0_coord=ngl.IOVec2(), var_uvcoord=ngl.IOVec2()) - render = ngl.Render(quad, program) - render.update_frag_resources(tex0=texture) - - return ngl.Group(children=(rtt, render)) - - -@test_fingerprint() -@scene() -def shape_geometry_rtt(cfg): - return _shape_geometry_rtt(cfg) - - -@test_fingerprint() -@scene() -def shape_geometry_rtt_samples(cfg): - return _shape_geometry_rtt(cfg, samples=4) - - def _get_morphing_coordinates(n, x_off, y_off): coords = [(random.uniform(0, 1) + x_off, random.uniform(0, 1) + y_off, 0) for i in range(n - 1)] From 2c80e15053feb716dc28e97c696f3415ac885aef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 13 Nov 2020 00:40:20 +0100 Subject: [PATCH 264/388] build: enable LTO by default in the venv --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 18157973cf..677123fc9c 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ endif ifeq ($(DEBUG),yes) MESON_SETUP += --buildtype=debugoptimized else -MESON_SETUP += --buildtype=release +MESON_SETUP += --buildtype=release -Db_lto=true endif ifneq ($(V),) MESON_COMPILE += -v From 61cbec714a7d7f8abe5f8e93d1ec7a8b733f94d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 13 Nov 2020 12:19:35 +0100 Subject: [PATCH 265/388] build: fix C args not being forwarded to native tools This is similar to what is done with project_args: # Also apply to local generator targets such as gen_doc # See https://github.com/mesonbuild/meson/issues/7940 add_project_arguments(project_args, language: 'c', native: true) In this case, we are fixing the missing C args when building gen_doc and gen_specs, which is typically causing a compilation failure. One example is when VAAPI is found but the corresponding compiler args are not set. --- libnodegl/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/libnodegl/meson.build b/libnodegl/meson.build index debc535b36..d3a3393802 100644 --- a/libnodegl/meson.build +++ b/libnodegl/meson.build @@ -356,6 +356,7 @@ foreach conf_key, conf_value : conf_data_dict endif endforeach add_project_arguments(c_args, language: 'c') +add_project_arguments(c_args, language: 'c', native: true) # See https://github.com/mesonbuild/meson/pull/4747 to follow advancement on # the native support for symbol visibility From 46f7ab26210c30f300eccaffda6205a9ec72c316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 13 Nov 2020 12:20:47 +0100 Subject: [PATCH 266/388] build: split nodegl-install rule with nodegl-setup We need this granularity for the next commit. --- Makefile | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 677123fc9c..f5a6f198bd 100644 --- a/Makefile +++ b/Makefile @@ -110,8 +110,11 @@ pynodegl-install: pynodegl-deps-install pynodegl-deps-install: $(PREFIX) nodegl-install (. $(ACTIVATE) && pip install -r ./pynodegl/requirements.txt) -nodegl-install: sxplayer-install - (. $(ACTIVATE) && $(MESON_SETUP) libnodegl builddir/libnodegl && $(MESON_COMPILE) -C builddir/libnodegl && $(MESON_INSTALL) -C builddir/libnodegl) +nodegl-install: nodegl-setup + (. $(ACTIVATE) && $(MESON_COMPILE) -C builddir/libnodegl && $(MESON_INSTALL) -C builddir/libnodegl) + +nodegl-setup: sxplayer-install + (. $(ACTIVATE) && $(MESON_SETUP) libnodegl builddir/libnodegl) sxplayer-install: sxplayer $(PREFIX) (. $(ACTIVATE) && $(MESON_SETUP) sxplayer builddir/sxplayer && $(MESON_COMPILE) -C builddir/sxplayer && $(MESON_INSTALL) -C builddir/sxplayer) @@ -188,7 +191,7 @@ coverage-xml: .PHONY: ngl-tools-install .PHONY: pynodegl-utils-install pynodegl-utils-deps-install .PHONY: pynodegl-install pynodegl-deps-install -.PHONY: nodegl-install +.PHONY: nodegl-install nodegl-setup .PHONY: sxplayer-install .PHONY: tests .PHONY: clean clean_py From 158ceb719bba43a8f4e7fabc8cd8f6cb6e55825d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 13 Nov 2020 12:25:41 +0100 Subject: [PATCH 267/388] build: do not make custom target calls depend on the install of the lib Most libnodegl targets do not depend on the compilation and installation of the library. Typically, nodegl-updatespecs only needs to call a Python script. Similarly, nodegl-updatedoc and nodegl-updatespecs are independant from the library itself (they share the source references and flags). Also, if a target does need the installation, the target chaining within meson handles it for us. In addition to making the explicit target calls simpler and faster, it also actually fixes the generation of the headers in nodegl-updateglwrappers when you decide to remove them beforehand (to make sure they are re-generated) because it won't try to compile with the missing headers anymore. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f5a6f198bd..44a4ee855a 100644 --- a/Makefile +++ b/Makefile @@ -160,7 +160,7 @@ tests: ngl-tools-install pynodegl-utils-install nodegl-tests nodegl-tests: nodegl-install (. $(ACTIVATE) && meson test -C builddir/libnodegl) -nodegl-%: nodegl-install +nodegl-%: nodegl-setup (. $(ACTIVATE) && $(MESON_COMPILE) -C builddir/libnodegl $(subst nodegl-,,$@)) clean_py: From 83d0a8166851dddf5eac906517432620cf58826d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 13 Nov 2020 11:34:21 +0100 Subject: [PATCH 268/388] build: move GL backend specific code to a dedicated directory This operation is a simple move to help the isolation of the backends themselves. Files have not been renamed, nor the include directives adjusted because are automatically picked relatively to the current directory of the source file. This also means that removing the _gl suffixes from filenames would typically still clash with the identically named files in libnodegl root directory. This needs to be addressed carefully in a future commit. The only source modifications are in api.c and node_media.c, respectively for Apple and Android platforms. --- libnodegl/api.c | 2 +- libnodegl/{ => backends/gl}/buffer_gl.c | 0 libnodegl/{ => backends/gl}/buffer_gl.h | 0 libnodegl/{ => backends/gl}/egl.h | 0 libnodegl/{ => backends/gl}/format_gl.c | 0 libnodegl/{ => backends/gl}/format_gl.h | 0 libnodegl/{ => backends/gl}/gctx_gl.c | 0 libnodegl/{ => backends/gl}/gctx_gl.h | 0 libnodegl/{ => backends/gl}/glcontext.c | 0 libnodegl/{ => backends/gl}/glcontext.h | 0 libnodegl/{ => backends/gl}/glcontext_eagl.m | 0 libnodegl/{ => backends/gl}/glcontext_egl.c | 0 libnodegl/{ => backends/gl}/glcontext_nsgl.m | 0 libnodegl/{ => backends/gl}/glcontext_wgl.c | 0 .../{ => backends/gl}/gldefinitions_data.h | 0 libnodegl/{ => backends/gl}/glfeatures_data.h | 0 libnodegl/{ => backends/gl}/glfunctions.h | 0 libnodegl/{ => backends/gl}/glincludes.h | 0 libnodegl/{ => backends/gl}/glstate.c | 0 libnodegl/{ => backends/gl}/glstate.h | 0 libnodegl/{ => backends/gl}/glwrappers.h | 0 libnodegl/{ => backends/gl}/gtimer_gl.c | 0 libnodegl/{ => backends/gl}/gtimer_gl.h | 0 .../gl}/hwupload_mediacodec_gl.c | 0 .../{ => backends/gl}/hwupload_vaapi_gl.c | 0 .../gl}/hwupload_videotoolbox_darwin_gl.c | 0 .../gl}/hwupload_videotoolbox_ios_gl.c | 0 libnodegl/{ => backends/gl}/pipeline_gl.c | 0 libnodegl/{ => backends/gl}/pipeline_gl.h | 0 libnodegl/{ => backends/gl}/program_gl.c | 0 libnodegl/{ => backends/gl}/program_gl.h | 0 libnodegl/{ => backends/gl}/rendertarget_gl.c | 0 libnodegl/{ => backends/gl}/rendertarget_gl.h | 0 libnodegl/{ => backends/gl}/texture_gl.c | 0 libnodegl/{ => backends/gl}/texture_gl.h | 0 libnodegl/{ => backends/gl}/topology_gl.c | 0 libnodegl/{ => backends/gl}/topology_gl.h | 0 libnodegl/{ => backends/gl}/type_gl.c | 0 libnodegl/{ => backends/gl}/type_gl.h | 0 libnodegl/meson.build | 38 +++++++++---------- libnodegl/node_media.c | 2 +- 41 files changed, 21 insertions(+), 21 deletions(-) rename libnodegl/{ => backends/gl}/buffer_gl.c (100%) rename libnodegl/{ => backends/gl}/buffer_gl.h (100%) rename libnodegl/{ => backends/gl}/egl.h (100%) rename libnodegl/{ => backends/gl}/format_gl.c (100%) rename libnodegl/{ => backends/gl}/format_gl.h (100%) rename libnodegl/{ => backends/gl}/gctx_gl.c (100%) rename libnodegl/{ => backends/gl}/gctx_gl.h (100%) rename libnodegl/{ => backends/gl}/glcontext.c (100%) rename libnodegl/{ => backends/gl}/glcontext.h (100%) rename libnodegl/{ => backends/gl}/glcontext_eagl.m (100%) rename libnodegl/{ => backends/gl}/glcontext_egl.c (100%) rename libnodegl/{ => backends/gl}/glcontext_nsgl.m (100%) rename libnodegl/{ => backends/gl}/glcontext_wgl.c (100%) rename libnodegl/{ => backends/gl}/gldefinitions_data.h (100%) rename libnodegl/{ => backends/gl}/glfeatures_data.h (100%) rename libnodegl/{ => backends/gl}/glfunctions.h (100%) rename libnodegl/{ => backends/gl}/glincludes.h (100%) rename libnodegl/{ => backends/gl}/glstate.c (100%) rename libnodegl/{ => backends/gl}/glstate.h (100%) rename libnodegl/{ => backends/gl}/glwrappers.h (100%) rename libnodegl/{ => backends/gl}/gtimer_gl.c (100%) rename libnodegl/{ => backends/gl}/gtimer_gl.h (100%) rename libnodegl/{ => backends/gl}/hwupload_mediacodec_gl.c (100%) rename libnodegl/{ => backends/gl}/hwupload_vaapi_gl.c (100%) rename libnodegl/{ => backends/gl}/hwupload_videotoolbox_darwin_gl.c (100%) rename libnodegl/{ => backends/gl}/hwupload_videotoolbox_ios_gl.c (100%) rename libnodegl/{ => backends/gl}/pipeline_gl.c (100%) rename libnodegl/{ => backends/gl}/pipeline_gl.h (100%) rename libnodegl/{ => backends/gl}/program_gl.c (100%) rename libnodegl/{ => backends/gl}/program_gl.h (100%) rename libnodegl/{ => backends/gl}/rendertarget_gl.c (100%) rename libnodegl/{ => backends/gl}/rendertarget_gl.h (100%) rename libnodegl/{ => backends/gl}/texture_gl.c (100%) rename libnodegl/{ => backends/gl}/texture_gl.h (100%) rename libnodegl/{ => backends/gl}/topology_gl.c (100%) rename libnodegl/{ => backends/gl}/topology_gl.h (100%) rename libnodegl/{ => backends/gl}/type_gl.c (100%) rename libnodegl/{ => backends/gl}/type_gl.h (100%) diff --git a/libnodegl/api.c b/libnodegl/api.c index 12920cb195..d1f8e26eef 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -47,7 +47,7 @@ #if defined(TARGET_DARWIN) || defined(TARGET_IPHONE) #if defined(BACKEND_GL) -#include "gctx_gl.h" +#include "backends/gl/gctx_gl.h" #endif #endif diff --git a/libnodegl/buffer_gl.c b/libnodegl/backends/gl/buffer_gl.c similarity index 100% rename from libnodegl/buffer_gl.c rename to libnodegl/backends/gl/buffer_gl.c diff --git a/libnodegl/buffer_gl.h b/libnodegl/backends/gl/buffer_gl.h similarity index 100% rename from libnodegl/buffer_gl.h rename to libnodegl/backends/gl/buffer_gl.h diff --git a/libnodegl/egl.h b/libnodegl/backends/gl/egl.h similarity index 100% rename from libnodegl/egl.h rename to libnodegl/backends/gl/egl.h diff --git a/libnodegl/format_gl.c b/libnodegl/backends/gl/format_gl.c similarity index 100% rename from libnodegl/format_gl.c rename to libnodegl/backends/gl/format_gl.c diff --git a/libnodegl/format_gl.h b/libnodegl/backends/gl/format_gl.h similarity index 100% rename from libnodegl/format_gl.h rename to libnodegl/backends/gl/format_gl.h diff --git a/libnodegl/gctx_gl.c b/libnodegl/backends/gl/gctx_gl.c similarity index 100% rename from libnodegl/gctx_gl.c rename to libnodegl/backends/gl/gctx_gl.c diff --git a/libnodegl/gctx_gl.h b/libnodegl/backends/gl/gctx_gl.h similarity index 100% rename from libnodegl/gctx_gl.h rename to libnodegl/backends/gl/gctx_gl.h diff --git a/libnodegl/glcontext.c b/libnodegl/backends/gl/glcontext.c similarity index 100% rename from libnodegl/glcontext.c rename to libnodegl/backends/gl/glcontext.c diff --git a/libnodegl/glcontext.h b/libnodegl/backends/gl/glcontext.h similarity index 100% rename from libnodegl/glcontext.h rename to libnodegl/backends/gl/glcontext.h diff --git a/libnodegl/glcontext_eagl.m b/libnodegl/backends/gl/glcontext_eagl.m similarity index 100% rename from libnodegl/glcontext_eagl.m rename to libnodegl/backends/gl/glcontext_eagl.m diff --git a/libnodegl/glcontext_egl.c b/libnodegl/backends/gl/glcontext_egl.c similarity index 100% rename from libnodegl/glcontext_egl.c rename to libnodegl/backends/gl/glcontext_egl.c diff --git a/libnodegl/glcontext_nsgl.m b/libnodegl/backends/gl/glcontext_nsgl.m similarity index 100% rename from libnodegl/glcontext_nsgl.m rename to libnodegl/backends/gl/glcontext_nsgl.m diff --git a/libnodegl/glcontext_wgl.c b/libnodegl/backends/gl/glcontext_wgl.c similarity index 100% rename from libnodegl/glcontext_wgl.c rename to libnodegl/backends/gl/glcontext_wgl.c diff --git a/libnodegl/gldefinitions_data.h b/libnodegl/backends/gl/gldefinitions_data.h similarity index 100% rename from libnodegl/gldefinitions_data.h rename to libnodegl/backends/gl/gldefinitions_data.h diff --git a/libnodegl/glfeatures_data.h b/libnodegl/backends/gl/glfeatures_data.h similarity index 100% rename from libnodegl/glfeatures_data.h rename to libnodegl/backends/gl/glfeatures_data.h diff --git a/libnodegl/glfunctions.h b/libnodegl/backends/gl/glfunctions.h similarity index 100% rename from libnodegl/glfunctions.h rename to libnodegl/backends/gl/glfunctions.h diff --git a/libnodegl/glincludes.h b/libnodegl/backends/gl/glincludes.h similarity index 100% rename from libnodegl/glincludes.h rename to libnodegl/backends/gl/glincludes.h diff --git a/libnodegl/glstate.c b/libnodegl/backends/gl/glstate.c similarity index 100% rename from libnodegl/glstate.c rename to libnodegl/backends/gl/glstate.c diff --git a/libnodegl/glstate.h b/libnodegl/backends/gl/glstate.h similarity index 100% rename from libnodegl/glstate.h rename to libnodegl/backends/gl/glstate.h diff --git a/libnodegl/glwrappers.h b/libnodegl/backends/gl/glwrappers.h similarity index 100% rename from libnodegl/glwrappers.h rename to libnodegl/backends/gl/glwrappers.h diff --git a/libnodegl/gtimer_gl.c b/libnodegl/backends/gl/gtimer_gl.c similarity index 100% rename from libnodegl/gtimer_gl.c rename to libnodegl/backends/gl/gtimer_gl.c diff --git a/libnodegl/gtimer_gl.h b/libnodegl/backends/gl/gtimer_gl.h similarity index 100% rename from libnodegl/gtimer_gl.h rename to libnodegl/backends/gl/gtimer_gl.h diff --git a/libnodegl/hwupload_mediacodec_gl.c b/libnodegl/backends/gl/hwupload_mediacodec_gl.c similarity index 100% rename from libnodegl/hwupload_mediacodec_gl.c rename to libnodegl/backends/gl/hwupload_mediacodec_gl.c diff --git a/libnodegl/hwupload_vaapi_gl.c b/libnodegl/backends/gl/hwupload_vaapi_gl.c similarity index 100% rename from libnodegl/hwupload_vaapi_gl.c rename to libnodegl/backends/gl/hwupload_vaapi_gl.c diff --git a/libnodegl/hwupload_videotoolbox_darwin_gl.c b/libnodegl/backends/gl/hwupload_videotoolbox_darwin_gl.c similarity index 100% rename from libnodegl/hwupload_videotoolbox_darwin_gl.c rename to libnodegl/backends/gl/hwupload_videotoolbox_darwin_gl.c diff --git a/libnodegl/hwupload_videotoolbox_ios_gl.c b/libnodegl/backends/gl/hwupload_videotoolbox_ios_gl.c similarity index 100% rename from libnodegl/hwupload_videotoolbox_ios_gl.c rename to libnodegl/backends/gl/hwupload_videotoolbox_ios_gl.c diff --git a/libnodegl/pipeline_gl.c b/libnodegl/backends/gl/pipeline_gl.c similarity index 100% rename from libnodegl/pipeline_gl.c rename to libnodegl/backends/gl/pipeline_gl.c diff --git a/libnodegl/pipeline_gl.h b/libnodegl/backends/gl/pipeline_gl.h similarity index 100% rename from libnodegl/pipeline_gl.h rename to libnodegl/backends/gl/pipeline_gl.h diff --git a/libnodegl/program_gl.c b/libnodegl/backends/gl/program_gl.c similarity index 100% rename from libnodegl/program_gl.c rename to libnodegl/backends/gl/program_gl.c diff --git a/libnodegl/program_gl.h b/libnodegl/backends/gl/program_gl.h similarity index 100% rename from libnodegl/program_gl.h rename to libnodegl/backends/gl/program_gl.h diff --git a/libnodegl/rendertarget_gl.c b/libnodegl/backends/gl/rendertarget_gl.c similarity index 100% rename from libnodegl/rendertarget_gl.c rename to libnodegl/backends/gl/rendertarget_gl.c diff --git a/libnodegl/rendertarget_gl.h b/libnodegl/backends/gl/rendertarget_gl.h similarity index 100% rename from libnodegl/rendertarget_gl.h rename to libnodegl/backends/gl/rendertarget_gl.h diff --git a/libnodegl/texture_gl.c b/libnodegl/backends/gl/texture_gl.c similarity index 100% rename from libnodegl/texture_gl.c rename to libnodegl/backends/gl/texture_gl.c diff --git a/libnodegl/texture_gl.h b/libnodegl/backends/gl/texture_gl.h similarity index 100% rename from libnodegl/texture_gl.h rename to libnodegl/backends/gl/texture_gl.h diff --git a/libnodegl/topology_gl.c b/libnodegl/backends/gl/topology_gl.c similarity index 100% rename from libnodegl/topology_gl.c rename to libnodegl/backends/gl/topology_gl.c diff --git a/libnodegl/topology_gl.h b/libnodegl/backends/gl/topology_gl.h similarity index 100% rename from libnodegl/topology_gl.h rename to libnodegl/backends/gl/topology_gl.h diff --git a/libnodegl/type_gl.c b/libnodegl/backends/gl/type_gl.c similarity index 100% rename from libnodegl/type_gl.c rename to libnodegl/backends/gl/type_gl.c diff --git a/libnodegl/type_gl.h b/libnodegl/backends/gl/type_gl.h similarity index 100% rename from libnodegl/type_gl.h rename to libnodegl/backends/gl/type_gl.h diff --git a/libnodegl/meson.build b/libnodegl/meson.build index d3a3393802..3685d788c1 100644 --- a/libnodegl/meson.build +++ b/libnodegl/meson.build @@ -261,42 +261,42 @@ endif gbackends_cfg = { 'gl': { 'src': files( - 'buffer_gl.c', - 'format_gl.c', - 'gctx_gl.c', - 'glcontext.c', - 'glstate.c', - 'gtimer_gl.c', - 'pipeline_gl.c', - 'program_gl.c', - 'rendertarget_gl.c', - 'texture_gl.c', - 'topology_gl.c', - 'type_gl.c', + 'backends/gl/buffer_gl.c', + 'backends/gl/format_gl.c', + 'backends/gl/gctx_gl.c', + 'backends/gl/glcontext.c', + 'backends/gl/glstate.c', + 'backends/gl/gtimer_gl.c', + 'backends/gl/pipeline_gl.c', + 'backends/gl/program_gl.c', + 'backends/gl/rendertarget_gl.c', + 'backends/gl/texture_gl.c', + 'backends/gl/topology_gl.c', + 'backends/gl/type_gl.c', ), 'cfg': 'BACKEND_GL', 'hosts_cfg': { 'linux': { - 'src': files('glcontext_egl.c'), + 'src': files('backends/gl/glcontext_egl.c'), 'cfg': 'HAVE_GLPLATFORM_EGL', 'deps': ['gl', 'egl'], }, 'darwin': { - 'src': files('glcontext_nsgl.m', 'hwupload_videotoolbox_darwin_gl.c'), + 'src': files('backends/gl/glcontext_nsgl.m', 'backends/gl/hwupload_videotoolbox_darwin_gl.c'), 'cfg': 'HAVE_GLPLATFORM_NSGL', 'frameworks': ['OpenGL'], }, 'android': { - 'src': files('glcontext_egl.c', 'hwupload_mediacodec_gl.c'), + 'src': files('backends/gl/glcontext_egl.c', 'backends/gl/hwupload_mediacodec_gl.c'), 'cfg': 'HAVE_GLPLATFORM_EGL', 'libs': ['EGL'], }, 'iphone': { - 'src': files('glcontext_eagl.m', 'hwupload_videotoolbox_ios_gl.c'), + 'src': files('backends/gl/glcontext_eagl.m', 'backends/gl/hwupload_videotoolbox_ios_gl.c'), 'cfg': 'HAVE_GLPLATFORM_EAGL', }, 'windows': { - 'src': files('glcontext_wgl.c'), + 'src': files('backends/gl/glcontext_wgl.c'), 'cfg': 'HAVE_GLPLATFORM_WGL', 'libs': ['opengl32', 'gdi32'], }, @@ -331,7 +331,7 @@ foreach gbackend_name, gbackend_cfg : gbackends_cfg if all_dep_found if gbackend_name == 'gl' and vaapi_enabled - lib_src += files('hwupload_vaapi_gl.c') + lib_src += files('backends/gl/hwupload_vaapi_gl.c') endif lib_src += gbackend_cfg.get('src') @@ -475,7 +475,7 @@ run_target( gl_generated_files[0].full_path(), gl_generated_files[1].full_path(), gl_generated_files[2].full_path(), - meson.current_source_dir() + meson.current_source_dir() / 'backends/gl' ], depends: gl_generated_files, ) diff --git a/libnodegl/node_media.c b/libnodegl/node_media.c index f3582573b4..d08dfe1270 100644 --- a/libnodegl/node_media.c +++ b/libnodegl/node_media.c @@ -35,7 +35,7 @@ #if defined(TARGET_ANDROID) #include "gctx.h" -#include "texture_gl.h" +#include "backends/gl/texture_gl.h" #endif static const struct param_choices sxplayer_log_level_choices = { From 260fac36bb50b1531272669411df8cab1b1f9ca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 13 Nov 2020 13:12:46 +0100 Subject: [PATCH 269/388] build: generate a config.h header instead of passing down c_args To make sure no include was forgotten, this commit has been checked with the following command: find libnodegl -regextype posix-egrep -iregex '.*\.(c|h|m)' -print0 | xargs -0 chk.py chk.py: #!/usr/bin/env python import sys defines = ( 'TARGET_IPHONE', 'TARGET_LINUX', 'TARGET_ANDROID', 'TARGET_MINGW_W64', 'TARGET_DARWIN', 'ARCH_', 'CONFIG_SMALL', 'DEBUG_GL', 'DEBUG_MEM', 'DEBUG_SCENE', 'HAVE_' ) for fname in sys.argv[1:]: print(f'{fname}... ', end='') with open(fname) as f: file_content = f.read() if any(define in file_content for define in defines): print('checking config.h include') assert '#include "config.h"' in file_content else: print('skip') --- libnodegl/api.c | 2 ++ libnodegl/backends/gl/egl.h | 1 + libnodegl/backends/gl/gctx_gl.c | 2 ++ libnodegl/backends/gl/gctx_gl.h | 2 ++ libnodegl/backends/gl/glcontext.c | 1 + libnodegl/backends/gl/glcontext_egl.c | 2 ++ libnodegl/backends/gl/glcontext_wgl.c | 1 + libnodegl/backends/gl/glincludes.h | 2 ++ libnodegl/backends/gl/glwrappers.h | 2 ++ libnodegl/backends/gl/rendertarget_gl.c | 1 + libnodegl/gen-gl-wrappers.py | 2 ++ libnodegl/hwupload.c | 1 + libnodegl/log.c | 1 + libnodegl/math_utils.h | 2 ++ libnodegl/memory.c | 2 ++ libnodegl/meson.build | 40 ++++++++++--------------- libnodegl/node_media.c | 2 ++ libnodegl/node_rtt.c | 1 + libnodegl/nodes.h | 2 ++ libnodegl/pgcraft.c | 1 + libnodegl/utils.h | 2 ++ libnodegl/vaapi.c | 2 ++ 22 files changed, 49 insertions(+), 25 deletions(-) diff --git a/libnodegl/api.c b/libnodegl/api.c index d1f8e26eef..ea8ae33025 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -24,6 +24,8 @@ #include #include +#include "config.h" + #if defined(TARGET_ANDROID) #include diff --git a/libnodegl/backends/gl/egl.h b/libnodegl/backends/gl/egl.h index eec5962c96..45fe771cc7 100644 --- a/libnodegl/backends/gl/egl.h +++ b/libnodegl/backends/gl/egl.h @@ -25,6 +25,7 @@ #include #include +#include "config.h" #include "glcontext.h" EGLImageKHR ngli_eglCreateImageKHR(struct glcontext *gl, diff --git a/libnodegl/backends/gl/gctx_gl.c b/libnodegl/backends/gl/gctx_gl.c index b2488908ba..fc420411f1 100644 --- a/libnodegl/backends/gl/gctx_gl.c +++ b/libnodegl/backends/gl/gctx_gl.c @@ -21,6 +21,8 @@ #include +#include "config.h" + #if defined(TARGET_IPHONE) #include #endif diff --git a/libnodegl/backends/gl/gctx_gl.h b/libnodegl/backends/gl/gctx_gl.h index 262154d246..c19acb6667 100644 --- a/libnodegl/backends/gl/gctx_gl.h +++ b/libnodegl/backends/gl/gctx_gl.h @@ -22,6 +22,8 @@ #ifndef GCTX_GL_H #define GCTX_GL_H +#include "config.h" + #if defined(TARGET_IPHONE) #include #endif diff --git a/libnodegl/backends/gl/glcontext.c b/libnodegl/backends/gl/glcontext.c index ba6b546257..43677e4ccc 100644 --- a/libnodegl/backends/gl/glcontext.c +++ b/libnodegl/backends/gl/glcontext.c @@ -24,6 +24,7 @@ #include #include "bstr.h" +#include "config.h" #include "glcontext.h" #include "limit.h" #include "log.h" diff --git a/libnodegl/backends/gl/glcontext_egl.c b/libnodegl/backends/gl/glcontext_egl.c index a824af23cb..1bbbeb5ddd 100644 --- a/libnodegl/backends/gl/glcontext_egl.c +++ b/libnodegl/backends/gl/glcontext_egl.c @@ -23,6 +23,8 @@ #include #include +#include "config.h" + #if defined(TARGET_LINUX) #include #endif diff --git a/libnodegl/backends/gl/glcontext_wgl.c b/libnodegl/backends/gl/glcontext_wgl.c index e88e71cf33..a47bc14fa2 100644 --- a/libnodegl/backends/gl/glcontext_wgl.c +++ b/libnodegl/backends/gl/glcontext_wgl.c @@ -26,6 +26,7 @@ #include #include +#include "config.h" #include "glcontext.h" #include "nodegl.h" #include "log.h" diff --git a/libnodegl/backends/gl/glincludes.h b/libnodegl/backends/gl/glincludes.h index 2e69db2483..c8852f461f 100644 --- a/libnodegl/backends/gl/glincludes.h +++ b/libnodegl/backends/gl/glincludes.h @@ -22,6 +22,8 @@ #ifndef GLINCLUDES_H #define GLINCLUDES_H +#include "config.h" + #if __APPLE__ # include # if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR diff --git a/libnodegl/backends/gl/glwrappers.h b/libnodegl/backends/gl/glwrappers.h index 7319f8250b..69f7ea803e 100644 --- a/libnodegl/backends/gl/glwrappers.h +++ b/libnodegl/backends/gl/glwrappers.h @@ -2,6 +2,8 @@ /* WARNING: this file must only be included once */ +#include "config.h" + #ifdef DEBUG_GL # define check_error_code ngli_glcontext_check_gl_error #else diff --git a/libnodegl/backends/gl/rendertarget_gl.c b/libnodegl/backends/gl/rendertarget_gl.c index 6117a8a003..b5336a51c1 100644 --- a/libnodegl/backends/gl/rendertarget_gl.c +++ b/libnodegl/backends/gl/rendertarget_gl.c @@ -21,6 +21,7 @@ #include +#include "config.h" #include "rendertarget_gl.h" #include "format.h" #include "gctx_gl.h" diff --git a/libnodegl/gen-gl-wrappers.py b/libnodegl/gen-gl-wrappers.py index 2bcce77a5b..8fe449c9be 100644 --- a/libnodegl/gen-gl-wrappers.py +++ b/libnodegl/gen-gl-wrappers.py @@ -271,6 +271,8 @@ def gen(gl_xml, func_file, def_file, wrap_file): glwrappers = do_not_edit + ''' /* WARNING: this file must only be included once */ +#include "config.h" + #ifdef DEBUG_GL # define check_error_code ngli_glcontext_check_gl_error #else diff --git a/libnodegl/hwupload.c b/libnodegl/hwupload.c index 6c1fd184be..d336468e9a 100644 --- a/libnodegl/hwupload.c +++ b/libnodegl/hwupload.c @@ -24,6 +24,7 @@ #include #include +#include "config.h" #include "hwupload.h" #include "log.h" #include "math_utils.h" diff --git a/libnodegl/log.c b/libnodegl/log.c index 5c4cb70e2c..7ad77810cc 100644 --- a/libnodegl/log.c +++ b/libnodegl/log.c @@ -24,6 +24,7 @@ #include #include +#include "config.h" #include "log.h" static void default_callback(void *arg, int level, const char *filename, int ln, diff --git a/libnodegl/math_utils.h b/libnodegl/math_utils.h index eeb5398e4e..f5f7280637 100644 --- a/libnodegl/math_utils.h +++ b/libnodegl/math_utils.h @@ -22,6 +22,8 @@ #ifndef MATH_UTILS_H #define MATH_UTILS_H +#include "config.h" + #ifndef M_PI #define M_PI 3.14159265358979323846 #endif diff --git a/libnodegl/memory.c b/libnodegl/memory.c index 66f308a4e4..0e296c00ed 100644 --- a/libnodegl/memory.c +++ b/libnodegl/memory.c @@ -19,6 +19,8 @@ * under the License. */ +#include "config.h" + #ifndef TARGET_MINGW_W64 #define _POSIX_C_SOURCE 200809L // posix_memalign() #endif diff --git a/libnodegl/meson.build b/libnodegl/meson.build index 3685d788c1..8b6a2688a9 100644 --- a/libnodegl/meson.build +++ b/libnodegl/meson.build @@ -55,16 +55,15 @@ endif install_rpath = get_option('rpath') ? get_option('prefix') / get_option('libdir') : '' debug_opts = get_option('debug_opts') -conf_data_dict = { - 'TARGET_' + host_system.to_upper(): true, - 'ARCH_' + cpu_family.to_upper(): true, - 'CONFIG_SMALL': get_option('small'), - 'DEBUG_GL': 'gl' in debug_opts, - 'DEBUG_MEM': 'mem' in debug_opts, - 'DEBUG_SCENE': 'scene' in debug_opts, -} +conf_data.set10('TARGET_' + host_system.to_upper(), true) +conf_data.set10('ARCH_' + cpu_family.to_upper(), true) +conf_data.set10('CONFIG_SMALL', get_option('small')) +conf_data.set10('DEBUG_GL', 'gl' in debug_opts) +conf_data.set10('DEBUG_MEM', 'mem' in debug_opts) +conf_data.set10('DEBUG_SCENE', 'scene' in debug_opts) + if host_system == 'windows' and cc.get_id() != 'msvc' - conf_data_dict += {'TARGET_MINGW_W64': true} + conf_data.set10('TARGET_MINGW_W64', true) endif # This trim prefix is used to make __FILE__ starts from the source dir with @@ -227,7 +226,7 @@ dep_wayland_client = dependency('wayland-client', required: opt_wayland) dep_wayland_egl = dependency('wayland-egl', required: opt_wayland) if dep_wayland_client.found() and dep_wayland_egl.found() lib_deps += [dep_wayland_client, dep_wayland_egl] - conf_data_dict += {'HAVE_WAYLAND': true} + conf_data.set10('HAVE_WAYLAND', true) endif opt_vaapi_x11 = get_option('vaapi-x11') @@ -236,7 +235,7 @@ dep_libva_x11_drm = dependency('libva-drm', version: libva_version, required: op if dep_libva_x11.found() and dep_libva_x11_drm.found() vaapi_enabled = true lib_deps += [dep_libva_x11, dep_libva_x11_drm] - conf_data_dict += {'HAVE_VAAPI_X11': true} + conf_data.set10('HAVE_VAAPI_X11', true) endif opt_vaapi_wayland = get_option('vaapi-wayland') @@ -245,12 +244,12 @@ dep_libva_wayland_drm = dependency('libva-drm', version: libva_version, required if dep_libva_wayland.found() and dep_libva_wayland_drm.found() vaapi_enabled = true lib_deps += [dep_libva_wayland, dep_libva_wayland_drm] - conf_data_dict += {'HAVE_VAAPI_WAYLAND': true} + conf_data.set10('HAVE_VAAPI_WAYLAND', true) endif if vaapi_enabled lib_src += files('vaapi.c') - conf_data_dict += {'HAVE_VAAPI': true} + conf_data.set10('HAVE_VAAPI', true) endif @@ -337,8 +336,8 @@ foreach gbackend_name, gbackend_cfg : gbackends_cfg lib_src += gbackend_cfg.get('src') lib_src += host_backend_cfg.get('src') lib_deps += gbackend_deps - conf_data_dict += {gbackend_cfg.get('cfg'): true} - conf_data_dict += {host_backend_cfg.get('cfg') : true} + conf_data.set10(gbackend_cfg.get('cfg'), true) + conf_data.set10(host_backend_cfg.get('cfg'), true) endif endforeach @@ -347,16 +346,7 @@ endforeach # Library # -summary(conf_data_dict) - -c_args = [] -foreach conf_key, conf_value : conf_data_dict - if conf_value - c_args += ['-D' + conf_key] - endif -endforeach -add_project_arguments(c_args, language: 'c') -add_project_arguments(c_args, language: 'c', native: true) +configure_file(output: 'config.h', configuration: conf_data) # See https://github.com/mesonbuild/meson/pull/4747 to follow advancement on # the native support for symbol visibility diff --git a/libnodegl/node_media.c b/libnodegl/node_media.c index d08dfe1270..b93e37d3bf 100644 --- a/libnodegl/node_media.c +++ b/libnodegl/node_media.c @@ -24,6 +24,8 @@ #include #include +#include "config.h" + #if defined(TARGET_ANDROID) #include #include "android_imagereader.h" diff --git a/libnodegl/node_rtt.c b/libnodegl/node_rtt.c index a585a0cebd..6b5334ac52 100644 --- a/libnodegl/node_rtt.c +++ b/libnodegl/node_rtt.c @@ -23,6 +23,7 @@ #include #include +#include "config.h" #include "rendertarget.h" #include "format.h" #include "gctx.h" diff --git a/libnodegl/nodes.h b/libnodegl/nodes.h index d41d4df041..f4b5bfb88f 100644 --- a/libnodegl/nodes.h +++ b/libnodegl/nodes.h @@ -26,6 +26,8 @@ #include #include +#include "config.h" + #if defined(HAVE_VAAPI_X11) #include #endif diff --git a/libnodegl/pgcraft.c b/libnodegl/pgcraft.c index 8467c80544..c86a4d707e 100644 --- a/libnodegl/pgcraft.c +++ b/libnodegl/pgcraft.c @@ -23,6 +23,7 @@ #include #include +#include "config.h" #include "gctx.h" #include "format.h" #include "hmap.h" diff --git a/libnodegl/utils.h b/libnodegl/utils.h index 47309d0871..3bea7e84e6 100644 --- a/libnodegl/utils.h +++ b/libnodegl/utils.h @@ -26,6 +26,8 @@ #include #include +#include "config.h" + #ifdef __GNUC__ # define ngli_printf_format(fmtpos, attrpos) __attribute__((__format__(__printf__, fmtpos, attrpos))) #else diff --git a/libnodegl/vaapi.c b/libnodegl/vaapi.c index a5e8f1489d..5d107cf08f 100644 --- a/libnodegl/vaapi.c +++ b/libnodegl/vaapi.c @@ -19,6 +19,8 @@ * under the License. */ +#include "config.h" + #if defined(HAVE_VAAPI_X11) #include #include From e45e78fb0f60471b29d1867c16f2b6ee812633c9 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 16 Nov 2020 09:42:05 +0100 Subject: [PATCH 270/388] gctx: expose ngli_gctx_{begin,end}_draw() Also inlines ngli_gctx_draw() in cmd_draw() as the graphics abstraction shouldn't have to deal with anything related to the node.gl context. --- libnodegl/api.c | 22 +++++++++++++++++++++- libnodegl/gctx.c | 29 ++++++----------------------- libnodegl/gctx.h | 3 ++- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/libnodegl/api.c b/libnodegl/api.c index ea8ae33025..a8f62dba6d 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -235,8 +235,28 @@ static int cmd_draw(struct ngl_ctx *s, void *arg) if (ret < 0) return ret; + ret = ngli_gctx_begin_draw(s->gctx, t); + if (ret < 0) + goto end; + + struct rendertarget *rt = ngli_gctx_get_default_rendertarget(s->gctx); + s->available_rendertargets[0] = rt; + s->available_rendertargets[1] = rt; + s->current_rendertarget = rt; + s->begin_render_pass = 0; + struct ngl_node *scene = s->scene; - return ngli_gctx_draw(s->gctx, scene, t); + if (scene) { + LOG(DEBUG, "draw scene %s @ t=%f", scene->label, t); + ngli_node_draw(scene); + } + +end:; + int end_ret = ngli_gctx_end_draw(s->gctx, t); + if (end_ret < 0) + return end_ret; + + return ret; } static int dispatch_cmd(struct ngl_ctx *s, cmd_func_type cmd_func, void *arg) diff --git a/libnodegl/gctx.c b/libnodegl/gctx.c index f3be101f77..2d602886ae 100644 --- a/libnodegl/gctx.c +++ b/libnodegl/gctx.c @@ -81,31 +81,14 @@ int ngli_gctx_resize(struct gctx *s, int width, int height, const int *viewport) return class->resize(s, width, height, viewport); } -int ngli_gctx_draw(struct gctx *s, struct ngl_node *scene, double t) +int ngli_gctx_begin_draw(struct gctx *s, double t) { - const struct gctx_class *class = s->class; - - int ret = class->begin_draw(s, t); - if (ret < 0) - goto end; - - if (scene) { - struct ngl_ctx *ctx = scene->ctx; - struct rendertarget *rt = ngli_gctx_get_default_rendertarget(s); - ctx->available_rendertargets[0] = rt; - ctx->available_rendertargets[1] = rt; - ctx->current_rendertarget = rt; - ctx->begin_render_pass = 0; - LOG(DEBUG, "draw scene %s @ t=%f", scene->label, t); - ngli_node_draw(scene); - } - -end:; - int end_ret = class->end_draw(s, t); - if (end_ret < 0) - return end_ret; + return s->class->begin_draw(s, t); +} - return ret; +int ngli_gctx_end_draw(struct gctx *s, double t) +{ + return s->class->end_draw(s, t); } void ngli_gctx_freep(struct gctx **sp) diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index b92967bb99..26e8291846 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -113,7 +113,8 @@ struct gctx { struct gctx *ngli_gctx_create(const struct ngl_config *config); int ngli_gctx_init(struct gctx *s); int ngli_gctx_resize(struct gctx *s, int width, int height, const int *viewport); -int ngli_gctx_draw(struct gctx *s, struct ngl_node *scene, double t); +int ngli_gctx_begin_draw(struct gctx *s, double t); +int ngli_gctx_end_draw(struct gctx *s, double t); void ngli_gctx_freep(struct gctx **sp); int ngli_gctx_transform_cull_mode(struct gctx *s, int cull_mode); From 328095f842da66006dd499b5876cca49cb1e38e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 17 Nov 2020 10:19:41 +0100 Subject: [PATCH 271/388] hud: store ctx at init for simpler access in widgets --- libnodegl/node_hud.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libnodegl/node_hud.c b/libnodegl/node_hud.c index 365f55f3eb..1dac0f26cf 100644 --- a/libnodegl/node_hud.c +++ b/libnodegl/node_hud.c @@ -45,6 +45,7 @@ #include "graphicstate.h" struct hud_priv { + struct ngl_ctx *ctx; struct ngl_node *child; int measure_window; int refresh_rate[2]; @@ -328,12 +329,10 @@ struct widget_spec { static int widget_latency_init(struct ngl_node *node, struct widget *widget) { - struct ngl_ctx *ctx = node->ctx; - struct gctx *gctx = ctx->gctx; struct hud_priv *s = node->priv_data; struct widget_latency *priv = widget->priv_data; - priv->timer = ngli_gtimer_create(gctx); + priv->timer = ngli_gtimer_create(s->ctx->gctx); if (!priv->timer) return NGL_ERROR_MEMORY; @@ -1231,6 +1230,8 @@ static int hud_init(struct ngl_node *node) struct gctx *gctx = ctx->gctx; struct hud_priv *s = node->priv_data; + s->ctx = ctx; + int ret = widgets_init(node); if (ret < 0) return ret; From 79aeb49bdaecf29b27921129178876a072d3c238 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 17 Nov 2020 10:20:34 +0100 Subject: [PATCH 272/388] hud: transmit the private context instead of the node where appropriate --- libnodegl/node_hud.c | 114 ++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 66 deletions(-) diff --git a/libnodegl/node_hud.c b/libnodegl/node_hud.c index 1dac0f26cf..5bb3b1c442 100644 --- a/libnodegl/node_hud.c +++ b/libnodegl/node_hud.c @@ -317,19 +317,18 @@ struct widget_spec { int graph_w, graph_h; int nb_data_graph; size_t priv_size; - int (*init)(struct ngl_node *node, struct widget *widget); - void (*make_stats)(struct ngl_node *node, struct widget *widget); - void (*draw)(struct ngl_node *node, struct widget *widget); - void (*csv_header)(struct ngl_node *node, struct widget *widget, struct bstr *dst); - void (*csv_report)(struct ngl_node *node, struct widget *widget, struct bstr *dst); - void (*uninit)(struct ngl_node *node, struct widget *widget); + int (*init)(struct hud_priv *s, struct widget *widget); + void (*make_stats)(struct hud_priv *s, struct widget *widget); + void (*draw)(struct hud_priv *s, struct widget *widget); + void (*csv_header)(struct hud_priv *s, struct widget *widget, struct bstr *dst); + void (*csv_report)(struct hud_priv *s, struct widget *widget, struct bstr *dst); + void (*uninit)(struct hud_priv *s, struct widget *widget); }; /* Widget init */ -static int widget_latency_init(struct ngl_node *node, struct widget *widget) +static int widget_latency_init(struct hud_priv *s, struct widget *widget) { - struct hud_priv *s = node->priv_data; struct widget_latency *priv = widget->priv_data; priv->timer = ngli_gtimer_create(s->ctx->gctx); @@ -404,9 +403,8 @@ static int make_nodes_set(struct ngl_node *scene, struct darray *nodes_list, con return 0; } -static int widget_memory_init(struct ngl_node *node, struct widget *widget) +static int widget_memory_init(struct hud_priv *s, struct widget *widget) { - struct hud_priv *s = node->priv_data; struct widget_memory *priv = widget->priv_data; for (int i = 0; i < NB_MEMORY; i++) { @@ -418,18 +416,16 @@ static int widget_memory_init(struct ngl_node *node, struct widget *widget) return 0; } -static int widget_activity_init(struct ngl_node *node, struct widget *widget) +static int widget_activity_init(struct hud_priv *s, struct widget *widget) { - struct hud_priv *s = node->priv_data; const struct activity_spec *spec = widget->user_data; struct widget_activity *priv = widget->priv_data; const int *node_types = spec->node_types; return make_nodes_set(s->child, &priv->nodes, node_types); } -static int widget_drawcall_init(struct ngl_node *node, struct widget *widget) +static int widget_drawcall_init(struct hud_priv *s, struct widget *widget) { - struct hud_priv *s = node->priv_data; const struct drawcall_spec *spec = widget->user_data; struct widget_drawcall *priv = widget->priv_data; const int *node_types = spec->node_types; @@ -446,10 +442,9 @@ static void register_time(struct hud_priv *s, struct latency_measure *m, int64_t m->count = NGLI_MIN(m->count + 1, s->measure_window); } -static int widget_latency_update(struct ngl_node *node, struct widget *widget, double t) +static int widget_latency_update(struct hud_priv *s, struct widget *widget, double t) { int ret; - struct hud_priv *s = node->priv_data; struct ngl_node *child = s->child; struct widget_latency *priv = widget->priv_data; @@ -468,9 +463,8 @@ static int widget_latency_update(struct ngl_node *node, struct widget *widget, d /* Widget make stats */ -static void widget_latency_make_stats(struct ngl_node *node, struct widget *widget) +static void widget_latency_make_stats(struct hud_priv *s, struct widget *widget) { - struct hud_priv *s = node->priv_data; struct widget_latency *priv = widget->priv_data; ngli_gtimer_start(priv->timer); @@ -494,7 +488,7 @@ static void widget_latency_make_stats(struct ngl_node *node, struct widget *widg register_time(s, &priv->measures[LATENCY_TOTAL_GPU], gpu_tdraw + gpu_tupdate); } -static void widget_memory_make_stats(struct ngl_node *node, struct widget *widget) +static void widget_memory_make_stats(struct hud_priv *s, struct widget *widget) { struct widget_memory *priv = widget->priv_data; @@ -545,7 +539,7 @@ static void widget_memory_make_stats(struct ngl_node *node, struct widget *widge } } -static void widget_activity_make_stats(struct ngl_node *node, struct widget *widget) +static void widget_activity_make_stats(struct hud_priv *s, struct widget *widget) { struct widget_activity *priv = widget->priv_data; struct darray *nodes_array = &priv->nodes; @@ -555,7 +549,7 @@ static void widget_activity_make_stats(struct ngl_node *node, struct widget *wid priv->nb_actives += nodes[i]->is_active; } -static void widget_drawcall_make_stats(struct ngl_node *node, struct widget *widget) +static void widget_drawcall_make_stats(struct hud_priv *s, struct widget *widget) { struct widget_drawcall *priv = widget->priv_data; struct darray *nodes_array = &priv->nodes; @@ -694,9 +688,8 @@ static int64_t get_latency_avg(const struct widget_latency *priv, int id) return m->total_times / m->count / (latency_specs[id].unit == 'u' ? 1 : 1000); } -static void widget_latency_draw(struct ngl_node *node, struct widget *widget) +static void widget_latency_draw(struct hud_priv *s, struct widget *widget) { - struct hud_priv *s = node->priv_data; struct widget_latency *priv = widget->priv_data; char buf[LATENCY_WIDGET_TEXT_LEN + 1]; @@ -723,9 +716,8 @@ static void widget_latency_draw(struct ngl_node *node, struct widget *widget) } } -static void widget_memory_draw(struct ngl_node *node, struct widget *widget) +static void widget_memory_draw(struct hud_priv *s, struct widget *widget) { - struct hud_priv *s = node->priv_data; struct widget_memory *priv = widget->priv_data; char buf[MEMORY_WIDGET_TEXT_LEN + 1]; @@ -761,9 +753,8 @@ static void widget_memory_draw(struct ngl_node *node, struct widget *widget) } } -static void widget_activity_draw(struct ngl_node *node, struct widget *widget) +static void widget_activity_draw(struct hud_priv *s, struct widget *widget) { - struct hud_priv *s = node->priv_data; struct widget_activity *priv = widget->priv_data; const struct activity_spec *spec = widget->user_data; const uint32_t color = 0x3df4f4ff; @@ -778,9 +769,8 @@ static void widget_activity_draw(struct ngl_node *node, struct widget *widget) draw_block_graph(s, d, &widget->graph_rect, d->amin, d->amax, color); } -static void widget_drawcall_draw(struct ngl_node *node, struct widget *widget) +static void widget_drawcall_draw(struct hud_priv *s, struct widget *widget) { - struct hud_priv *s = node->priv_data; struct widget_drawcall *priv = widget->priv_data; const struct drawcall_spec *spec = widget->user_data; const uint32_t color = 0x3df43dff; @@ -797,25 +787,25 @@ static void widget_drawcall_draw(struct ngl_node *node, struct widget *widget) /* Widget CSV header */ -static void widget_latency_csv_header(struct ngl_node *node, struct widget *widget, struct bstr *dst) +static void widget_latency_csv_header(struct hud_priv *s, struct widget *widget, struct bstr *dst) { for (int i = 0; i < NB_LATENCY; i++) ngli_bstr_printf(dst, "%s%s", i ? "," : "", latency_specs[i].label); } -static void widget_memory_csv_header(struct ngl_node *node, struct widget *widget, struct bstr *dst) +static void widget_memory_csv_header(struct hud_priv *s, struct widget *widget, struct bstr *dst) { for (int i = 0; i < NB_MEMORY; i++) ngli_bstr_printf(dst, "%s%s memory", i ? "," : "", memory_specs[i].label); } -static void widget_activity_csv_header(struct ngl_node *node, struct widget *widget, struct bstr *dst) +static void widget_activity_csv_header(struct hud_priv *s, struct widget *widget, struct bstr *dst) { const struct activity_spec *spec = widget->user_data; ngli_bstr_printf(dst, "%s count,%s total", spec->label, spec->label); } -static void widget_drawcall_csv_header(struct ngl_node *node, struct widget *widget, struct bstr *dst) +static void widget_drawcall_csv_header(struct hud_priv *s, struct widget *widget, struct bstr *dst) { const struct drawcall_spec *spec = widget->user_data; ngli_bstr_print(dst, spec->label); @@ -823,7 +813,7 @@ static void widget_drawcall_csv_header(struct ngl_node *node, struct widget *wid /* Widget CSV report */ -static void widget_latency_csv_report(struct ngl_node *node, struct widget *widget, struct bstr *dst) +static void widget_latency_csv_report(struct hud_priv *s, struct widget *widget, struct bstr *dst) { const struct widget_latency *priv = widget->priv_data; for (int i = 0; i < NB_LATENCY; i++) { @@ -832,7 +822,7 @@ static void widget_latency_csv_report(struct ngl_node *node, struct widget *widg } } -static void widget_memory_csv_report(struct ngl_node *node, struct widget *widget, struct bstr *dst) +static void widget_memory_csv_report(struct hud_priv *s, struct widget *widget, struct bstr *dst) { const struct widget_memory *priv = widget->priv_data; for (int i = 0; i < NB_MEMORY; i++) { @@ -841,13 +831,13 @@ static void widget_memory_csv_report(struct ngl_node *node, struct widget *widge } } -static void widget_activity_csv_report(struct ngl_node *node, struct widget *widget, struct bstr *dst) +static void widget_activity_csv_report(struct hud_priv *s, struct widget *widget, struct bstr *dst) { const struct widget_activity *priv = widget->priv_data; ngli_bstr_printf(dst, "%d,%d", priv->nb_actives, priv->nodes.count); } -static void widget_drawcall_csv_report(struct ngl_node *node, struct widget *widget, struct bstr *dst) +static void widget_drawcall_csv_report(struct hud_priv *s, struct widget *widget, struct bstr *dst) { const struct widget_drawcall *priv = widget->priv_data; ngli_bstr_printf(dst, "%d", priv->nb_draws); @@ -855,7 +845,7 @@ static void widget_drawcall_csv_report(struct ngl_node *node, struct widget *wid /* Widget uninit */ -static void widget_latency_uninit(struct ngl_node *node, struct widget *widget) +static void widget_latency_uninit(struct hud_priv *s, struct widget *widget) { struct widget_latency *priv = widget->priv_data; for (int i = 0; i < NB_LATENCY; i++) @@ -863,20 +853,20 @@ static void widget_latency_uninit(struct ngl_node *node, struct widget *widget) ngli_gtimer_freep(&priv->timer); } -static void widget_memory_uninit(struct ngl_node *node, struct widget *widget) +static void widget_memory_uninit(struct hud_priv *s, struct widget *widget) { struct widget_memory *priv = widget->priv_data; for (int i = 0; i < NB_MEMORY; i++) ngli_darray_reset(&priv->nodes[i]); } -static void widget_activity_uninit(struct ngl_node *node, struct widget *widget) +static void widget_activity_uninit(struct hud_priv *s, struct widget *widget) { struct widget_activity *priv = widget->priv_data; ngli_darray_reset(&priv->nodes); } -static void widget_drawcall_uninit(struct ngl_node *node, struct widget *widget) +static void widget_drawcall_uninit(struct hud_priv *s, struct widget *widget) { struct widget_drawcall *priv = widget->priv_data; ngli_darray_reset(&priv->nodes); @@ -1014,10 +1004,8 @@ static int create_widget(struct hud_priv *s, enum widget_type type, const void * return 0; } -static int widgets_init(struct ngl_node *node) +static int widgets_init(struct hud_priv *s) { - struct hud_priv *s = node->priv_data; - ngli_darray_init(&s->widgets, sizeof(struct widget), 0); /* Smallest dimensions possible (in pixels) */ @@ -1076,7 +1064,7 @@ static int widgets_init(struct ngl_node *node) struct widget *widgets = ngli_darray_data(widgets_array); for (int i = 0; i < ngli_darray_count(widgets_array); i++) { struct widget *widget = &widgets[i]; - int ret = widget_specs[widget->type].init(node, widget); + int ret = widget_specs[widget->type].init(s, widget); if (ret < 0) return ret; } @@ -1095,10 +1083,8 @@ static void widget_drawcall_reset_draws(struct widget *widget) } } -static void widgets_make_stats(struct ngl_node *node) +static void widgets_make_stats(struct hud_priv *s) { - struct hud_priv *s = node->priv_data; - /* HACK: reset drawcall draw counts before calling * widget_latency_make_stats(). This is needed here because several draws * can happen without update (for instance in case of a resize). */ @@ -1112,25 +1098,22 @@ static void widgets_make_stats(struct ngl_node *node) for (int i = 0; i < ngli_darray_count(widgets_array); i++) { struct widget *widget = &widgets[i]; - widget_specs[widget->type].make_stats(node, widget); + widget_specs[widget->type].make_stats(s, widget); } } -static void widgets_draw(struct ngl_node *node) +static void widgets_draw(struct hud_priv *s) { - struct hud_priv *s = node->priv_data; struct darray *widgets_array = &s->widgets; struct widget *widgets = ngli_darray_data(widgets_array); for (int i = 0; i < ngli_darray_count(widgets_array); i++) { struct widget *widget = &widgets[i]; - widget_specs[widget->type].draw(node, widget); + widget_specs[widget->type].draw(s, widget); } } -static int widgets_csv_header(struct ngl_node *node) +static int widgets_csv_header(struct hud_priv *s) { - struct hud_priv *s = node->priv_data; - s->fd_export = open(s->export_filename, O_CREAT | O_WRONLY | O_TRUNC, 0644); if (s->fd_export == -1) { LOG(ERROR, "unable to open \"%s\" for writing", s->export_filename); @@ -1148,7 +1131,7 @@ static int widgets_csv_header(struct ngl_node *node) for (int i = 0; i < ngli_darray_count(widgets_array); i++) { struct widget *widget = &widgets[i]; ngli_bstr_print(s->csv_line, i ? "," : ""); - widget_specs[widget->type].csv_header(node, widget, s->csv_line); + widget_specs[widget->type].csv_header(s, widget, s->csv_line); } ngli_bstr_print(s->csv_line, "\n"); @@ -1176,7 +1159,7 @@ static void widgets_csv_report(struct ngl_node *node) for (int i = 0; i < ngli_darray_count(widgets_array); i++) { ngli_bstr_print(s->csv_line, ","); struct widget *widget = &widgets[i]; - widget_specs[widget->type].csv_report(node, widget, s->csv_line); + widget_specs[widget->type].csv_report(s, widget, s->csv_line); } ngli_bstr_print(s->csv_line, "\n"); @@ -1192,14 +1175,13 @@ static void free_widget(struct widget *widget) ngli_free(widget->data_graph); } -static void widgets_uninit(struct ngl_node *node) +static void widgets_uninit(struct hud_priv *s) { - struct hud_priv *s = node->priv_data; struct darray *widgets_array = &s->widgets; struct widget *widgets = ngli_darray_data(widgets_array); for (int i = 0; i < ngli_darray_count(widgets_array); i++) { struct widget *widget = &widgets[i]; - widget_specs[widget->type].uninit(node, widget); + widget_specs[widget->type].uninit(s, widget); free_widget(widget); } ngli_darray_reset(&s->widgets); @@ -1232,7 +1214,7 @@ static int hud_init(struct ngl_node *node) s->ctx = ctx; - int ret = widgets_init(node); + int ret = widgets_init(s); if (ret < 0) return ret; @@ -1241,7 +1223,7 @@ static int hud_init(struct ngl_node *node) s->last_refresh_time = -1; if (s->export_filename) - return widgets_csv_header(node); + return widgets_csv_header(s); s->canvas.buf = ngli_calloc(s->canvas.w * s->canvas.h, 4); if (!s->canvas.buf) @@ -1372,7 +1354,7 @@ static int hud_update(struct ngl_node *node, double t) struct hud_priv *s = node->priv_data; struct darray *widgets_array = &s->widgets; struct widget *widgets = ngli_darray_data(widgets_array); - return widget_latency_update(node, &widgets[0], t); + return widget_latency_update(s, &widgets[0], t); } static void hud_draw(struct ngl_node *node) @@ -1381,7 +1363,7 @@ static void hud_draw(struct ngl_node *node) struct gctx *gctx = ctx->gctx; struct hud_priv *s = node->priv_data; - widgets_make_stats(node); + widgets_make_stats(s); if (s->export_filename) { widgets_csv_report(node); return; @@ -1392,7 +1374,7 @@ static void hud_draw(struct ngl_node *node) if (need_refresh) { s->last_refresh_time = t; widgets_clear(s); - widgets_draw(node); + widgets_draw(s); } int viewport[4]; @@ -1439,7 +1421,7 @@ static void hud_uninit(struct ngl_node *node) ngli_texture_freep(&s->texture); ngli_buffer_freep(&s->coords); - widgets_uninit(node); + widgets_uninit(s); ngli_free(s->canvas.buf); if (s->export_filename) { close(s->fd_export); From ee048c575ed6f93a712ea3c2a2da53f7775b5b69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 17 Nov 2020 10:21:44 +0100 Subject: [PATCH 273/388] hud: rename struct hud_priv to struct hud This prepares the incoming refactoring. --- libnodegl/node_hud.c | 102 +++++++++++++++++++++---------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/libnodegl/node_hud.c b/libnodegl/node_hud.c index 5bb3b1c442..414e7048d1 100644 --- a/libnodegl/node_hud.c +++ b/libnodegl/node_hud.c @@ -44,7 +44,7 @@ #include "gtimer.h" #include "graphicstate.h" -struct hud_priv { +struct hud { struct ngl_ctx *ctx; struct ngl_node *child; int measure_window; @@ -70,7 +70,7 @@ struct hud_priv { int projection_matrix_index; }; -#define OFFSET(x) offsetof(struct hud_priv, x) +#define OFFSET(x) offsetof(struct hud, x) static const struct node_param hud_params[] = { {"child", PARAM_TYPE_NODE, OFFSET(child), .flags=PARAM_FLAG_NON_NULL, .desc=NGLI_DOCSTRING("scene to benchmark")}, @@ -317,17 +317,17 @@ struct widget_spec { int graph_w, graph_h; int nb_data_graph; size_t priv_size; - int (*init)(struct hud_priv *s, struct widget *widget); - void (*make_stats)(struct hud_priv *s, struct widget *widget); - void (*draw)(struct hud_priv *s, struct widget *widget); - void (*csv_header)(struct hud_priv *s, struct widget *widget, struct bstr *dst); - void (*csv_report)(struct hud_priv *s, struct widget *widget, struct bstr *dst); - void (*uninit)(struct hud_priv *s, struct widget *widget); + int (*init)(struct hud *s, struct widget *widget); + void (*make_stats)(struct hud *s, struct widget *widget); + void (*draw)(struct hud *s, struct widget *widget); + void (*csv_header)(struct hud *s, struct widget *widget, struct bstr *dst); + void (*csv_report)(struct hud *s, struct widget *widget, struct bstr *dst); + void (*uninit)(struct hud *s, struct widget *widget); }; /* Widget init */ -static int widget_latency_init(struct hud_priv *s, struct widget *widget) +static int widget_latency_init(struct hud *s, struct widget *widget) { struct widget_latency *priv = widget->priv_data; @@ -403,7 +403,7 @@ static int make_nodes_set(struct ngl_node *scene, struct darray *nodes_list, con return 0; } -static int widget_memory_init(struct hud_priv *s, struct widget *widget) +static int widget_memory_init(struct hud *s, struct widget *widget) { struct widget_memory *priv = widget->priv_data; @@ -416,7 +416,7 @@ static int widget_memory_init(struct hud_priv *s, struct widget *widget) return 0; } -static int widget_activity_init(struct hud_priv *s, struct widget *widget) +static int widget_activity_init(struct hud *s, struct widget *widget) { const struct activity_spec *spec = widget->user_data; struct widget_activity *priv = widget->priv_data; @@ -424,7 +424,7 @@ static int widget_activity_init(struct hud_priv *s, struct widget *widget) return make_nodes_set(s->child, &priv->nodes, node_types); } -static int widget_drawcall_init(struct hud_priv *s, struct widget *widget) +static int widget_drawcall_init(struct hud *s, struct widget *widget) { const struct drawcall_spec *spec = widget->user_data; struct widget_drawcall *priv = widget->priv_data; @@ -434,7 +434,7 @@ static int widget_drawcall_init(struct hud_priv *s, struct widget *widget) /* Widget update */ -static void register_time(struct hud_priv *s, struct latency_measure *m, int64_t t) +static void register_time(struct hud *s, struct latency_measure *m, int64_t t) { m->total_times = m->total_times - m->times[m->pos] + t; m->times[m->pos] = t; @@ -442,7 +442,7 @@ static void register_time(struct hud_priv *s, struct latency_measure *m, int64_t m->count = NGLI_MIN(m->count + 1, s->measure_window); } -static int widget_latency_update(struct hud_priv *s, struct widget *widget, double t) +static int widget_latency_update(struct hud *s, struct widget *widget, double t) { int ret; struct ngl_node *child = s->child; @@ -463,7 +463,7 @@ static int widget_latency_update(struct hud_priv *s, struct widget *widget, doub /* Widget make stats */ -static void widget_latency_make_stats(struct hud_priv *s, struct widget *widget) +static void widget_latency_make_stats(struct hud *s, struct widget *widget) { struct widget_latency *priv = widget->priv_data; @@ -488,7 +488,7 @@ static void widget_latency_make_stats(struct hud_priv *s, struct widget *widget) register_time(s, &priv->measures[LATENCY_TOTAL_GPU], gpu_tdraw + gpu_tupdate); } -static void widget_memory_make_stats(struct hud_priv *s, struct widget *widget) +static void widget_memory_make_stats(struct hud *s, struct widget *widget) { struct widget_memory *priv = widget->priv_data; @@ -539,7 +539,7 @@ static void widget_memory_make_stats(struct hud_priv *s, struct widget *widget) } } -static void widget_activity_make_stats(struct hud_priv *s, struct widget *widget) +static void widget_activity_make_stats(struct hud *s, struct widget *widget) { struct widget_activity *priv = widget->priv_data; struct darray *nodes_array = &priv->nodes; @@ -549,7 +549,7 @@ static void widget_activity_make_stats(struct hud_priv *s, struct widget *widget priv->nb_actives += nodes[i]->is_active; } -static void widget_drawcall_make_stats(struct hud_priv *s, struct widget *widget) +static void widget_drawcall_make_stats(struct hud *s, struct widget *widget) { struct widget_drawcall *priv = widget->priv_data; struct darray *nodes_array = &priv->nodes; @@ -570,7 +570,7 @@ static inline uint8_t *set_color(uint8_t *p, uint32_t rgba) return p + 4; } -static inline int get_pixel_pos(struct hud_priv *s, int px, int py) +static inline int get_pixel_pos(struct hud *s, int px, int py) { return (py * s->canvas.w + px) * 4; } @@ -584,7 +584,7 @@ static int clip(int x, int min, int max) return x; } -static void draw_block_graph(struct hud_priv *s, +static void draw_block_graph(struct hud *s, const struct data_graph *d, const struct rect *rect, int64_t graph_min, int64_t graph_max, @@ -607,7 +607,7 @@ static void draw_block_graph(struct hud_priv *s, } } -static void draw_line_graph(struct hud_priv *s, +static void draw_line_graph(struct hud *s, const struct data_graph *d, const struct rect *rect, int64_t graph_min, int64_t graph_max, @@ -638,12 +638,12 @@ static void draw_line_graph(struct hud_priv *s, } } -static void print_text(struct hud_priv *s, int x, int y, const char *buf, const uint32_t c) +static void print_text(struct hud *s, int x, int y, const char *buf, const uint32_t c) { ngli_drawutils_print(&s->canvas, x, y, buf, c); } -static void widgets_clear(struct hud_priv *s) +static void widgets_clear(struct hud *s) { struct darray *widgets_array = &s->widgets; struct widget *widgets = ngli_darray_data(widgets_array); @@ -688,7 +688,7 @@ static int64_t get_latency_avg(const struct widget_latency *priv, int id) return m->total_times / m->count / (latency_specs[id].unit == 'u' ? 1 : 1000); } -static void widget_latency_draw(struct hud_priv *s, struct widget *widget) +static void widget_latency_draw(struct hud *s, struct widget *widget) { struct widget_latency *priv = widget->priv_data; @@ -716,7 +716,7 @@ static void widget_latency_draw(struct hud_priv *s, struct widget *widget) } } -static void widget_memory_draw(struct hud_priv *s, struct widget *widget) +static void widget_memory_draw(struct hud *s, struct widget *widget) { struct widget_memory *priv = widget->priv_data; char buf[MEMORY_WIDGET_TEXT_LEN + 1]; @@ -753,7 +753,7 @@ static void widget_memory_draw(struct hud_priv *s, struct widget *widget) } } -static void widget_activity_draw(struct hud_priv *s, struct widget *widget) +static void widget_activity_draw(struct hud *s, struct widget *widget) { struct widget_activity *priv = widget->priv_data; const struct activity_spec *spec = widget->user_data; @@ -769,7 +769,7 @@ static void widget_activity_draw(struct hud_priv *s, struct widget *widget) draw_block_graph(s, d, &widget->graph_rect, d->amin, d->amax, color); } -static void widget_drawcall_draw(struct hud_priv *s, struct widget *widget) +static void widget_drawcall_draw(struct hud *s, struct widget *widget) { struct widget_drawcall *priv = widget->priv_data; const struct drawcall_spec *spec = widget->user_data; @@ -787,25 +787,25 @@ static void widget_drawcall_draw(struct hud_priv *s, struct widget *widget) /* Widget CSV header */ -static void widget_latency_csv_header(struct hud_priv *s, struct widget *widget, struct bstr *dst) +static void widget_latency_csv_header(struct hud *s, struct widget *widget, struct bstr *dst) { for (int i = 0; i < NB_LATENCY; i++) ngli_bstr_printf(dst, "%s%s", i ? "," : "", latency_specs[i].label); } -static void widget_memory_csv_header(struct hud_priv *s, struct widget *widget, struct bstr *dst) +static void widget_memory_csv_header(struct hud *s, struct widget *widget, struct bstr *dst) { for (int i = 0; i < NB_MEMORY; i++) ngli_bstr_printf(dst, "%s%s memory", i ? "," : "", memory_specs[i].label); } -static void widget_activity_csv_header(struct hud_priv *s, struct widget *widget, struct bstr *dst) +static void widget_activity_csv_header(struct hud *s, struct widget *widget, struct bstr *dst) { const struct activity_spec *spec = widget->user_data; ngli_bstr_printf(dst, "%s count,%s total", spec->label, spec->label); } -static void widget_drawcall_csv_header(struct hud_priv *s, struct widget *widget, struct bstr *dst) +static void widget_drawcall_csv_header(struct hud *s, struct widget *widget, struct bstr *dst) { const struct drawcall_spec *spec = widget->user_data; ngli_bstr_print(dst, spec->label); @@ -813,7 +813,7 @@ static void widget_drawcall_csv_header(struct hud_priv *s, struct widget *widget /* Widget CSV report */ -static void widget_latency_csv_report(struct hud_priv *s, struct widget *widget, struct bstr *dst) +static void widget_latency_csv_report(struct hud *s, struct widget *widget, struct bstr *dst) { const struct widget_latency *priv = widget->priv_data; for (int i = 0; i < NB_LATENCY; i++) { @@ -822,7 +822,7 @@ static void widget_latency_csv_report(struct hud_priv *s, struct widget *widget, } } -static void widget_memory_csv_report(struct hud_priv *s, struct widget *widget, struct bstr *dst) +static void widget_memory_csv_report(struct hud *s, struct widget *widget, struct bstr *dst) { const struct widget_memory *priv = widget->priv_data; for (int i = 0; i < NB_MEMORY; i++) { @@ -831,13 +831,13 @@ static void widget_memory_csv_report(struct hud_priv *s, struct widget *widget, } } -static void widget_activity_csv_report(struct hud_priv *s, struct widget *widget, struct bstr *dst) +static void widget_activity_csv_report(struct hud *s, struct widget *widget, struct bstr *dst) { const struct widget_activity *priv = widget->priv_data; ngli_bstr_printf(dst, "%d,%d", priv->nb_actives, priv->nodes.count); } -static void widget_drawcall_csv_report(struct hud_priv *s, struct widget *widget, struct bstr *dst) +static void widget_drawcall_csv_report(struct hud *s, struct widget *widget, struct bstr *dst) { const struct widget_drawcall *priv = widget->priv_data; ngli_bstr_printf(dst, "%d", priv->nb_draws); @@ -845,7 +845,7 @@ static void widget_drawcall_csv_report(struct hud_priv *s, struct widget *widget /* Widget uninit */ -static void widget_latency_uninit(struct hud_priv *s, struct widget *widget) +static void widget_latency_uninit(struct hud *s, struct widget *widget) { struct widget_latency *priv = widget->priv_data; for (int i = 0; i < NB_LATENCY; i++) @@ -853,20 +853,20 @@ static void widget_latency_uninit(struct hud_priv *s, struct widget *widget) ngli_gtimer_freep(&priv->timer); } -static void widget_memory_uninit(struct hud_priv *s, struct widget *widget) +static void widget_memory_uninit(struct hud *s, struct widget *widget) { struct widget_memory *priv = widget->priv_data; for (int i = 0; i < NB_MEMORY; i++) ngli_darray_reset(&priv->nodes[i]); } -static void widget_activity_uninit(struct hud_priv *s, struct widget *widget) +static void widget_activity_uninit(struct hud *s, struct widget *widget) { struct widget_activity *priv = widget->priv_data; ngli_darray_reset(&priv->nodes); } -static void widget_drawcall_uninit(struct hud_priv *s, struct widget *widget) +static void widget_drawcall_uninit(struct hud *s, struct widget *widget) { struct widget_drawcall *priv = widget->priv_data; ngli_darray_reset(&priv->nodes); @@ -945,7 +945,7 @@ static inline int get_widget_height(enum widget_type type) + WIDGET_PADDING * (2 + vertical_layout); } -static int create_widget(struct hud_priv *s, enum widget_type type, const void *user_data, int x, int y) +static int create_widget(struct hud *s, enum widget_type type, const void *user_data, int x, int y) { if (x < 0) x = s->canvas.w + x; @@ -1004,7 +1004,7 @@ static int create_widget(struct hud_priv *s, enum widget_type type, const void * return 0; } -static int widgets_init(struct hud_priv *s) +static int widgets_init(struct hud *s) { ngli_darray_init(&s->widgets, sizeof(struct widget), 0); @@ -1083,7 +1083,7 @@ static void widget_drawcall_reset_draws(struct widget *widget) } } -static void widgets_make_stats(struct hud_priv *s) +static void widgets_make_stats(struct hud *s) { /* HACK: reset drawcall draw counts before calling * widget_latency_make_stats(). This is needed here because several draws @@ -1102,7 +1102,7 @@ static void widgets_make_stats(struct hud_priv *s) } } -static void widgets_draw(struct hud_priv *s) +static void widgets_draw(struct hud *s) { struct darray *widgets_array = &s->widgets; struct widget *widgets = ngli_darray_data(widgets_array); @@ -1112,7 +1112,7 @@ static void widgets_draw(struct hud_priv *s) } } -static int widgets_csv_header(struct hud_priv *s) +static int widgets_csv_header(struct hud *s) { s->fd_export = open(s->export_filename, O_CREAT | O_WRONLY | O_TRUNC, 0644); if (s->fd_export == -1) { @@ -1148,7 +1148,7 @@ static int widgets_csv_header(struct hud_priv *s) static void widgets_csv_report(struct ngl_node *node) { - struct hud_priv *s = node->priv_data; + struct hud *s = node->priv_data; ngli_bstr_clear(s->csv_line); /* Quoting to prevent locale issues with float printing */ @@ -1175,7 +1175,7 @@ static void free_widget(struct widget *widget) ngli_free(widget->data_graph); } -static void widgets_uninit(struct hud_priv *s) +static void widgets_uninit(struct hud *s) { struct darray *widgets_array = &s->widgets; struct widget *widgets = ngli_darray_data(widgets_array); @@ -1210,7 +1210,7 @@ static int hud_init(struct ngl_node *node) { struct ngl_ctx *ctx = node->ctx; struct gctx *gctx = ctx->gctx; - struct hud_priv *s = node->priv_data; + struct hud *s = node->priv_data; s->ctx = ctx; @@ -1351,7 +1351,7 @@ static int hud_init(struct ngl_node *node) static int hud_update(struct ngl_node *node, double t) { - struct hud_priv *s = node->priv_data; + struct hud *s = node->priv_data; struct darray *widgets_array = &s->widgets; struct widget *widgets = ngli_darray_data(widgets_array); return widget_latency_update(s, &widgets[0], t); @@ -1361,7 +1361,7 @@ static void hud_draw(struct ngl_node *node) { struct ngl_ctx *ctx = node->ctx; struct gctx *gctx = ctx->gctx; - struct hud_priv *s = node->priv_data; + struct hud *s = node->priv_data; widgets_make_stats(s); if (s->export_filename) { @@ -1414,7 +1414,7 @@ static void hud_draw(struct ngl_node *node) static void hud_uninit(struct ngl_node *node) { - struct hud_priv *s = node->priv_data; + struct hud *s = node->priv_data; ngli_pipeline_freep(&s->pipeline); ngli_pgcraft_freep(&s->crafter); @@ -1436,7 +1436,7 @@ const struct node_class ngli_hud_class = { .update = hud_update, .draw = hud_draw, .uninit = hud_uninit, - .priv_size = sizeof(struct hud_priv), + .priv_size = sizeof(struct hud), .params = hud_params, .file = __FILE__, }; From a4ed646238aaac7a483ae9dd6d617212839ad321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 17 Nov 2020 10:25:02 +0100 Subject: [PATCH 274/388] hud: use the private context instead of the node in the csv report --- libnodegl/node_hud.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/libnodegl/node_hud.c b/libnodegl/node_hud.c index 414e7048d1..aaa878e33a 100644 --- a/libnodegl/node_hud.c +++ b/libnodegl/node_hud.c @@ -1146,13 +1146,11 @@ static int widgets_csv_header(struct hud *s) return 0; } -static void widgets_csv_report(struct ngl_node *node) +static void widgets_csv_report(struct hud *s, double t) { - struct hud *s = node->priv_data; - ngli_bstr_clear(s->csv_line); /* Quoting to prevent locale issues with float printing */ - ngli_bstr_printf(s->csv_line, "\"%f\"", node->last_update_time); + ngli_bstr_printf(s->csv_line, "\"%f\"", t); struct darray *widgets_array = &s->widgets; struct widget *widgets = ngli_darray_data(widgets_array); @@ -1365,7 +1363,7 @@ static void hud_draw(struct ngl_node *node) widgets_make_stats(s); if (s->export_filename) { - widgets_csv_report(node); + widgets_csv_report(s, node->last_update_time); return; } From ae918a15dc0a6bdf527f359dcb1613f64a54652e Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 13 Nov 2020 15:58:26 +0100 Subject: [PATCH 275/388] hud: remove gpu update latency measure This commit is a first step towards removing the gtimer abstraction as it is not compatible with modern backends. --- libnodegl/node_hud.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/libnodegl/node_hud.c b/libnodegl/node_hud.c index aaa878e33a..bdf2aa4381 100644 --- a/libnodegl/node_hud.c +++ b/libnodegl/node_hud.c @@ -95,7 +95,6 @@ static const struct node_param hud_params[] = { enum { LATENCY_UPDATE_CPU, - LATENCY_UPDATE_GPU, LATENCY_DRAW_CPU, LATENCY_DRAW_GPU, LATENCY_TOTAL_CPU, @@ -169,7 +168,6 @@ static const struct { char unit; } latency_specs[] = { [LATENCY_UPDATE_CPU] = {"update CPU", 0xF43DF4FF, 'u'}, - [LATENCY_UPDATE_GPU] = {"update GPU", 0x3D3DF4FF, 'n'}, [LATENCY_DRAW_CPU] = {"draw CPU", 0x3DF4F4FF, 'u'}, [LATENCY_DRAW_GPU] = {"draw GPU", 0x3DF43DFF, 'n'}, [LATENCY_TOTAL_CPU] = {"total CPU", 0xF4F43DFF, 'u'}, @@ -448,15 +446,11 @@ static int widget_latency_update(struct hud *s, struct widget *widget, double t) struct ngl_node *child = s->child; struct widget_latency *priv = widget->priv_data; - ngli_gtimer_start(priv->timer); int64_t update_start = ngli_gettime_relative(); ret = ngli_node_update(child, t); int64_t update_end = ngli_gettime_relative(); - ngli_gtimer_stop(priv->timer); - const int64_t gpu_tupdate = ngli_gtimer_read(priv->timer); register_time(s, &priv->measures[LATENCY_UPDATE_CPU], update_end - update_start); - register_time(s, &priv->measures[LATENCY_UPDATE_GPU], gpu_tupdate); return ret; } @@ -479,13 +473,10 @@ static void widget_latency_make_stats(struct hud *s, struct widget *widget) register_time(s, &priv->measures[LATENCY_DRAW_GPU], gpu_tdraw); const struct latency_measure *cpu_up = &priv->measures[LATENCY_UPDATE_CPU]; - const struct latency_measure *gpu_up = &priv->measures[LATENCY_UPDATE_GPU]; const int last_cpu_up_pos = (cpu_up->pos ? cpu_up->pos : s->measure_window) - 1; - const int last_gpu_up_pos = (gpu_up->pos ? gpu_up->pos : s->measure_window) - 1; const int64_t cpu_tupdate = cpu_up->times[last_cpu_up_pos]; - const int64_t gpu_tupdate = gpu_up->times[last_gpu_up_pos]; register_time(s, &priv->measures[LATENCY_TOTAL_CPU], cpu_tdraw + cpu_tupdate); - register_time(s, &priv->measures[LATENCY_TOTAL_GPU], gpu_tdraw + gpu_tupdate); + register_time(s, &priv->measures[LATENCY_TOTAL_GPU], gpu_tdraw); } static void widget_memory_make_stats(struct hud *s, struct widget *widget) From 74fc265bf92828fdd62f630ffeb15dbf70efef04 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 17 Nov 2020 10:05:22 +0100 Subject: [PATCH 276/388] hud: remove total gpu latency measure --- libnodegl/node_hud.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/libnodegl/node_hud.c b/libnodegl/node_hud.c index bdf2aa4381..8e5e1f9dee 100644 --- a/libnodegl/node_hud.c +++ b/libnodegl/node_hud.c @@ -98,7 +98,6 @@ enum { LATENCY_DRAW_CPU, LATENCY_DRAW_GPU, LATENCY_TOTAL_CPU, - LATENCY_TOTAL_GPU, NB_LATENCY }; @@ -171,7 +170,6 @@ static const struct { [LATENCY_DRAW_CPU] = {"draw CPU", 0x3DF4F4FF, 'u'}, [LATENCY_DRAW_GPU] = {"draw GPU", 0x3DF43DFF, 'n'}, [LATENCY_TOTAL_CPU] = {"total CPU", 0xF4F43DFF, 'u'}, - [LATENCY_TOTAL_GPU] = {"total GPU", 0xF43D3DFF, 'n'}, }; static const struct { @@ -476,7 +474,6 @@ static void widget_latency_make_stats(struct hud *s, struct widget *widget) const int last_cpu_up_pos = (cpu_up->pos ? cpu_up->pos : s->measure_window) - 1; const int64_t cpu_tupdate = cpu_up->times[last_cpu_up_pos]; register_time(s, &priv->measures[LATENCY_TOTAL_CPU], cpu_tdraw + cpu_tupdate); - register_time(s, &priv->measures[LATENCY_TOTAL_GPU], gpu_tdraw); } static void widget_memory_make_stats(struct hud *s, struct widget *widget) From 4977dfa96cbf10235f90a2c19732b726dcaa1c8c Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 17 Nov 2020 11:24:11 +0100 Subject: [PATCH 277/388] hud: move the draw GPU measure below the total CPU measure --- libnodegl/node_hud.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/libnodegl/node_hud.c b/libnodegl/node_hud.c index 8e5e1f9dee..1477c99661 100644 --- a/libnodegl/node_hud.c +++ b/libnodegl/node_hud.c @@ -96,8 +96,8 @@ static const struct node_param hud_params[] = { enum { LATENCY_UPDATE_CPU, LATENCY_DRAW_CPU, - LATENCY_DRAW_GPU, LATENCY_TOTAL_CPU, + LATENCY_DRAW_GPU, NB_LATENCY }; @@ -168,8 +168,8 @@ static const struct { } latency_specs[] = { [LATENCY_UPDATE_CPU] = {"update CPU", 0xF43DF4FF, 'u'}, [LATENCY_DRAW_CPU] = {"draw CPU", 0x3DF4F4FF, 'u'}, - [LATENCY_DRAW_GPU] = {"draw GPU", 0x3DF43DFF, 'n'}, [LATENCY_TOTAL_CPU] = {"total CPU", 0xF4F43DFF, 'u'}, + [LATENCY_DRAW_GPU] = {"draw GPU", 0x3DF43DFF, 'n'}, }; static const struct { @@ -466,14 +466,15 @@ static void widget_latency_make_stats(struct hud *s, struct widget *widget) ngli_gtimer_stop(priv->timer); int64_t cpu_tdraw = draw_end - draw_start; - int64_t gpu_tdraw = ngli_gtimer_read(priv->timer); register_time(s, &priv->measures[LATENCY_DRAW_CPU], cpu_tdraw); - register_time(s, &priv->measures[LATENCY_DRAW_GPU], gpu_tdraw); const struct latency_measure *cpu_up = &priv->measures[LATENCY_UPDATE_CPU]; const int last_cpu_up_pos = (cpu_up->pos ? cpu_up->pos : s->measure_window) - 1; const int64_t cpu_tupdate = cpu_up->times[last_cpu_up_pos]; register_time(s, &priv->measures[LATENCY_TOTAL_CPU], cpu_tdraw + cpu_tupdate); + + int64_t gpu_tdraw = ngli_gtimer_read(priv->timer); + register_time(s, &priv->measures[LATENCY_DRAW_GPU], gpu_tdraw); } static void widget_memory_make_stats(struct hud *s, struct widget *widget) From b1bf2fbce3ad8dbeee1a2c8136ac19c8cea4d00f Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 16 Nov 2020 10:21:34 +0100 Subject: [PATCH 278/388] hud: use nearest filtering for the HUD texture Since adbac2c1c21e502611310444248fcdce9da12f65 the HUD rendering is not scaled anymore depending on the output surface size, thus, trilinear filtering is not needed anymore on the HUD texture. --- libnodegl/node_hud.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libnodegl/node_hud.c b/libnodegl/node_hud.c index 1477c99661..349fcf38d3 100644 --- a/libnodegl/node_hud.c +++ b/libnodegl/node_hud.c @@ -1244,9 +1244,8 @@ static int hud_init(struct ngl_node *node) .format = NGLI_FORMAT_R8G8B8A8_UNORM, .width = s->canvas.w, .height = s->canvas.h, - .min_filter = NGLI_FILTER_LINEAR, + .min_filter = NGLI_FILTER_NEAREST, .mag_filter = NGLI_FILTER_NEAREST, - .mipmap_filter = NGLI_MIPMAP_FILTER_LINEAR, }; s->texture = ngli_texture_create(gctx); if (!s->texture) From 7bea1c792dd403f5047782fa675f77432c2c5388 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 16 Nov 2020 13:24:52 +0100 Subject: [PATCH 279/388] utils: remove HUD node support HUD support will be re-introduced later on in the native tools. --- pynodegl-utils/pynodegl_utils/com.py | 9 --------- pynodegl-utils/pynodegl_utils/config.py | 6 ------ pynodegl-utils/pynodegl_utils/ui/main_window.py | 1 - pynodegl-utils/pynodegl_utils/ui/toolbar.py | 11 ----------- 4 files changed, 27 deletions(-) diff --git a/pynodegl-utils/pynodegl_utils/com.py b/pynodegl-utils/pynodegl_utils/com.py index edcf890ba7..a822eaa8f8 100644 --- a/pynodegl-utils/pynodegl_utils/com.py +++ b/pynodegl-utils/pynodegl_utils/com.py @@ -121,15 +121,6 @@ def query_inplace(**idict): del odict['scene'] scene.set_label(scene_name) - # Make extra adjustments to the scene according to user options - if idict.get('enable_hud'): - scale = idict['hud_scale'] - fr = odict['framerate'] - measure_window = fr[0] / (4 * fr[1]) # 1/4-second measurement window - scene = ngl.HUD(scene, - scale=scale, - measure_window=measure_window) - # Prepare output data odict['scene'] = scene.dot() if idict.get('fmt') == 'dot' else scene.serialize() diff --git a/pynodegl-utils/pynodegl_utils/config.py b/pynodegl-utils/pynodegl_utils/config.py index abd5cab6b0..f792766299 100644 --- a/pynodegl-utils/pynodegl_utils/config.py +++ b/pynodegl-utils/pynodegl_utils/config.py @@ -69,8 +69,6 @@ def __init__(self, module_pkgname): 'framerate': (60, 1), 'log_level': 'info', 'clear_color': (0.0, 0.0, 0.0, 1.0), - 'enable_hud': False, - 'hud_scale': 1, 'backend': 'opengl', # Export @@ -184,10 +182,6 @@ def set_clear_color(self, color): def set_log_level(self, level): self._set_cfg('log_level', level) - @QtCore.Slot(bool) - def set_hud(self, hud): - self._set_cfg('enable_hud', hud) - @QtCore.Slot(str) def set_backend(self, backend): self._set_cfg('backend', backend) diff --git a/pynodegl-utils/pynodegl_utils/ui/main_window.py b/pynodegl-utils/pynodegl_utils/ui/main_window.py index 013f2f459f..e1fb4654d3 100644 --- a/pynodegl-utils/pynodegl_utils/ui/main_window.py +++ b/pynodegl-utils/pynodegl_utils/ui/main_window.py @@ -92,7 +92,6 @@ def __init__(self, module_pkgname, hooksdirs): self._scene_toolbar.logLevelChanged.connect(self._config.set_log_level) self._scene_toolbar.clearColorChanged.connect(self._config.set_clear_color) self._scene_toolbar.backendChanged.connect(self._config.set_backend) - self._scene_toolbar.hudChanged.connect(self._config.set_hud) self._errbuf = QtWidgets.QPlainTextEdit() self._errbuf.setFont(QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont)) diff --git a/pynodegl-utils/pynodegl_utils/ui/toolbar.py b/pynodegl-utils/pynodegl_utils/ui/toolbar.py index ac640be8db..277247df84 100644 --- a/pynodegl-utils/pynodegl_utils/ui/toolbar.py +++ b/pynodegl-utils/pynodegl_utils/ui/toolbar.py @@ -56,9 +56,6 @@ def __init__(self, config): self._current_scene_data = None - self._hud_chkbox = QtWidgets.QCheckBox('Enable HUD') - self._hud_chkbox.setChecked(config.get('enable_hud')) - all_ar = config.CHOICES['aspect_ratio'] default_ar = config.get('aspect_ratio') self._ar_cbbox = QtWidgets.QComboBox() @@ -146,7 +143,6 @@ def __init__(self, config): self.reload_btn = QtWidgets.QPushButton('Force scripts reload') self._scene_toolbar_layout = QtWidgets.QVBoxLayout(self) - self._scene_toolbar_layout.addWidget(self._hud_chkbox) self._scene_toolbar_layout.addLayout(ar_hbox) self._scene_toolbar_layout.addLayout(far_hbox) self._scene_toolbar_layout.addLayout(samples_hbox) @@ -159,7 +155,6 @@ def __init__(self, config): self._scn_view.clicked.connect(self._scn_view_selected) self._scn_view.activated.connect(self._scn_view_selected) - self._hud_chkbox.stateChanged.connect(self._hud_chkbox_changed) self._ar_cbbox.currentIndexChanged.connect(self._set_aspect_ratio) self._samples_cbbox.currentIndexChanged.connect(self._set_samples) self._fr_cbbox.currentIndexChanged.connect(self._set_frame_rate) @@ -206,7 +201,6 @@ def get_cfg(self): 'framerate': choices['framerate'][self._fr_cbbox.currentIndex()], 'samples': choices['samples'][self._samples_cbbox.currentIndex()], 'extra_args': self._scene_extra_args, - 'enable_hud': self._hud_chkbox.isChecked(), 'hud_scale': round(self.devicePixelRatioF()), 'clear_color': self._clear_color, 'backend': choices['backend'][self._backend_cbbox.currentIndex()], @@ -289,11 +283,6 @@ def set_cfg(self, cfg): except ValueError: pass - @QtCore.Slot() - def _hud_chkbox_changed(self): - self.hudChanged.emit(self._hud_chkbox.isChecked()) - self._load_current_scene() - @QtCore.Slot(int) def _set_loglevel(self, index): level_str = Config.CHOICES['log_level'][index] From 73a58a156560cd4fd3efdb15fde0352529a02e9c Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 16 Nov 2020 10:08:33 +0100 Subject: [PATCH 280/388] hud: move the HUD to an internal module --- libnodegl/api.c | 52 ++++++ libnodegl/doc/libnodegl.md | 14 -- libnodegl/{node_hud.c => hud.c} | 171 +++++++----------- libnodegl/hud.h | 35 ++++ libnodegl/meson.build | 2 +- libnodegl/nodegl.h.in | 12 +- libnodegl/nodes.h | 6 + libnodegl/nodes.specs | 7 - libnodegl/nodes_register.h | 1 - pynodegl-utils/pynodegl_utils/tests/cmp.py | 6 +- .../pynodegl_utils/tests/cmp_resources.py | 11 +- pynodegl-utils/pynodegl_utils/ui/toolbar.py | 2 - pynodegl/pynodegl.pyx | 16 ++ tests/api.py | 5 +- 14 files changed, 198 insertions(+), 142 deletions(-) rename libnodegl/{node_hud.c => hud.c} (92%) create mode 100644 libnodegl/hud.h diff --git a/libnodegl/api.c b/libnodegl/api.c index a8f62dba6d..1e2a5f5bbe 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include "config.h" @@ -86,6 +87,8 @@ static int cmd_stop(struct ngl_ctx *s, void *arg) #endif ngli_texture_freep(&s->font_atlas); // allocated by the first node text ngli_pgcache_reset(&s->pgcache); + ngli_hud_freep(&s->hud); + ngli_gtimer_freep(&s->gpu_timer); ngli_gctx_freep(&s->gctx); return 0; @@ -160,6 +163,24 @@ static int cmd_configure(struct ngl_ctx *s, void *arg) } } + if (config->hud) { + s->gpu_timer = ngli_gtimer_create(s->gctx); + if (!s->gpu_timer) + return NGL_ERROR_MEMORY; + + ret = ngli_gtimer_init(s->gpu_timer); + if (ret < 0) + return ret; + + s->hud = ngli_hud_create(s); + if (!s->hud) + return NGL_ERROR_MEMORY; + + ret = ngli_hud_init(s->hud); + if (ret < 0) + return ret; + } + return 0; } @@ -197,6 +218,20 @@ static int cmd_set_scene(struct ngl_ctx *s, void *arg) } s->scene = ngl_node_ref(scene); + + const struct ngl_config *config = &s->config; + if (config->hud) { + ngli_hud_freep(&s->hud); + + s->hud = ngli_hud_create(s); + if (!s->hud) + return NGL_ERROR_MEMORY; + + ret = ngli_hud_init(s->hud); + if (ret < 0) + return ret; + } + return 0; } @@ -211,6 +246,8 @@ static int cmd_prepare_draw(struct ngl_ctx *s, void *arg) LOG(DEBUG, "prepare scene %s @ t=%f", scene->label, t); + const int64_t start_time = s->hud ? ngli_gettime_relative() : 0; + ngli_darray_clear(&s->activitycheck_nodes); int ret = ngli_node_visit(scene, 1, t); if (ret < 0) @@ -224,6 +261,8 @@ static int cmd_prepare_draw(struct ngl_ctx *s, void *arg) if (ret < 0) return ret; + s->cpu_update_time = s->hud ? ngli_gettime_relative() - start_time : 0; + return 0; } @@ -239,6 +278,12 @@ static int cmd_draw(struct ngl_ctx *s, void *arg) if (ret < 0) goto end; + int64_t cpu_start_time = 0; + if (s->hud) { + ngli_gtimer_start(s->gpu_timer); + cpu_start_time = ngli_gettime_relative(); + } + struct rendertarget *rt = ngli_gctx_get_default_rendertarget(s->gctx); s->available_rendertargets[0] = rt; s->available_rendertargets[1] = rt; @@ -251,6 +296,13 @@ static int cmd_draw(struct ngl_ctx *s, void *arg) ngli_node_draw(scene); } + if (s->hud) { + s->cpu_draw_time = ngli_gettime_relative() - cpu_start_time; + ngli_gtimer_stop(s->gpu_timer); + s->gpu_draw_time = ngli_gtimer_read(s->gpu_timer); + ngli_hud_draw(s->hud); + } + end:; int end_ret = ngli_gctx_end_draw(s->gctx, t); if (end_ret < 0) diff --git a/libnodegl/doc/libnodegl.md b/libnodegl/doc/libnodegl.md index c537107b4d..1f408a4bcb 100644 --- a/libnodegl/doc/libnodegl.md +++ b/libnodegl/doc/libnodegl.md @@ -337,20 +337,6 @@ Parameter | Live-chg. | Type | Description | Default **Source**: [node_group.c](/libnodegl/node_group.c) -## HUD - -Parameter | Live-chg. | Type | Description | Default ---------- | :-------: | ---- | ----------- | :-----: -`child` | | [`Node`](#parameter-types) | scene to benchmark | -`measure_window` | | [`int`](#parameter-types) | window size for latency measures | `60` -`refresh_rate` | | [`rational`](#parameter-types) | refresh data buffer every `update_rate` second | -`export_filename` | | [`string`](#parameter-types) | path to export file (CSV), disable display if enabled | -`scale` | | [`int`](#parameter-types) | scaling applied to the HUD, useful for high DPI displays | `0` - - -**Source**: [node_hud.c](/libnodegl/node_hud.c) - - ## Identity **Source**: [node_identity.c](/libnodegl/node_identity.c) diff --git a/libnodegl/node_hud.c b/libnodegl/hud.c similarity index 92% rename from libnodegl/node_hud.c rename to libnodegl/hud.c index 349fcf38d3..dc2baad26e 100644 --- a/libnodegl/node_hud.c +++ b/libnodegl/hud.c @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -41,15 +40,15 @@ #include "pipeline.h" #include "type.h" #include "topology.h" -#include "gtimer.h" #include "graphicstate.h" +#include "hud.h" struct hud { struct ngl_ctx *ctx; - struct ngl_node *child; + int measure_window; int refresh_rate[2]; - char *export_filename; + const char *export_filename; int scale; struct darray widgets; @@ -70,21 +69,6 @@ struct hud { int projection_matrix_index; }; -#define OFFSET(x) offsetof(struct hud, x) -static const struct node_param hud_params[] = { - {"child", PARAM_TYPE_NODE, OFFSET(child), .flags=PARAM_FLAG_NON_NULL, - .desc=NGLI_DOCSTRING("scene to benchmark")}, - {"measure_window", PARAM_TYPE_INT, OFFSET(measure_window), {.i64=60}, - .desc=NGLI_DOCSTRING("window size for latency measures")}, - {"refresh_rate", PARAM_TYPE_RATIONAL, OFFSET(refresh_rate), - .desc=NGLI_DOCSTRING("refresh data buffer every `update_rate` second")}, - {"export_filename", PARAM_TYPE_STR, OFFSET(export_filename), - .desc=NGLI_DOCSTRING("path to export file (CSV), disable display if enabled")}, - {"scale", PARAM_TYPE_INT, OFFSET(scale), - .desc=NGLI_DOCSTRING("scaling applied to the HUD, useful for high DPI displays")}, - {NULL} -}; - #define WIDGET_PADDING 4 #define WIDGET_MARGIN 2 @@ -280,7 +264,6 @@ struct latency_measure { struct widget_latency { struct latency_measure measures[NB_LATENCY]; - struct gtimer *timer; }; struct widget_memory { @@ -327,14 +310,6 @@ static int widget_latency_init(struct hud *s, struct widget *widget) { struct widget_latency *priv = widget->priv_data; - priv->timer = ngli_gtimer_create(s->ctx->gctx); - if (!priv->timer) - return NGL_ERROR_MEMORY; - - int ret = ngli_gtimer_init(priv->timer); - if (ret < 0) - return ret; - ngli_assert(NB_LATENCY == NGLI_ARRAY_NB(priv->measures)); s->measure_window = NGLI_MAX(s->measure_window, 1); @@ -373,6 +348,9 @@ static int track_children_per_types(struct hmap *map, struct ngl_node *node, int static int make_nodes_set(struct ngl_node *scene, struct darray *nodes_list, const int *node_types) { + if (!scene) + return 0; + /* construct a set of the nodes of a given type(s) */ struct hmap *nodes_set = ngli_hmap_create(); if (!nodes_set) @@ -401,11 +379,13 @@ static int make_nodes_set(struct ngl_node *scene, struct darray *nodes_list, con static int widget_memory_init(struct hud *s, struct widget *widget) { + struct ngl_ctx *ctx = s->ctx; + struct ngl_node *scene = ctx->scene; struct widget_memory *priv = widget->priv_data; for (int i = 0; i < NB_MEMORY; i++) { const int *node_types = memory_specs[i].node_types; - int ret = make_nodes_set(s->child, &priv->nodes[i], node_types); + int ret = make_nodes_set(scene, &priv->nodes[i], node_types); if (ret < 0) return ret; } @@ -414,18 +394,22 @@ static int widget_memory_init(struct hud *s, struct widget *widget) static int widget_activity_init(struct hud *s, struct widget *widget) { + struct ngl_ctx *ctx = s->ctx; + struct ngl_node *scene = ctx->scene; const struct activity_spec *spec = widget->user_data; struct widget_activity *priv = widget->priv_data; const int *node_types = spec->node_types; - return make_nodes_set(s->child, &priv->nodes, node_types); + return make_nodes_set(scene, &priv->nodes, node_types); } static int widget_drawcall_init(struct hud *s, struct widget *widget) { + struct ngl_ctx *ctx = s->ctx; + struct ngl_node *scene = ctx->scene; const struct drawcall_spec *spec = widget->user_data; struct widget_drawcall *priv = widget->priv_data; const int *node_types = spec->node_types; - return make_nodes_set(s->child, &priv->nodes, node_types); + return make_nodes_set(scene, &priv->nodes, node_types); } /* Widget update */ @@ -438,43 +422,17 @@ static void register_time(struct hud *s, struct latency_measure *m, int64_t t) m->count = NGLI_MIN(m->count + 1, s->measure_window); } -static int widget_latency_update(struct hud *s, struct widget *widget, double t) -{ - int ret; - struct ngl_node *child = s->child; - struct widget_latency *priv = widget->priv_data; - - int64_t update_start = ngli_gettime_relative(); - ret = ngli_node_update(child, t); - int64_t update_end = ngli_gettime_relative(); - - register_time(s, &priv->measures[LATENCY_UPDATE_CPU], update_end - update_start); - - return ret; -} - /* Widget make stats */ static void widget_latency_make_stats(struct hud *s, struct widget *widget) { + struct ngl_ctx *ctx = s->ctx; struct widget_latency *priv = widget->priv_data; - ngli_gtimer_start(priv->timer); - const int64_t draw_start = ngli_gettime_relative(); - ngli_node_draw(s->child); - const int64_t draw_end = ngli_gettime_relative(); - ngli_gtimer_stop(priv->timer); - - int64_t cpu_tdraw = draw_end - draw_start; - register_time(s, &priv->measures[LATENCY_DRAW_CPU], cpu_tdraw); - - const struct latency_measure *cpu_up = &priv->measures[LATENCY_UPDATE_CPU]; - const int last_cpu_up_pos = (cpu_up->pos ? cpu_up->pos : s->measure_window) - 1; - const int64_t cpu_tupdate = cpu_up->times[last_cpu_up_pos]; - register_time(s, &priv->measures[LATENCY_TOTAL_CPU], cpu_tdraw + cpu_tupdate); - - int64_t gpu_tdraw = ngli_gtimer_read(priv->timer); - register_time(s, &priv->measures[LATENCY_DRAW_GPU], gpu_tdraw); + register_time(s, &priv->measures[LATENCY_UPDATE_CPU], ctx->cpu_update_time); + register_time(s, &priv->measures[LATENCY_DRAW_CPU], ctx->cpu_draw_time); + register_time(s, &priv->measures[LATENCY_TOTAL_CPU], ctx->cpu_update_time + ctx->cpu_draw_time); + register_time(s, &priv->measures[LATENCY_DRAW_GPU], ctx->gpu_draw_time); } static void widget_memory_make_stats(struct hud *s, struct widget *widget) @@ -839,7 +797,6 @@ static void widget_latency_uninit(struct hud *s, struct widget *widget) struct widget_latency *priv = widget->priv_data; for (int i = 0; i < NB_LATENCY; i++) ngli_free(priv->measures[i].times); - ngli_gtimer_freep(&priv->timer); } static void widget_memory_uninit(struct hud *s, struct widget *widget) @@ -1074,21 +1031,20 @@ static void widget_drawcall_reset_draws(struct widget *widget) static void widgets_make_stats(struct hud *s) { - /* HACK: reset drawcall draw counts before calling - * widget_latency_make_stats(). This is needed here because several draws - * can happen without update (for instance in case of a resize). */ struct darray *widgets_array = &s->widgets; struct widget *widgets = ngli_darray_data(widgets_array); + for (int i = 0; i < ngli_darray_count(widgets_array); i++) { + struct widget *widget = &widgets[i]; + widget_specs[widget->type].make_stats(s, widget); + } + /* HACK: reset drawcall draw counts after calling + * widget_latency_make_stats(). This is needed here because several draws + * can happen without update (for instance in case of a resize). */ for (int i = 0; i < ngli_darray_count(widgets_array); i++) { struct widget *w = &widgets[i]; if (w->type == WIDGET_DRAWCALL) widget_drawcall_reset_draws(w); } - - for (int i = 0; i < ngli_darray_count(widgets_array); i++) { - struct widget *widget = &widgets[i]; - widget_specs[widget->type].make_stats(s, widget); - } } static void widgets_draw(struct hud *s) @@ -1135,11 +1091,14 @@ static int widgets_csv_header(struct hud *s) return 0; } -static void widgets_csv_report(struct hud *s, double t) +static void widgets_csv_report(struct hud *s) { + const struct ngl_ctx *ctx = s->ctx; + const struct ngl_node *scene = ctx->scene; + ngli_bstr_clear(s->csv_line); /* Quoting to prevent locale issues with float printing */ - ngli_bstr_printf(s->csv_line, "\"%f\"", t); + ngli_bstr_printf(s->csv_line, "\"%f\"", scene ? scene->last_update_time : 0); struct darray *widgets_array = &s->widgets; struct widget *widgets = ngli_darray_data(widgets_array); @@ -1193,22 +1152,39 @@ static const struct pgcraft_iovar vert_out_vars[] = { {.name = "var_tex_coord", .type = NGLI_TYPE_VEC2}, }; -static int hud_init(struct ngl_node *node) +struct hud *ngli_hud_create(struct ngl_ctx *ctx) +{ + struct hud *s = ngli_calloc(1, sizeof(*s)); + if (!s) + return NULL; + s->ctx = ctx; + return s; +} + +int ngli_hud_init(struct hud *s) { - struct ngl_ctx *ctx = node->ctx; + struct ngl_ctx *ctx = s->ctx; + const struct ngl_config *config = &ctx->config; struct gctx *gctx = ctx->gctx; - struct hud *s = node->priv_data; - s->ctx = ctx; + s->scale = config->hud_scale; + s->measure_window = config->hud_measure_window; + s->refresh_rate[0] = config->hud_refresh_rate[0]; + s->refresh_rate[1] = config->hud_refresh_rate[1]; + s->export_filename = config->hud_export_filename; + s->scale = config->hud_scale; - int ret = widgets_init(s); - if (ret < 0) - return ret; + if (!s->measure_window) + s->measure_window = 60; if (s->refresh_rate[1]) s->refresh_rate_interval = s->refresh_rate[0] / (double)s->refresh_rate[1]; s->last_refresh_time = -1; + int ret = widgets_init(s); + if (ret < 0) + return ret; + if (s->export_filename) return widgets_csv_header(s); @@ -1335,23 +1311,14 @@ static int hud_init(struct ngl_node *node) return 0; } -static int hud_update(struct ngl_node *node, double t) +void ngli_hud_draw(struct hud *s) { - struct hud *s = node->priv_data; - struct darray *widgets_array = &s->widgets; - struct widget *widgets = ngli_darray_data(widgets_array); - return widget_latency_update(s, &widgets[0], t); -} - -static void hud_draw(struct ngl_node *node) -{ - struct ngl_ctx *ctx = node->ctx; + struct ngl_ctx *ctx = s->ctx; struct gctx *gctx = ctx->gctx; - struct hud *s = node->priv_data; widgets_make_stats(s); if (s->export_filename) { - widgets_csv_report(s, node->last_update_time); + widgets_csv_report(s); return; } @@ -1398,9 +1365,11 @@ static void hud_draw(struct ngl_node *node) ngli_pipeline_draw(s->pipeline, 4, 1); } -static void hud_uninit(struct ngl_node *node) +void ngli_hud_freep(struct hud **sp) { - struct hud *s = node->priv_data; + struct hud *s = *sp; + if (!s) + return; ngli_pipeline_freep(&s->pipeline); ngli_pgcraft_freep(&s->crafter); @@ -1413,16 +1382,6 @@ static void hud_uninit(struct ngl_node *node) close(s->fd_export); ngli_bstr_freep(&s->csv_line); } -} -const struct node_class ngli_hud_class = { - .id = NGL_NODE_HUD, - .name = "HUD", - .init = hud_init, - .update = hud_update, - .draw = hud_draw, - .uninit = hud_uninit, - .priv_size = sizeof(struct hud), - .params = hud_params, - .file = __FILE__, -}; + ngli_freep(sp); +} diff --git a/libnodegl/hud.h b/libnodegl/hud.h new file mode 100644 index 0000000000..a979d4227b --- /dev/null +++ b/libnodegl/hud.h @@ -0,0 +1,35 @@ +/* + * Copyright 2020 GoPro Inc. + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef HUD_H +#define HUD_H + +#include + +struct ngl_ctx; +struct hud; + +struct hud *ngli_hud_create(struct ngl_ctx *ctx); +int ngli_hud_init(struct hud *s); +void ngli_hud_draw(struct hud *s); +void ngli_hud_freep(struct hud **sp); + +#endif diff --git a/libnodegl/meson.build b/libnodegl/meson.build index 8b6a2688a9..8b057644ca 100644 --- a/libnodegl/meson.build +++ b/libnodegl/meson.build @@ -106,6 +106,7 @@ lib_src = files( 'gctx.c', 'gtimer.c', 'hmap.c', + 'hud.c', 'hwconv.c', 'hwupload.c', 'hwupload_common.c', @@ -125,7 +126,6 @@ lib_src = files( 'node_geometry.c', 'node_graphicconfig.c', 'node_group.c', - 'node_hud.c', 'node_identity.c', 'node_io.c', 'node_media.c', diff --git a/libnodegl/nodegl.h.in b/libnodegl/nodegl.h.in index 65aa5009c8..f706159595 100644 --- a/libnodegl/nodegl.h.in +++ b/libnodegl/nodegl.h.in @@ -144,7 +144,6 @@ struct ngl_node; #define NGL_NODE_GEOMETRY NGLI_FOURCC('G','e','o','m') #define NGL_NODE_GRAPHICCONFIG NGLI_FOURCC('G','r','C','f') #define NGL_NODE_GROUP NGLI_FOURCC('G','r','p',' ') -#define NGL_NODE_HUD NGLI_FOURCC('H','U','D',' ') #define NGL_NODE_IDENTITY NGLI_FOURCC('I','d',' ',' ') #define NGL_NODE_IOINT NGLI_FOURCC('I','O','i','1') #define NGL_NODE_IOIVEC2 NGLI_FOURCC('I','O','i','2') @@ -402,6 +401,17 @@ struct ngl_config { uint8_t *capture_buffer; /* RGBA offscreen capture buffer. If allocated, its size must be at least width * height * 4 bytes. */ + + int hud; /* Enable the debug HUD */ + + int hud_measure_window; /* Window size for the latency measures displayed by the HUD. + Defaults to 60 */ + + int hud_refresh_rate[2]; /* HUD refresh rate */ + + const char *hud_export_filename; /* Path to the HUD export file (CSV). Disables display if enabled. */ + + int hud_scale; /* Scaling applied to the HUD, useful for high DPI displays */ }; #define NGL_CAP_BLOCK NGL_NODE_BLOCK diff --git a/libnodegl/nodes.h b/libnodegl/nodes.h index f4b5bfb88f..caace9eb66 100644 --- a/libnodegl/nodes.h +++ b/libnodegl/nodes.h @@ -56,6 +56,7 @@ #include "drawutils.h" #include "graphicstate.h" #include "hmap.h" +#include "hud.h" #include "hwconv.h" #include "hwupload.h" #include "image.h" @@ -106,6 +107,11 @@ struct ngl_ctx { #if defined(TARGET_ANDROID) struct android_ctx android_ctx; #endif + struct hud *hud; + struct gtimer *gpu_timer; + int64_t cpu_update_time; + int64_t cpu_draw_time; + int64_t gpu_draw_time; /* Shared fields */ pthread_mutex_t lock; diff --git a/libnodegl/nodes.specs b/libnodegl/nodes.specs index 2c54e258aa..a2c56797ce 100644 --- a/libnodegl/nodes.specs +++ b/libnodegl/nodes.specs @@ -217,13 +217,6 @@ - Group: - [children, NodeList] -- HUD: - - [child, Node] - - [measure_window, int] - - [refresh_rate, rational] - - [export_filename, string] - - [scale, int] - - Identity: - _IOVar: diff --git a/libnodegl/nodes_register.h b/libnodegl/nodes_register.h index 847a56b2bc..bedcab2f17 100644 --- a/libnodegl/nodes_register.h +++ b/libnodegl/nodes_register.h @@ -77,7 +77,6 @@ action(NGL_NODE_GEOMETRY, ngli_geometry_class) \ action(NGL_NODE_GRAPHICCONFIG, ngli_graphicconfig_class) \ action(NGL_NODE_GROUP, ngli_group_class) \ - action(NGL_NODE_HUD, ngli_hud_class) \ action(NGL_NODE_IDENTITY, ngli_identity_class) \ action(NGL_NODE_IOINT, ngli_ioint_class) \ action(NGL_NODE_IOIVEC2, ngli_ioivec2_class) \ diff --git a/pynodegl-utils/pynodegl_utils/tests/cmp.py b/pynodegl-utils/pynodegl_utils/tests/cmp.py index 6e67bb2909..07bbd017a1 100644 --- a/pynodegl-utils/pynodegl_utils/tests/cmp.py +++ b/pynodegl-utils/pynodegl_utils/tests/cmp.py @@ -86,6 +86,8 @@ def __init__(self, self._exercise_dot = exercise_dot self._scene_wrap = scene_wrap self._samples = samples + self._hud = 0 + self._hud_export_filename = None def render_frames(self): # We make sure the lists of medias is explicitely empty. If we don't a @@ -108,7 +110,9 @@ def render_frames(self): backend=get_backend(backend) if backend else ngl.BACKEND_AUTO, samples=self._samples, clear_color=self._clear_color, - capture_buffer=capture_buffer) == 0 + capture_buffer=capture_buffer, + hud=self._hud, + hud_export_filename=self._hud_export_filename) == 0 timescale = duration / float(self._nb_keyframes) if self._scene_wrap: diff --git a/pynodegl-utils/pynodegl_utils/tests/cmp_resources.py b/pynodegl-utils/pynodegl_utils/tests/cmp_resources.py index baad3230b7..f7c6903fc4 100644 --- a/pynodegl-utils/pynodegl_utils/tests/cmp_resources.py +++ b/pynodegl-utils/pynodegl_utils/tests/cmp_resources.py @@ -47,17 +47,16 @@ class _CompareResources(CompareSceneBase): def __init__(self, scene_func, columns=_COLS, **kwargs): - super().__init__(scene_func, width=320, height=240, - scene_wrap=self._scene_wrap, - **kwargs) - self._columns = columns + super().__init__(scene_func, width=320, height=240, **kwargs) - def _scene_wrap(self, scene): # We can't use NamedTemporaryFile because we may not be able to open it # twice on some systems fd, self._csvfile = tempfile.mkstemp(suffix='.csv', prefix='ngl-test-resources-') os.close(fd) - return ngl.HUD(scene, export_filename=self._csvfile) + + self._columns = columns + self._hud = 1 + self._hud_export_filename = self._csvfile def get_out_data(self, debug=False, debug_func=None): for frame in self.render_frames(): diff --git a/pynodegl-utils/pynodegl_utils/ui/toolbar.py b/pynodegl-utils/pynodegl_utils/ui/toolbar.py index 277247df84..28900983e3 100644 --- a/pynodegl-utils/pynodegl_utils/ui/toolbar.py +++ b/pynodegl-utils/pynodegl_utils/ui/toolbar.py @@ -39,7 +39,6 @@ class Toolbar(QtWidgets.QWidget): logLevelChanged = QtCore.Signal(str) clearColorChanged = QtCore.Signal(tuple) backendChanged = QtCore.Signal(str) - hudChanged = QtCore.Signal(bool) def __init__(self, config): super().__init__() @@ -201,7 +200,6 @@ def get_cfg(self): 'framerate': choices['framerate'][self._fr_cbbox.currentIndex()], 'samples': choices['samples'][self._samples_cbbox.currentIndex()], 'extra_args': self._scene_extra_args, - 'hud_scale': round(self.devicePixelRatioF()), 'clear_color': self._clear_color, 'backend': choices['backend'][self._backend_cbbox.currentIndex()], } diff --git a/pynodegl/pynodegl.pyx b/pynodegl/pynodegl.pyx index 32b4c06b69..22509f3e94 100644 --- a/pynodegl/pynodegl.pyx +++ b/pynodegl/pynodegl.pyx @@ -102,6 +102,11 @@ cdef extern from "nodegl.h": int set_surface_pts float clear_color[4] uint8_t *capture_buffer + int hud + int hud_measure_window + int hud_refresh_rate[2] + const char *hud_export_filename + int hud_scale ngl_ctx *ngl_create() int ngl_backends_probe(const ngl_config *user_config, int *nb_backendsp, ngl_backend **backendsp) @@ -245,6 +250,7 @@ def probe_backends(**kwargs): cdef class Context: cdef ngl_ctx *ctx cdef object capture_buffer + cdef object hud_export_filename def __cinit__(self): self.ctx = ngl_create() @@ -274,9 +280,19 @@ cdef class Context: capture_buffer = kwargs.get('capture_buffer') if capture_buffer is not None: config.capture_buffer = capture_buffer + config.hud = kwargs.get('hud', 0) + config.hud_measure_window = kwargs.get('hud_measure_window', 0) + hud_refresh_rate = kwargs.get('hud_refresh_rate', (0, 0)) + config.hud_refresh_rate[0] = hud_refresh_rate[0] + config.hud_refresh_rate[1] = hud_refresh_rate[1] + hud_export_filename = kwargs.get('hud_export_filename') + if hud_export_filename is not None: + config.hud_export_filename = hud_export_filename + config.hud_scale = kwargs.get('hud_scale', 0) def configure(self, **kwargs): self.capture_buffer = kwargs.get('capture_buffer') + self.hud_export_filename = kwargs.get('hud_export_filename') cdef ngl_config config Context._init_ngl_config_from_dict(&config, kwargs) return ngl_configure(self.ctx, &config) diff --git a/tests/api.py b/tests/api.py index 26f8598399..50eebc9817 100644 --- a/tests/api.py +++ b/tests/api.py @@ -135,9 +135,8 @@ def api_capture_buffer_lifetime(width=1024, height=1024): # just for blind coverage and similar code instrumentalization. def api_hud(width=234, height=123): ctx = ngl.Context() - assert ctx.configure(offscreen=1, width=width, height=height, backend=_backend) == 0 - render = _get_scene() - scene = ngl.HUD(render) + assert ctx.configure(offscreen=1, width=width, height=height, backend=_backend, hud=1) == 0 + scene = _get_scene() assert ctx.set_scene(scene) == 0 for i in range(60 * 3): assert ctx.draw(i / 60.) == 0 From b28b1d44fe34ff8aebd233da1124d3c470cc1953 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 16 Nov 2020 22:43:00 +0100 Subject: [PATCH 281/388] utils/cmp: remove unused scene_wrap parameter --- pynodegl-utils/pynodegl_utils/tests/cmp.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pynodegl-utils/pynodegl_utils/tests/cmp.py b/pynodegl-utils/pynodegl_utils/tests/cmp.py index 07bbd017a1..4a6ec4bccf 100644 --- a/pynodegl-utils/pynodegl_utils/tests/cmp.py +++ b/pynodegl-utils/pynodegl_utils/tests/cmp.py @@ -72,7 +72,6 @@ def __init__(self, clear_color=(0.0, 0.0, 0.0, 1.0), exercise_serialization=True, exercise_dot=True, - scene_wrap=None, samples=0, **scene_kwargs): self._width = width @@ -84,7 +83,6 @@ def __init__(self, self._scene_kwargs = scene_kwargs self._exercise_serialization = exercise_serialization self._exercise_dot = exercise_dot - self._scene_wrap = scene_wrap self._samples = samples self._hud = 0 self._hud_export_filename = None @@ -115,9 +113,6 @@ def render_frames(self): hud_export_filename=self._hud_export_filename) == 0 timescale = duration / float(self._nb_keyframes) - if self._scene_wrap: - scene = self._scene_wrap(scene) - if self._exercise_dot: assert scene.dot() From 02d00f884028e6c769e9fd9f254cb1516316be24 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 13 Nov 2020 16:21:39 +0100 Subject: [PATCH 282/388] gl: add glQueryCounter{,EXT} functions --- libnodegl/backends/gl/gldefinitions_data.h | 2 ++ libnodegl/backends/gl/glfeatures_data.h | 1 + libnodegl/backends/gl/glfunctions.h | 2 ++ libnodegl/backends/gl/glincludes.h | 1 + libnodegl/backends/gl/glwrappers.h | 12 ++++++++++++ libnodegl/gen-gl-wrappers.py | 2 ++ 6 files changed, 20 insertions(+) diff --git a/libnodegl/backends/gl/gldefinitions_data.h b/libnodegl/backends/gl/gldefinitions_data.h index dbe37fc925..b921bf52ea 100644 --- a/libnodegl/backends/gl/gldefinitions_data.h +++ b/libnodegl/backends/gl/gldefinitions_data.h @@ -119,6 +119,8 @@ static const struct gldefinition { {"glMemoryBarrier", offsetof(struct glfunctions, MemoryBarrier), 0}, {"glPixelStorei", offsetof(struct glfunctions, PixelStorei), M}, {"glPolygonMode", offsetof(struct glfunctions, PolygonMode), 0}, + {"glQueryCounter", offsetof(struct glfunctions, QueryCounter), 0}, + {"glQueryCounterEXT", offsetof(struct glfunctions, QueryCounterEXT), 0}, {"glReadBuffer", offsetof(struct glfunctions, ReadBuffer), 0}, {"glReadPixels", offsetof(struct glfunctions, ReadPixels), M}, {"glReleaseShaderCompiler", offsetof(struct glfunctions, ReleaseShaderCompiler), M}, diff --git a/libnodegl/backends/gl/glfeatures_data.h b/libnodegl/backends/gl/glfeatures_data.h index a024e780ff..a57855b43a 100644 --- a/libnodegl/backends/gl/glfeatures_data.h +++ b/libnodegl/backends/gl/glfeatures_data.h @@ -135,6 +135,7 @@ static const struct glfeature { OFFSET(EndQueryEXT), OFFSET(GenQueriesEXT), OFFSET(DeleteQueriesEXT), + OFFSET(QueryCounterEXT), OFFSET(GetQueryObjectui64vEXT), -1} }, { diff --git a/libnodegl/backends/gl/glfunctions.h b/libnodegl/backends/gl/glfunctions.h index 83e8d77652..3b14459dd1 100644 --- a/libnodegl/backends/gl/glfunctions.h +++ b/libnodegl/backends/gl/glfunctions.h @@ -112,6 +112,8 @@ struct glfunctions { NGLI_GL_APIENTRY void (*MemoryBarrier)(GLbitfield barriers); NGLI_GL_APIENTRY void (*PixelStorei)(GLenum pname, GLint param); NGLI_GL_APIENTRY void (*PolygonMode)(GLenum face, GLenum mode); + NGLI_GL_APIENTRY void (*QueryCounter)(GLuint id, GLenum target); + NGLI_GL_APIENTRY void (*QueryCounterEXT)(GLuint id, GLenum target); NGLI_GL_APIENTRY void (*ReadBuffer)(GLenum src); NGLI_GL_APIENTRY void (*ReadPixels)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void * pixels); NGLI_GL_APIENTRY void (*ReleaseShaderCompiler)(); diff --git a/libnodegl/backends/gl/glincludes.h b/libnodegl/backends/gl/glincludes.h index c8852f461f..46206bd7c3 100644 --- a/libnodegl/backends/gl/glincludes.h +++ b/libnodegl/backends/gl/glincludes.h @@ -207,6 +207,7 @@ typedef void (NGLI_GL_APIENTRY *GLDEBUGPROC)(GLenum source, GLenum type, GLuint # define GL_WRITE_ONLY 0x88B9 # define GL_READ_WRITE 0x88BA # define GL_TIME_ELAPSED 0x88BF +# define GL_TIMESTAMP 0x8E28 # define GL_STREAM_READ 0x88E1 # define GL_STREAM_COPY 0x88E2 # define GL_STATIC_READ 0x88E5 diff --git a/libnodegl/backends/gl/glwrappers.h b/libnodegl/backends/gl/glwrappers.h index 69f7ea803e..f4df885cd2 100644 --- a/libnodegl/backends/gl/glwrappers.h +++ b/libnodegl/backends/gl/glwrappers.h @@ -657,6 +657,18 @@ static inline void ngli_glPolygonMode(const struct glcontext *gl, GLenum face, G check_error_code(gl, "glPolygonMode"); } +static inline void ngli_glQueryCounter(const struct glcontext *gl, GLuint id, GLenum target) +{ + gl->funcs.QueryCounter(id, target); + check_error_code(gl, "glQueryCounter"); +} + +static inline void ngli_glQueryCounterEXT(const struct glcontext *gl, GLuint id, GLenum target) +{ + gl->funcs.QueryCounterEXT(id, target); + check_error_code(gl, "glQueryCounterEXT"); +} + static inline void ngli_glReadBuffer(const struct glcontext *gl, GLenum src) { gl->funcs.ReadBuffer(src); diff --git a/libnodegl/gen-gl-wrappers.py b/libnodegl/gen-gl-wrappers.py index 8fe449c9be..a5a7ddb72b 100644 --- a/libnodegl/gen-gl-wrappers.py +++ b/libnodegl/gen-gl-wrappers.py @@ -71,6 +71,7 @@ 'glEndQuery', 'glGenQueries', 'glDeleteQueries', + 'glQueryCounter', 'glGetQueryObjectui64v', # Debug @@ -81,6 +82,7 @@ 'glEndQueryEXT', 'glGenQueriesEXT', 'glDeleteQueriesEXT', + 'glQueryCounterEXT', 'glGetQueryObjectui64vEXT', # Instancing From ef181fa7720c1121a67ac56be045b5a73fb23ba2 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 13 Nov 2020 16:25:00 +0100 Subject: [PATCH 283/388] gctx: add ngli_gctx_query_draw_time() --- libnodegl/backends/gl/gctx_gl.c | 69 +++++++++++++++++++++++++++++++++ libnodegl/backends/gl/gctx_gl.h | 6 +++ libnodegl/gctx.c | 5 +++ libnodegl/gctx.h | 4 ++ 4 files changed, 84 insertions(+) diff --git a/libnodegl/backends/gl/gctx_gl.c b/libnodegl/backends/gl/gctx_gl.c index fc420411f1..286b4cb645 100644 --- a/libnodegl/backends/gl/gctx_gl.c +++ b/libnodegl/backends/gl/gctx_gl.c @@ -259,6 +259,45 @@ static void rendertarget_reset(struct gctx *s) s_priv->capture_func = NULL; } +static void noop(const struct glcontext *gl, ...) +{ +} + +static int timer_init(struct gctx *s) +{ + struct gctx_gl *s_priv = (struct gctx_gl *)s; + struct glcontext *gl = s_priv->glcontext; + + if (gl->features & NGLI_FEATURE_TIMER_QUERY) { + s_priv->glGenQueries = ngli_glGenQueries; + s_priv->glDeleteQueries = ngli_glDeleteQueries; + s_priv->glQueryCounter = ngli_glQueryCounter; + s_priv->glGetQueryObjectui64v = ngli_glGetQueryObjectui64v; + } else if (gl->features & NGLI_FEATURE_EXT_DISJOINT_TIMER_QUERY) { + s_priv->glGenQueries = ngli_glGenQueriesEXT; + s_priv->glDeleteQueries = ngli_glDeleteQueriesEXT; + s_priv->glQueryCounter = ngli_glQueryCounterEXT; + s_priv->glGetQueryObjectui64v = ngli_glGetQueryObjectui64vEXT; + } else { + s_priv->glGenQueries = (void *)noop; + s_priv->glDeleteQueries = (void *)noop; + s_priv->glQueryCounter = (void *)noop; + s_priv->glGetQueryObjectui64v = (void *)noop; + } + s_priv->glGenQueries(gl, 2, s_priv->queries); + + return 0; +} + +static void timer_reset(struct gctx *s) +{ + struct gctx_gl *s_priv = (struct gctx_gl *)s; + struct glcontext *gl = s_priv->glcontext; + + if (s_priv->glDeleteQueries) + s_priv->glDeleteQueries(gl, 2, s_priv->queries); +} + static struct gctx *gl_create(const struct ngl_config *config) { struct gctx_gl *s = ngli_calloc(1, sizeof(*s)); @@ -308,6 +347,10 @@ static int gl_init(struct gctx *s) if (ret < 0) return ret; + ret = timer_init(s); + if (ret < 0) + return ret; + s->version = gl->version; s->language_version = gl->glsl_version; s->features = gl->features; @@ -376,6 +419,9 @@ static int gl_begin_draw(struct gctx *s, double t) struct glcontext *gl = s_priv->glcontext; const struct ngl_config *config = &s->config; + if (config->hud) + s_priv->glQueryCounter(gl, s_priv->queries[0], GL_TIMESTAMP); + ngli_gctx_begin_render_pass(s, s_priv->rt); const float *color = config->clear_color; @@ -409,9 +455,31 @@ static int gl_end_draw(struct gctx *s, double t) return ret; } +static int gl_query_draw_time(struct gctx *s, int64_t *time) +{ + struct gctx_gl *s_priv = (struct gctx_gl *)s; + struct glcontext *gl = s_priv->glcontext; + + const struct ngl_config *config = &s->config; + if (!config->hud) + return NGL_ERROR_INVALID_USAGE; + + s_priv->glQueryCounter(gl, s_priv->queries[1], GL_TIMESTAMP); + + GLuint64 start_time = 0; + s_priv->glGetQueryObjectui64v(gl, s_priv->queries[0], GL_QUERY_RESULT, &start_time); + + GLuint64 end_time = 0; + s_priv->glGetQueryObjectui64v(gl, s_priv->queries[1], GL_QUERY_RESULT, &end_time); + + *time = end_time - start_time; + return 0; +} + static void gl_destroy(struct gctx *s) { struct gctx_gl *s_priv = (struct gctx_gl *)s; + timer_reset(s); rendertarget_reset(s); ngli_glcontext_freep(&s_priv->glcontext); } @@ -542,6 +610,7 @@ const struct gctx_class ngli_gctx_gl = { .resize = gl_resize, .begin_draw = gl_begin_draw, .end_draw = gl_end_draw, + .query_draw_time = gl_query_draw_time, .destroy = gl_destroy, .transform_cull_mode = gl_transform_cull_mode, diff --git a/libnodegl/backends/gl/gctx_gl.h b/libnodegl/backends/gl/gctx_gl.h index c19acb6667..1005de9009 100644 --- a/libnodegl/backends/gl/gctx_gl.h +++ b/libnodegl/backends/gl/gctx_gl.h @@ -63,6 +63,12 @@ struct gctx_gl { CVPixelBufferRef capture_cvbuffer; CVOpenGLESTextureRef capture_cvtexture; #endif + /* Timer */ + GLuint queries[2]; + void (*glGenQueries)(const struct glcontext *gl, GLsizei n, GLuint * ids); + void (*glDeleteQueries)(const struct glcontext *gl, GLsizei n, const GLuint *ids); + void (*glQueryCounter)(const struct glcontext *gl, GLuint id, GLenum target); + void (*glGetQueryObjectui64v)(const struct glcontext *gl, GLuint id, GLenum pname, GLuint64 *params); }; #endif diff --git a/libnodegl/gctx.c b/libnodegl/gctx.c index 2d602886ae..8b08a695c1 100644 --- a/libnodegl/gctx.c +++ b/libnodegl/gctx.c @@ -91,6 +91,11 @@ int ngli_gctx_end_draw(struct gctx *s, double t) return s->class->end_draw(s, t); } +int ngli_gctx_query_draw_time(struct gctx *s, int64_t *time) +{ + return s->class->query_draw_time(s, time); +} + void ngli_gctx_freep(struct gctx **sp) { if (!*sp) diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index 26e8291846..b8ca4b0d05 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -22,6 +22,8 @@ #ifndef GCTX_H #define GCTX_H +#include + #include "buffer.h" #include "feature.h" #include "gtimer.h" @@ -39,6 +41,7 @@ struct gctx_class { int (*resize)(struct gctx *s, int width, int height, const int *viewport); int (*begin_draw)(struct gctx *s, double t); int (*end_draw)(struct gctx *s, double t); + int (*query_draw_time)(struct gctx *s, int64_t *time); void (*destroy)(struct gctx *s); int (*transform_cull_mode)(struct gctx *s, int cull_mode); @@ -114,6 +117,7 @@ struct gctx *ngli_gctx_create(const struct ngl_config *config); int ngli_gctx_init(struct gctx *s); int ngli_gctx_resize(struct gctx *s, int width, int height, const int *viewport); int ngli_gctx_begin_draw(struct gctx *s, double t); +int ngli_gctx_query_draw_time(struct gctx *s, int64_t *time); int ngli_gctx_end_draw(struct gctx *s, double t); void ngli_gctx_freep(struct gctx **sp); From 5688e359d826fa2635947b1aedc8182b838330f5 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 16 Nov 2020 14:55:24 +0100 Subject: [PATCH 284/388] gctx_gl: use GL_TIME_ELAPSED query on macOS GL_TIMESTAMP queries are not implemented on macOS. --- libnodegl/backends/gl/gctx_gl.c | 17 +++++++++++++++++ libnodegl/backends/gl/gctx_gl.h | 2 ++ 2 files changed, 19 insertions(+) diff --git a/libnodegl/backends/gl/gctx_gl.c b/libnodegl/backends/gl/gctx_gl.c index 286b4cb645..de0273ad63 100644 --- a/libnodegl/backends/gl/gctx_gl.c +++ b/libnodegl/backends/gl/gctx_gl.c @@ -271,16 +271,22 @@ static int timer_init(struct gctx *s) if (gl->features & NGLI_FEATURE_TIMER_QUERY) { s_priv->glGenQueries = ngli_glGenQueries; s_priv->glDeleteQueries = ngli_glDeleteQueries; + s_priv->glBeginQuery = ngli_glBeginQuery; + s_priv->glEndQuery = ngli_glEndQuery; s_priv->glQueryCounter = ngli_glQueryCounter; s_priv->glGetQueryObjectui64v = ngli_glGetQueryObjectui64v; } else if (gl->features & NGLI_FEATURE_EXT_DISJOINT_TIMER_QUERY) { s_priv->glGenQueries = ngli_glGenQueriesEXT; s_priv->glDeleteQueries = ngli_glDeleteQueriesEXT; + s_priv->glBeginQuery = ngli_glBeginQueryEXT; + s_priv->glEndQuery = ngli_glEndQueryEXT; s_priv->glQueryCounter = ngli_glQueryCounterEXT; s_priv->glGetQueryObjectui64v = ngli_glGetQueryObjectui64vEXT; } else { s_priv->glGenQueries = (void *)noop; s_priv->glDeleteQueries = (void *)noop; + s_priv->glBeginQuery = (void *)noop; + s_priv->glEndQuery = (void *)noop; s_priv->glQueryCounter = (void *)noop; s_priv->glGetQueryObjectui64v = (void *)noop; } @@ -420,7 +426,11 @@ static int gl_begin_draw(struct gctx *s, double t) const struct ngl_config *config = &s->config; if (config->hud) +#if defined(TARGET_DARWIN) + s_priv->glBeginQuery(gl, GL_TIME_ELAPSED, s_priv->queries[0]); +#else s_priv->glQueryCounter(gl, s_priv->queries[0], GL_TIMESTAMP); +#endif ngli_gctx_begin_render_pass(s, s_priv->rt); @@ -464,6 +474,12 @@ static int gl_query_draw_time(struct gctx *s, int64_t *time) if (!config->hud) return NGL_ERROR_INVALID_USAGE; +#if defined(TARGET_DARWIN) + GLuint64 time_elapsed = 0; + s_priv->glEndQuery(gl, GL_TIME_ELAPSED); + s_priv->glGetQueryObjectui64v(gl, s_priv->queries[0], GL_QUERY_RESULT, &time_elapsed); + *time = time_elapsed; +#else s_priv->glQueryCounter(gl, s_priv->queries[1], GL_TIMESTAMP); GLuint64 start_time = 0; @@ -473,6 +489,7 @@ static int gl_query_draw_time(struct gctx *s, int64_t *time) s_priv->glGetQueryObjectui64v(gl, s_priv->queries[1], GL_QUERY_RESULT, &end_time); *time = end_time - start_time; +#endif return 0; } diff --git a/libnodegl/backends/gl/gctx_gl.h b/libnodegl/backends/gl/gctx_gl.h index 1005de9009..a1b80e345b 100644 --- a/libnodegl/backends/gl/gctx_gl.h +++ b/libnodegl/backends/gl/gctx_gl.h @@ -67,6 +67,8 @@ struct gctx_gl { GLuint queries[2]; void (*glGenQueries)(const struct glcontext *gl, GLsizei n, GLuint * ids); void (*glDeleteQueries)(const struct glcontext *gl, GLsizei n, const GLuint *ids); + void (*glBeginQuery)(const struct glcontext *gl, GLenum target, GLuint id); + void (*glEndQuery)(const struct glcontext *gl, GLenum target); void (*glQueryCounter)(const struct glcontext *gl, GLuint id, GLenum target); void (*glGetQueryObjectui64v)(const struct glcontext *gl, GLuint id, GLenum pname, GLuint64 *params); }; From 899b1063b715527d8aaa2ad4addf00105acc4a5d Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 16 Nov 2020 11:59:39 +0100 Subject: [PATCH 285/388] internal: use ngli_gctx_query_draw_time() instead of the gtimer API --- libnodegl/api.c | 25 +++++++++---------------- libnodegl/nodes.h | 1 - 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/libnodegl/api.c b/libnodegl/api.c index 1e2a5f5bbe..dbfd4d247d 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -88,7 +88,6 @@ static int cmd_stop(struct ngl_ctx *s, void *arg) ngli_texture_freep(&s->font_atlas); // allocated by the first node text ngli_pgcache_reset(&s->pgcache); ngli_hud_freep(&s->hud); - ngli_gtimer_freep(&s->gpu_timer); ngli_gctx_freep(&s->gctx); return 0; @@ -164,14 +163,6 @@ static int cmd_configure(struct ngl_ctx *s, void *arg) } if (config->hud) { - s->gpu_timer = ngli_gtimer_create(s->gctx); - if (!s->gpu_timer) - return NGL_ERROR_MEMORY; - - ret = ngli_gtimer_init(s->gpu_timer); - if (ret < 0) - return ret; - s->hud = ngli_hud_create(s); if (!s->hud) return NGL_ERROR_MEMORY; @@ -278,11 +269,7 @@ static int cmd_draw(struct ngl_ctx *s, void *arg) if (ret < 0) goto end; - int64_t cpu_start_time = 0; - if (s->hud) { - ngli_gtimer_start(s->gpu_timer); - cpu_start_time = ngli_gettime_relative(); - } + const int64_t cpu_start_time = s->hud ? ngli_gettime_relative() : 0; struct rendertarget *rt = ngli_gctx_get_default_rendertarget(s->gctx); s->available_rendertargets[0] = rt; @@ -298,8 +285,14 @@ static int cmd_draw(struct ngl_ctx *s, void *arg) if (s->hud) { s->cpu_draw_time = ngli_gettime_relative() - cpu_start_time; - ngli_gtimer_stop(s->gpu_timer); - s->gpu_draw_time = ngli_gtimer_read(s->gpu_timer); + + if (!s->begin_render_pass) { + ngli_gctx_end_render_pass(s->gctx); + s->current_rendertarget = s->available_rendertargets[1]; + s->begin_render_pass = 1; + } + ngli_gctx_query_draw_time(s->gctx, &s->gpu_draw_time); + ngli_hud_draw(s->hud); } diff --git a/libnodegl/nodes.h b/libnodegl/nodes.h index caace9eb66..aa646c4c05 100644 --- a/libnodegl/nodes.h +++ b/libnodegl/nodes.h @@ -108,7 +108,6 @@ struct ngl_ctx { struct android_ctx android_ctx; #endif struct hud *hud; - struct gtimer *gpu_timer; int64_t cpu_update_time; int64_t cpu_draw_time; int64_t gpu_draw_time; From eb114b7e3286c6554b04ac52cc62b4876a027a02 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 13 Nov 2020 16:02:05 +0100 Subject: [PATCH 286/388] internal: remove gtimer abstraction The current gtimer abstraction is not compatible with modern backends as they require an explicit command buffer to write the timestamp queries. Command buffers are not exposed by the current node.gl graphics abstraction. Doing so would be quite complex because we would need to emulate the entire command buffer API for the OpenGL backends. --- libnodegl/backends/gl/gctx_gl.c | 16 +--- libnodegl/backends/gl/gctx_gl.h | 2 - libnodegl/backends/gl/gtimer_gl.c | 128 ------------------------------ libnodegl/backends/gl/gtimer_gl.h | 49 ------------ libnodegl/gctx.h | 8 -- libnodegl/gtimer.c | 55 ------------- libnodegl/gtimer.h | 40 ---------- libnodegl/meson.build | 2 - 8 files changed, 1 insertion(+), 299 deletions(-) delete mode 100644 libnodegl/backends/gl/gtimer_gl.c delete mode 100644 libnodegl/backends/gl/gtimer_gl.h delete mode 100644 libnodegl/gtimer.c delete mode 100644 libnodegl/gtimer.h diff --git a/libnodegl/backends/gl/gctx_gl.c b/libnodegl/backends/gl/gctx_gl.c index de0273ad63..71787fddae 100644 --- a/libnodegl/backends/gl/gctx_gl.c +++ b/libnodegl/backends/gl/gctx_gl.c @@ -31,7 +31,6 @@ #include "gctx.h" #include "gctx_gl.h" #include "glcontext.h" -#include "gtimer_gl.h" #include "log.h" #include "math_utils.h" #include "memory.h" @@ -652,13 +651,6 @@ const struct gctx_class ngli_gctx_gl = { .buffer_upload = ngli_buffer_gl_upload, .buffer_freep = ngli_buffer_gl_freep, - .gtimer_create = ngli_gtimer_gl_create, - .gtimer_init = ngli_gtimer_gl_init, - .gtimer_start = ngli_gtimer_gl_start, - .gtimer_stop = ngli_gtimer_gl_stop, - .gtimer_read = ngli_gtimer_gl_read, - .gtimer_freep = ngli_gtimer_gl_freep, - .pipeline_create = ngli_pipeline_gl_create, .pipeline_init = ngli_pipeline_gl_init, .pipeline_set_resources = ngli_pipeline_gl_set_resources, @@ -696,6 +688,7 @@ const struct gctx_class ngli_gctx_gles = { .resize = gl_resize, .begin_draw = gl_begin_draw, .end_draw = gl_end_draw, + .query_draw_time = gl_query_draw_time, .destroy = gl_destroy, .transform_cull_mode = gl_transform_cull_mode, @@ -720,13 +713,6 @@ const struct gctx_class ngli_gctx_gles = { .buffer_upload = ngli_buffer_gl_upload, .buffer_freep = ngli_buffer_gl_freep, - .gtimer_create = ngli_gtimer_gl_create, - .gtimer_init = ngli_gtimer_gl_init, - .gtimer_start = ngli_gtimer_gl_start, - .gtimer_stop = ngli_gtimer_gl_stop, - .gtimer_read = ngli_gtimer_gl_read, - .gtimer_freep = ngli_gtimer_gl_freep, - .pipeline_create = ngli_pipeline_gl_create, .pipeline_init = ngli_pipeline_gl_init, .pipeline_set_resources = ngli_pipeline_gl_set_resources, diff --git a/libnodegl/backends/gl/gctx_gl.h b/libnodegl/backends/gl/gctx_gl.h index a1b80e345b..08ac7e99b1 100644 --- a/libnodegl/backends/gl/gctx_gl.h +++ b/libnodegl/backends/gl/gctx_gl.h @@ -34,7 +34,6 @@ #include "rendertarget.h" #include "pgcache.h" #include "pipeline.h" -#include "gtimer.h" #include "gctx.h" struct ngl_ctx; @@ -51,7 +50,6 @@ struct gctx_gl { struct rendertarget *rendertarget; int viewport[4]; int scissor[4]; - int timer_active; struct rendertarget *rt; /* Offscreen render target resources */ struct texture *color; diff --git a/libnodegl/backends/gl/gtimer_gl.c b/libnodegl/backends/gl/gtimer_gl.c deleted file mode 100644 index 466edd349c..0000000000 --- a/libnodegl/backends/gl/gtimer_gl.c +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2020 GoPro Inc. - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#include - -#include "gctx.h" -#include "gctx_gl.h" -#include "gtimer_gl.h" -#include "log.h" -#include "memory.h" - -static void noop(const struct glcontext *gl, ...) -{ -} - -struct gtimer *ngli_gtimer_gl_create(struct gctx *gctx) -{ - struct gtimer_gl *s = ngli_calloc(1, sizeof(*s)); - if (!s) - return NULL; - s->parent.gctx = gctx; - return (struct gtimer *)s; -} - -int ngli_gtimer_gl_init(struct gtimer *s) -{ - struct gtimer_gl *s_priv = (struct gtimer_gl *)s; - struct gctx_gl *gctx = (struct gctx_gl *)s->gctx; - struct glcontext *gl = gctx->glcontext; - - if (gl->features & NGLI_FEATURE_TIMER_QUERY) { - s_priv->glGenQueries = ngli_glGenQueries; - s_priv->glDeleteQueries = ngli_glDeleteQueries; - s_priv->glBeginQuery = ngli_glBeginQuery; - s_priv->glEndQuery = ngli_glEndQuery; - s_priv->glGetQueryObjectui64v = ngli_glGetQueryObjectui64v; - } else if (gl->features & NGLI_FEATURE_EXT_DISJOINT_TIMER_QUERY) { - s_priv->glGenQueries = ngli_glGenQueriesEXT; - s_priv->glDeleteQueries = ngli_glDeleteQueriesEXT; - s_priv->glBeginQuery = ngli_glBeginQueryEXT; - s_priv->glEndQuery = ngli_glEndQueryEXT; - s_priv->glGetQueryObjectui64v = ngli_glGetQueryObjectui64vEXT; - } else { - s_priv->glGenQueries = (void *)noop; - s_priv->glDeleteQueries = (void *)noop; - s_priv->glBeginQuery = (void *)noop; - s_priv->glEndQuery = (void *)noop; - s_priv->glGetQueryObjectui64v = (void *)noop; - } - - s_priv->glGenQueries(gl, 1, &s_priv->query); - return 0; -} - -int ngli_gtimer_gl_start(struct gtimer *s) -{ - struct gtimer_gl *s_priv = (struct gtimer_gl *)s; - struct gctx_gl *gctx = (struct gctx_gl *)s->gctx; - struct glcontext *gl = gctx->glcontext; - - if (gctx->timer_active) { - LOG(WARNING, "only one instance of GPU timings can be present " - "in the same graph due to OpenGL limitations"); - return 0; - } - - /* - * This specific instance of gtimer was able to grab the global - * "timer active" lock - */ - gctx->timer_active = 1; - s_priv->started = 1; - s_priv->query_result = 0; - s_priv->glBeginQuery(gl, GL_TIME_ELAPSED, s_priv->query); - return 0; -} - -int ngli_gtimer_gl_stop(struct gtimer *s) -{ - struct gtimer_gl *s_priv = (struct gtimer_gl *)s; - struct gctx_gl *gctx = (struct gctx_gl *)s->gctx; - struct glcontext *gl = gctx->glcontext; - - if (s_priv->started) { - s_priv->glEndQuery(gl, GL_TIME_ELAPSED); - s_priv->glGetQueryObjectui64v(gl, s_priv->query, GL_QUERY_RESULT, &s_priv->query_result); - s_priv->started = 0; - gctx->timer_active = 0; - } - return 0; -} - -int64_t ngli_gtimer_gl_read(struct gtimer *s) -{ - struct gtimer_gl *s_priv = (struct gtimer_gl *)s; - return s_priv->query_result; -} - -void ngli_gtimer_gl_freep(struct gtimer **sp) -{ - if (!*sp) - return; - - struct gtimer *s = *sp; - struct gtimer_gl *s_priv = (struct gtimer_gl *)s; - struct gctx_gl *gctx = (struct gctx_gl *)s->gctx; - struct glcontext *gl = gctx->glcontext; - s_priv->glDeleteQueries(gl, 1, &s_priv->query); - ngli_freep(sp); -} diff --git a/libnodegl/backends/gl/gtimer_gl.h b/libnodegl/backends/gl/gtimer_gl.h deleted file mode 100644 index cb2b951088..0000000000 --- a/libnodegl/backends/gl/gtimer_gl.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2020 GoPro Inc. - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#ifndef GTIMER_GL_H -#define GTIMER_GL_H - -#include "glincludes.h" -#include "gtimer.h" - -struct gctx; - -struct gtimer_gl { - struct gtimer parent; - int started; - GLuint query; - GLuint64 query_result; - void (*glGenQueries)(const struct glcontext *gl, GLsizei n, GLuint * ids); - void (*glDeleteQueries)(const struct glcontext *gl, GLsizei n, const GLuint * ids); - void (*glBeginQuery)(const struct glcontext *gl, GLenum target, GLuint id); - void (*glEndQuery)(const struct glcontext *gl, GLenum target); - void (*glGetQueryObjectui64v)(const struct glcontext *gl, GLuint id, GLenum pname, GLuint64 *params); -}; - -struct gtimer *ngli_gtimer_gl_create(struct gctx *gctx); -int ngli_gtimer_gl_init(struct gtimer *s); -int ngli_gtimer_gl_start(struct gtimer *s); -int ngli_gtimer_gl_stop(struct gtimer *s); -int64_t ngli_gtimer_gl_read(struct gtimer *s); -void ngli_gtimer_gl_freep(struct gtimer **sp); - -#endif diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index b8ca4b0d05..80afeacb3a 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -26,7 +26,6 @@ #include "buffer.h" #include "feature.h" -#include "gtimer.h" #include "limit.h" #include "nodegl.h" #include "pipeline.h" @@ -66,13 +65,6 @@ struct gctx_class { int (*buffer_upload)(struct buffer *s, const void *data, int size); void (*buffer_freep)(struct buffer **sp); - struct gtimer *(*gtimer_create)(struct gctx *ctx); - int (*gtimer_init)(struct gtimer *s); - int (*gtimer_start)(struct gtimer *s); - int (*gtimer_stop)(struct gtimer *s); - int64_t (*gtimer_read)(struct gtimer *s); - void (*gtimer_freep)(struct gtimer **sp); - struct pipeline *(*pipeline_create)(struct gctx *ctx); int (*pipeline_init)(struct pipeline *s, const struct pipeline_params *params); int (*pipeline_set_resources)(struct pipeline *s, const struct pipeline_resource_params *data_params); diff --git a/libnodegl/gtimer.c b/libnodegl/gtimer.c deleted file mode 100644 index 6fe288a5d6..0000000000 --- a/libnodegl/gtimer.c +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2020 GoPro Inc. - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#include "gctx.h" -#include "gtimer.h" - -struct gtimer *ngli_gtimer_create(struct gctx *gctx) -{ - return gctx->class->gtimer_create(gctx); -} - -int ngli_gtimer_init(struct gtimer *s) -{ - return s->gctx->class->gtimer_init(s); -} - -int ngli_gtimer_start(struct gtimer *s) -{ - return s->gctx->class->gtimer_start(s); -} - -int ngli_gtimer_stop(struct gtimer *s) -{ - return s->gctx->class->gtimer_stop(s); -} - -int64_t ngli_gtimer_read(struct gtimer *s) -{ - return s->gctx->class->gtimer_read(s); -} - -void ngli_gtimer_freep(struct gtimer **sp) -{ - if (!*sp) - return; - (*sp)->gctx->class->gtimer_freep(sp); -} diff --git a/libnodegl/gtimer.h b/libnodegl/gtimer.h deleted file mode 100644 index 9b65e9fad8..0000000000 --- a/libnodegl/gtimer.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2020 GoPro Inc. - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -#ifndef GTIMER_H -#define GTIMER_H - -#include - -struct gctx; - -struct gtimer { - struct gctx *gctx; -}; - -struct gtimer *ngli_gtimer_create(struct gctx *gctx); -int ngli_gtimer_init(struct gtimer *s); -int ngli_gtimer_start(struct gtimer *s); -int ngli_gtimer_stop(struct gtimer *s); -int64_t ngli_gtimer_read(struct gtimer *s); -void ngli_gtimer_freep(struct gtimer **sp); - -#endif diff --git a/libnodegl/meson.build b/libnodegl/meson.build index 8b057644ca..5f8a6ba80e 100644 --- a/libnodegl/meson.build +++ b/libnodegl/meson.build @@ -104,7 +104,6 @@ lib_src = files( 'drawutils.c', 'format.c', 'gctx.c', - 'gtimer.c', 'hmap.c', 'hud.c', 'hwconv.c', @@ -265,7 +264,6 @@ gbackends_cfg = { 'backends/gl/gctx_gl.c', 'backends/gl/glcontext.c', 'backends/gl/glstate.c', - 'backends/gl/gtimer_gl.c', 'backends/gl/pipeline_gl.c', 'backends/gl/program_gl.c', 'backends/gl/rendertarget_gl.c', From 6da3c974caaaf5cd9a8e1d5c1302364122ac272d Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 16 Nov 2020 13:12:12 +0100 Subject: [PATCH 287/388] tools/player: add hud support --- doc/ref/ngl-tools.md | 1 + ngl-tools/player.c | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/doc/ref/ngl-tools.md b/doc/ref/ngl-tools.md index 8f5a4a9bf5..8c00139d81 100644 --- a/doc/ref/ngl-tools.md +++ b/doc/ref/ngl-tools.md @@ -126,3 +126,4 @@ Key | Action `f` | toggle windowed/fullscreen `s` | take a screenshot `k` | kill the current scene +`h` | toggle the HUD diff --git a/ngl-tools/player.c b/ngl-tools/player.c index 948d57d52a..56ce6f92a8 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -221,6 +221,14 @@ static void reset_running_time(void) p->clock_off = gettime_relative() - p->frame_ts; } +static int toggle_hud(void) +{ + struct player *p = g_player; + struct ngl_config *config = &p->ngl_config; + config->hud ^= 1; + return ngl_configure(p->ngl, config); +} + static int key_callback(SDL_Window *window, SDL_KeyboardEvent *event) { struct player *p = g_player; @@ -238,6 +246,8 @@ static int key_callback(SDL_Window *window, SDL_KeyboardEvent *event) p->fullscreen ^= 1; SDL_SetWindowFullscreen(window, p->fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); break; + case SDLK_h: + return toggle_hud(); case SDLK_s: screenshot(); break; @@ -481,6 +491,16 @@ int player_init(struct player *p, const char *win_title, struct ngl_node *scene, p->ngl_config.viewport[3] = cfg->height; } + static const int refresh_rate[2] = {1, 60}; + p->ngl_config.hud_refresh_rate[0] = refresh_rate[0]; + p->ngl_config.hud_refresh_rate[1] = refresh_rate[1]; + p->ngl_config.hud_measure_window = refresh_rate[1] / (4 * refresh_rate[0]); /* 1/4-second measurement window */ + + int ww, wh, dw, dh; + SDL_GetWindowSize(p->window, &ww, &wh); + SDL_GL_GetDrawableSize(p->window, &dw, &dh); + p->ngl_config.hud_scale = dw / ww; + int ret = wsi_set_ngl_config(&p->ngl_config, p->window); if (ret < 0) return ret; From eb82d8471c2099a7551ca5f4193840ccbea08085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 16 Nov 2020 18:31:33 +0100 Subject: [PATCH 288/388] utils/tests: remove unused get_temp_dir imports --- pynodegl-utils/pynodegl_utils/tests/cmp_cuepoints.py | 2 +- pynodegl-utils/pynodegl_utils/tests/cmp_fingerprint.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pynodegl-utils/pynodegl_utils/tests/cmp_cuepoints.py b/pynodegl-utils/pynodegl_utils/tests/cmp_cuepoints.py index b2cbec2b3d..4463b83ee1 100644 --- a/pynodegl-utils/pynodegl_utils/tests/cmp_cuepoints.py +++ b/pynodegl-utils/pynodegl_utils/tests/cmp_cuepoints.py @@ -23,7 +23,7 @@ import os.path as op from PIL import Image -from .cmp import CompareBase, CompareSceneBase, get_test_decorator, get_temp_dir +from .cmp import CompareBase, CompareSceneBase, get_test_decorator _MODE = 'RGBA' diff --git a/pynodegl-utils/pynodegl_utils/tests/cmp_fingerprint.py b/pynodegl-utils/pynodegl_utils/tests/cmp_fingerprint.py index 1c616b65f1..9275050b11 100644 --- a/pynodegl-utils/pynodegl_utils/tests/cmp_fingerprint.py +++ b/pynodegl-utils/pynodegl_utils/tests/cmp_fingerprint.py @@ -23,7 +23,7 @@ import os.path as op from PIL import Image -from .cmp import CompareBase, CompareSceneBase, get_test_decorator, get_temp_dir +from .cmp import CompareBase, CompareSceneBase, get_test_decorator _HSIZE = 8 From 67e8f09542048c1f211d0ae1f333eec7317472b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 16 Nov 2020 18:34:13 +0100 Subject: [PATCH 289/388] utils/misc: add and use get_nodegl_tempdir() --- pynodegl-utils/pynodegl_utils/misc.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pynodegl-utils/pynodegl_utils/misc.py b/pynodegl-utils/pynodegl_utils/misc.py index 30f63f2efa..b648d30dbb 100644 --- a/pynodegl-utils/pynodegl_utils/misc.py +++ b/pynodegl-utils/pynodegl_utils/misc.py @@ -19,6 +19,7 @@ # under the License. # +import os import os.path as op import tempfile import platform @@ -132,9 +133,16 @@ def framerate_float(self): return self._framerate[0] / float(self._framerate[1]) +def get_nodegl_tempdir(): + tmpdir = op.join(tempfile.gettempdir(), 'nodegl') + if not op.exists(tmpdir): + os.makedirs(tmpdir) + return tmpdir + + class SceneCfg: - _DEFAULT_MEDIA_FILE = op.join(tempfile.gettempdir(), 'ngl-media.mp4') + _DEFAULT_MEDIA_FILE = op.join(get_nodegl_tempdir(), 'ngl-media.mp4') _DEFAULT_FIELDS = { 'aspect_ratio': (16, 9), 'duration': 30.0, From 9d9a674d539bbe2418c72f999b9ed215bfb20a9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 16 Nov 2020 18:34:38 +0100 Subject: [PATCH 290/388] utils/config: use misc.get_nodegl_tempdir() --- pynodegl-utils/pynodegl_utils/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pynodegl-utils/pynodegl_utils/config.py b/pynodegl-utils/pynodegl_utils/config.py index f792766299..dae3bf77d7 100644 --- a/pynodegl-utils/pynodegl_utils/config.py +++ b/pynodegl-utils/pynodegl_utils/config.py @@ -22,9 +22,9 @@ import os import os.path as op -import tempfile import json from PySide2 import QtCore +from .misc import get_nodegl_tempdir class Config(QtCore.QObject): @@ -74,7 +74,7 @@ def __init__(self, module_pkgname): # Export 'export_width': 1280, 'export_height': 720, - 'export_filename': op.join(tempfile.gettempdir(), 'ngl-export.mp4'), + 'export_filename': op.join(get_nodegl_tempdir(), 'ngl-export.mp4'), 'export_extra_enc_args': '', # Medias From abace64f5094b53ab7854cbfe8deb7dae8b1ea2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 16 Nov 2020 18:35:06 +0100 Subject: [PATCH 291/388] utils/export: use misc.get_nodegl_tempdir() --- pynodegl-utils/pynodegl_utils/export.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pynodegl-utils/pynodegl_utils/export.py b/pynodegl-utils/pynodegl_utils/export.py index c82f4f692d..706fd0d63a 100644 --- a/pynodegl-utils/pynodegl_utils/export.py +++ b/pynodegl-utils/pynodegl_utils/export.py @@ -21,14 +21,13 @@ import os import os.path as op -import tempfile import subprocess import pynodegl as ngl from PySide2 import QtGui, QtCore from .com import query_inplace -from .misc import get_backend, get_viewport +from .misc import get_backend, get_viewport, get_nodegl_tempdir class Exporter(QtCore.QThread): @@ -51,7 +50,7 @@ def run(self): filename, width, height = self._filename, self._width, self._height if filename.endswith('gif'): - palette_filename = op.join(tempfile.gettempdir(), 'palette.png') + palette_filename = op.join(get_nodegl_tempdir(), 'palette.png') pass1_args = ['-vf', 'palettegen'] pass2_args = self._extra_enc_args + ['-i', palette_filename, '-lavfi', 'paletteuse'] ok = self._export(palette_filename, width, height, pass1_args) From 55e56ccaf6348ce21e701f5175a272244fbba8a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 16 Nov 2020 18:36:43 +0100 Subject: [PATCH 292/388] utils/tests: use get_nodegl_tempdir() --- pynodegl-utils/pynodegl_utils/tests/cmp.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/pynodegl-utils/pynodegl_utils/tests/cmp.py b/pynodegl-utils/pynodegl_utils/tests/cmp.py index 4a6ec4bccf..4236905b52 100644 --- a/pynodegl-utils/pynodegl_utils/tests/cmp.py +++ b/pynodegl-utils/pynodegl_utils/tests/cmp.py @@ -24,8 +24,7 @@ import os.path as op import difflib import pynodegl as ngl -from pynodegl_utils.misc import get_backend -import tempfile +from pynodegl_utils.misc import get_backend, get_nodegl_tempdir class CompareBase: @@ -55,7 +54,10 @@ def compare_data(self, test_name, ref_data, out_data): @staticmethod def dump_image(img, dump_index, func_name=None): - filename = op.join(get_temp_dir(), f'{func_name}_{dump_index:03}.png') + test_tmpdir = op.join(get_nodegl_tempdir(), 'tests') + if not op.exists(test_tmpdir): + os.makedirs(test_tmpdir) + filename = op.join(test_tmpdir, f'{func_name}_{dump_index:03}.png') print(f'Dumping output image to {filename}') img.save(filename) dump_index += 1 @@ -141,10 +143,3 @@ def test_decorator(user_func): return user_func return test_decorator return test_func - - -def get_temp_dir(): - dir = op.join(tempfile.gettempdir(), 'nodegl/tests') - if not op.exists(dir): - os.makedirs(dir) - return dir From 977a1216aeb67d786009b7fdc05fb3019a029320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 16 Nov 2020 18:42:10 +0100 Subject: [PATCH 293/388] utils/graph_view: use get_nodegl_tempdir() --- pynodegl-utils/pynodegl_utils/ui/graph_view.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pynodegl-utils/pynodegl_utils/ui/graph_view.py b/pynodegl-utils/pynodegl_utils/ui/graph_view.py index 8dda108e54..c58fb96ede 100644 --- a/pynodegl-utils/pynodegl_utils/ui/graph_view.py +++ b/pynodegl-utils/pynodegl_utils/ui/graph_view.py @@ -21,7 +21,6 @@ # import os.path as op -import tempfile import subprocess from PySide2 import QtCore, QtGui, QtWidgets, QtSvg @@ -189,7 +188,7 @@ def _init_ctx(self, rendering_backend): ) def _update_graph(self, dot_scene): - basename = op.join(tempfile.gettempdir(), 'ngl_scene.') + basename = op.join(misc.get_nodegl_tempdir(), 'ngl_scene.') dotfile = basename + 'dot' svgfile = basename + 'svg' with open(dotfile, 'w') as f: From 16816ceb6ec29b87f1018469037c9906bdb392a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Fri, 13 Nov 2020 17:03:02 +0100 Subject: [PATCH 294/388] utils/examples: add missing writable properties in compute demos --- pynodegl-utils/pynodegl_utils/examples/misc.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pynodegl-utils/pynodegl_utils/examples/misc.py b/pynodegl-utils/pynodegl_utils/examples/misc.py index 3719976f36..1284bdc823 100644 --- a/pynodegl-utils/pynodegl_utils/examples/misc.py +++ b/pynodegl-utils/pynodegl_utils/examples/misc.py @@ -273,6 +273,7 @@ def particules(cfg, particules=32): uduration = ngl.UniformFloat(cfg.duration) cp = ngl.ComputeProgram(compute_shader) + cp.update_properties(opositions=ngl.ResourceProps(writable=True)) c = ngl.Compute(x, particules, 1, cp) c.update_resources( @@ -512,6 +513,7 @@ def histogram(cfg): g.add_children(rtt) compute_program = ngl.ComputeProgram(cfg.get_comp('histogram-clear')) + compute_program.update_properties(hist=ngl.ResourceProps(writable=True)) compute = ngl.Compute(256, 1, 1, compute_program, label='histogram-clear') compute.update_resources(hist=h) g.add_children(compute) @@ -522,6 +524,7 @@ def histogram(cfg): compute_program = ngl.ComputeProgram(compute_shader) compute = ngl.Compute(group_size, group_size, 1, compute_program, label='histogram-exec') compute.update_resources(hist=h, source=proxy) + compute_program.update_properties(hist=ngl.ResourceProps(writable=True)) compute_program.update_properties(source=ngl.ResourceProps(as_image=True)) g.add_children(compute) From 25c315e6ba2fd7e27453f25a8c888c8b15a06168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 17 Nov 2020 14:22:49 +0100 Subject: [PATCH 295/388] utils/misc: use a try-except form in get_nodegl_tempdir() os.makedirs() can be called in a race condition scenario, which typically happens during the tests with parallel jobs. The try..except makes this code resistant to such a scenario. --- pynodegl-utils/pynodegl_utils/misc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pynodegl-utils/pynodegl_utils/misc.py b/pynodegl-utils/pynodegl_utils/misc.py index b648d30dbb..19f0f6337c 100644 --- a/pynodegl-utils/pynodegl_utils/misc.py +++ b/pynodegl-utils/pynodegl_utils/misc.py @@ -135,8 +135,10 @@ def framerate_float(self): def get_nodegl_tempdir(): tmpdir = op.join(tempfile.gettempdir(), 'nodegl') - if not op.exists(tmpdir): + try: os.makedirs(tmpdir) + except FileExistsError: + pass return tmpdir From bc71fcf0494287c148ce7cd59ea5efe01e059514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 17 Nov 2020 14:57:50 +0100 Subject: [PATCH 296/388] build: remap DEBUG_{GL,MEM,SCENE} to libnodegl debug_opts Forgotten in 825c7e87dd792be82e4f6077334e7cbb602ee973. --- Makefile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 44a4ee855a..1a6218ab6e 100644 --- a/Makefile +++ b/Makefile @@ -60,6 +60,12 @@ endif ifneq ($(V),) MESON_COMPILE += -v endif +NODEGL_DEBUG_OPTS-$(DEBUG_GL) += gl +NODEGL_DEBUG_OPTS-$(DEBUG_MEM) += mem +NODEGL_DEBUG_OPTS-$(DEBUG_SCENE) += scene +ifneq ($(NODEGL_DEBUG_OPTS-yes),) +NODEGL_DEBUG_OPTS = -Ddebug_opts=$(shell echo $(NODEGL_DEBUG_OPTS-yes) | tr ' ' ',') +endif # Workaround Debian/Ubuntu bug; see https://github.com/mesonbuild/meson/issues/5925 ifeq ($(TARGET_OS),Linux) @@ -114,7 +120,7 @@ nodegl-install: nodegl-setup (. $(ACTIVATE) && $(MESON_COMPILE) -C builddir/libnodegl && $(MESON_INSTALL) -C builddir/libnodegl) nodegl-setup: sxplayer-install - (. $(ACTIVATE) && $(MESON_SETUP) libnodegl builddir/libnodegl) + (. $(ACTIVATE) && $(MESON_SETUP) $(NODEGL_DEBUG_OPTS) libnodegl builddir/libnodegl) sxplayer-install: sxplayer $(PREFIX) (. $(ACTIVATE) && $(MESON_SETUP) sxplayer builddir/sxplayer && $(MESON_COMPILE) -C builddir/sxplayer && $(MESON_INSTALL) -C builddir/sxplayer) From 775463bf2b86131d1f506450b3c29402c2b4575f Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 18 Nov 2020 15:45:39 +0100 Subject: [PATCH 297/388] rendertarget_gl: fix depth stencil texture attachment on OpenGLES 2.0 GL_OES_packed_depth_stencil does not define a single combined attachment point (GL_DEPTH_STENCIL_ATTACHMENT), thus, we need to attach the texture to both GL_DEPTH_ATTACHMENT and GL_STENCIL_ATTACHMENT. --- libnodegl/backends/gl/rendertarget_gl.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libnodegl/backends/gl/rendertarget_gl.c b/libnodegl/backends/gl/rendertarget_gl.c index b5336a51c1..941d246f80 100644 --- a/libnodegl/backends/gl/rendertarget_gl.c +++ b/libnodegl/backends/gl/rendertarget_gl.c @@ -156,7 +156,12 @@ static int create_fbo(struct rendertarget *s, int resolve) } break; case GL_TEXTURE_2D: - ngli_glFramebufferTexture2D(gl, GL_FRAMEBUFFER, attachment_index, GL_TEXTURE_2D, texture_gl->id, 0); + if (gl->backend == NGL_BACKEND_OPENGLES && gl->version < 300 && attachment_index == GL_DEPTH_STENCIL_ATTACHMENT) { + ngli_glFramebufferTexture2D(gl, GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texture_gl->id, 0); + ngli_glFramebufferTexture2D(gl, GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texture_gl->id, 0); + } else { + ngli_glFramebufferTexture2D(gl, GL_FRAMEBUFFER, attachment_index, GL_TEXTURE_2D, texture_gl->id, 0); + } break; default: ngli_assert(0); From 846772b899c95a917c564007bc1191ac97577b0a Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 18 Nov 2020 16:54:03 +0100 Subject: [PATCH 298/388] tests/data: remove usage of length() in the data test shader Makes the data test shader compatible with OpenGLES 2.0. --- pynodegl-utils/pynodegl_utils/tests/data.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pynodegl-utils/pynodegl_utils/tests/data.py b/pynodegl-utils/pynodegl_utils/tests/data.py index 8e749270ed..8ab830ba5c 100644 --- a/pynodegl-utils/pynodegl_utils/tests/data.py +++ b/pynodegl-utils/pynodegl_utils/tests/data.py @@ -56,7 +56,7 @@ vec3 get_color_%(field_name)s(float w, float h, float x, float y) { float amount = 0.0; - int len = %(fields_prefix)s%(field_name)s.length(); + int len = %(field_len)d; for (int i = 0; i < len; i++) { %(amount_code)s } @@ -103,10 +103,11 @@ ) -def _get_display_glsl_func(layout, field_name, field_type, is_array=False): +def _get_display_glsl_func(layout, field_name, field_type, field_len=None): rows, cols, scale = _TYPE_SPEC[field_type] nb_comp = rows * cols + is_array = field_len is not None tpl = _ARRAY_TPL if is_array else _SINGLE_TPL rect_tpl = _RECT_ARRAY_TPL if is_array else _RECT_SINGLE_TPL amount_tpl = _COMMON_INT_TPL if scale is not None else _COMMON_FLT_TPL @@ -115,6 +116,7 @@ def _get_display_glsl_func(layout, field_name, field_type, is_array=False): colors_prefix='color_' if layout == 'uniform' else 'colors.', fields_prefix='field_' if layout == 'uniform' else 'fields.', field_name=field_name, + field_len=field_len, nb_comp=nb_comp, ) @@ -207,9 +209,9 @@ def get_render(cfg, quad, fields, block_definition, color_definition, block_fiel func_calls = [] func_definitions = [] for i, field in enumerate(fields): - is_array = 'len' in field + field_len = field.get('len') func_calls.append('get_color_%s(w, h, 0.0, %f * h)' % (field['name'], i)) - func_definitions.append(_get_display_glsl_func(layout, field['name'], field['type'], is_array=is_array)) + func_definitions.append(_get_display_glsl_func(layout, field['name'], field['type'], field_len=field_len)) frag_data = dict( func_definitions='\n'.join(func_definitions), From f88397470842a0e346728211e1aeb296d3a12c86 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 18 Nov 2020 17:15:59 +0100 Subject: [PATCH 299/388] tests/texture: use a dedicated fragment shader to render textures Makes the output of texture_data and texture_data_unaligned correct on OpenGLES 2.0. On OpenGLES 2.0, single component textures must use the GL_LUMINANCE format (instead of GL_RED). GL_LUMINANCE textures have their source color set to (L, L, L, 1), thus, we must use a dedicated fragment shader that only picks the first component, in order to match the output of GL_RED textures. --- tests/texture.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/texture.py b/tests/texture.py index 5639c2de31..24121fb6ea 100644 --- a/tests/texture.py +++ b/tests/texture.py @@ -31,12 +31,21 @@ from pynodegl_utils.toolbox.colors import get_random_color_buffer +_RENDER_BUFFER_FRAG = ''' +void main() +{ + float color = ngl_tex2d(tex0, var_tex0_coord).r; + ngl_out_color = vec4(color, 0.0, 0.0, 1.0); +} +''' + + def _render_buffer(cfg, w, h): n = w * h data = array.array('B', [i * 255 // n for i in range(n)]) buf = ngl.BufferUByte(data=data) texture = ngl.Texture2D(width=w, height=h, data_src=buf) - program = ngl.Program(vertex=cfg.get_vert('texture'), fragment=cfg.get_frag('texture')) + program = ngl.Program(vertex=cfg.get_vert('texture'), fragment=_RENDER_BUFFER_FRAG) program.update_vert_out_vars(var_tex0_coord=ngl.IOVec2(), var_uvcoord=ngl.IOVec2()) render = ngl.Render(ngl.Quad(), program) render.update_frag_resources(tex0=texture) From 53a1b6f2d1ebabc76157865c13f02eda2477461b Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 23 Nov 2020 09:20:44 +0100 Subject: [PATCH 300/388] glfeatures_data: add missing include Required by offsetof(). --- libnodegl/backends/gl/glfeatures_data.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libnodegl/backends/gl/glfeatures_data.h b/libnodegl/backends/gl/glfeatures_data.h index a57855b43a..37bc9caea6 100644 --- a/libnodegl/backends/gl/glfeatures_data.h +++ b/libnodegl/backends/gl/glfeatures_data.h @@ -21,6 +21,8 @@ /* WARNING: this file must only be included once */ +#include + #include "glcontext.h" #define OFFSET(x) offsetof(struct glfunctions, x) From 04e32b95eb9ae500329030080a5dc8683ac2004c Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 23 Nov 2020 09:34:00 +0100 Subject: [PATCH 301/388] glfeatures: add shader_texture_lod feature --- libnodegl/backends/gl/glfeatures_data.h | 6 ++++++ libnodegl/feature.h | 1 + 2 files changed, 7 insertions(+) diff --git a/libnodegl/backends/gl/glfeatures_data.h b/libnodegl/backends/gl/glfeatures_data.h index 37bc9caea6..db37286913 100644 --- a/libnodegl/backends/gl/glfeatures_data.h +++ b/libnodegl/backends/gl/glfeatures_data.h @@ -280,5 +280,11 @@ static const struct glfeature { .flag = NGLI_FEATURE_SHADING_LANGUAGE_420PACK, .version = 420, .extensions = (const char*[]){"GL_ARB_shading_language_420pack", NULL}, + }, { + .name = "shader_texture_lod", + .flag = NGLI_FEATURE_SHADER_TEXTURE_LOD, + .version = 300, + .es_version = 300, + .es_extensions = (const char*[]){"GL_EXT_shader_texture_lod", NULL}, } }; diff --git a/libnodegl/feature.h b/libnodegl/feature.h index e4ff24fa5d..806823dfb8 100644 --- a/libnodegl/feature.h +++ b/libnodegl/feature.h @@ -57,6 +57,7 @@ #define NGLI_FEATURE_CLEAR_BUFFER (1ULL << 32) #define NGLI_FEATURE_SHADER_IMAGE_SIZE (1ULL << 33) #define NGLI_FEATURE_SHADING_LANGUAGE_420PACK (1ULL << 34) +#define NGLI_FEATURE_SHADER_TEXTURE_LOD (1ULL << 35) #define NGLI_FEATURE_COMPUTE_SHADER_ALL (NGLI_FEATURE_COMPUTE_SHADER | \ NGLI_FEATURE_PROGRAM_INTERFACE_QUERY | \ From 2ec8f428bcf4fe9cdfa53a734b9e29982e37eb9d Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 23 Nov 2020 09:56:59 +0100 Subject: [PATCH 302/388] pgcraft: define ngl_tex*lod() --- libnodegl/pgcraft.c | 11 +++++++++++ tests/texture.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/libnodegl/pgcraft.c b/libnodegl/pgcraft.c index c86a4d707e..55292b662f 100644 --- a/libnodegl/pgcraft.c +++ b/libnodegl/pgcraft.c @@ -531,6 +531,7 @@ static void set_glsl_header(struct pgcraft *s, struct bstr *b, const struct pgcr const int require_image_external_feature = ngli_darray_count(&s->texture_infos) > 0 && s->glsl_version < 300; const int require_image_external_essl3_feature = ngli_darray_count(&s->texture_infos) > 0 && s->glsl_version >= 300; #endif + const int enable_shader_texture_lod = (gctx->features & NGLI_FEATURE_SHADER_TEXTURE_LOD) == NGLI_FEATURE_SHADER_TEXTURE_LOD; const struct { int backend; @@ -550,6 +551,7 @@ static void set_glsl_header(struct pgcraft *s, struct bstr *b, const struct pgcr {NGL_BACKEND_OPENGLES, "GL_OES_EGL_image_external", INT_MAX, require_image_external_feature}, {NGL_BACKEND_OPENGLES, "GL_OES_EGL_image_external_essl3", INT_MAX, require_image_external_essl3_feature}, #endif + {NGL_BACKEND_OPENGLES, "GL_EXT_shader_texture_lod", 300, enable_shader_texture_lod}, }; for (int i = 0; i < NGLI_ARRAY_NB(features); i++) { @@ -568,6 +570,15 @@ static void set_glsl_header(struct pgcraft *s, struct bstr *b, const struct pgcr ngli_bstr_print(b, "#define ngl_tex2d texture2D\n" "#define ngl_tex3d texture3D\n" "#define ngl_texcube textureCube\n"); + + if (config->backend == NGL_BACKEND_OPENGLES && s->glsl_version < 300) + ngli_bstr_print(b, "#define ngl_tex2dlod texture2DLodEXT\n" + "#define ngl_tex3dlod texture3DLodEXT\n" + "#define ngl_texcubelod textureCubeLodEXT\n"); + else + ngli_bstr_print(b, "#define ngl_tex2dlod textureLod\n" + "#define ngl_tex3dlod textureLod\n" + "#define ngl_texcubelod textureLod\n"); } ngli_bstr_print(b, "\n"); diff --git a/tests/texture.py b/tests/texture.py index 24121fb6ea..e5be5791f4 100644 --- a/tests/texture.py +++ b/tests/texture.py @@ -278,7 +278,7 @@ def texture_3d(cfg): _RENDER_TEXTURE_LOD_FRAG = ''' void main() { - ngl_out_color = textureLod(tex0, var_uvcoord, 0.5); + ngl_out_color = ngl_tex2dlod(tex0, var_uvcoord, 0.5); } ''' From cf9cb33271059ac21a85848d07ddaaf28c7b77aa Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 23 Nov 2020 11:25:05 +0100 Subject: [PATCH 303/388] internal: sort capabilities array alphabetically --- libnodegl/api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libnodegl/api.c b/libnodegl/api.c index dbfd4d247d..15ddb16219 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -430,6 +430,7 @@ static int load_caps(struct ngl_backend *backend, const struct gctx *gctx) const struct ngl_cap caps[] = { CAP(NGL_CAP_BLOCK, has_block), CAP(NGL_CAP_COMPUTE, has_compute), + CAP(NGL_CAP_INSTANCED_DRAW, has_instanced_draw), CAP(NGL_CAP_MAX_COMPUTE_GROUP_COUNT_X, limits->max_compute_work_group_counts[0]), CAP(NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Y, limits->max_compute_work_group_counts[1]), CAP(NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Z, limits->max_compute_work_group_counts[2]), @@ -437,7 +438,6 @@ static int load_caps(struct ngl_backend *backend, const struct gctx *gctx) CAP(NGL_CAP_MAX_COMPUTE_GROUP_SIZE_X, limits->max_compute_work_group_sizes[0]), CAP(NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Y, limits->max_compute_work_group_sizes[1]), CAP(NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Z, limits->max_compute_work_group_sizes[2]), - CAP(NGL_CAP_INSTANCED_DRAW, has_instanced_draw), CAP(NGL_CAP_MAX_SAMPLES, limits->max_samples), CAP(NGL_CAP_NPOT_TEXTURE, has_npot_texture), CAP(NGL_CAP_TEXTURE_3D, has_texture_3d), From 22ebe075d20bb6616378e1fa4a7da6709d77e852 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 23 Nov 2020 10:05:25 +0100 Subject: [PATCH 304/388] api: add NGL_CAP_SHADER_TEXTURE_LOD --- libnodegl/api.c | 3 +++ libnodegl/nodegl.h.in | 1 + 2 files changed, 4 insertions(+) diff --git a/libnodegl/api.c b/libnodegl/api.c index 15ddb16219..7632c3fc73 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -407,6 +407,7 @@ static const char *get_cap_string_id(unsigned cap_id) case NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Z: return "max_compute_group_size_z"; case NGL_CAP_MAX_SAMPLES: return "max_samples"; case NGL_CAP_NPOT_TEXTURE: return "npot_texture"; + case NGL_CAP_SHADER_TEXTURE_LOD: return "shader_texture_lod"; case NGL_CAP_TEXTURE_3D: return "texture_3d"; case NGL_CAP_TEXTURE_CUBE: return "texture_cube"; } @@ -423,6 +424,7 @@ static int load_caps(struct ngl_backend *backend, const struct gctx *gctx) const int has_compute = ALL_FEATURES(gctx->features, NGLI_FEATURE_COMPUTE_SHADER_ALL); const int has_instanced_draw = ALL_FEATURES(gctx->features, NGLI_FEATURE_INSTANCED_ARRAY); const int has_npot_texture = ALL_FEATURES(gctx->features, NGLI_FEATURE_TEXTURE_NPOT); + const int has_shader_texture_lod = ALL_FEATURES(gctx->features, NGLI_FEATURE_SHADER_TEXTURE_LOD); const int has_texture_3d = ALL_FEATURES(gctx->features, NGLI_FEATURE_TEXTURE_3D); const int has_texture_cube = ALL_FEATURES(gctx->features, NGLI_FEATURE_TEXTURE_CUBE_MAP); @@ -440,6 +442,7 @@ static int load_caps(struct ngl_backend *backend, const struct gctx *gctx) CAP(NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Z, limits->max_compute_work_group_sizes[2]), CAP(NGL_CAP_MAX_SAMPLES, limits->max_samples), CAP(NGL_CAP_NPOT_TEXTURE, has_npot_texture), + CAP(NGL_CAP_SHADER_TEXTURE_LOD, has_shader_texture_lod), CAP(NGL_CAP_TEXTURE_3D, has_texture_3d), CAP(NGL_CAP_TEXTURE_CUBE, has_texture_cube), }; diff --git a/libnodegl/nodegl.h.in b/libnodegl/nodegl.h.in index f706159595..e68e84a8f7 100644 --- a/libnodegl/nodegl.h.in +++ b/libnodegl/nodegl.h.in @@ -426,6 +426,7 @@ struct ngl_config { #define NGL_CAP_MAX_COMPUTE_GROUP_SIZE_Z NGLI_FOURCC('C','G','s','z') #define NGL_CAP_MAX_SAMPLES NGLI_FOURCC('M','S','A','A') #define NGL_CAP_NPOT_TEXTURE NGLI_FOURCC('N','P','O','T') +#define NGL_CAP_SHADER_TEXTURE_LOD NGLI_FOURCC('S','T','L','d') #define NGL_CAP_TEXTURE_3D NGL_NODE_TEXTURE3D #define NGL_CAP_TEXTURE_CUBE NGL_NODE_TEXTURECUBE From 4f71609d86ac0510ce7be520c115f63c07ddb4c5 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 23 Nov 2020 11:00:05 +0100 Subject: [PATCH 305/388] glfeatures: enable texture_3D feature if GL_OES_texture_3D is available --- libnodegl/backends/gl/glfeatures_data.h | 1 + 1 file changed, 1 insertion(+) diff --git a/libnodegl/backends/gl/glfeatures_data.h b/libnodegl/backends/gl/glfeatures_data.h index db37286913..50d3b838ae 100644 --- a/libnodegl/backends/gl/glfeatures_data.h +++ b/libnodegl/backends/gl/glfeatures_data.h @@ -52,6 +52,7 @@ static const struct glfeature { .flag = NGLI_FEATURE_TEXTURE_3D, .version = 200, .es_version = 300, + .es_extensions = (const char*[]){"GL_OES_texture_3D", NULL}, .funcs_offsets = (const size_t[]){OFFSET(TexImage3D), OFFSET(TexSubImage3D), -1} From 4ba3519f0796d820d3ce92886d0443a8ea4fd786 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 23 Nov 2020 11:00:35 +0100 Subject: [PATCH 306/388] pgcraft: add support for GL_OES_texture_3d --- libnodegl/pgcraft.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libnodegl/pgcraft.c b/libnodegl/pgcraft.c index 55292b662f..b4db39a03b 100644 --- a/libnodegl/pgcraft.c +++ b/libnodegl/pgcraft.c @@ -532,6 +532,7 @@ static void set_glsl_header(struct pgcraft *s, struct bstr *b, const struct pgcr const int require_image_external_essl3_feature = ngli_darray_count(&s->texture_infos) > 0 && s->glsl_version >= 300; #endif const int enable_shader_texture_lod = (gctx->features & NGLI_FEATURE_SHADER_TEXTURE_LOD) == NGLI_FEATURE_SHADER_TEXTURE_LOD; + const int enable_texture_3d = (gctx->features & NGLI_FEATURE_TEXTURE_3D) == NGLI_FEATURE_TEXTURE_3D; const struct { int backend; @@ -552,6 +553,7 @@ static void set_glsl_header(struct pgcraft *s, struct bstr *b, const struct pgcr {NGL_BACKEND_OPENGLES, "GL_OES_EGL_image_external_essl3", INT_MAX, require_image_external_essl3_feature}, #endif {NGL_BACKEND_OPENGLES, "GL_EXT_shader_texture_lod", 300, enable_shader_texture_lod}, + {NGL_BACKEND_OPENGLES, "GL_OES_texture_3D", 300, enable_texture_3d}, }; for (int i = 0; i < NGLI_ARRAY_NB(features); i++) { From 16f6542ea023e71c276281cac870e9c03b316a03 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 23 Nov 2020 11:09:44 +0100 Subject: [PATCH 307/388] api: add NGL_CAP_MAX_COLOR_ATTACHMENTS --- libnodegl/api.c | 2 ++ libnodegl/nodegl.h.in | 1 + 2 files changed, 3 insertions(+) diff --git a/libnodegl/api.c b/libnodegl/api.c index 7632c3fc73..cb34aecb00 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -398,6 +398,7 @@ static const char *get_cap_string_id(unsigned cap_id) case NGL_CAP_BLOCK: return "block"; case NGL_CAP_COMPUTE: return "compute"; case NGL_CAP_INSTANCED_DRAW: return "instanced_draw"; + case NGL_CAP_MAX_COLOR_ATTACHMENTS: return "max_color_attachments"; case NGL_CAP_MAX_COMPUTE_GROUP_COUNT_X: return "max_compute_group_count_x"; case NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Y: return "max_compute_group_count_y"; case NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Z: return "max_compute_group_count_z"; @@ -433,6 +434,7 @@ static int load_caps(struct ngl_backend *backend, const struct gctx *gctx) CAP(NGL_CAP_BLOCK, has_block), CAP(NGL_CAP_COMPUTE, has_compute), CAP(NGL_CAP_INSTANCED_DRAW, has_instanced_draw), + CAP(NGL_CAP_MAX_COLOR_ATTACHMENTS, limits->max_color_attachments), CAP(NGL_CAP_MAX_COMPUTE_GROUP_COUNT_X, limits->max_compute_work_group_counts[0]), CAP(NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Y, limits->max_compute_work_group_counts[1]), CAP(NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Z, limits->max_compute_work_group_counts[2]), diff --git a/libnodegl/nodegl.h.in b/libnodegl/nodegl.h.in index e68e84a8f7..30df45acea 100644 --- a/libnodegl/nodegl.h.in +++ b/libnodegl/nodegl.h.in @@ -417,6 +417,7 @@ struct ngl_config { #define NGL_CAP_BLOCK NGL_NODE_BLOCK #define NGL_CAP_COMPUTE NGL_NODE_COMPUTE #define NGL_CAP_INSTANCED_DRAW NGLI_FOURCC('I','D','r','w') +#define NGL_CAP_MAX_COLOR_ATTACHMENTS NGLI_FOURCC('M','C','A','t') #define NGL_CAP_MAX_COMPUTE_GROUP_COUNT_X NGLI_FOURCC('C','G','c','x') #define NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Y NGLI_FOURCC('C','G','c','y') #define NGL_CAP_MAX_COMPUTE_GROUP_COUNT_Z NGLI_FOURCC('C','G','c','z') From 13a4b0fa1f306f36b952bce5278981db4bbfec0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Wed, 18 Nov 2020 11:57:51 +0100 Subject: [PATCH 308/388] tests: run tests with meson With this commit, running the tests on an environement without make becomes possible. The native parallelism is more efficient than make, and we still have the ability to chain dependencies, typically for generating an intermediate media file. Another benefit is the final test summary being more convenient than what make -k provides. Also, the test suite now runs automatically on all available backends by default. If you want to select a particular backend, you can use make tests TESTS_SUITE=opengl. Similarly, you can pick a particular suite with something like TESTS_SUITE=shape. --- .github/workflows/ci_coverage.yml | 7 +- .github/workflows/ci_linux.yml | 7 +- Makefile | 14 +- tests/.gitignore | 1 - tests/Makefile | 93 --------- tests/anim.mak | 31 --- tests/api.mak | 34 ---- tests/blending.mak | 31 --- tests/compute.mak | 32 ---- tests/data.mak | 69 ------- tests/live.mak | 41 ---- tests/media.mak | 33 ---- tests/meson.build | 304 ++++++++++++++++++++++++++++++ tests/rtt.mak | 41 ---- tests/shape.mak | 48 ----- tests/text.mak | 35 ---- tests/texture.mak | 39 ---- tests/transform.mak | 37 ---- 18 files changed, 319 insertions(+), 578 deletions(-) delete mode 100644 tests/Makefile delete mode 100644 tests/anim.mak delete mode 100644 tests/api.mak delete mode 100644 tests/blending.mak delete mode 100644 tests/compute.mak delete mode 100644 tests/data.mak delete mode 100644 tests/live.mak delete mode 100644 tests/media.mak create mode 100644 tests/meson.build delete mode 100644 tests/rtt.mak delete mode 100644 tests/shape.mak delete mode 100644 tests/text.mak delete mode 100644 tests/texture.mak delete mode 100644 tests/transform.mak diff --git a/.github/workflows/ci_coverage.yml b/.github/workflows/ci_coverage.yml index 71b279b1ca..7ee07c8b9f 100644 --- a/.github/workflows/ci_coverage.yml +++ b/.github/workflows/ci_coverage.yml @@ -18,12 +18,9 @@ jobs: sudo apt -y install libsdl2-dev python3-venv gcovr sudo apt -y install ffmpeg libavcodec-dev libavutil-dev libavformat-dev libavdevice-dev libavfilter-dev libswscale-dev libswresample-dev libpostproc-dev - - name: Run tests with GL backend + - name: Run tests run: | - BACKEND=opengl DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests COVERAGE=yes - - name: Run tests with GLES backend - run: | - BACKEND=opengles DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests COVERAGE=yes + DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests COVERAGE=yes - name: Get coverage run: | make coverage-xml diff --git a/.github/workflows/ci_linux.yml b/.github/workflows/ci_linux.yml index 7196985491..5d93b54540 100644 --- a/.github/workflows/ci_linux.yml +++ b/.github/workflows/ci_linux.yml @@ -31,9 +31,6 @@ jobs: - name: Build run: | make -j$(($(nproc)+1)) - - name: Run tests with GL backend + - name: Run tests run: | - BACKEND=opengl DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests - - name: Run tests with GLES backend - run: | - BACKEND=opengles DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests + DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests diff --git a/Makefile b/Makefile index 1a6218ab6e..89bbf7ca1a 100644 --- a/Makefile +++ b/Makefile @@ -75,6 +75,10 @@ MESON_SETUP += --libdir lib endif endif +ifneq ($(TESTS_SUITE),) +MESON_TESTS_SUITE_OPTS += --suite $(TESTS_SUITE) +endif + all: ngl-tools-install pynodegl-utils-install @echo @echo " Install completed." @@ -160,8 +164,11 @@ else (. $(ACTIVATE) && pip install meson ninja) endif -tests: ngl-tools-install pynodegl-utils-install nodegl-tests - (. $(ACTIVATE) && $(MAKE) -C tests) +tests: nodegl-tests tests-setup + (. $(ACTIVATE) && meson test $(MESON_TESTS_SUITE_OPTS) -C builddir/tests) + +tests-setup: ngl-tools-install pynodegl-utils-install + (. $(ACTIVATE) && $(MESON_SETUP) builddir/tests tests) nodegl-tests: nodegl-install (. $(ACTIVATE) && meson test -C builddir/libnodegl) @@ -183,6 +190,7 @@ clean: clean_py $(RM) -r builddir/sxplayer $(RM) -r builddir/libnodegl $(RM) -r builddir/ngl-tools + $(RM) -r builddir/tests # You need to build and run with COVERAGE set to generate data. # For example: `make clean && make -j8 tests COVERAGE=yes` @@ -199,6 +207,6 @@ coverage-xml: .PHONY: pynodegl-install pynodegl-deps-install .PHONY: nodegl-install nodegl-setup .PHONY: sxplayer-install -.PHONY: tests +.PHONY: tests tests-setup .PHONY: clean clean_py .PHONY: coverage-html coverage-xml diff --git a/tests/.gitignore b/tests/.gitignore index 78722384da..0d20b6487c 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1,2 +1 @@ *.pyc -ngl-media-test.nut diff --git a/tests/Makefile b/tests/Makefile deleted file mode 100644 index 4e77f9e4e8..0000000000 --- a/tests/Makefile +++ /dev/null @@ -1,93 +0,0 @@ -# -# Copyright 2017 GoPro Inc. -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -BACKEND ?= auto -HAS_COMPUTE := $(or $(shell ngl-probe -l quiet -b $(BACKEND) --cap compute | tail -n1),0) -MAX_SAMPLES := $(or $(shell ngl-probe -l quiet -b $(BACKEND) --cap max_samples | tail -n1),0) - -ifeq ($(V),) -Q := @ -OUT := > /dev/null -else -Q := -OUT := -endif - -all: tests - -# 1:category, 2:name -define DECLARE_TEST -test-$(1)-$(2): FUNC_NAME = $(1)_$(2) -TESTS_$(1) += test-$(1)-$(2) -endef - -# 1:destvar 2:category, 3:namelist -define DECLARE_TESTS -$(foreach X,$(3),$(eval $(call DECLARE_TEST,$(2),$(X)))) -test-$(2): $$(TESTS_$(2)) -.PHONY: test-$(2) -$(1) += $$(TESTS_$(2)) -$$(TESTS_$(2)): MODULE = $(2) -endef - -# 1:category, 2:namelist -define DECLARE_SIMPLE_TESTS -$(call DECLARE_TESTS,SIMPLE_TESTS,$(1),$(2)) -endef - -# 1:category, 2:namelist -define DECLARE_REF_TESTS -$(call DECLARE_TESTS,REF_TESTS,$(1),$(2)) -endef - -include anim.mak -include api.mak -include blending.mak -include compute.mak -include data.mak -include live.mak -include media.mak -include rtt.mak -include shape.mak -include text.mak -include texture.mak -include transform.mak - -$(REF_TESTS): - @echo $@ - $(Q)ngl-test $(MODULE).py $(FUNC_NAME) refs/$(FUNC_NAME).ref $(OUT) - -$(SIMPLE_TESTS): - @echo $@ - $(Q)ngl-test $(MODULE).py $(FUNC_NAME) $(OUT) - -TESTS = $(SIMPLE_TESTS) $(REF_TESTS) - -tests: $(TESTS) - -$(TESTS): tests-info -tests-info: - $(Q)ngl-probe -l info -b $(BACKEND) - -tests-list: - @for test in $(TESTS); do echo $$test; done - -.PHONY: all tests tests-info tests-list $(TESTS) diff --git a/tests/anim.mak b/tests/anim.mak deleted file mode 100644 index b82a8f9467..0000000000 --- a/tests/anim.mak +++ /dev/null @@ -1,31 +0,0 @@ -# -# Copyright 2020 GoPro Inc. -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -ANIM_TEST_NAMES = \ - forward_api \ - forward_float \ - forward_vec2 \ - forward_vec3 \ - forward_vec4 \ - forward_quat \ - resolution_api \ - -$(eval $(call DECLARE_REF_TESTS,anim,$(ANIM_TEST_NAMES))) diff --git a/tests/api.mak b/tests/api.mak deleted file mode 100644 index 084eb4ae07..0000000000 --- a/tests/api.mak +++ /dev/null @@ -1,34 +0,0 @@ -# -# Copyright 2019 GoPro Inc. -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -API_TEST_NAMES = \ - backend \ - reconfigure \ - reconfigure_clearcolor \ - reconfigure_fail \ - ctx_ownership \ - ctx_ownership_subgraph \ - capture_buffer_lifetime \ - hud \ - text_live_change \ - media_sharing_failure \ - -$(eval $(call DECLARE_SIMPLE_TESTS,api,$(API_TEST_NAMES))) diff --git a/tests/blending.mak b/tests/blending.mak deleted file mode 100644 index 346db036c9..0000000000 --- a/tests/blending.mak +++ /dev/null @@ -1,31 +0,0 @@ -# -# Copyright 2020 GoPro Inc. -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -BLENDING_TEST_NAMES = \ - all_diamond \ - all_timed_diamond \ - none \ - multiply \ - screen \ - darken \ - lighten \ - -$(eval $(call DECLARE_REF_TESTS,blending,$(BLENDING_TEST_NAMES))) diff --git a/tests/compute.mak b/tests/compute.mak deleted file mode 100644 index 90bace9f67..0000000000 --- a/tests/compute.mak +++ /dev/null @@ -1,32 +0,0 @@ -# -# Copyright 2020 GoPro Inc. -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -COMPUTE_TEST_NAMES = - -ifeq ($(HAS_COMPUTE),1) -COMPUTE_TEST_NAMES += \ - animation \ - histogram \ - particules \ - -endif - -$(eval $(call DECLARE_REF_TESTS,compute,$(COMPUTE_TEST_NAMES))) diff --git a/tests/data.mak b/tests/data.mak deleted file mode 100644 index 8390d35c08..0000000000 --- a/tests/data.mak +++ /dev/null @@ -1,69 +0,0 @@ -# -# Copyright 2020 GoPro Inc. -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -DATA_TEST_UNIFORM_NAMES = \ - single_bool \ - single_float \ - single_int \ - single_ivec2 \ - single_ivec3 \ - single_ivec4 \ - single_uint \ - single_uvec2 \ - single_uvec3 \ - single_uvec4 \ - single_mat4 \ - single_quat_mat4 \ - single_quat_vec4 \ - single_vec2 \ - single_vec3 \ - single_vec4 \ - animated_quat_mat4 \ - animated_quat_vec4 \ - array_float \ - array_vec2 \ - array_vec3 \ - array_vec4 \ - -DATA_TEST_BLOCK_NAMES = \ - $(DATA_TEST_UNIFORM_NAMES) \ - animated_buffer_float \ - animated_buffer_vec2 \ - animated_buffer_vec3 \ - animated_buffer_vec4 \ - array_int \ - array_ivec2 \ - array_ivec3 \ - array_ivec4 \ - array_mat4 \ - -DATA_TEST_NAMES += $(addsuffix _uniform,$(DATA_TEST_UNIFORM_NAMES)) -DATA_TEST_NAMES += $(addsuffix _std140,$(DATA_TEST_BLOCK_NAMES)) - -ifeq ($(HAS_COMPUTE),1) -DATA_TEST_NAMES += $(addsuffix _std430,$(DATA_TEST_BLOCK_NAMES)) -endif - -DATA_TEST_NAMES += \ - streamed_buffer_vec4 \ - streamed_buffer_vec4_time_anim \ - -$(eval $(call DECLARE_REF_TESTS,data,$(DATA_TEST_NAMES))) diff --git a/tests/live.mak b/tests/live.mak deleted file mode 100644 index f32abbd4cb..0000000000 --- a/tests/live.mak +++ /dev/null @@ -1,41 +0,0 @@ -# -# Copyright 2020 GoPro Inc. -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -LIVE_TEST_BASE_NAMES = \ - single_bool \ - single_float \ - single_int \ - single_mat4 \ - single_quat_mat4 \ - single_quat_vec4 \ - single_vec2 \ - single_vec3 \ - single_vec4 \ - trf_single_mat4 \ - -LIVE_TEST_NAMES += $(addsuffix _uniform,$(LIVE_TEST_BASE_NAMES)) -LIVE_TEST_NAMES += $(addsuffix _std140,$(LIVE_TEST_BASE_NAMES)) - -ifeq ($(HAS_COMPUTE),1) -LIVE_TEST_NAMES += $(addsuffix _std430,$(LIVE_TEST_BASE_NAMES)) -endif - -$(eval $(call DECLARE_REF_TESTS,live,$(LIVE_TEST_NAMES))) diff --git a/tests/media.mak b/tests/media.mak deleted file mode 100644 index 1fea0d8950..0000000000 --- a/tests/media.mak +++ /dev/null @@ -1,33 +0,0 @@ -# -# Copyright 2020 GoPro Inc. -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -MEDIA_TEST_NAMES = \ - phases_display \ - phases_resources \ - -$(eval $(call DECLARE_REF_TESTS,media,$(MEDIA_TEST_NAMES))) - -MEDIA_TEST_FILE = ngl-media-test.nut - -$(MEDIA_TEST_FILE): - ffmpeg -nostdin -nostats -f lavfi -i testsrc=d=30:r=60 -c:v ffv1 -y $@ - -$(TESTS_media): $(MEDIA_TEST_FILE) diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 0000000000..9a34e34f5f --- /dev/null +++ b/tests/meson.build @@ -0,0 +1,304 @@ +project('ngl-tests', meson_version: '>= 0.56.0') + +ngl_test = find_program('ngl-test') +ngl_probe = find_program('ngl-probe') + +backends = [] +probe_output = run_command(ngl_probe).stdout() +message('ngl-probe:\n' + probe_output) +foreach line : probe_output.split('\n') + if line.startswith('- ') + backends += line.substring(2, -1) + endif +endforeach + +media_test_filename = 'ngl-media-test.nut' +media_test_file = custom_target( + media_test_filename, + command: [ + find_program('ffmpeg'), + '-nostdin', '-nostats', + '-f', 'lavfi', '-i', 'testsrc=d=30:r=60', + '-c:v', 'ffv1', + '-y', '@OUTPUT@', + ], + output: media_test_filename, +) + +foreach backend : backends + cap_cmd = [ngl_probe, '-l', 'quiet', '-b', backend, '--cap'] + # ngl-probe may display garbage on stdout: typically Mesa does this on + # offscreen at initialization, this is why we have .split()[-1] to read the + # last word in the output. + has_compute = run_command(cap_cmd + ['compute']).stdout().split()[-1].to_int() == 1 ? true : false + max_samples = run_command(cap_cmd + ['max_samples']).stdout().split()[-1].to_int() + + message('Backend: ' + backend) + message('- Compute: @0@'.format(has_compute)) + message('- Max samples: @0@'.format(max_samples)) + + tests_anim = [ + 'forward_api', + 'forward_float', + 'forward_vec2', + 'forward_vec3', + 'forward_vec4', + 'forward_quat', + 'resolution_api', + ] + + tests_api = [ + 'backend', + 'reconfigure', + 'reconfigure_clearcolor', + 'reconfigure_fail', + 'ctx_ownership', + 'ctx_ownership_subgraph', + 'capture_buffer_lifetime', + 'hud', + 'text_live_change', + 'media_sharing_failure', + ] + + tests_blending = [ + 'all_diamond', + 'all_timed_diamond', + 'none', + 'multiply', + 'screen', + 'darken', + 'lighten', + ] + + tests_compute = [] + if has_compute + tests_compute += [ + 'animation', + 'histogram', + 'particules', + ] + endif + + uniform_names = [ + 'single_bool', + 'single_float', + 'single_int', + 'single_ivec2', + 'single_ivec3', + 'single_ivec4', + 'single_uint', + 'single_uvec2', + 'single_uvec3', + 'single_uvec4', + 'single_mat4', + 'single_quat_mat4', + 'single_quat_vec4', + 'single_vec2', + 'single_vec3', + 'single_vec4', + 'animated_quat_mat4', + 'animated_quat_vec4', + 'array_float', + 'array_vec2', + 'array_vec3', + 'array_vec4', + ] + + block_names = uniform_names + [ + 'animated_buffer_float', + 'animated_buffer_vec2', + 'animated_buffer_vec3', + 'animated_buffer_vec4', + 'array_int', + 'array_ivec2', + 'array_ivec3', + 'array_ivec4', + 'array_mat4', + ] + + tests_data = [ + 'streamed_buffer_vec4', + 'streamed_buffer_vec4_time_anim', + ] + foreach test_name : uniform_names + tests_data += test_name + '_uniform' + endforeach + foreach test_name : block_names + tests_data += test_name + '_std140' + endforeach + if has_compute + foreach test_name : block_names + tests_data += test_name + '_std430' + endforeach + endif + + live_names = [ + 'single_bool', + 'single_float', + 'single_int', + 'single_mat4', + 'single_quat_mat4', + 'single_quat_vec4', + 'single_vec2', + 'single_vec3', + 'single_vec4', + 'trf_single_mat4', + ] + + tests_live = [] + foreach test_name : live_names + tests_live += test_name + '_uniform' + endforeach + foreach test_name : live_names + tests_live += test_name + '_std140' + endforeach + if has_compute + foreach test_name : live_names + tests_live += test_name + '_std430' + endforeach + endif + + tests_media = [ + 'phases_display', + 'phases_resources', + ] + + tests_rtt = [ + 'load_attachment', + 'feature_depth', + 'feature_depth_stencil', + 'mipmap', + 'sample_depth', + 'texture_depth', + 'texture_depth_stencil', + 'clear_attachment_with_timeranges', + ] + + if max_samples >= 4 + tests_rtt += [ + 'feature_depth_msaa', + 'feature_depth_stencil_msaa', + 'texture_depth_msaa', + 'texture_depth_stencil_msaa', + ] + endif + + tests_shape = [ + 'precision_iovar', + 'triangle', + 'triangle_cull_back', + 'triangle_cull_front', + 'triangles_mat4_attribute', + 'quad', + 'quad_cull_back', + 'quad_cull_front', + 'circle', + 'circle_cull_back', + 'circle_cull_front', + 'diamond_colormask', + 'geometry', + 'geometry_normals', + 'geometry_indices', + 'geometry_normals_indices', + 'morphing', + 'cropboard', + 'cropboard_indices', + ] + + if max_samples >= 4 + tests_shape += [ + 'triangle_msaa', + ] + endif + + tests_text = [ + 'colors', + '0_to_127', + 'align_cc', + 'align_cr', + 'align_cl', + 'align_bc', + 'align_br', + 'align_bl', + 'align_tc', + 'align_tr', + 'align_tl', + ] + + tests_texture = [ + '3d', + 'clear_and_scissor', + 'cubemap', + 'cubemap_from_mrt', + 'data', + 'data_animated', + 'data_unaligned_row', + 'mipmap', + 'scissor', + ] + + if max_samples >= 4 + tests_texture += [ + 'cubemap_from_mrt_msaa', + ] + endif + + tests_transform = [ + 'animated_camera', + 'matrix', + 'translate', + 'translate_animated', + 'scale', + 'scale_animated', + 'scale_anchor', + 'scale_anchor_animated', + 'rotate', + 'rotate_anchor', + 'rotate_quat', + 'rotate_quat_anchor', + 'rotate_quat_animated', + ] + + tests = { + 'api': {'tests': tests_api, 'has_refs': false}, + 'anim': {'tests': tests_anim}, + 'blending': {'tests': tests_blending}, + 'compute': {'tests': tests_compute}, + 'data': {'tests': tests_data}, + 'live': {'tests': tests_live}, + 'media': {'tests': tests_media, 'depends': media_test_file}, + 'rtt': {'tests': tests_rtt}, + 'shape': {'tests': tests_shape}, + 'text': {'tests': tests_text}, + 'texture': {'tests': tests_texture}, + 'transform': {'tests': tests_transform}, + } + + env = ['BACKEND=' + backend] + + foreach category, test_specs : tests + test_names = test_specs.get('tests') + has_refs = test_specs.get('has_refs', true) + depends = test_specs.get('depends', []) + + foreach test_name : test_names + func_name = '@0@_@1@'.format(category, test_name) + + args = [files(category + '.py'), func_name] + if has_refs + # We don't use files() here because the file may not exist yet (in case + # we just added the test and want to generate it) + args += meson.current_source_dir() / 'refs/@0@.ref'.format(func_name) + endif + + test( + test_name, + ngl_test, + args: args, + env: env, + depends: depends, + suite: [backend, category], + ) + endforeach + endforeach +endforeach diff --git a/tests/rtt.mak b/tests/rtt.mak deleted file mode 100644 index 8099227af3..0000000000 --- a/tests/rtt.mak +++ /dev/null @@ -1,41 +0,0 @@ -# -# Copyright 2020 GoPro Inc. -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -RTT_TEST_NAMES = \ - load_attachment \ - feature_depth \ - feature_depth_stencil \ - mipmap \ - sample_depth \ - texture_depth \ - texture_depth_stencil \ - clear_attachment_with_timeranges \ - -ifneq ($(MAX_SAMPLES),$(filter $(MAX_SAMPLES),0 1)) -RTT_TEST_NAMES += \ - feature_depth_msaa \ - feature_depth_stencil_msaa \ - texture_depth_msaa \ - texture_depth_stencil_msaa \ - -endif - -$(eval $(call DECLARE_REF_TESTS,rtt,$(RTT_TEST_NAMES))) diff --git a/tests/shape.mak b/tests/shape.mak deleted file mode 100644 index 9fc78b519d..0000000000 --- a/tests/shape.mak +++ /dev/null @@ -1,48 +0,0 @@ -# -# Copyright 2020 GoPro Inc. -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -SHAPE_TEST_NAMES = \ - precision_iovar \ - triangle \ - triangle_cull_back \ - triangle_cull_front \ - triangles_mat4_attribute \ - quad \ - quad_cull_back \ - quad_cull_front \ - circle \ - circle_cull_back \ - circle_cull_front \ - diamond_colormask \ - geometry \ - geometry_normals \ - geometry_indices \ - geometry_normals_indices \ - morphing \ - cropboard \ - cropboard_indices \ - -ifneq ($(MAX_SAMPLES),$(filter $(MAX_SAMPLES),0 1)) -SHAPE_TEST_NAMES += triangle_msaa \ - -endif - -$(eval $(call DECLARE_REF_TESTS,shape,$(SHAPE_TEST_NAMES))) diff --git a/tests/text.mak b/tests/text.mak deleted file mode 100644 index b2d57a61fe..0000000000 --- a/tests/text.mak +++ /dev/null @@ -1,35 +0,0 @@ -# -# Copyright 2020 GoPro Inc. -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -TEXT_TEST_NAMES = \ - colors \ - 0_to_127 \ - align_cc \ - align_cr \ - align_cl \ - align_bc \ - align_br \ - align_bl \ - align_tc \ - align_tr \ - align_tl \ - -$(eval $(call DECLARE_REF_TESTS,text,$(TEXT_TEST_NAMES))) diff --git a/tests/texture.mak b/tests/texture.mak deleted file mode 100644 index ae26d68a84..0000000000 --- a/tests/texture.mak +++ /dev/null @@ -1,39 +0,0 @@ -# -# Copyright 2020 GoPro Inc. -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -TEXTURE_TEST_NAMES = \ - 3d \ - clear_and_scissor \ - cubemap \ - cubemap_from_mrt \ - data \ - data_animated \ - data_unaligned_row \ - mipmap \ - scissor \ - -ifneq ($(MAX_SAMPLES),$(filter $(MAX_SAMPLES),0 1)) -TEXTURE_TEST_NAMES += \ - cubemap_from_mrt_msaa \ - -endif - -$(eval $(call DECLARE_REF_TESTS,texture,$(TEXTURE_TEST_NAMES))) diff --git a/tests/transform.mak b/tests/transform.mak deleted file mode 100644 index 90db64d982..0000000000 --- a/tests/transform.mak +++ /dev/null @@ -1,37 +0,0 @@ -# -# Copyright 2020 GoPro Inc. -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -TRANSFORM_TEST_NAMES = \ - animated_camera \ - matrix \ - translate \ - translate_animated \ - scale \ - scale_animated \ - scale_anchor \ - scale_anchor_animated \ - rotate \ - rotate_anchor \ - rotate_quat \ - rotate_quat_anchor \ - rotate_quat_animated \ - -$(eval $(call DECLARE_REF_TESTS,transform,$(TRANSFORM_TEST_NAMES))) From da75d3664dfe40fd8bb90b461b995837f2c100b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Wed, 18 Nov 2020 14:57:13 +0100 Subject: [PATCH 309/388] build: expose more default settings This is typically useful for shell completion. --- Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Makefile b/Makefile index 89bbf7ca1a..c0a9cd0791 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,12 @@ PYTHON ?= python$(if $(shell which python$(PYTHON_MAJOR) 2> /dev/null),$(PYT TAR ?= tar TARGET_OS ?= $(shell uname -s) +DEBUG_GL ?= no +DEBUG_MEM ?= no +DEBUG_SCENE ?= no +TESTS_SUITE ?= +V ?= + ifneq ($(shell $(PYTHON) -c "import sys;print(sys.version_info.major)"),$(PYTHON_MAJOR)) $(error "Python $(PYTHON_MAJOR) not found") endif From 5301641d4d1b35dd09710735c9d5d3177520021a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 23 Nov 2020 11:59:53 +0100 Subject: [PATCH 310/388] api: add NGL_CAP_UINT_UNIFORMS --- libnodegl/api.c | 3 +++ libnodegl/nodegl.h.in | 1 + 2 files changed, 4 insertions(+) diff --git a/libnodegl/api.c b/libnodegl/api.c index cb34aecb00..ab114044d8 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -411,6 +411,7 @@ static const char *get_cap_string_id(unsigned cap_id) case NGL_CAP_SHADER_TEXTURE_LOD: return "shader_texture_lod"; case NGL_CAP_TEXTURE_3D: return "texture_3d"; case NGL_CAP_TEXTURE_CUBE: return "texture_cube"; + case NGL_CAP_UINT_UNIFORMS: return "uint_uniforms"; } ngli_assert(0); } @@ -428,6 +429,7 @@ static int load_caps(struct ngl_backend *backend, const struct gctx *gctx) const int has_shader_texture_lod = ALL_FEATURES(gctx->features, NGLI_FEATURE_SHADER_TEXTURE_LOD); const int has_texture_3d = ALL_FEATURES(gctx->features, NGLI_FEATURE_TEXTURE_3D); const int has_texture_cube = ALL_FEATURES(gctx->features, NGLI_FEATURE_TEXTURE_CUBE_MAP); + const int has_uint_uniforms = ALL_FEATURES(gctx->features, NGLI_FEATURE_UINT_UNIFORMS); const struct limits *limits = &gctx->limits; const struct ngl_cap caps[] = { @@ -447,6 +449,7 @@ static int load_caps(struct ngl_backend *backend, const struct gctx *gctx) CAP(NGL_CAP_SHADER_TEXTURE_LOD, has_shader_texture_lod), CAP(NGL_CAP_TEXTURE_3D, has_texture_3d), CAP(NGL_CAP_TEXTURE_CUBE, has_texture_cube), + CAP(NGL_CAP_UINT_UNIFORMS, has_uint_uniforms), }; backend->nb_caps = NGLI_ARRAY_NB(caps); diff --git a/libnodegl/nodegl.h.in b/libnodegl/nodegl.h.in index 30df45acea..292bb0a543 100644 --- a/libnodegl/nodegl.h.in +++ b/libnodegl/nodegl.h.in @@ -430,6 +430,7 @@ struct ngl_config { #define NGL_CAP_SHADER_TEXTURE_LOD NGLI_FOURCC('S','T','L','d') #define NGL_CAP_TEXTURE_3D NGL_NODE_TEXTURE3D #define NGL_CAP_TEXTURE_CUBE NGL_NODE_TEXTURECUBE +#define NGL_CAP_UINT_UNIFORMS NGLI_FOURCC('U','i','n','t') struct ngl_cap { unsigned id; /* any of NGL_CAP_* */ From 7aad40016c97dfc423e9b99108c71011c4cad059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 23 Nov 2020 12:00:03 +0100 Subject: [PATCH 311/388] tests: use backend capability probing more extensively This allows to pass all the tests with OpenGLES 2. --- tests/meson.build | 99 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 68 insertions(+), 31 deletions(-) diff --git a/tests/meson.build b/tests/meson.build index 9a34e34f5f..c9882680ac 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -30,11 +30,25 @@ foreach backend : backends # ngl-probe may display garbage on stdout: typically Mesa does this on # offscreen at initialization, this is why we have .split()[-1] to read the # last word in the output. - has_compute = run_command(cap_cmd + ['compute']).stdout().split()[-1].to_int() == 1 ? true : false - max_samples = run_command(cap_cmd + ['max_samples']).stdout().split()[-1].to_int() + has_block = run_command(cap_cmd + ['block']).stdout().split()[-1].to_int() == 1 ? true : false + has_compute = run_command(cap_cmd + ['compute']).stdout().split()[-1].to_int() == 1 ? true : false + has_instanced_draw = run_command(cap_cmd + ['instanced_draw']).stdout().split()[-1].to_int() == 1 ? true : false + has_shader_texture_lod = run_command(cap_cmd + ['shader_texture_lod']).stdout().split()[-1].to_int() == 1 ? true : false + has_texture_3d = run_command(cap_cmd + ['texture_3d']).stdout().split()[-1].to_int() == 1 ? true : false + has_texture_cube = run_command(cap_cmd + ['texture_cube']).stdout().split()[-1].to_int() == 1 ? true : false + has_uint_uniforms = run_command(cap_cmd + ['uint_uniforms']).stdout().split()[-1].to_int() == 1 ? true : false + max_color_attachments = run_command(cap_cmd + ['max_color_attachments']).stdout().split()[-1].to_int() + max_samples = run_command(cap_cmd + ['max_samples']).stdout().split()[-1].to_int() message('Backend: ' + backend) + message('- Block: @0@'.format(has_block)) message('- Compute: @0@'.format(has_compute)) + message('- Instanced draw: @0@'.format(has_instanced_draw)) + message('- Shader texture lod: @0@'.format(has_shader_texture_lod)) + message('- Texture 3D: @0@'.format(has_texture_3d)) + message('- Texture Cube: @0@'.format(has_texture_cube)) + message('- Unsigned int uniforms: @0@'.format(has_uint_uniforms)) + message('- Max color attachments: @0@'.format(max_color_attachments)) message('- Max samples: @0@'.format(max_samples)) tests_anim = [ @@ -86,10 +100,6 @@ foreach backend : backends 'single_ivec2', 'single_ivec3', 'single_ivec4', - 'single_uint', - 'single_uvec2', - 'single_uvec3', - 'single_uvec4', 'single_mat4', 'single_quat_mat4', 'single_quat_vec4', @@ -103,6 +113,14 @@ foreach backend : backends 'array_vec3', 'array_vec4', ] + if has_uint_uniforms + uniform_names += [ + 'single_uint', + 'single_uvec2', + 'single_uvec3', + 'single_uvec4', + ] + endif block_names = uniform_names + [ 'animated_buffer_float', @@ -116,20 +134,24 @@ foreach backend : backends 'array_mat4', ] - tests_data = [ - 'streamed_buffer_vec4', - 'streamed_buffer_vec4_time_anim', - ] + tests_data = [] foreach test_name : uniform_names tests_data += test_name + '_uniform' endforeach - foreach test_name : block_names - tests_data += test_name + '_std140' - endforeach - if has_compute + + if has_block + tests_data += [ + 'streamed_buffer_vec4', + 'streamed_buffer_vec4_time_anim', + ] foreach test_name : block_names - tests_data += test_name + '_std430' + tests_data += test_name + '_std140' endforeach + if has_compute + foreach test_name : block_names + tests_data += test_name + '_std430' + endforeach + endif endif live_names = [ @@ -149,13 +171,15 @@ foreach backend : backends foreach test_name : live_names tests_live += test_name + '_uniform' endforeach - foreach test_name : live_names - tests_live += test_name + '_std140' - endforeach - if has_compute + if has_block foreach test_name : live_names - tests_live += test_name + '_std430' + tests_live += test_name + '_std140' endforeach + if has_compute + foreach test_name : live_names + tests_live += test_name + '_std430' + endforeach + endif endif tests_media = [ @@ -188,7 +212,6 @@ foreach backend : backends 'triangle', 'triangle_cull_back', 'triangle_cull_front', - 'triangles_mat4_attribute', 'quad', 'quad_cull_back', 'quad_cull_front', @@ -201,10 +224,16 @@ foreach backend : backends 'geometry_indices', 'geometry_normals_indices', 'morphing', - 'cropboard', - 'cropboard_indices', ] + if has_instanced_draw + tests_shape += [ + 'cropboard', + 'cropboard_indices', + 'triangles_mat4_attribute', + ] + endif + if max_samples >= 4 tests_shape += [ 'triangle_msaa', @@ -226,21 +255,29 @@ foreach backend : backends ] tests_texture = [ - '3d', 'clear_and_scissor', - 'cubemap', - 'cubemap_from_mrt', 'data', 'data_animated', 'data_unaligned_row', - 'mipmap', 'scissor', ] - if max_samples >= 4 - tests_texture += [ - 'cubemap_from_mrt_msaa', - ] + if has_shader_texture_lod + tests_texture += 'mipmap' + endif + + if has_texture_3d + tests_texture += '3d' + endif + + if has_texture_cube + tests_texture += 'cubemap' + if max_color_attachments >= 6 + tests_texture += 'cubemap_from_mrt' + if max_samples >= 4 + tests_texture += 'cubemap_from_mrt_msaa' + endif + endif endif tests_transform = [ From bc4b8fb2a5c99e0b757eb9b1f118037e42fe9e14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 23 Nov 2020 12:07:44 +0100 Subject: [PATCH 312/388] ci/linux: add an OpenGLES 2 job --- .github/workflows/ci_linux.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/ci_linux.yml b/.github/workflows/ci_linux.yml index 5d93b54540..6b6ce6c57c 100644 --- a/.github/workflows/ci_linux.yml +++ b/.github/workflows/ci_linux.yml @@ -34,3 +34,23 @@ jobs: - name: Run tests run: | DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests + + gles2: + + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v1 + + - name: Install dependencies + run: | + sudo apt -y update + sudo apt -y install libsdl2-dev python3-venv + sudo apt -y install ffmpeg libavcodec-dev libavutil-dev libavformat-dev libavdevice-dev libavfilter-dev libswscale-dev libswresample-dev libpostproc-dev + + - name: Build + run: | + make -j$(($(nproc)+1)) + - name: Run tests with OpenGLES2 + run: | + MESA_GLES_VERSION_OVERRIDE=2.0 DEBUG=yes DEBUG_GL=yes make -k -j$(($(nproc)+1)) tests TESTS_SUITE=opengles From 7037cccb4f5c459441e2365ab371be6b9c243dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Mon, 23 Nov 2020 13:29:22 +0100 Subject: [PATCH 313/388] build: disable LTO on MinGW-w64 Fixes crashes in ngl-test and ngl-python on MinGW. --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c0a9cd0791..4f4ce9943a 100644 --- a/Makefile +++ b/Makefile @@ -61,7 +61,10 @@ endif ifeq ($(DEBUG),yes) MESON_SETUP += --buildtype=debugoptimized else -MESON_SETUP += --buildtype=release -Db_lto=true +MESON_SETUP += --buildtype=release +ifneq ($(TARGET_OS),MinGW-w64) +MESON_SETUP += -Db_lto=true +endif endif ifneq ($(V),) MESON_COMPILE += -v From 19cd8cd75396399eb776b2c21a9fed46e55879d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 24 Nov 2020 14:01:53 +0100 Subject: [PATCH 314/388] tests/compute: fix compute_particules workgroups count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this patch, we had 128x1x1 workgroups of 4x4x1 compute instances, so a total of 2048 compute instances for 128 particles. This patch brings down the number of workgroup to 128/4²=8 (4² being the size of a work group, 4x4x1) so that we have a matching number of compute instances and particules: 8x1x1 x 4x4x1 = 128. This issue was detected while experimenting with MoltenVK in the Vulkan development branch. --- tests/compute.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/compute.py b/tests/compute.py index 32b0d15136..83684dd2fb 100644 --- a/tests/compute.py +++ b/tests/compute.py @@ -93,10 +93,10 @@ def compute_particules(cfg): time = ngl.AnimatedFloat(animkf) duration = ngl.UniformFloat(cfg.duration) - group_size = nb_particules / local_size + group_size = nb_particules / local_size**2 program = ngl.ComputeProgram(_PARTICULES_COMPUTE % dict(local_size=local_size)) program.update_properties(odata=ngl.ResourceProps(writable=True)) - compute = ngl.Compute(nb_particules, 1, 1, program) + compute = ngl.Compute(group_size, 1, 1, program) compute.update_resources(time=time, duration=duration, idata=ipositions, odata=opositions) circle = ngl.Circle(radius=0.05) From 65608f94778f89c5ee352f8313028c156cc44ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 24 Nov 2020 14:21:58 +0100 Subject: [PATCH 315/388] tests/compute: derivate the number of particules from group and local sizes This makes the code more robust to mismatching mistakes. --- tests/compute.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/compute.py b/tests/compute.py index 83684dd2fb..1389a175f9 100644 --- a/tests/compute.py +++ b/tests/compute.py @@ -31,7 +31,7 @@ _PARTICULES_COMPUTE = ''' -layout(local_size_x = %(local_size)d, local_size_y = %(local_size)d, local_size_z = 1) in; +layout(local_size_x = %(local_size_x)d, local_size_y = %(local_size_y)d, local_size_z = %(local_size_z)d) in; void main() { @@ -61,8 +61,10 @@ def compute_particules(cfg): random.seed(0) cfg.duration = 10 - local_size = 4 - nb_particules = 128 + group_size = (8, 1, 1) + local_size = (4, 4, 1) + nb_particules = group_size[0] * group_size[1] * group_size[2] \ + * local_size[0] * local_size[1] * local_size[2] positions = array.array('f') velocities = array.array('f') @@ -93,10 +95,12 @@ def compute_particules(cfg): time = ngl.AnimatedFloat(animkf) duration = ngl.UniformFloat(cfg.duration) - group_size = nb_particules / local_size**2 - program = ngl.ComputeProgram(_PARTICULES_COMPUTE % dict(local_size=local_size)) + program = ngl.ComputeProgram(_PARTICULES_COMPUTE % dict( + local_size_x=local_size[0], + local_size_y=local_size[1], + local_size_z=local_size[2])) program.update_properties(odata=ngl.ResourceProps(writable=True)) - compute = ngl.Compute(group_size, 1, 1, program) + compute = ngl.Compute(group_size[0], group_size[1], group_size[2], program) compute.update_resources(time=time, duration=duration, idata=ipositions, odata=opositions) circle = ngl.Circle(radius=0.05) From 325923215733f60d5f92323e23882360f2e8ed15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 24 Nov 2020 18:10:14 +0100 Subject: [PATCH 316/388] tests/compute: rename group_size to workgroups group_size is confusing because it does not indicate the size of one group (this is the local size) but the number of work groups. --- tests/compute.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/compute.py b/tests/compute.py index 1389a175f9..031a070df5 100644 --- a/tests/compute.py +++ b/tests/compute.py @@ -61,9 +61,9 @@ def compute_particules(cfg): random.seed(0) cfg.duration = 10 - group_size = (8, 1, 1) + workgroups = (8, 1, 1) local_size = (4, 4, 1) - nb_particules = group_size[0] * group_size[1] * group_size[2] \ + nb_particules = workgroups[0] * workgroups[1] * workgroups[2] \ * local_size[0] * local_size[1] * local_size[2] positions = array.array('f') @@ -100,7 +100,7 @@ def compute_particules(cfg): local_size_y=local_size[1], local_size_z=local_size[2])) program.update_properties(odata=ngl.ResourceProps(writable=True)) - compute = ngl.Compute(group_size[0], group_size[1], group_size[2], program) + compute = ngl.Compute(workgroups[0], workgroups[1], workgroups[2], program) compute.update_resources(time=time, duration=duration, idata=ipositions, odata=opositions) circle = ngl.Circle(radius=0.05) From 0baf6139d6987d0b6e6c6d8c6aebdce161eedf71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 24 Nov 2020 18:08:55 +0100 Subject: [PATCH 317/388] tests/compute: adjust the index formula to work with any workgroup configuration --- tests/compute.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/compute.py b/tests/compute.py index 031a070df5..c93d93c315 100644 --- a/tests/compute.py +++ b/tests/compute.py @@ -35,7 +35,10 @@ void main() { - uint i = gl_WorkGroupID.x * gl_WorkGroupSize.x * gl_WorkGroupSize.y + gl_LocalInvocationIndex; + uvec3 total_size = gl_WorkGroupSize * gl_NumWorkGroups; + uint i = gl_GlobalInvocationID.z * total_size.x * total_size.y + + gl_GlobalInvocationID.y * total_size.x + + gl_GlobalInvocationID.x; vec3 iposition = idata.positions[i]; vec2 ivelocity = idata.velocities[i]; vec3 position; From ecc464a3c56a5d0015576a66f226651ba61c0664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 24 Nov 2020 18:13:24 +0100 Subject: [PATCH 318/388] tests/compute: diversify the workgroups layout --- tests/compute.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/compute.py b/tests/compute.py index c93d93c315..93669b29cd 100644 --- a/tests/compute.py +++ b/tests/compute.py @@ -64,7 +64,7 @@ def compute_particules(cfg): random.seed(0) cfg.duration = 10 - workgroups = (8, 1, 1) + workgroups = (2, 1, 4) local_size = (4, 4, 1) nb_particules = workgroups[0] * workgroups[1] * workgroups[2] \ * local_size[0] * local_size[1] * local_size[2] From 1748271a9013805bbd67a12b869e3ae84c8a2a8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 24 Nov 2020 18:14:59 +0100 Subject: [PATCH 319/388] tests/compute: rename particules to particles The former is the French word. --- tests/compute.py | 12 ++++++------ tests/meson.build | 2 +- ...{compute_particules.ref => compute_particles.ref} | 0 3 files changed, 7 insertions(+), 7 deletions(-) rename tests/refs/{compute_particules.ref => compute_particles.ref} (100%) diff --git a/tests/compute.py b/tests/compute.py index 93669b29cd..3bfb5fe9d0 100644 --- a/tests/compute.py +++ b/tests/compute.py @@ -61,17 +61,17 @@ @test_fingerprint(nb_keyframes=10, tolerance=1) @scene() -def compute_particules(cfg): +def compute_particles(cfg): random.seed(0) cfg.duration = 10 workgroups = (2, 1, 4) local_size = (4, 4, 1) - nb_particules = workgroups[0] * workgroups[1] * workgroups[2] \ - * local_size[0] * local_size[1] * local_size[2] + nb_particles = workgroups[0] * workgroups[1] * workgroups[2] \ + * local_size[0] * local_size[1] * local_size[2] positions = array.array('f') velocities = array.array('f') - for i in range(nb_particules): + for i in range(nb_particles): positions.extend([ random.uniform(-2.0, 1.0), random.uniform(-1.0, 1.0), @@ -89,7 +89,7 @@ def compute_particules(cfg): ], layout='std430', ) - opositions = ngl.Block(fields=[ngl.BufferVec3(count=nb_particules, label='positions')], layout='std140') + opositions = ngl.Block(fields=[ngl.BufferVec3(count=nb_particles, label='positions')], layout='std140') animkf = [ ngl.AnimKeyFrameFloat(0, 0), @@ -108,7 +108,7 @@ def compute_particules(cfg): circle = ngl.Circle(radius=0.05) program = ngl.Program(vertex=_PARTICULES_VERT, fragment=cfg.get_frag('color')) - render = ngl.Render(circle, program, nb_instances=nb_particules) + render = ngl.Render(circle, program, nb_instances=nb_particles) render.update_frag_resources(color=ngl.UniformVec4(value=COLORS['sgreen'])) render.update_vert_resources(data=opositions) diff --git a/tests/meson.build b/tests/meson.build index c9882680ac..577cb63325 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -89,7 +89,7 @@ foreach backend : backends tests_compute += [ 'animation', 'histogram', - 'particules', + 'particles', ] endif diff --git a/tests/refs/compute_particules.ref b/tests/refs/compute_particles.ref similarity index 100% rename from tests/refs/compute_particules.ref rename to tests/refs/compute_particles.ref From 178f9db409c56e02dd19ed82188e1f0386e31100 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Tue, 24 Nov 2020 18:15:30 +0100 Subject: [PATCH 320/388] examples/misc: rename particules to particles --- pynodegl-utils/pynodegl_utils/examples/misc.py | 14 +++++++------- .../shaders/{particules.comp => particles.comp} | 0 .../shaders/{particules.vert => particles.vert} | 0 3 files changed, 7 insertions(+), 7 deletions(-) rename pynodegl-utils/pynodegl_utils/examples/shaders/{particules.comp => particles.comp} (100%) rename pynodegl-utils/pynodegl_utils/examples/shaders/{particules.vert => particles.vert} (100%) diff --git a/pynodegl-utils/pynodegl_utils/examples/misc.py b/pynodegl-utils/pynodegl_utils/examples/misc.py index 1284bdc823..bec1c12cf0 100644 --- a/pynodegl-utils/pynodegl_utils/examples/misc.py +++ b/pynodegl-utils/pynodegl_utils/examples/misc.py @@ -234,19 +234,19 @@ def audiotex(cfg, freq_precision=7, overlay=0.6): return render -@scene(particules=scene.Range(range=[1, 1023])) -def particules(cfg, particules=32): +@scene(particles=scene.Range(range=[1, 1023])) +def particles(cfg, particles=32): '''Particules demo using compute shaders and instancing''' random.seed(0) - compute_shader = cfg.get_comp('particules') - vertex_shader = cfg.get_vert('particules') + compute_shader = cfg.get_comp('particles') + vertex_shader = cfg.get_vert('particles') fragment_shader = cfg.get_frag('color') cfg.duration = 6 x = 64 - p = x * particules + p = x * particles positions = array.array('f') velocities = array.array('f') @@ -275,7 +275,7 @@ def particules(cfg, particules=32): cp = ngl.ComputeProgram(compute_shader) cp.update_properties(opositions=ngl.ResourceProps(writable=True)) - c = ngl.Compute(x, particules, 1, cp) + c = ngl.Compute(x, particles, 1, cp) c.update_resources( time=utime, duration=uduration, @@ -295,7 +295,7 @@ def particules(cfg, particules=32): fragment=fragment_shader, ) p.update_vert_out_vars(var_uvcoord=ngl.IOVec2(), var_tex0_coord=ngl.IOVec2()) - r = ngl.Render(quad, p, nb_instances=particules) + r = ngl.Render(quad, p, nb_instances=particles) r.update_frag_resources(color=ngl.UniformVec4(value=(0, .6, .8, .9))) r.update_vert_resources(positions=opositions) diff --git a/pynodegl-utils/pynodegl_utils/examples/shaders/particules.comp b/pynodegl-utils/pynodegl_utils/examples/shaders/particles.comp similarity index 100% rename from pynodegl-utils/pynodegl_utils/examples/shaders/particules.comp rename to pynodegl-utils/pynodegl_utils/examples/shaders/particles.comp diff --git a/pynodegl-utils/pynodegl_utils/examples/shaders/particules.vert b/pynodegl-utils/pynodegl_utils/examples/shaders/particles.vert similarity index 100% rename from pynodegl-utils/pynodegl_utils/examples/shaders/particules.vert rename to pynodegl-utils/pynodegl_utils/examples/shaders/particles.vert From 71e9bbf67ca8f0964ae297ef44a9ca2c9282a320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Wed, 25 Nov 2020 15:18:20 +0100 Subject: [PATCH 321/388] build: properly check for CONFIG_* settings Fixes regression since 260fac36bb50b1531272669411df8cab1b1f9ca7. These defines were not previously set at all, but since the switch to the configuration, they are defined to 0 instead, causing the #ifdef forms to be evaluated to true. --- libnodegl/backends/gl/gctx_gl.c | 4 ++-- libnodegl/backends/gl/glcontext.c | 2 +- libnodegl/backends/gl/glcontext_egl.c | 2 +- libnodegl/backends/gl/glwrappers.h | 2 +- libnodegl/gen-gl-wrappers.py | 2 +- libnodegl/memory.c | 2 +- libnodegl/node_rtt.c | 2 +- libnodegl/utils.h | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libnodegl/backends/gl/gctx_gl.c b/libnodegl/backends/gl/gctx_gl.c index 71787fddae..34706b3bcb 100644 --- a/libnodegl/backends/gl/gctx_gl.c +++ b/libnodegl/backends/gl/gctx_gl.c @@ -311,7 +311,7 @@ static struct gctx *gl_create(const struct ngl_config *config) return (struct gctx *)s; } -#ifdef DEBUG_GL +#if DEBUG_GL #define GL_DEBUG_LOG(log_level, ...) ngli_log_print(log_level, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) static void NGLI_GL_APIENTRY gl_debug_message_callback(GLenum source, @@ -341,7 +341,7 @@ static int gl_init(struct gctx *s) struct glcontext *gl = s_priv->glcontext; s->features = gl->features; -#ifdef DEBUG_GL +#if DEBUG_GL if ((gl->features & NGLI_FEATURE_KHR_DEBUG)) { ngli_glEnable(gl, GL_DEBUG_OUTPUT_SYNCHRONOUS); ngli_glDebugMessageCallback(gl, gl_debug_message_callback, NULL); diff --git a/libnodegl/backends/gl/glcontext.c b/libnodegl/backends/gl/glcontext.c index 43677e4ccc..eee559c196 100644 --- a/libnodegl/backends/gl/glcontext.c +++ b/libnodegl/backends/gl/glcontext.c @@ -602,7 +602,7 @@ int ngli_glcontext_check_gl_error(const struct glcontext *glcontext, const char else LOG(ERROR, "GL error in %s: %04x", context, error); -#ifdef DEBUG_GL +#if DEBUG_GL ngli_assert(0); #endif diff --git a/libnodegl/backends/gl/glcontext_egl.c b/libnodegl/backends/gl/glcontext_egl.c index 1bbbeb5ddd..fe98dbf48e 100644 --- a/libnodegl/backends/gl/glcontext_egl.c +++ b/libnodegl/backends/gl/glcontext_egl.c @@ -320,7 +320,7 @@ try_again:; EGL_CONTEXT_MAJOR_VERSION_KHR, gl_versions[i].major, EGL_CONTEXT_MINOR_VERSION_KHR, gl_versions[i].minor, EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR, -#ifdef DEBUG_GL +#if DEBUG_GL EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, #endif EGL_NONE diff --git a/libnodegl/backends/gl/glwrappers.h b/libnodegl/backends/gl/glwrappers.h index f4df885cd2..9684d2083d 100644 --- a/libnodegl/backends/gl/glwrappers.h +++ b/libnodegl/backends/gl/glwrappers.h @@ -4,7 +4,7 @@ #include "config.h" -#ifdef DEBUG_GL +#if DEBUG_GL # define check_error_code ngli_glcontext_check_gl_error #else # define check_error_code(gl, glfuncname) do { } while (0) diff --git a/libnodegl/gen-gl-wrappers.py b/libnodegl/gen-gl-wrappers.py index a5a7ddb72b..93b557140f 100644 --- a/libnodegl/gen-gl-wrappers.py +++ b/libnodegl/gen-gl-wrappers.py @@ -275,7 +275,7 @@ def gen(gl_xml, func_file, def_file, wrap_file): #include "config.h" -#ifdef DEBUG_GL +#if DEBUG_GL # define check_error_code ngli_glcontext_check_gl_error #else # define check_error_code(gl, glfuncname) do { } while (0) diff --git a/libnodegl/memory.c b/libnodegl/memory.c index 0e296c00ed..b763c7f598 100644 --- a/libnodegl/memory.c +++ b/libnodegl/memory.c @@ -31,7 +31,7 @@ #include "memory.h" #include "utils.h" -#ifdef DEBUG_MEM +#if DEBUG_MEM static int failure_requested(void) { static int alloc_counter; diff --git a/libnodegl/node_rtt.c b/libnodegl/node_rtt.c index 6b5334ac52..d639697247 100644 --- a/libnodegl/node_rtt.c +++ b/libnodegl/node_rtt.c @@ -143,7 +143,7 @@ static int rtt_prepare(struct ngl_node *node) struct renderpass_children_info info = {0}; get_renderpass_children_info(s->child, &info); if (info.render_counts[0] && info.render_counts[1]) { -#ifdef DEBUG_SCENE +#if DEBUG_SCENE LOG(WARNING, "the underlying render pass might not be optimal as it contains a rtt or compute node in the middle of it"); #endif s->use_rt_resume = 1; diff --git a/libnodegl/utils.h b/libnodegl/utils.h index 3bea7e84e6..e364077f6b 100644 --- a/libnodegl/utils.h +++ b/libnodegl/utils.h @@ -56,7 +56,7 @@ #define NGLI_ALIGNED_VEC(vname) float __attribute__ ((aligned (NGLI_ALIGN_VAL))) vname[4] #define NGLI_ALIGNED_MAT(mname) float __attribute__ ((aligned (NGLI_ALIGN_VAL))) mname[4*4] -#ifdef CONFIG_SMALL +#if CONFIG_SMALL #define NGLI_DOCSTRING(s) (NULL) #else #define NGLI_DOCSTRING(s) (s) From 8ab0d714bec7e6056bc1b4cdb182c100b8e566c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Wed, 25 Nov 2020 15:21:30 +0100 Subject: [PATCH 322/388] build: refuse to build gen_doc when small option is set gen_doc needs parameter descriptions to be set. --- libnodegl/meson.build | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libnodegl/meson.build b/libnodegl/meson.build index 5f8a6ba80e..3c786fd3e6 100644 --- a/libnodegl/meson.build +++ b/libnodegl/meson.build @@ -415,6 +415,7 @@ run_target( # Doc # +if not get_option('small') gen_doc = executable( 'gen_doc', lib_src + files('gen_doc.c'), @@ -432,6 +433,7 @@ run_target( 'updatedoc', command: [cp, doc_file, meson.current_source_dir() / 'doc'], ) +endif # From c888a2ce20b2309b7a67ed405a719b4754386df4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Wed, 25 Nov 2020 15:21:45 +0100 Subject: [PATCH 323/388] build: reindent after previous commit --- libnodegl/meson.build | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/libnodegl/meson.build b/libnodegl/meson.build index 3c786fd3e6..08689eff25 100644 --- a/libnodegl/meson.build +++ b/libnodegl/meson.build @@ -416,23 +416,23 @@ run_target( # if not get_option('small') -gen_doc = executable( - 'gen_doc', - lib_src + files('gen_doc.c'), - dependencies: lib_deps, - build_by_default: false, - native: true, -) -doc_file = custom_target( - 'libnodegl.md', - command: gen_doc, - capture: true, - output: 'libnodegl.md', -) -run_target( - 'updatedoc', - command: [cp, doc_file, meson.current_source_dir() / 'doc'], -) + gen_doc = executable( + 'gen_doc', + lib_src + files('gen_doc.c'), + dependencies: lib_deps, + build_by_default: false, + native: true, + ) + doc_file = custom_target( + 'libnodegl.md', + command: gen_doc, + capture: true, + output: 'libnodegl.md', + ) + run_target( + 'updatedoc', + command: [cp, doc_file, meson.current_source_dir() / 'doc'], + ) endif From 381b78d61f0c1d86063407fd06488e054d83be11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Wed, 25 Nov 2020 15:22:01 +0100 Subject: [PATCH 324/388] gen_doc: refuse to compile when CONFIG_SMALL is set --- libnodegl/gen_doc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libnodegl/gen_doc.c b/libnodegl/gen_doc.c index 4e323354f8..046c811b79 100644 --- a/libnodegl/gen_doc.c +++ b/libnodegl/gen_doc.c @@ -21,6 +21,7 @@ #include +#include "config.h" #include "hmap.h" #include "memory.h" #include "nodegl.h" @@ -28,6 +29,10 @@ #include "nodes_register.h" #include "utils.h" +#if CONFIG_SMALL +#error gen doc can not work with CONFIG_SMALL set +#endif + #define CLASS_LIST(type_name, class) extern const struct node_class class; NODE_MAP_TYPE2CLASS(CLASS_LIST) From 67acb98160dbfc945f61676fcd6b6ca5e71031a6 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 2 Dec 2020 13:46:29 +0100 Subject: [PATCH 325/388] gctx_gl: move CVPixelBuffer wrapping code to a dedicated function --- libnodegl/backends/gl/gctx_gl.c | 105 +++++++++++++++++++------------- 1 file changed, 64 insertions(+), 41 deletions(-) diff --git a/libnodegl/backends/gl/gctx_gl.c b/libnodegl/backends/gl/gctx_gl.c index 34706b3bcb..38f68000c1 100644 --- a/libnodegl/backends/gl/gctx_gl.c +++ b/libnodegl/backends/gl/gctx_gl.c @@ -62,6 +62,68 @@ static void capture_ios(struct gctx *s) ngli_glFinish(gl); } +#if defined(TARGET_IPHONE) +static int wrap_capture_cvpixelbuffer(struct gctx *s, + CVPixelBufferRef buffer, + struct texture **texturep, + CVOpenGLESTextureRef *cv_texturep) +{ + struct gctx_gl *s_priv = (struct gctx_gl *)s; + struct glcontext *gl = s_priv->glcontext; + + CVOpenGLESTextureRef cv_texture = NULL; + CVOpenGLESTextureCacheRef *cache = ngli_glcontext_get_texture_cache(gl); + const size_t width = CVPixelBufferGetWidth(buffer); + const size_t height = CVPixelBufferGetHeight(buffer); + CVReturn cv_ret = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, + *cache, + buffer, + NULL, + GL_TEXTURE_2D, + GL_RGBA, + width, + height, + GL_BGRA, + GL_UNSIGNED_BYTE, + 0, + &cv_texture); + if (cv_ret != kCVReturnSuccess) { + LOG(ERROR, "could not create CoreVideo texture from CVPixelBuffer: %d", cv_ret); + return NGL_ERROR_EXTERNAL; + } + + GLuint id = CVOpenGLESTextureGetName(cv_texture); + ngli_glBindTexture(gl, GL_TEXTURE_2D, id); + ngli_glTexParameteri(gl, GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + ngli_glTexParameteri(gl, GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + ngli_glBindTexture(gl, GL_TEXTURE_2D, 0); + + struct texture *texture = ngli_texture_create(s); + if (!texture) { + CFRelease(cv_texture); + return NGL_ERROR_MEMORY; + } + + struct texture_params attachment_params = { + .type = NGLI_TEXTURE_TYPE_2D, + .format = NGLI_FORMAT_B8G8R8A8_UNORM, + .width = width, + .height = height, + }; + int ret = ngli_texture_gl_wrap(texture, &attachment_params, id); + if (ret < 0) { + CFRelease(cv_texture); + ngli_texture_freep(&texture); + return ret; + } + + *texturep = texture; + *cv_texturep = cv_texture; + + return 0; +} +#endif + static int offscreen_rendertarget_init(struct gctx *s) { struct gctx_gl *s_priv = (struct gctx_gl *)s; @@ -77,47 +139,8 @@ static int offscreen_rendertarget_init(struct gctx *s) const int ios_capture = gl->platform == NGL_PLATFORM_IOS && config->window; if (ios_capture) { #if defined(TARGET_IPHONE) - CVPixelBufferRef capture_cvbuffer = (CVPixelBufferRef)config->window; - s_priv->capture_cvbuffer = (CVPixelBufferRef)CFRetain(capture_cvbuffer); - if (!s_priv->capture_cvbuffer) - return NGL_ERROR_MEMORY; - - CVOpenGLESTextureCacheRef *cache = ngli_glcontext_get_texture_cache(gl); - int width = CVPixelBufferGetWidth(s_priv->capture_cvbuffer); - int height = CVPixelBufferGetHeight(s_priv->capture_cvbuffer); - CVReturn err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, - *cache, - s_priv->capture_cvbuffer, - NULL, - GL_TEXTURE_2D, - GL_RGBA, - width, - height, - GL_BGRA, - GL_UNSIGNED_BYTE, - 0, - &s_priv->capture_cvtexture); - if (err != noErr) { - LOG(ERROR, "could not create CoreVideo texture from CVPixelBuffer: 0x%x", err); - return NGL_ERROR_EXTERNAL; - } - - GLuint id = CVOpenGLESTextureGetName(s_priv->capture_cvtexture); - ngli_glBindTexture(gl, GL_TEXTURE_2D, id); - ngli_glTexParameteri(gl, GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - ngli_glTexParameteri(gl, GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - ngli_glBindTexture(gl, GL_TEXTURE_2D, 0); - - struct texture_params attachment_params = { - .type = NGLI_TEXTURE_TYPE_2D, - .format = NGLI_FORMAT_B8G8R8A8_UNORM, - .width = width, - .height = height, - }; - s_priv->color = ngli_texture_create(s); - if (!s_priv->color) - return NGL_ERROR_MEMORY; - int ret = ngli_texture_gl_wrap(s_priv->color, &attachment_params, id); + s_priv->capture_cvbuffer = (CVPixelBufferRef)CFRetain((CVPixelBufferRef)config->window); + int ret = wrap_capture_cvpixelbuffer(s, s_priv->capture_cvtexture, &s_priv->color, &s_priv->capture_cvtexture); if (ret < 0) return ret; #endif From 7998169fb7b45b19a8971e67e81e9de411730497 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 2 Dec 2020 14:28:06 +0100 Subject: [PATCH 326/388] gctx_gl: move CVPixelBuffer cleanup code to a dedicated function --- libnodegl/backends/gl/gctx_gl.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/libnodegl/backends/gl/gctx_gl.c b/libnodegl/backends/gl/gctx_gl.c index 38f68000c1..295d1f32a2 100644 --- a/libnodegl/backends/gl/gctx_gl.c +++ b/libnodegl/backends/gl/gctx_gl.c @@ -122,6 +122,20 @@ static int wrap_capture_cvpixelbuffer(struct gctx *s, return 0; } + +static void reset_capture_cvpixelbuffer(struct gctx *s) +{ + struct gctx_gl *s_priv = (struct gctx_gl *)s; + + if (s_priv->capture_cvbuffer) { + CFRelease(s_priv->capture_cvbuffer); + s_priv->capture_cvbuffer = NULL; + } + if (s_priv->capture_cvtexture) { + CFRelease(s_priv->capture_cvtexture); + s_priv->capture_cvtexture = NULL; + } +} #endif static int offscreen_rendertarget_init(struct gctx *s) @@ -269,14 +283,7 @@ static void rendertarget_reset(struct gctx *s) ngli_texture_freep(&s_priv->ms_color); ngli_texture_freep(&s_priv->depth); #if defined(TARGET_IPHONE) - if (s_priv->capture_cvbuffer) { - CFRelease(s_priv->capture_cvbuffer); - s_priv->capture_cvbuffer = NULL; - } - if (s_priv->capture_cvtexture) { - CFRelease(s_priv->capture_cvtexture); - s_priv->capture_cvtexture = NULL; - } + reset_capture_cvpixelbuffer(s); #endif s_priv->capture_func = NULL; } From 807ba25908de1ec17072888387b433c9c89d098a Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 30 Nov 2020 18:13:25 +0100 Subject: [PATCH 327/388] api: add capture_buffer_type field to ngl_config structure --- libnodegl/backends/gl/gctx_gl.c | 46 ++++++++++++++++++++++++++------- libnodegl/nodegl.h.in | 19 +++++++++++--- pynodegl/pynodegl.pyx | 5 ++-- 3 files changed, 56 insertions(+), 14 deletions(-) diff --git a/libnodegl/backends/gl/gctx_gl.c b/libnodegl/backends/gl/gctx_gl.c index 295d1f32a2..3dad237f09 100644 --- a/libnodegl/backends/gl/gctx_gl.c +++ b/libnodegl/backends/gl/gctx_gl.c @@ -58,8 +58,10 @@ static void capture_ios(struct gctx *s) { struct gctx_gl *s_priv = (struct gctx_gl *)s; struct glcontext *gl = s_priv->glcontext; + struct ngl_config *config = &s->config; - ngli_glFinish(gl); + if (config->capture_buffer) + ngli_glFinish(gl); } #if defined(TARGET_IPHONE) @@ -150,15 +152,34 @@ static int offscreen_rendertarget_init(struct gctx *s) config->samples = 0; } - const int ios_capture = gl->platform == NGL_PLATFORM_IOS && config->window; - if (ios_capture) { + if (config->capture_buffer_type == NGL_CAPTURE_BUFFER_TYPE_COREVIDEO) { #if defined(TARGET_IPHONE) - s_priv->capture_cvbuffer = (CVPixelBufferRef)CFRetain((CVPixelBufferRef)config->window); - int ret = wrap_capture_cvpixelbuffer(s, s_priv->capture_cvtexture, &s_priv->color, &s_priv->capture_cvtexture); - if (ret < 0) - return ret; + if (config->capture_buffer) { + s_priv->capture_cvbuffer = (CVPixelBufferRef)CFRetain(config->capture_buffer); + int ret = wrap_capture_cvpixelbuffer(s, s_priv->capture_cvbuffer, &s_priv->color, &s_priv->capture_cvtexture); + if (ret < 0) + return ret; + } else { + s_priv->color = ngli_texture_create(s); + if (!s_priv->color) + return NGL_ERROR_MEMORY; + + struct texture_params params = { + .type = NGLI_TEXTURE_TYPE_2D, + .format = NGLI_FORMAT_R8G8B8A8_UNORM, + .width = config->width, + .height = config->height, + .usage = NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY, + }; + int ret = ngli_texture_init(s_priv->color, ¶ms); + if (ret < 0) + return ret; + } +#else + LOG(ERROR, "CoreVideo capture is only supported on iOS"); + return NGL_ERROR_UNSUPPORTED; #endif - } else { + } else if (config->capture_buffer_type == NGL_CAPTURE_BUFFER_TYPE_CPU) { struct texture_params params = { .type = NGLI_TEXTURE_TYPE_2D, .format = NGLI_FORMAT_R8G8B8A8_UNORM, @@ -172,6 +193,9 @@ static int offscreen_rendertarget_init(struct gctx *s) int ret = ngli_texture_init(s_priv->color, ¶ms); if (ret < 0) return ret; + } else { + LOG(ERROR, "unsupported capture buffer type: %d", config->capture_buffer_type); + return NGL_ERROR_UNSUPPORTED; } if (config->samples) { @@ -234,7 +258,11 @@ static int offscreen_rendertarget_init(struct gctx *s) if (ret < 0) return ret; - s_priv->capture_func = ios_capture ? capture_ios : capture_default; + static const capture_func_type capture_func_map[] = { + [NGL_CAPTURE_BUFFER_TYPE_CPU] = capture_default, + [NGL_CAPTURE_BUFFER_TYPE_COREVIDEO] = capture_ios, + }; + s_priv->capture_func = capture_func_map[config->capture_buffer_type]; const int vp[4] = {0, 0, config->width, config->height}; ngli_gctx_set_viewport(s, vp); diff --git a/libnodegl/nodegl.h.in b/libnodegl/nodegl.h.in index 292bb0a543..3768fec797 100644 --- a/libnodegl/nodegl.h.in +++ b/libnodegl/nodegl.h.in @@ -362,6 +362,14 @@ enum { NGL_BACKEND_OPENGLES, }; +/** + * Capture buffer types + */ +enum { + NGL_CAPTURE_BUFFER_TYPE_CPU, + NGL_CAPTURE_BUFFER_TYPE_COREVIDEO, +}; + /** * node.gl configuration */ @@ -398,9 +406,14 @@ struct ngl_config { float clear_color[4]; /* Clear color (red, green, blue, alpha) */ - uint8_t *capture_buffer; /* RGBA offscreen capture buffer. If allocated, - its size must be at least width * height * 4 - bytes. */ + void *capture_buffer; /* An optional pointer to a capture buffer. + - If the capture buffer type is CPU, the user + allocated size of the specified buffer must be of + at least width * height * 4 bytes (RGBA) + - If the capture buffer type is COREVIDEO, the + specified pointer must reference a CVPixelBuffer */ + + int capture_buffer_type; /* Any of NGL_CAPTURE_BUFFER_TYPE_* */ int hud; /* Enable the debug HUD */ diff --git a/pynodegl/pynodegl.pyx b/pynodegl/pynodegl.pyx index 22509f3e94..2b0f38de52 100644 --- a/pynodegl/pynodegl.pyx +++ b/pynodegl/pynodegl.pyx @@ -101,7 +101,8 @@ cdef extern from "nodegl.h": int samples int set_surface_pts float clear_color[4] - uint8_t *capture_buffer + void *capture_buffer + int capture_buffer_type int hud int hud_measure_window int hud_refresh_rate[2] @@ -279,7 +280,7 @@ cdef class Context: config.clear_color[i] = clear_color[i] capture_buffer = kwargs.get('capture_buffer') if capture_buffer is not None: - config.capture_buffer = capture_buffer + config.capture_buffer = capture_buffer config.hud = kwargs.get('hud', 0) config.hud_measure_window = kwargs.get('hud_measure_window', 0) hud_refresh_rate = kwargs.get('hud_refresh_rate', (0, 0)) From 05f73ff584a865ca077f353962be76d34be86bc1 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 2 Dec 2020 14:28:32 +0100 Subject: [PATCH 328/388] api: add ngl_set_capture_buffer() --- libnodegl/api.c | 41 ++++++++++++ libnodegl/backends/gl/gctx_gl.c | 112 ++++++++++++++++++++++++++++++++ libnodegl/gctx.c | 6 ++ libnodegl/gctx.h | 2 + libnodegl/nodegl.h.in | 15 +++++ pynodegl/pynodegl.pyx | 8 +++ 6 files changed, 184 insertions(+) diff --git a/libnodegl/api.c b/libnodegl/api.c index ab114044d8..9ba5f47ad5 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -187,6 +187,26 @@ static int cmd_resize(struct ngl_ctx *s, void *arg) return ngli_gctx_resize(s->gctx, params->width, params->height, params->viewport); } +static int cmd_set_capture_buffer(struct ngl_ctx *s, void *capture_buffer) +{ + struct ngl_config *config = &s->config; + + int ret = ngli_gctx_set_capture_buffer(s->gctx, capture_buffer); + if (ret < 0) { + if (s->scene) { + ngli_node_detach_ctx(s->scene, s); + ngl_node_unrefp(&s->scene); + } + cmd_stop(s, NULL); + config->capture_buffer = NULL; + return ret; + } + + config->capture_buffer = capture_buffer; + + return 0; +} + static int cmd_set_scene(struct ngl_ctx *s, void *arg) { if (s->scene) { @@ -640,6 +660,27 @@ int ngl_resize(struct ngl_ctx *s, int width, int height, const int *viewport) #endif } +int ngl_set_capture_buffer(struct ngl_ctx *s, void *capture_buffer) +{ + if (!s->configured) { + LOG(ERROR, "context must be configured before setting a capture buffer"); + return NGL_ERROR_INVALID_USAGE; + } + + const struct ngl_config *config = &s->config; + if (!config->offscreen) { + LOG(ERROR, "capture buffers are only supported with offscreen rendering"); + return NGL_ERROR_INVALID_USAGE; + } + + int ret = dispatch_cmd(s, cmd_set_capture_buffer, capture_buffer); + if (ret < 0) { + s->configured = 0; + return ret; + } + return ret; +} + int ngl_set_scene(struct ngl_ctx *s, struct ngl_node *scene) { if (!s->configured) { diff --git a/libnodegl/backends/gl/gctx_gl.c b/libnodegl/backends/gl/gctx_gl.c index 3dad237f09..4f225e6027 100644 --- a/libnodegl/backends/gl/gctx_gl.c +++ b/libnodegl/backends/gl/gctx_gl.c @@ -476,6 +476,116 @@ static int gl_resize(struct gctx *s, int width, int height, const int *viewport) return 0; } +#if defined(TARGET_IPHONE) +static int update_capture_cvpixelbuffer(struct gctx *s, CVPixelBufferRef capture_buffer) +{ + struct gctx_gl *s_priv = (struct gctx_gl *)s; + struct ngl_config *config = &s->config; + + struct texture *texture = NULL; + CVOpenGLESTextureRef cv_texture = NULL; + struct rendertarget *rt = NULL; + + int ret = 0; + if (capture_buffer) { + ret = wrap_capture_cvpixelbuffer(s, capture_buffer, &texture, &cv_texture); + if (ret < 0) + goto fail; + } else { + texture = ngli_texture_create(s); + if (!texture) { + ret = NGL_ERROR_MEMORY; + goto fail; + } + + struct texture_params params = { + .type = NGLI_TEXTURE_TYPE_2D, + .format = NGLI_FORMAT_R8G8B8A8_UNORM, + .width = config->width, + .height = config->height, + .usage = NGLI_TEXTURE_USAGE_ATTACHMENT_ONLY, + }; + ret = ngli_texture_init(texture, ¶ms); + if (ret < 0) + goto fail; + } + + rt = ngli_rendertarget_create(s); + if (!rt) { + ret = NGL_ERROR_MEMORY; + goto fail; + } + + struct rendertarget_params rt_params = { + .width = config->width, + .height = config->height, + .nb_colors = 1, + .colors[0] = { + .attachment = config->samples ? s_priv->ms_color : texture, + .resolve_target = config->samples ? texture : NULL, + .load_op = NGLI_LOAD_OP_LOAD, + .clear_value[0] = config->clear_color[0], + .clear_value[1] = config->clear_color[1], + .clear_value[2] = config->clear_color[2], + .clear_value[3] = config->clear_color[3], + .store_op = NGLI_STORE_OP_STORE, + }, + .depth_stencil = { + .attachment = s_priv->depth, + .load_op = NGLI_LOAD_OP_LOAD, + .store_op = NGLI_STORE_OP_STORE, + }, + }; + ret = ngli_rendertarget_init(rt, &rt_params); + if (ret < 0) + goto fail; + + ngli_rendertarget_freep(&s_priv->rt); + ngli_texture_freep(&s_priv->color); + reset_capture_cvpixelbuffer(s); + + s_priv->rt = rt; + s_priv->color = texture; + s_priv->capture_cvbuffer = (CVPixelBufferRef)CFRetain(capture_buffer); + s_priv->capture_cvtexture = cv_texture; + + return 0; + +fail: + ngli_rendertarget_freep(&rt); + ngli_texture_freep(&texture); + if (cv_texture) + CFRelease(cv_texture); + + return ret; +} +#endif + +static int gl_set_capture_buffer(struct gctx *s, void *capture_buffer) +{ + struct gctx_gl *s_priv = (struct gctx_gl *)s; + struct glcontext *gl = s_priv->glcontext; + + if (!gl->offscreen) + return NGL_ERROR_INVALID_USAGE; + + struct ngl_config *config = &s->config; + + if (config->capture_buffer_type == NGL_CAPTURE_BUFFER_TYPE_COREVIDEO) { +#if defined(TARGET_IPHONE) + int ret = update_capture_cvpixelbuffer(s, capture_buffer); + if (ret < 0) + return ret; +#else + return NGL_ERROR_UNSUPPORTED; +#endif + } + + config->capture_buffer = capture_buffer; + + return 0; +} + static int gl_begin_draw(struct gctx *s, double t) { struct gctx_gl *s_priv = (struct gctx_gl *)s; @@ -682,6 +792,7 @@ const struct gctx_class ngli_gctx_gl = { .create = gl_create, .init = gl_init, .resize = gl_resize, + .set_capture_buffer = gl_set_capture_buffer, .begin_draw = gl_begin_draw, .end_draw = gl_end_draw, .query_draw_time = gl_query_draw_time, @@ -744,6 +855,7 @@ const struct gctx_class ngli_gctx_gles = { .create = gl_create, .init = gl_init, .resize = gl_resize, + .set_capture_buffer = gl_set_capture_buffer, .begin_draw = gl_begin_draw, .end_draw = gl_end_draw, .query_draw_time = gl_query_draw_time, diff --git a/libnodegl/gctx.c b/libnodegl/gctx.c index 8b08a695c1..9a7386f935 100644 --- a/libnodegl/gctx.c +++ b/libnodegl/gctx.c @@ -81,6 +81,12 @@ int ngli_gctx_resize(struct gctx *s, int width, int height, const int *viewport) return class->resize(s, width, height, viewport); } +int ngli_gctx_set_capture_buffer(struct gctx *s, void *capture_buffer) +{ + const struct gctx_class *class = s->class; + return class->set_capture_buffer(s, capture_buffer); +} + int ngli_gctx_begin_draw(struct gctx *s, double t) { return s->class->begin_draw(s, t); diff --git a/libnodegl/gctx.h b/libnodegl/gctx.h index 80afeacb3a..c4166c57ab 100644 --- a/libnodegl/gctx.h +++ b/libnodegl/gctx.h @@ -38,6 +38,7 @@ struct gctx_class { struct gctx *(*create)(const struct ngl_config *config); int (*init)(struct gctx *s); int (*resize)(struct gctx *s, int width, int height, const int *viewport); + int (*set_capture_buffer)(struct gctx *s, void *capture_buffer); int (*begin_draw)(struct gctx *s, double t); int (*end_draw)(struct gctx *s, double t); int (*query_draw_time)(struct gctx *s, int64_t *time); @@ -108,6 +109,7 @@ struct gctx { struct gctx *ngli_gctx_create(const struct ngl_config *config); int ngli_gctx_init(struct gctx *s); int ngli_gctx_resize(struct gctx *s, int width, int height, const int *viewport); +int ngli_gctx_set_capture_buffer(struct gctx *s, void *capture_buffer); int ngli_gctx_begin_draw(struct gctx *s, double t); int ngli_gctx_query_draw_time(struct gctx *s, int64_t *time); int ngli_gctx_end_draw(struct gctx *s, double t); diff --git a/libnodegl/nodegl.h.in b/libnodegl/nodegl.h.in index 3768fec797..417be12829 100644 --- a/libnodegl/nodegl.h.in +++ b/libnodegl/nodegl.h.in @@ -530,6 +530,21 @@ int ngl_configure(struct ngl_ctx *s, struct ngl_config *config); */ int ngl_resize(struct ngl_ctx *s, int width, int height, const int *viewport); +/** + * Set a new buffer for offscreen capture + * + * Set a new buffer used for offscreen capture (and replace any previous one). + * The capture buffer type must match the ngl_config.capture_buffer_type used + * to configure the node.gl context. + * + * @param s pointer to a node.gl context + * @params capture_buffer pointer to a capture buffer, if NULL no capture will + * be performed for the following ngl_draw() calls. + * + * @return 0 on success, NGL_ERROR_* (< 0) on error + */ +int ngl_set_capture_buffer(struct ngl_ctx *s, void *capture_buffer); + /** * Associate a scene with a node.gl context. * diff --git a/pynodegl/pynodegl.pyx b/pynodegl/pynodegl.pyx index 2b0f38de52..9beafc0fef 100644 --- a/pynodegl/pynodegl.pyx +++ b/pynodegl/pynodegl.pyx @@ -114,6 +114,7 @@ cdef extern from "nodegl.h": void ngl_backends_freep(ngl_backend **backendsp) int ngl_configure(ngl_ctx *s, ngl_config *config) int ngl_resize(ngl_ctx *s, int width, int height, const int *viewport); + int ngl_set_capture_buffer(ngl_ctx *s, void *capture_buffer); int ngl_set_scene(ngl_ctx *s, ngl_node *scene) int ngl_draw(ngl_ctx *s, double t) nogil char *ngl_dot(ngl_ctx *s, double t) nogil @@ -306,6 +307,13 @@ cdef class Context: c_viewport[i] = viewport[i] return ngl_resize(self.ctx, width, height, c_viewport) + def set_capture_buffer(self, capture_buffer): + self.capture_buffer = capture_buffer + cdef uint8_t *ptr = NULL + if self.capture_buffer is not None: + ptr = self.capture_buffer + return ngl_set_capture_buffer(self.ctx, ptr) + def set_scene(self, _Node scene): return ngl_set_scene(self.ctx, NULL if scene is None else scene.ctx) From 30f1fc762d18d62b687922c6d6b43475f0049acf Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 3 Dec 2020 16:52:25 +0100 Subject: [PATCH 329/388] gctx_gl: rename capture_{default,ios]() to capture_{cpu,corevideo}() --- libnodegl/backends/gl/gctx_gl.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libnodegl/backends/gl/gctx_gl.c b/libnodegl/backends/gl/gctx_gl.c index 4f225e6027..40f1fb8292 100644 --- a/libnodegl/backends/gl/gctx_gl.c +++ b/libnodegl/backends/gl/gctx_gl.c @@ -44,7 +44,7 @@ #include "vaapi.h" #endif -static void capture_default(struct gctx *s) +static void capture_cpu(struct gctx *s) { struct gctx_gl *s_priv = (struct gctx_gl *)s; struct ngl_config *config = &s->config; @@ -54,7 +54,7 @@ static void capture_default(struct gctx *s) ngli_rendertarget_read_pixels(rt, config->capture_buffer); } -static void capture_ios(struct gctx *s) +static void capture_corevideo(struct gctx *s) { struct gctx_gl *s_priv = (struct gctx_gl *)s; struct glcontext *gl = s_priv->glcontext; @@ -259,8 +259,8 @@ static int offscreen_rendertarget_init(struct gctx *s) return ret; static const capture_func_type capture_func_map[] = { - [NGL_CAPTURE_BUFFER_TYPE_CPU] = capture_default, - [NGL_CAPTURE_BUFFER_TYPE_COREVIDEO] = capture_ios, + [NGL_CAPTURE_BUFFER_TYPE_CPU] = capture_cpu, + [NGL_CAPTURE_BUFFER_TYPE_COREVIDEO] = capture_corevideo, }; s_priv->capture_func = capture_func_map[config->capture_buffer_type]; From 59c33220cdaf727733bf1cbcd3820a3f7682787c Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 3 Dec 2020 17:08:29 +0100 Subject: [PATCH 330/388] gctx_gl: only call the capture function if there is a capture buffer --- libnodegl/backends/gl/gctx_gl.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/libnodegl/backends/gl/gctx_gl.c b/libnodegl/backends/gl/gctx_gl.c index 40f1fb8292..bd2a8d2951 100644 --- a/libnodegl/backends/gl/gctx_gl.c +++ b/libnodegl/backends/gl/gctx_gl.c @@ -50,18 +50,15 @@ static void capture_cpu(struct gctx *s) struct ngl_config *config = &s->config; struct rendertarget *rt = s_priv->rt; - if (config->capture_buffer) - ngli_rendertarget_read_pixels(rt, config->capture_buffer); + ngli_rendertarget_read_pixels(rt, config->capture_buffer); } static void capture_corevideo(struct gctx *s) { struct gctx_gl *s_priv = (struct gctx_gl *)s; struct glcontext *gl = s_priv->glcontext; - struct ngl_config *config = &s->config; - if (config->capture_buffer) - ngli_glFinish(gl); + ngli_glFinish(gl); } #if defined(TARGET_IPHONE) @@ -617,7 +614,7 @@ static int gl_end_draw(struct gctx *s, double t) ngli_gctx_end_render_pass(s); - if (s_priv->capture_func) + if (s_priv->capture_func && config->capture_buffer) s_priv->capture_func(s); int ret = 0; From e40c2c35748bd40cb48f6513ef27de0880457431 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 2 Dec 2020 16:11:05 +0100 Subject: [PATCH 331/388] tests/api: remove superfluous context creation --- tests/api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/api.py b/tests/api.py index 50eebc9817..bc6d841c5e 100644 --- a/tests/api.py +++ b/tests/api.py @@ -59,7 +59,6 @@ def api_reconfigure(): def api_reconfigure_clearcolor(width=16, height=16): import zlib - ctx = ngl.Context() capture_buffer = bytearray(width * height * 4) ctx = ngl.Context() assert ctx.configure(offscreen=1, width=width, height=height, backend=_backend, capture_buffer=capture_buffer) == 0 From 88c030b7564300ddea869cf6c9ee05f04b3d4079 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 2 Dec 2020 16:15:57 +0100 Subject: [PATCH 332/388] tests/api: add api_capture_buffer test --- tests/api.py | 16 ++++++++++++++++ tests/meson.build | 1 + 2 files changed, 17 insertions(+) diff --git a/tests/api.py b/tests/api.py index bc6d841c5e..20ba378baf 100644 --- a/tests/api.py +++ b/tests/api.py @@ -85,6 +85,22 @@ def api_reconfigure_fail(): del ctx +def api_capture_buffer(width=16, height=16): + import zlib + ctx = ngl.Context() + assert ctx.configure(offscreen=1, width=width, height=height, backend=_backend) == 0 + scene = _get_scene() + assert ctx.set_scene(scene) == 0 + for i in range(2): + capture_buffer = bytearray(width * height * 4) + assert ctx.set_capture_buffer(capture_buffer) == 0 + assert ctx.draw(0) == 0 + assert ctx.set_capture_buffer(None) == 0 + assert ctx.draw(0) == 0 + assert zlib.crc32(capture_buffer) == 0xb4bd32fa + del ctx + + def api_ctx_ownership(): ctx = ngl.Context() ctx2 = ngl.Context() diff --git a/tests/meson.build b/tests/meson.build index 577cb63325..bd65fabc98 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -66,6 +66,7 @@ foreach backend : backends 'reconfigure', 'reconfigure_clearcolor', 'reconfigure_fail', + 'capture_buffer', 'ctx_ownership', 'ctx_ownership_subgraph', 'capture_buffer_lifetime', From ad86ee2470dc50ad15aa571e55f6b854f2d48fa8 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Fri, 4 Dec 2020 21:42:17 +0100 Subject: [PATCH 333/388] tools: use read/write instead of recv/send where appropriate The recv and send functions must only be used on sockets (otherwise they return ENOTSOCK). Fixes a regression introduced by e4e6fe0d842d83163250a34094f8f2bfab53ab13. This regression has not been caught because these operations are used for file transfers which are not used for local hooks. --- ngl-tools/ngl-desktop.c | 2 +- ngl-tools/ngl-ipc.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ngl-tools/ngl-desktop.c b/ngl-tools/ngl-desktop.c index 751d99c8bf..a3e866d83d 100644 --- a/ngl-tools/ngl-desktop.c +++ b/ngl-tools/ngl-desktop.c @@ -219,7 +219,7 @@ static int handle_tag_filepart(struct ctx *s, const uint8_t *data, int size) return ipc_pkt_add_rtag_fileend(s->send_pkt, s->upload_path); } - const ssize_t n = send(s->upload_fd, data, size, 0); + const ssize_t n = write(s->upload_fd, data, size); if (n < 0) { perror("write"); close_upload_file(s); diff --git a/ngl-tools/ngl-ipc.c b/ngl-tools/ngl-ipc.c index 9d069aa300..f29461165a 100644 --- a/ngl-tools/ngl-ipc.c +++ b/ngl-tools/ngl-ipc.c @@ -251,7 +251,7 @@ static int handle_response(struct ctx *s, const struct ipc_pkt *pkt) if (s->upload_fd > 0) { ipc_pkt_reset(s->send_pkt); - const ssize_t n = recv(s->upload_fd, s->upload_buffer, UPLOAD_CHUNK_SIZE, 0); + const ssize_t n = read(s->upload_fd, s->upload_buffer, UPLOAD_CHUNK_SIZE); if (n < 0) return NGL_ERROR_IO; int ret = ipc_pkt_add_qtag_filepart(s->send_pkt, s->upload_buffer, n); From 87ab544f804286012c5f5ee73e58a85063d57111 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 7 Dec 2020 09:11:22 +0100 Subject: [PATCH 334/388] tools/desktop: improve listening socket shutdown code Only calls close() on Unix systems. closesocket() is its Windows counterpart which works specifically on sockets. Also calls the shutdown function unconditionally for all the platforms as we want to disallow further transmissions on the listening socket. Even though it has different behaviours on different platforms, calling it unconditionally doesn't hurt. This will also make this part of the code re-usable for shutting down client connections. --- ngl-tools/ngl-desktop.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ngl-tools/ngl-desktop.c b/ngl-tools/ngl-desktop.c index a3e866d83d..8b9d1b62c9 100644 --- a/ngl-tools/ngl-desktop.c +++ b/ngl-tools/ngl-desktop.c @@ -41,6 +41,7 @@ #include #include #include +#define SHUT_RDWR SD_BOTH #else #include #include @@ -628,19 +629,19 @@ int main(int argc, char *argv[]) * of behaviour is to prevent a possible race scenario where the same * FD would be re-used between a close() and an accept(). * - * On Windows, both shutdown() and close() won't work, we have to use a - * Windows specific one: closesocket(). + * On Windows, the specific closesocket() function must be used instead of + * close() to close a socket. * * See https://bugzilla.kernel.org/show_bug.cgi?id=106241 for more * information. */ + if (shutdown(s.sock_fd, SHUT_RDWR) < 0) + perror("shutdown"); #if _WIN32 closesocket(s.sock_fd); #else - if (shutdown(s.sock_fd, SHUT_RDWR) < 0) - perror("shutdown"); -#endif close(s.sock_fd); +#endif } if (s.addr_info) From f0b9c5b2134e531fafb232f1d857a3f8fb6b22d9 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 7 Dec 2020 10:07:40 +0100 Subject: [PATCH 335/388] tools/desktop: move socket shutdown code to a dedicated function --- ngl-tools/ngl-desktop.c | 54 ++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/ngl-tools/ngl-desktop.c b/ngl-tools/ngl-desktop.c index 8b9d1b62c9..f12a81eb7d 100644 --- a/ngl-tools/ngl-desktop.c +++ b/ngl-tools/ngl-desktop.c @@ -376,6 +376,33 @@ static int handle_commands(struct ctx *s, int fd) } } +static void close_socket(int socket) +{ + /* + * On Solaris and MacOS, close() is the only way to terminate the + * blocking accept(). The shutdown() call will fail with errno ENOTCONN + * on these systems. + * + * On Linux though, shutdown() is required to terminate the blocking + * accept(), because close() will not. The reason for this difference + * of behaviour is to prevent a possible race scenario where the same + * FD would be re-used between a close() and an accept(). + * + * On Windows, the specific closesocket() function must be used instead of + * close() to close a socket. + * + * See https://bugzilla.kernel.org/show_bug.cgi?id=106241 for more + * information. + */ + if (shutdown(socket, SHUT_RDWR) < 0) + perror("shutdown"); +#if _WIN32 + closesocket(socket); +#else + close(socket); +#endif +} + static void close_conn(struct ctx *s, int conn_fd) { close(conn_fd); @@ -618,31 +645,8 @@ int main(int argc, char *argv[]) if (s.thread_started) stop_server(&s); - if (s.sock_fd != -1) { - /* - * On Solaris and MacOS, close() is the only way to terminate the - * blocking accept(). The shutdown() call will fail with errno ENOTCONN - * on these systems. - * - * On Linux though, shutdown() is required to terminate the blocking - * accept(), because close() will not. The reason for this difference - * of behaviour is to prevent a possible race scenario where the same - * FD would be re-used between a close() and an accept(). - * - * On Windows, the specific closesocket() function must be used instead of - * close() to close a socket. - * - * See https://bugzilla.kernel.org/show_bug.cgi?id=106241 for more - * information. - */ - if (shutdown(s.sock_fd, SHUT_RDWR) < 0) - perror("shutdown"); -#if _WIN32 - closesocket(s.sock_fd); -#else - close(s.sock_fd); -#endif - } + if (s.sock_fd != -1) + close_socket(s.sock_fd); if (s.addr_info) freeaddrinfo(s.addr_info); From b885dbb66957f5693bd244258a4150f546c57cfc Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Mon, 7 Dec 2020 10:09:55 +0100 Subject: [PATCH 336/388] tools/desktop: use close_socket() to shutdown client connections This allows to call the proper function to close the socket depending on the platform (typically, we want to call closesocket() instead of close on Windows). Fixes the ngl-ipc client being stuck on the recv call on MinGW. --- ngl-tools/ngl-desktop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ngl-tools/ngl-desktop.c b/ngl-tools/ngl-desktop.c index f12a81eb7d..c24ae5ed36 100644 --- a/ngl-tools/ngl-desktop.c +++ b/ngl-tools/ngl-desktop.c @@ -405,7 +405,7 @@ static void close_socket(int socket) static void close_conn(struct ctx *s, int conn_fd) { - close(conn_fd); + close_socket(conn_fd); fprintf(stderr, "<< client %d disconnected\n", conn_fd); /* close uploading file when the connection ends */ From 0a6da501c523365dd8f788cdf71213d8d8d30194 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Sat, 5 Dec 2020 12:47:23 +0100 Subject: [PATCH 337/388] tools/ipc: fix file reading on MinGW O_BINARY must be specified on MinGW otherwise reading can end prematurely if a null character is found in the file. --- ngl-tools/ngl-ipc.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ngl-tools/ngl-ipc.c b/ngl-tools/ngl-ipc.c index f29461165a..e41ba807ac 100644 --- a/ngl-tools/ngl-ipc.c +++ b/ngl-tools/ngl-ipc.c @@ -42,6 +42,10 @@ #include "ipc.h" #include "opts.h" +#ifndef O_BINARY +#define O_BINARY 0 +#endif + #define UPLOAD_CHUNK_SIZE (1024 * 1024) struct ctx { @@ -110,7 +114,7 @@ static int craft_packet(struct ctx *s, struct ipc_pkt *pkt) const size_t name_size = name_len + 1; const char *filename = s->uploadfile + name_size; - s->upload_fd = open(filename, O_RDONLY); + s->upload_fd = open(filename, O_RDONLY | O_BINARY); if (s->upload_fd == -1) { perror("open"); return NGL_ERROR_IO; From 5a90ab41f55493f82a802dc30513610ee422bb0a Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Sat, 5 Dec 2020 13:49:07 +0100 Subject: [PATCH 338/388] doc: add newly available dependencies for MinGW installation --- doc/howto/installation.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/howto/installation.md b/doc/howto/installation.md index 90c6c8703f..3fbe7388df 100644 --- a/doc/howto/installation.md +++ b/doc/howto/installation.md @@ -49,7 +49,9 @@ On Windows, the bootstrap is slightly more complex: pacman -Syuu # and restart the shell pacman -S git make pacman -S mingw-w64-x86_64-{toolchain,ffmpeg,python} +pacman -S mingw-w64-x86_64-python-watchdog pacman -S mingw-w64-x86_64-python3-{pillow,pip} +pacman -S mingw-w64-x86_64-pyside2-qt5 pacman -S mingw-w64-x86_64-meson make TARGET_OS=MinGW-w64 ``` From ad343485c8f7787a6fe535cdbab3d675c38c18cc Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Sat, 5 Dec 2020 13:51:37 +0100 Subject: [PATCH 339/388] build: update comments regarding the ngl-control situation on MinGW --- Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 4f4ce9943a..86121f78f8 100644 --- a/Makefile +++ b/Makefile @@ -110,10 +110,14 @@ pynodegl-utils-install: pynodegl-utils-deps-install # available on PyPi. # # We do not pull the requirements on Windows because of various issues: -# - PySide2 can't be pulled +# - PySide2 can't be pulled (required to be installed by the user outside the +# Python virtualenv) # - Pillow fails to find zlib (required to be installed by the user outside the # Python virtualenv) -# - ngl-control can not currently work because of temporary files handling +# - ngl-control can not currently work because of: +# - temporary files handling +# - subprocess usage, passing fd is not supported on Windows +# - subprocess usage, Windows cannot execute directly hooks shell scripts # # Still, we want the module to be installed so we can access the scene() # decorator and other related utils. From cb66a8e0c6868e04fcbac73920d30c4a44510c93 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 9 Dec 2020 10:38:28 +0100 Subject: [PATCH 340/388] tools/ipc: move upload file size probing to a dedicated function --- ngl-tools/ngl-ipc.c | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/ngl-tools/ngl-ipc.c b/ngl-tools/ngl-ipc.c index e41ba807ac..ec4514dec4 100644 --- a/ngl-tools/ngl-ipc.c +++ b/ngl-tools/ngl-ipc.c @@ -66,8 +66,8 @@ struct ctx { struct ipc_pkt *recv_pkt; int upload_fd; uint8_t *upload_buffer; - struct stat upload_stat; - off_t uploaded_size; // same type as stat.st_size + int64_t upload_size; + int64_t uploaded_size; }; #define OFFSET(x) offsetof(struct ctx, x) @@ -85,6 +85,18 @@ static const struct opt options[] = { {"-g", "--reconfigure", OPT_TYPE_TOGGLE, .offset=OFFSET(reconfigure)}, }; +static int get_filesize(const char *filename, int64_t *size) +{ + struct stat st; + int ret = stat(filename, &st); + if (ret == -1) { + perror(filename); + return NGL_ERROR_IO; + } + *size = st.st_size; + return 0; +} + static int craft_packet(struct ctx *s, struct ipc_pkt *pkt) { if (s->scene) { @@ -114,22 +126,21 @@ static int craft_packet(struct ctx *s, struct ipc_pkt *pkt) const size_t name_size = name_len + 1; const char *filename = s->uploadfile + name_size; + int ret = get_filesize(filename, &s->upload_size); + if (ret < 0) + return ret; + s->upload_fd = open(filename, O_RDONLY | O_BINARY); if (s->upload_fd == -1) { perror("open"); return NGL_ERROR_IO; } - if (fstat(s->upload_fd, &s->upload_stat) < 0) { - perror("stat"); - return NGL_ERROR_IO; - } - s->upload_buffer = malloc(UPLOAD_CHUNK_SIZE); if (!s->upload_buffer) return NGL_ERROR_MEMORY; - int ret = ipc_pkt_add_qtag_file(pkt, name); + ret = ipc_pkt_add_qtag_file(pkt, name); if (ret < 0) return ret; } @@ -203,7 +214,7 @@ static int handle_filepart(struct ctx *s, const uint8_t *data, int size) return NGL_ERROR_INVALID_DATA; const int written = IPC_U32_READ(data); s->uploaded_size += written; - fprintf(stderr, "\ruploading %s... %d%%", s->uploadfile, (int)(s->uploaded_size * 100LL / s->upload_stat.st_size)); + fprintf(stderr, "\ruploading %s... %d%%", s->uploadfile, (int)(s->uploaded_size * 100LL / s->upload_size)); return 0; } From 062c869296ef1f977e4255636ec57eccc2554ed8 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 9 Dec 2020 10:47:27 +0100 Subject: [PATCH 341/388] tools/ipc: implement get_filesize() on Windows --- ngl-tools/ngl-ipc.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/ngl-tools/ngl-ipc.c b/ngl-tools/ngl-ipc.c index ec4514dec4..0d8558b858 100644 --- a/ngl-tools/ngl-ipc.c +++ b/ngl-tools/ngl-ipc.c @@ -28,12 +28,14 @@ #ifdef _WIN32 #include #include +#include +#include #else #include #include -#endif #include #include +#endif #include #include @@ -87,6 +89,19 @@ static const struct opt options[] = { static int get_filesize(const char *filename, int64_t *size) { +#ifdef _WIN32 + HANDLE file_handle = CreateFile(TEXT(filename), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (file_handle == INVALID_HANDLE_VALUE) + return NGL_ERROR_IO; + + LARGE_INTEGER file_size; + if (!GetFileSizeEx(file_handle, &file_size)) { + CloseHandle(file_handle); + return NGL_ERROR_IO; + } + *size = file_size.QuadPart; + CloseHandle(file_handle); +#else struct stat st; int ret = stat(filename, &st); if (ret == -1) { @@ -94,6 +109,7 @@ static int get_filesize(const char *filename, int64_t *size) return NGL_ERROR_IO; } *size = st.st_size; +#endif return 0; } From 5801fbf45bde23b0d0123b42e785059810be6fad Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 8 Dec 2020 17:40:29 +0100 Subject: [PATCH 342/388] tools/ipc: use fopen()/fread() instead of open()/read() Makes this part of the code compatible with MSVC. --- ngl-tools/ngl-ipc.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/ngl-tools/ngl-ipc.c b/ngl-tools/ngl-ipc.c index 0d8558b858..a4a572a3aa 100644 --- a/ngl-tools/ngl-ipc.c +++ b/ngl-tools/ngl-ipc.c @@ -36,7 +36,6 @@ #include #include #endif -#include #include @@ -66,7 +65,7 @@ struct ctx { struct ipc_pkt *send_pkt; struct ipc_pkt *recv_pkt; - int upload_fd; + FILE *upload_fp; uint8_t *upload_buffer; int64_t upload_size; int64_t uploaded_size; @@ -146,9 +145,9 @@ static int craft_packet(struct ctx *s, struct ipc_pkt *pkt) if (ret < 0) return ret; - s->upload_fd = open(filename, O_RDONLY | O_BINARY); - if (s->upload_fd == -1) { - perror("open"); + s->upload_fp = fopen(filename, "rb"); + if (!s->upload_fp) { + perror("fopen"); return NGL_ERROR_IO; } @@ -208,9 +207,9 @@ static int craft_packet(struct ctx *s, struct ipc_pkt *pkt) static void close_upload_file(struct ctx *s) { - if (s->upload_fd > 0) - close(s->upload_fd); - s->upload_fd = 0; + if (s->upload_fp) + fclose(s->upload_fp); + s->upload_fp = NULL; free(s->upload_buffer); s->upload_buffer = NULL; } @@ -279,11 +278,11 @@ static int handle_response(struct ctx *s, const struct ipc_pkt *pkt) data_size -= size; } - if (s->upload_fd > 0) { + if (s->upload_fp) { ipc_pkt_reset(s->send_pkt); - const ssize_t n = read(s->upload_fd, s->upload_buffer, UPLOAD_CHUNK_SIZE); - if (n < 0) + const size_t n = fread(s->upload_buffer, 1, UPLOAD_CHUNK_SIZE, s->upload_fp); + if (ferror(s->upload_fp)) return NGL_ERROR_IO; int ret = ipc_pkt_add_qtag_filepart(s->send_pkt, s->upload_buffer, n); if (ret < 0) @@ -378,7 +377,7 @@ int main(int argc, char *argv[]) if (ret < 0) goto end; - } while (s.upload_fd > 0); + } while (s.upload_fp); end: if (addr_info) From 40bf60416607a215ac7c4c21011049a66491bbe4 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Tue, 8 Dec 2020 17:49:04 +0100 Subject: [PATCH 343/388] tools/desktop: use fopen()/fwrite() instead of open()/write() Makes this part of the code compatible with MSVC. --- ngl-tools/ngl-desktop.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ngl-tools/ngl-desktop.c b/ngl-tools/ngl-desktop.c index c24ae5ed36..bc60122684 100644 --- a/ngl-tools/ngl-desktop.c +++ b/ngl-tools/ngl-desktop.c @@ -89,7 +89,7 @@ struct ctx { int own_session_file; struct ipc_pkt *send_pkt; struct ipc_pkt *recv_pkt; - int upload_fd; + FILE *upload_fp; char upload_path[1024]; }; @@ -169,7 +169,7 @@ static int handle_tag_file(struct ctx *s, const uint8_t *data, int size) if (size < 1 || data[size - 1] != 0) // check if string is nul-terminated return NGL_ERROR_INVALID_DATA; - if (s->upload_fd) { + if (s->upload_fp) { fprintf(stderr, "a file is already uploading"); return NGL_ERROR_INVALID_USAGE; } @@ -192,8 +192,8 @@ static int handle_tag_file(struct ctx *s, const uint8_t *data, int size) if (ret < 0 || ret >= sizeof(s->upload_path)) return NGL_ERROR_MEMORY; - s->upload_fd = open(s->upload_path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0600); - if (s->upload_fd == -1) { + s->upload_fp = fopen(s->upload_path, "wb"); + if (!s->upload_fp) { perror(s->upload_path); return NGL_ERROR_IO; } @@ -203,14 +203,14 @@ static int handle_tag_file(struct ctx *s, const uint8_t *data, int size) static void close_upload_file(struct ctx *s) { - if (s->upload_fd > 0) - close(s->upload_fd); - s->upload_fd = 0; + if (s->upload_fp) + fclose(s->upload_fp); + s->upload_fp = NULL; } static int handle_tag_filepart(struct ctx *s, const uint8_t *data, int size) { - if (s->upload_fd <= 0) { + if (!s->upload_fp) { fprintf(stderr, "file is not opened\n"); return NGL_ERROR_INVALID_USAGE; } @@ -220,9 +220,9 @@ static int handle_tag_filepart(struct ctx *s, const uint8_t *data, int size) return ipc_pkt_add_rtag_fileend(s->send_pkt, s->upload_path); } - const ssize_t n = write(s->upload_fd, data, size); - if (n < 0) { - perror("write"); + const size_t n = fwrite(data, 1, size, s->upload_fp); + if (ferror(s->upload_fp)) { + perror("fwrite"); close_upload_file(s); return NGL_ERROR_IO; } From 0d3c816764df7d29ca2389405585b55c5d2f969e Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 9 Dec 2020 12:05:22 +0100 Subject: [PATCH 344/388] utils: add ngli_get_filesize() --- libnodegl/meson.build | 4 ++-- libnodegl/utils.c | 36 ++++++++++++++++++++++++++++++++++++ libnodegl/utils.h | 1 + 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/libnodegl/meson.build b/libnodegl/meson.build index 08689eff25..bb92436061 100644 --- a/libnodegl/meson.build +++ b/libnodegl/meson.build @@ -495,11 +495,11 @@ test_progs = { }, 'Hash map': { 'exe': 'test_hmap', - 'src': files('test_hmap.c', 'utils.c', 'memory.c'), + 'src': files('test_hmap.c', 'log.c', 'utils.c', 'memory.c'), }, 'Utils': { 'exe': 'test_utils', - 'src': files('test_utils.c', 'utils.c', 'memory.c'), + 'src': files('test_utils.c', 'log.c', 'utils.c', 'memory.c'), }, } diff --git a/libnodegl/utils.c b/libnodegl/utils.c index 9753e5745f..e86b1f52f8 100644 --- a/libnodegl/utils.c +++ b/libnodegl/utils.c @@ -22,6 +22,16 @@ #define _GNU_SOURCE #include +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#endif + #include #include #include @@ -153,3 +163,29 @@ void ngli_thread_set_name(const char *name) pthread_setname_np(pthread_self(), name); #endif } + +int ngli_get_filesize(const char *filename, int64_t *size) +{ +#ifdef _WIN32 + HANDLE file_handle = CreateFile(TEXT(filename), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (file_handle == INVALID_HANDLE_VALUE) + return NGL_ERROR_IO; + + LARGE_INTEGER file_size; + if (!GetFileSizeEx(file_handle, &file_size)) { + CloseHandle(file_handle); + return NGL_ERROR_IO; + } + *size = file_size.QuadPart; + CloseHandle(file_handle); +#else + struct stat st; + int ret = stat(filename, &st); + if (ret == -1) { + LOG(ERROR, "could not stat '%s': %s", filename, strerror(errno)); + return NGL_ERROR_IO; + } + *size = st.st_size; +#endif + return 0; +} diff --git a/libnodegl/utils.h b/libnodegl/utils.h index e364077f6b..0db0164819 100644 --- a/libnodegl/utils.h +++ b/libnodegl/utils.h @@ -101,5 +101,6 @@ int64_t ngli_gettime_relative(void); char *ngli_asprintf(const char *fmt, ...) ngli_printf_format(1, 2); uint32_t ngli_crc32(const char *s); void ngli_thread_set_name(const char *name); +int ngli_get_filesize(const char *name, int64_t *size); #endif /* UTILS_H */ From 2ffa1c43d4a383ad395235539e690a6ed1032445 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 9 Dec 2020 12:10:19 +0100 Subject: [PATCH 345/388] buffer: use ngli_get_filesize() --- libnodegl/node_buffer.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/libnodegl/node_buffer.c b/libnodegl/node_buffer.c index 51c80a6242..e3f081b3fc 100644 --- a/libnodegl/node_buffer.c +++ b/libnodegl/node_buffer.c @@ -20,6 +20,8 @@ */ #include +#include +#include #include #include #include @@ -32,6 +34,7 @@ #include "nodegl.h" #include "nodes.h" #include "type.h" +#include "utils.h" #define OFFSET(x) offsetof(struct buffer_priv, x) static const struct node_param buffer_params[] = { @@ -127,19 +130,17 @@ static int buffer_init_from_filename(struct ngl_node *node) { struct buffer_priv *s = node->priv_data; - s->fd = open(s->filename, O_RDONLY); - if (s->fd < 0) { - LOG(ERROR, "could not open '%s'", s->filename); - return NGL_ERROR_IO; - } + int64_t size; + int ret = ngli_get_filesize(s->filename, &size); + if (ret < 0) + return ret; - off_t filesize = lseek(s->fd, 0, SEEK_END); - off_t ret = lseek(s->fd, 0, SEEK_SET); - if (filesize < 0 || ret < 0) { - LOG(ERROR, "could not seek in '%s'", s->filename); - return NGL_ERROR_IO; + if (size > INT_MAX) { + LOG(ERROR, "'%s' size (%" PRId64 ") exceeds supported limit (%d)", s->filename, size, INT_MAX); + return NGL_ERROR_UNSUPPORTED; } - s->data_size = filesize; + + s->data_size = size; s->count = s->count ? s->count : s->data_size / s->data_stride; if (s->data_size != s->count * s->data_stride) { @@ -155,6 +156,12 @@ static int buffer_init_from_filename(struct ngl_node *node) if (!s->data) return NGL_ERROR_MEMORY; + s->fd = open(s->filename, O_RDONLY); + if (s->fd < 0) { + LOG(ERROR, "could not open '%s'", s->filename); + return NGL_ERROR_IO; + } + ssize_t n = read(s->fd, s->data, s->data_size); if (n < 0) { LOG(ERROR, "could not read '%s': %zd", s->filename, n); From 9b1cfccaca10787678f4225d2ea3682af5a09bdc Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Wed, 2 Dec 2020 14:27:29 -0800 Subject: [PATCH 346/388] internal: use NGL_PLATFORM_WINDOWS for msvc and mingw --- libnodegl/api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libnodegl/api.c b/libnodegl/api.c index 9ba5f47ad5..34a56af59f 100644 --- a/libnodegl/api.c +++ b/libnodegl/api.c @@ -70,7 +70,7 @@ static int get_default_platform(void) return NGL_PLATFORM_MACOS; #elif defined(TARGET_ANDROID) return NGL_PLATFORM_ANDROID; -#elif defined(TARGET_MINGW_W64) +#elif defined(TARGET_WINDOWS) return NGL_PLATFORM_WINDOWS; #else return NGL_ERROR_UNSUPPORTED; From d311d304bf3eac6b37aec6b90847a6723aa7bf6a Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Wed, 25 Nov 2020 08:27:45 -0800 Subject: [PATCH 347/388] internal: use declspec align on windows --- libnodegl/utils.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libnodegl/utils.h b/libnodegl/utils.h index 0db0164819..671eee44cc 100644 --- a/libnodegl/utils.h +++ b/libnodegl/utils.h @@ -53,8 +53,14 @@ #define NGLI_ALIGN(v, a) (((v) + (a) - 1) & ~((a) - 1)) #define NGLI_ALIGN_VAL 16 -#define NGLI_ALIGNED_VEC(vname) float __attribute__ ((aligned (NGLI_ALIGN_VAL))) vname[4] -#define NGLI_ALIGNED_MAT(mname) float __attribute__ ((aligned (NGLI_ALIGN_VAL))) mname[4*4] +#ifdef _WIN32 +#define NGLI_ATTR_ALIGNED __declspec(align(NGLI_ALIGN_VAL)) +#else +#define NGLI_ATTR_ALIGNED __attribute__ ((aligned (NGLI_ALIGN_VAL))) +#endif + +#define NGLI_ALIGNED_VEC(vname) float NGLI_ATTR_ALIGNED vname[4] +#define NGLI_ALIGNED_MAT(mname) float NGLI_ATTR_ALIGNED mname[4*4] #if CONFIG_SMALL #define NGLI_DOCSTRING(s) (NULL) From 0561e344413498d0241e72c5ef5236e80a6883c6 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Wed, 2 Dec 2020 14:34:49 -0800 Subject: [PATCH 348/388] camera: remove unused unistd.h include --- libnodegl/node_camera.c | 1 - 1 file changed, 1 deletion(-) diff --git a/libnodegl/node_camera.c b/libnodegl/node_camera.c index b21a0655bf..38862a1dd7 100644 --- a/libnodegl/node_camera.c +++ b/libnodegl/node_camera.c @@ -23,7 +23,6 @@ #include #include #include -#include #include "gctx.h" #include "log.h" From 1494a18c11b2466e3c2c89fa133205bc3ac9be90 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Fri, 4 Dec 2020 11:47:51 -0800 Subject: [PATCH 349/388] log: don't use isatty() on windows --- libnodegl/log.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libnodegl/log.c b/libnodegl/log.c index 7ad77810cc..4b58e89611 100644 --- a/libnodegl/log.c +++ b/libnodegl/log.c @@ -22,7 +22,9 @@ #include #include #include +#if !defined(TARGET_IPHONE) && !defined(TARGET_ANDROID) && !defined(TARGET_WINDOWS) #include +#endif #include "config.h" #include "log.h" @@ -42,7 +44,7 @@ static void default_callback(void *arg, int level, const char *filename, int ln, const char *color_start = "", *color_end = ""; -#if !defined(TARGET_IPHONE) && !defined(TARGET_ANDROID) && !defined(TARGET_MINGW_W64) +#if !defined(TARGET_IPHONE) && !defined(TARGET_ANDROID) && !defined(TARGET_WINDOWS) if (isatty(1) && getenv("TERM")) { static const char * const colors[] = { [NGL_LOG_DEBUG] = "\033[32m", // green From b353f4d53d27e1155ce29ebb2dedb6cca2b30a31 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Fri, 4 Dec 2020 11:48:18 -0800 Subject: [PATCH 350/388] internal: replace read()/write() with fread()/fwrite() --- libnodegl/hud.c | 16 +++++++--------- libnodegl/node_buffer.c | 16 +++++++--------- libnodegl/nodes.h | 2 +- libnodegl/test_draw.c | 10 ++++------ 4 files changed, 19 insertions(+), 25 deletions(-) diff --git a/libnodegl/hud.c b/libnodegl/hud.c index dc2baad26e..99f51bc13f 100644 --- a/libnodegl/hud.c +++ b/libnodegl/hud.c @@ -19,14 +19,12 @@ * under the License. */ -#include #include #include #include #include #include #include -#include #include "gctx.h" #include "hmap.h" @@ -53,7 +51,7 @@ struct hud { struct darray widgets; uint32_t bg_color_u32; - int fd_export; + FILE *fp_export; struct bstr *csv_line; struct canvas canvas; double refresh_rate_interval; @@ -1059,8 +1057,8 @@ static void widgets_draw(struct hud *s) static int widgets_csv_header(struct hud *s) { - s->fd_export = open(s->export_filename, O_CREAT | O_WRONLY | O_TRUNC, 0644); - if (s->fd_export == -1) { + s->fp_export = fopen(s->export_filename, "wb"); + if (!s->fp_export) { LOG(ERROR, "unable to open \"%s\" for writing", s->export_filename); return NGL_ERROR_IO; } @@ -1082,7 +1080,7 @@ static int widgets_csv_header(struct hud *s) ngli_bstr_print(s->csv_line, "\n"); const int len = ngli_bstr_len(s->csv_line); - ssize_t n = write(s->fd_export, ngli_bstr_strptr(s->csv_line), len); + size_t n = fwrite(ngli_bstr_strptr(s->csv_line), 1, len, s->fp_export); if (n != len) { LOG(ERROR, "unable to write CSV header"); return NGL_ERROR_IO; @@ -1110,7 +1108,7 @@ static void widgets_csv_report(struct hud *s) ngli_bstr_print(s->csv_line, "\n"); const int len = ngli_bstr_len(s->csv_line); - write(s->fd_export, ngli_bstr_strptr(s->csv_line), len); + fwrite(ngli_bstr_strptr(s->csv_line), 1, len, s->fp_export); } static void free_widget(struct widget *widget) @@ -1378,8 +1376,8 @@ void ngli_hud_freep(struct hud **sp) widgets_uninit(s); ngli_free(s->canvas.buf); - if (s->export_filename) { - close(s->fd_export); + if (s->fp_export) { + fclose(s->fp_export); ngli_bstr_freep(&s->csv_line); } diff --git a/libnodegl/node_buffer.c b/libnodegl/node_buffer.c index e3f081b3fc..462d385988 100644 --- a/libnodegl/node_buffer.c +++ b/libnodegl/node_buffer.c @@ -19,14 +19,12 @@ * under the License. */ -#include #include #include #include #include #include #include -#include #include "buffer.h" #include "log.h" @@ -156,15 +154,15 @@ static int buffer_init_from_filename(struct ngl_node *node) if (!s->data) return NGL_ERROR_MEMORY; - s->fd = open(s->filename, O_RDONLY); - if (s->fd < 0) { + s->fp = fopen(s->filename, "rb"); + if (!s->fp) { LOG(ERROR, "could not open '%s'", s->filename); return NGL_ERROR_IO; } - ssize_t n = read(s->fd, s->data, s->data_size); - if (n < 0) { - LOG(ERROR, "could not read '%s': %zd", s->filename, n); + size_t n = fread(s->data, 1, s->data_size, s->fp); + if (n != s->data_size) { + LOG(ERROR, "could not read '%s'", s->filename); return NGL_ERROR_IO; } @@ -263,8 +261,8 @@ static void buffer_uninit(struct ngl_node *node) ngli_freep(&s->data); s->data_size = 0; - if (s->fd) { - int ret = close(s->fd); + if (s->fp) { + int ret = fclose(s->fp); if (ret < 0) { LOG(ERROR, "could not properly close '%s'", s->filename); } diff --git a/libnodegl/nodes.h b/libnodegl/nodes.h index aa646c4c05..48f9f4f574 100644 --- a/libnodegl/nodes.h +++ b/libnodegl/nodes.h @@ -205,7 +205,7 @@ struct buffer_priv { int timebase[2]; struct ngl_node *time_anim; - int fd; + FILE *fp; int dynamic; int data_type; // any of NGLI_TYPE_* int last_index; diff --git a/libnodegl/test_draw.c b/libnodegl/test_draw.c index 128c12e187..ae74f4fce9 100644 --- a/libnodegl/test_draw.c +++ b/libnodegl/test_draw.c @@ -24,8 +24,6 @@ #include #include #include -#include -#include #include "drawutils.h" @@ -36,8 +34,8 @@ static int save_ppm(const char *filename, uint8_t *data, int width, int height) { int ret = 0; - int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644); - if (fd == -1) { + FILE *fp = fopen(filename, "wb"); + if (!fp) { fprintf(stderr, "Unable to open '%s'\n", filename); return -1; } @@ -63,7 +61,7 @@ static int save_ppm(const char *filename, uint8_t *data, int width, int height) } const int size = header_size + width * height * 3; - ret = write(fd, buf, size); + ret = fwrite(buf, 1, size, fp); if (ret != size) { fprintf(stderr, "Failed to write PPM data\n"); goto end; @@ -71,7 +69,7 @@ static int save_ppm(const char *filename, uint8_t *data, int width, int height) end: free(buf); - close(fd); + fclose(fp); return ret; } From 24353d8268f81fa6923ed53de9b4711b56cfcf32 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Tue, 1 Dec 2020 08:40:20 -0800 Subject: [PATCH 351/388] utils: remove unused ngli_gettime() --- libnodegl/utils.c | 8 -------- libnodegl/utils.h | 1 - 2 files changed, 9 deletions(-) diff --git a/libnodegl/utils.c b/libnodegl/utils.c index e86b1f52f8..9f569f74b4 100644 --- a/libnodegl/utils.c +++ b/libnodegl/utils.c @@ -57,14 +57,6 @@ char *ngli_strdup(const char *s) return r; } -int64_t ngli_gettime(void) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - return 1000000 * (int64_t)tv.tv_sec + tv.tv_usec; -} - int64_t ngli_gettime_relative(void) { struct timespec ts; diff --git a/libnodegl/utils.h b/libnodegl/utils.h index 671eee44cc..5e6cf94134 100644 --- a/libnodegl/utils.h +++ b/libnodegl/utils.h @@ -102,7 +102,6 @@ char *ngli_strdup(const char *s); -int64_t ngli_gettime(void); int64_t ngli_gettime_relative(void); char *ngli_asprintf(const char *fmt, ...) ngli_printf_format(1, 2); uint32_t ngli_crc32(const char *s); From 6e2d7b896bab9aba365d953300e221ccdf2aca1e Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Fri, 4 Dec 2020 10:41:08 -0800 Subject: [PATCH 352/388] utils: don't use clock_gettime() on windows --- libnodegl/utils.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/libnodegl/utils.c b/libnodegl/utils.c index 9f569f74b4..6cc3818e00 100644 --- a/libnodegl/utils.c +++ b/libnodegl/utils.c @@ -37,7 +37,12 @@ #include #include #include +#ifdef _WIN32 +#define POW10_9 1000000000 +#include +#else #include +#endif #include "log.h" #include "memory.h" @@ -59,10 +64,24 @@ char *ngli_strdup(const char *s) int64_t ngli_gettime_relative(void) { +#ifdef _WIN32 + // reference: https://github.com/mirror/mingw-w64/blob/master/mingw-w64-libraries/winpthreads/src/clock.c + LARGE_INTEGER pf, pc; + QueryPerformanceFrequency(&pf); + QueryPerformanceCounter(&pc); + int64_t tv_sec = pc.QuadPart / pf.QuadPart; + int64_t tv_nsec = (int)(((pc.QuadPart % pf.QuadPart) * POW10_9 + (pf.QuadPart >> 1)) / pf.QuadPart); + if (tv_nsec >= POW10_9) { + tv_sec++; + tv_nsec -= POW10_9; + } + return tv_sec * 1000000 + tv_nsec / 1000; +#else struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return 1000000 * (int64_t)ts.tv_sec + ts.tv_nsec / 1000; +#endif } char *ngli_asprintf(const char *fmt, ...) From fa1cdb9cff869849bd5b554c60f1c410c62d100d Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Fri, 4 Dec 2020 10:41:49 -0800 Subject: [PATCH 353/388] utils: check return value of clock_gettime() --- libnodegl/utils.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libnodegl/utils.c b/libnodegl/utils.c index 6cc3818e00..ba6482fa14 100644 --- a/libnodegl/utils.c +++ b/libnodegl/utils.c @@ -78,9 +78,8 @@ int64_t ngli_gettime_relative(void) return tv_sec * 1000000 + tv_nsec / 1000; #else struct timespec ts; - - clock_gettime(CLOCK_MONOTONIC, &ts); - return 1000000 * (int64_t)ts.tv_sec + ts.tv_nsec / 1000; + int ret = clock_gettime(CLOCK_MONOTONIC, &ts); + return ret != 0 ? 0 : ts.tv_sec * 1000000 + ts.tv_nsec / 1000; #endif } From 11d45d4ad46876d86b7a817c454bda5d91aa5f99 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Wed, 2 Dec 2020 14:57:46 -0800 Subject: [PATCH 354/388] tools: remove unused sys/time.h --- ngl-tools/opts.c | 1 - 1 file changed, 1 deletion(-) diff --git a/ngl-tools/opts.c b/ngl-tools/opts.c index a2cd3a5e94..57a1fa9899 100644 --- a/ngl-tools/opts.c +++ b/ngl-tools/opts.c @@ -22,7 +22,6 @@ #include #include #include -#include #include From b4de18f4371eabf45c085d18df0a3bdc89663a69 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 9 Dec 2020 18:36:32 +0100 Subject: [PATCH 355/388] tools/ipc: remove unused O_BINARY define Forgotten in 5801fbf45bde23b0d0123b42e785059810be6fad. --- ngl-tools/ngl-ipc.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ngl-tools/ngl-ipc.c b/ngl-tools/ngl-ipc.c index a4a572a3aa..b7138d186f 100644 --- a/ngl-tools/ngl-ipc.c +++ b/ngl-tools/ngl-ipc.c @@ -43,10 +43,6 @@ #include "ipc.h" #include "opts.h" -#ifndef O_BINARY -#define O_BINARY 0 -#endif - #define UPLOAD_CHUNK_SIZE (1024 * 1024) struct ctx { From 04f2744c6d1daa8ef2a954b27cbf9361d48a37e5 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Wed, 9 Dec 2020 18:41:04 +0100 Subject: [PATCH 356/388] tools/ipc: use an int instead of a ssize_t to store the send()/recv() return On Windows, send() and recv() return an int, whereas on Unix systems they return a ssize_t (which is not supported on Windows). Using an int to store the return of send() and recv() is fine for our case as we are only reading or writing packets that cannot exceed the limits of an int. This makes the code compatible with MSVC. --- ngl-tools/ipc.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ngl-tools/ipc.c b/ngl-tools/ipc.c index 1a2edd85e9..e5a0dcb0c9 100644 --- a/ngl-tools/ipc.c +++ b/ngl-tools/ipc.c @@ -176,24 +176,24 @@ void ipc_pkt_freep(struct ipc_pkt **pktp) int ipc_send(int fd, const struct ipc_pkt *pkt) { - const ssize_t n = send(fd, pkt->data, pkt->size, 0); + const int n = send(fd, pkt->data, pkt->size, 0); if (n < 0) { perror("send"); return NGL_ERROR_IO; } // XXX: should we loop instead? if (n != pkt->size) { - fprintf(stderr, "unable write packet (%zd/%d sent)\n", n, pkt->size); + fprintf(stderr, "unable write packet (%d/%d sent)\n", n, pkt->size); return NGL_ERROR_IO; } return 0; } -static int readbuf(int fd, uint8_t *buf, ssize_t size) +static int readbuf(int fd, uint8_t *buf, int size) { - ssize_t nr = 0; + int nr = 0; while (nr != size) { - const ssize_t n = recv(fd, buf + nr, size - nr, 0); + const int n = recv(fd, buf + nr, size - nr, 0); if (n == 0) return 0; if (n < 0) { From 2f7cc3408715e991858aafb394d886ce03446bd3 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Wed, 9 Dec 2020 18:42:25 +0100 Subject: [PATCH 357/388] tools/wsi_windows: add missing include Required by fprintf(). --- ngl-tools/wsi_windows.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ngl-tools/wsi_windows.c b/ngl-tools/wsi_windows.c index 7d9f5799b0..dd37102589 100644 --- a/ngl-tools/wsi_windows.c +++ b/ngl-tools/wsi_windows.c @@ -19,6 +19,7 @@ * under the License. */ +#include #include #include #include From 0d78b4cdf7e971649ae5eba1cb748f1e10f18772 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Wed, 9 Dec 2020 18:43:55 +0100 Subject: [PATCH 358/388] tools/common: implement gettime{,_relative}() on windows --- ngl-tools/common.c | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/ngl-tools/common.c b/ngl-tools/common.c index a89a75c1c2..8a5ee0dca8 100644 --- a/ngl-tools/common.c +++ b/ngl-tools/common.c @@ -23,27 +23,57 @@ #include #include -#include #include #include #include #include +#ifdef _WIN32 +#include +#define POW10_9 1000000000 +#else +#include +#endif + #include "common.h" int64_t gettime(void) { +#ifdef _WIN32 + FILETIME t0; + GetSystemTimeAsFileTime(&t0); + ULARGE_INTEGER t1; + t1.LowPart = t0.dwLowDateTime; + t1.HighPart = t0.dwHighDateTime; + int64_t t2 = t1.QuadPart; + return t2 / 10; // convert to microseconds +#else struct timeval tv; gettimeofday(&tv, NULL); return 1000000 * (int64_t)tv.tv_sec + tv.tv_usec; +#endif } int64_t gettime_relative(void) { +#ifdef _WIN32 + // reference: https://github.com/mirror/mingw-w64/blob/master/mingw-w64-libraries/winpthreads/src/clock.c + LARGE_INTEGER pf, pc; + QueryPerformanceFrequency(&pf); + QueryPerformanceCounter(&pc); + int64_t tv_sec = pc.QuadPart / pf.QuadPart; + int64_t tv_nsec = (int)(((pc.QuadPart % pf.QuadPart) * POW10_9 + (pf.QuadPart >> 1)) / pf.QuadPart); + if (tv_nsec >= POW10_9) { + tv_sec++; + tv_nsec -= POW10_9; + } + return tv_sec * 1000000 + tv_nsec / 1000; +#else struct timespec ts; int ret = clock_gettime(CLOCK_MONOTONIC, &ts); return ret != 0 ? 0 : ts.tv_sec * 1000000 + ts.tv_nsec / 1000; +#endif } double clipd(double v, double min, double max) From 3e7d82f6fd8675c838a22493c68e4591f3b4eda3 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Wed, 9 Dec 2020 22:56:27 +0100 Subject: [PATCH 359/388] tools/desktop: scope include to non-windows systems --- ngl-tools/ngl-desktop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ngl-tools/ngl-desktop.c b/ngl-tools/ngl-desktop.c index bc60122684..e2a4cb52e0 100644 --- a/ngl-tools/ngl-desktop.c +++ b/ngl-tools/ngl-desktop.c @@ -46,10 +46,10 @@ #include #include #include +#include #endif #include #include -#include #include #include From 41cba1a04bbb7897c1aafdfb27842598f8d1fd61 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 9 Dec 2020 23:02:33 +0100 Subject: [PATCH 360/388] tools/ipc: remove unused include --- ngl-tools/ipc.c | 1 - 1 file changed, 1 deletion(-) diff --git a/ngl-tools/ipc.c b/ngl-tools/ipc.c index e5a0dcb0c9..8616b52161 100644 --- a/ngl-tools/ipc.c +++ b/ngl-tools/ipc.c @@ -22,7 +22,6 @@ #include #include #include -#include #ifdef _WIN32 #include #else From f2b857cddb07156ca19f05d4198438fb271f3bef Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Wed, 9 Dec 2020 22:57:58 +0100 Subject: [PATCH 361/388] tools/serialize: open file descriptor in binary mode This is useful for doing binary I/O on non-UNIX environments. --- ngl-tools/ngl-serialize.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ngl-tools/ngl-serialize.c b/ngl-tools/ngl-serialize.c index 072acf6f5c..7d0b58fd21 100644 --- a/ngl-tools/ngl-serialize.c +++ b/ngl-tools/ngl-serialize.c @@ -40,9 +40,9 @@ static FILE *open_ofile(const char *output) close(fd); return NULL; } - return fdopen(fd, "w"); + return fdopen(fd, "wb"); } - return fopen(output, "w"); + return fopen(output, "wb"); } int main(int argc, char *argv[]) From 38a06cb2d10101313641bede5b5b4550c9f926ec Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Wed, 9 Dec 2020 18:45:36 +0100 Subject: [PATCH 362/388] tools/player: use fopen()/fwrite() instead of open()/write() --- ngl-tools/player.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ngl-tools/player.c b/ngl-tools/player.c index 56ce6f92a8..d8b2e5c58c 100644 --- a/ngl-tools/player.c +++ b/ngl-tools/player.c @@ -28,8 +28,6 @@ #include #include #include -#include -#include #include "common.h" #include "player.h" @@ -44,8 +42,8 @@ static struct player *g_player; static int save_ppm(const char *filename, uint8_t *data, int width, int height) { int ret = 0; - int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644); - if (fd == -1) { + FILE *fp = fopen(filename, "wb"); + if (!fp) { fprintf(stderr, "Unable to open '%s'\n", filename); return -1; } @@ -71,7 +69,7 @@ static int save_ppm(const char *filename, uint8_t *data, int width, int height) } const int size = header_size + width * height * 3; - ret = write(fd, buf, size); + ret = fwrite(buf, 1, size, fp); if (ret != size) { fprintf(stderr, "Failed to write PPM data\n"); goto end; @@ -79,7 +77,7 @@ static int save_ppm(const char *filename, uint8_t *data, int width, int height) end: free(buf); - close(fd); + fclose(fp); return ret; } From a8aa7068c5a94fa47e663035e1e4ec044e1c4576 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Wed, 9 Dec 2020 23:03:34 +0100 Subject: [PATCH 363/388] tools/common: use fopen()/fread() instead of open()/read() --- ngl-tools/common.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/ngl-tools/common.c b/ngl-tools/common.c index 8a5ee0dca8..7962a1ff3b 100644 --- a/ngl-tools/common.c +++ b/ngl-tools/common.c @@ -25,8 +25,6 @@ #include #include #include -#include -#include #ifdef _WIN32 #include @@ -115,15 +113,15 @@ char *get_text_file_content(const char *filename) { char *buf = NULL; - int fd = filename ? open(filename, O_RDONLY) : STDIN_FILENO; - if (fd == -1) { + FILE *fp = filename ? fopen(filename, "rb") : stdin; + if (!fp) { fprintf(stderr, "unable to open %s\n", filename); goto end; } - ssize_t pos = 0; + size_t pos = 0; for (;;) { - const ssize_t needed = pos + BUF_SIZE + 1; + const size_t needed = pos + BUF_SIZE + 1; void *new_buf = realloc(buf, needed); if (!new_buf) { free(buf); @@ -131,21 +129,21 @@ char *get_text_file_content(const char *filename) goto end; } buf = new_buf; - const ssize_t n = read(fd, buf + pos, BUF_SIZE); - if (n < 0) { + const size_t n = fread(buf + pos, 1, BUF_SIZE, fp); + if (ferror(fp)) { free(buf); buf = NULL; goto end; } - if (n == 0) { + pos += n; + if (feof(fp)) { buf[pos] = 0; break; } - pos += n; } end: - if (fd != -1 && fd != STDIN_FILENO) - close(fd); + if (fp && fp != stdin) + fclose(fp); return buf; } From fb74bf37452ae419bb3fcd9de56fb110bc743e77 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Wed, 9 Dec 2020 23:06:00 +0100 Subject: [PATCH 364/388] tools: define STDOUT_FILENO and STDERR_FILENO for msvc --- ngl-tools/ngl-player.c | 5 +++++ ngl-tools/ngl-render.c | 5 +++++ ngl-tools/ngl-serialize.c | 7 ++++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/ngl-tools/ngl-player.c b/ngl-tools/ngl-player.c index 1be5ab8bac..0879aec4f5 100644 --- a/ngl-tools/ngl-player.c +++ b/ngl-tools/ngl-player.c @@ -20,6 +20,11 @@ */ #include +#include +#if defined(_WIN32) && defined(_MSC_VER) +#define STDOUT_FILENO _fileno(stdout) +#define STDERR_FILENO _fileno(stderr) +#endif #include #include diff --git a/ngl-tools/ngl-render.c b/ngl-tools/ngl-render.c index 756472761a..7e0220eb94 100644 --- a/ngl-tools/ngl-render.c +++ b/ngl-tools/ngl-render.c @@ -25,7 +25,12 @@ #include #include #include +#if defined(_WIN32) && defined(_MSC_VER) +#define STDOUT_FILENO _fileno(stdout) +#define STDERR_FILENO _fileno(stderr) +#else #include +#endif #include diff --git a/ngl-tools/ngl-serialize.c b/ngl-tools/ngl-serialize.c index 7d0b58fd21..7c99766224 100644 --- a/ngl-tools/ngl-serialize.c +++ b/ngl-tools/ngl-serialize.c @@ -19,12 +19,17 @@ * under the License. */ +#if defined(_WIN32) && defined(_MSC_VER) +#define STDOUT_FILENO _fileno(stdout) +#define STDERR_FILENO _fileno(stderr) +#else #define _POSIX_C_SOURCE 200809L // fdopen +#include +#endif #include #include #include -#include #include From 9ea84b33922c36ec79c048f478921a8bce93cbc0 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Wed, 2 Dec 2020 14:37:14 -0800 Subject: [PATCH 365/388] glcontext_wgl: use uppercase Windows.h --- libnodegl/backends/gl/glcontext_wgl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libnodegl/backends/gl/glcontext_wgl.c b/libnodegl/backends/gl/glcontext_wgl.c index a47bc14fa2..7998b48a06 100644 --- a/libnodegl/backends/gl/glcontext_wgl.c +++ b/libnodegl/backends/gl/glcontext_wgl.c @@ -21,7 +21,7 @@ #include #include -#include +#include #include #include From 05bd9ff0e1d742a788504a0fb824024b26189907 Mon Sep 17 00:00:00 2001 From: Matthieu Bouron Date: Thu, 10 Dec 2020 10:04:34 +0100 Subject: [PATCH 366/388] build: require sxplayer version 9.7.0 sxplayer 9.7.0 has MSVC support. --- Makefile | 2 +- libnodegl/meson.build | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 86121f78f8..7a81d11986 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ ifneq ($(shell $(PYTHON) -c "import sys;print(sys.version_info.major)"),$(PYTHON $(error "Python $(PYTHON_MAJOR) not found") endif -SXPLAYER_VERSION ?= 9.6.0 +SXPLAYER_VERSION ?= 9.7.0 ACTIVATE = $(PREFIX)/bin/activate diff --git a/libnodegl/meson.build b/libnodegl/meson.build index bb92436061..1138c683b9 100644 --- a/libnodegl/meson.build +++ b/libnodegl/meson.build @@ -196,7 +196,7 @@ hosts_cfg = { lib_deps = [ cc.find_library('m', required: false), - dependency('libsxplayer', version: '>= 9.4.0'), + dependency('libsxplayer', version: '>= 9.7.0'), dependency('threads'), ] From 13a323bd8820bd2d3cc9ef9129178f59a2a66ca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20B=C5=93sch?= Date: Thu, 10 Dec 2020 11:33:44 +0100 Subject: [PATCH 367/388] build: isolate sxplayer download in a dedicated external directory This directory can be shared for other potential external dependencies. --- .gitignore | 1 - Makefile | 33 +++++----------------------- external/.gitignore | 1 + external/Makefile | 52 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 29 deletions(-) create mode 100644 external/.gitignore create mode 100644 external/Makefile diff --git a/.gitignore b/.gitignore index 27859dbe6f..19ef5791b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ builddir nodegl-env/ -sxplayer* diff --git a/Makefile b/Makefile index 7a81d11986..20f7046807 100644 --- a/Makefile +++ b/Makefile @@ -28,9 +28,7 @@ PYTHON_MAJOR = 3 # DEBUG ?= no COVERAGE ?= no -CURL ?= curl PYTHON ?= python$(if $(shell which python$(PYTHON_MAJOR) 2> /dev/null),$(PYTHON_MAJOR),) -TAR ?= tar TARGET_OS ?= $(shell uname -s) DEBUG_GL ?= no @@ -43,8 +41,6 @@ ifneq ($(shell $(PYTHON) -c "import sys;print(sys.version_info.major)"),$(PYTHON $(error "Python $(PYTHON_MAJOR) not found") endif -SXPLAYER_VERSION ?= 9.7.0 - ACTIVATE = $(PREFIX)/bin/activate RPATH_LDFLAGS ?= -Wl,-rpath,$(PREFIX)/lib @@ -139,31 +135,11 @@ nodegl-install: nodegl-setup nodegl-setup: sxplayer-install (. $(ACTIVATE) && $(MESON_SETUP) $(NODEGL_DEBUG_OPTS) libnodegl builddir/libnodegl) -sxplayer-install: sxplayer $(PREFIX) - (. $(ACTIVATE) && $(MESON_SETUP) sxplayer builddir/sxplayer && $(MESON_COMPILE) -C builddir/sxplayer && $(MESON_INSTALL) -C builddir/sxplayer) - -# Note for developers: in order to customize the sxplayer you're building -# against, you can use your own sources post-install: -# -# % unlink sxplayer -# % ln -snf /path/to/sxplayer.git sxplayer -# % touch /path/to/sxplayer.git -# -# The `touch` command makes sure the source target directory is more recent -# than the prerequisite directory of the sxplayer rule. If this isn't true, the -# symlink will be re-recreated on the next `make` call -sxplayer: sxplayer-$(SXPLAYER_VERSION) -ifneq ($(TARGET_OS),MinGW-w64) - ln -snf $< $@ -else - cp -r $< $@ -endif - -sxplayer-$(SXPLAYER_VERSION): sxplayer-$(SXPLAYER_VERSION).tar.gz - $(TAR) xf $< +sxplayer-install: external-download $(PREFIX) + (. $(ACTIVATE) && $(MESON_SETUP) external/sxplayer builddir/sxplayer && $(MESON_COMPILE) -C builddir/sxplayer && $(MESON_INSTALL) -C builddir/sxplayer) -sxplayer-$(SXPLAYER_VERSION).tar.gz: - $(CURL) -L https://github.com/Stupeflix/sxplayer/archive/v$(SXPLAYER_VERSION).tar.gz -o $@ +external-download: + $(MAKE) -C external # # We do not pull meson from pip on Windows for the same reasons we don't pull @@ -223,3 +199,4 @@ coverage-xml: .PHONY: tests tests-setup .PHONY: clean clean_py .PHONY: coverage-html coverage-xml +.PHONY: external-download diff --git a/external/.gitignore b/external/.gitignore new file mode 100644 index 0000000000..be8653c13b --- /dev/null +++ b/external/.gitignore @@ -0,0 +1 @@ +sxplayer* diff --git a/external/Makefile b/external/Makefile new file mode 100644 index 0000000000..ae377be1c4 --- /dev/null +++ b/external/Makefile @@ -0,0 +1,52 @@ +# +# Copyright 2020 GoPro Inc. +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +TAR ?= tar +CURL ?= curl + +SXPLAYER_VERSION ?= 9.7.0 + +all: sxplayer + +# Note for developers: in order to customize the sxplayer you're building +# against, you can use your own sources post-install: +# +# % unlink sxplayer +# % ln -snf /path/to/sxplayer.git sxplayer +# % touch /path/to/sxplayer.git +# +# The `touch` command makes sure the source target directory is more recent +# than the prerequisite directory of the sxplayer rule. If this isn't true, the +# symlink will be re-recreated on the next `make` call +sxplayer: sxplayer-$(SXPLAYER_VERSION) +ifneq ($(TARGET_OS),MinGW-w64) + ln -snf $< $@ +else + cp -r $< $@ +endif + +sxplayer-$(SXPLAYER_VERSION): sxplayer-$(SXPLAYER_VERSION).tar.gz + $(TAR) -xf $< + +sxplayer-$(SXPLAYER_VERSION).tar.gz: + $(CURL) -L https://github.com/Stupeflix/sxplayer/archive/v$(SXPLAYER_VERSION).tar.gz -o $@ + +.PHONY: all From d91962ce2f78705f84f10b9f5821a5a0e1918c9f Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Mon, 30 Nov 2020 11:48:12 -0800 Subject: [PATCH 368/388] build: update gitignore --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 19ef5791b8..b4353fecca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ -builddir +/builddir nodegl-env/ +/sxplayer* +/external/win64 +external/token.cfg From 559d49dab1dce6b6caa1a656097ffb54f05daf3c Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Fri, 11 Dec 2020 15:54:26 -0800 Subject: [PATCH 369/388] build: port Makefile to Windows --- Makefile | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 74 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 20f7046807..172b614ebe 100644 --- a/Makefile +++ b/Makefile @@ -19,8 +19,6 @@ # under the License. # -PREFIX ?= $(PWD)/nodegl-env - PYTHON_MAJOR = 3 # @@ -28,9 +26,21 @@ PYTHON_MAJOR = 3 # DEBUG ?= no COVERAGE ?= no -PYTHON ?= python$(if $(shell which python$(PYTHON_MAJOR) 2> /dev/null),$(PYTHON_MAJOR),) TARGET_OS ?= $(shell uname -s) +ifeq ($(TARGET_OS),Windows) +PYTHON ?= python.exe +PREFIX ?= nodegl-env +W_PWD = $(shell wslpath -w .) +W_PREFIX ?= $(W_PWD)\$(PREFIX) +$(info PYTHON: $(PYTHON)) +$(info PREFIX: $(PREFIX)) +$(info W_PREFIX: $(W_PREFIX)) +else +PYTHON ?= python$(if $(shell which python$(PYTHON_MAJOR) 2> /dev/null),$(PYTHON_MAJOR),) +PREFIX ?= $(PWD)/nodegl-env +endif + DEBUG_GL ?= no DEBUG_MEM ?= no DEBUG_SCENE ?= no @@ -41,14 +51,28 @@ ifneq ($(shell $(PYTHON) -c "import sys;print(sys.version_info.major)"),$(PYTHON $(error "Python $(PYTHON_MAJOR) not found") endif +ifeq ($(TARGET_OS), Windows) +#TODO: identify correct path +VCVARS64 = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat" +ACTIVATE = $(VCVARS64) \&\& $(PREFIX)\\Scripts\\activate.bat +else ACTIVATE = $(PREFIX)/bin/activate +endif RPATH_LDFLAGS ?= -Wl,-rpath,$(PREFIX)/lib +ifeq ($(TARGET_OS),Windows) +MESON_SETUP = meson setup --backend vs --prefix="$(W_PREFIX)" --pkg-config-path=$(PREFIX)\\Lib\\pkgconfig -Drpath=true +else MESON_SETUP = meson setup --prefix=$(PREFIX) --pkg-config-path=$(PREFIX)/lib/pkgconfig -Drpath=true +endif # MAKEFLAGS= is a workaround for the issue described here: # https://github.com/ninja-build/ninja/issues/1139#issuecomment-724061270 +ifeq ($(TARGET_OS),Windows) +MESON_COMPILE = meson compile +else MESON_COMPILE = MAKEFLAGS= meson compile +endif MESON_INSTALL = meson install ifeq ($(COVERAGE),yes) MESON_SETUP += -Db_coverage=true @@ -89,14 +113,26 @@ all: ngl-tools-install pynodegl-utils-install @echo " Install completed." @echo @echo " You can now enter the venv with:" +ifeq ($(TARGET_OS),Windows) + @echo " . $(PREFIX)\\Scripts\\activate.bat" +else @echo " . $(ACTIVATE)" +endif @echo ngl-tools-install: nodegl-install +ifeq ($(TARGET_OS),Windows) + (cmd.exe /C $(ACTIVATE) \&\& $(MESON_SETUP) ngl-tools builddir\\ngl-tools \&\& $(MESON_COMPILE) -C builddir\\ngl-tools \&\& $(MESON_INSTALL) -C builddir\\ngl-tools) +else (. $(ACTIVATE) && $(MESON_SETUP) ngl-tools builddir/ngl-tools && $(MESON_COMPILE) -C builddir/ngl-tools && $(MESON_INSTALL) -C builddir/ngl-tools) +endif pynodegl-utils-install: pynodegl-utils-deps-install +ifeq ($(TARGET_OS),Windows) + (cmd.exe /C $(ACTIVATE) \&\& pip -v install -e pynodegl-utils) +else (. $(ACTIVATE) && pip -v install -e ./pynodegl-utils) +endif # # pynodegl-install is in dependency to prevent from trying to install pynodegl @@ -119,24 +155,49 @@ pynodegl-utils-install: pynodegl-utils-deps-install # decorator and other related utils. # pynodegl-utils-deps-install: pynodegl-install -ifneq ($(TARGET_OS),MinGW-w64) +ifeq ($(TARGET_OS),Windows) + (cmd.exe /C $(ACTIVATE) \&\& pip install -r pynodegl-utils\\requirements.txt) +else ifneq ($(TARGET_OS),MinGW-w64) (. $(ACTIVATE) && pip install -r ./pynodegl-utils/requirements.txt) endif pynodegl-install: pynodegl-deps-install +ifeq ($(TARGET_OS),Windows) + (PKG_CONFIG_PATH="$(W_PREFIX)\Lib\pkgconfig" WSLENV=PKG_CONFIG_PATH/w cmd.exe /C $(ACTIVATE) \&\& pip -v install -e .\\pynodegl) + #Copy DLLs to runtime search path + (cp external/win64/ffmpeg_x64-windows/bin/*.dll pynodegl/.) + (cp external/win64/pthreads_x64-windows/bin/*.dll pynodegl/.) +else (. $(ACTIVATE) && PKG_CONFIG_PATH=$(PREFIX)/lib/pkgconfig LDFLAGS=$(RPATH_LDFLAGS) pip -v install -e ./pynodegl) +endif pynodegl-deps-install: $(PREFIX) nodegl-install +ifeq ($(TARGET_OS),Windows) + (cmd.exe /C $(ACTIVATE) \&\& pip install -r pynodegl\\requirements.txt) +else (. $(ACTIVATE) && pip install -r ./pynodegl/requirements.txt) +endif nodegl-install: nodegl-setup +ifeq ($(TARGET_OS),Windows) + (cmd.exe /C $(ACTIVATE) \&\& $(MESON_COMPILE) -C builddir\\libnodegl \&\& $(MESON_INSTALL) -C builddir\\libnodegl) +else (. $(ACTIVATE) && $(MESON_COMPILE) -C builddir/libnodegl && $(MESON_INSTALL) -C builddir/libnodegl) +endif nodegl-setup: sxplayer-install +ifeq ($(TARGET_OS),Windows) + (cmd.exe /C $(ACTIVATE) \&\& $(MESON_SETUP) $(NODEGL_DEBUG_OPTS) --default-library static libnodegl builddir\\libnodegl) +else (. $(ACTIVATE) && $(MESON_SETUP) $(NODEGL_DEBUG_OPTS) libnodegl builddir/libnodegl) +endif sxplayer-install: external-download $(PREFIX) +ifeq ($(TARGET_OS),Windows) + (cmd.exe /C $(ACTIVATE) \&\& $(MESON_SETUP) --default-library static external\\sxplayer builddir\\sxplayer \&\& $(MESON_COMPILE) -C builddir\\sxplayer \&\& $(MESON_INSTALL) -C builddir\\sxplayer) +else (. $(ACTIVATE) && $(MESON_SETUP) external/sxplayer builddir/sxplayer && $(MESON_COMPILE) -C builddir/sxplayer && $(MESON_INSTALL) -C builddir/sxplayer) +endif external-download: $(MAKE) -C external @@ -146,7 +207,15 @@ external-download: # Pillow and PySide2. We require the users to have it on their system. # $(PREFIX): -ifeq ($(TARGET_OS),MinGW-w64) +ifeq ($(TARGET_OS),Windows) + (cd external && bash scripts/sync.sh win64) + $(PYTHON) -m venv $(PREFIX) + (cmd.exe /C copy external\\win64\\pkg-config.exe nodegl-env\\Scripts) + (cmd.exe /C mkdir $(PREFIX)\\Lib\\pkgconfig) + (cmd.exe /C pushd external\\win64\\ffmpeg_x64-windows \&\& python.exe scripts/install.py "$(W_PREFIX)" \& popd) + (cmd.exe /C pushd external\\win64\\pthreads_x64-windows \&\& python.exe scripts/install.py "$(W_PREFIX)" \& popd) + (cmd.exe /C $(ACTIVATE) \&\& pip install meson ninja) +else ifeq ($(TARGET_OS),MinGW-w64) $(PYTHON) -m venv --system-site-packages $(PREFIX) else $(PYTHON) -m venv $(PREFIX) From 618da1636200ea576f65cdecc92b5c66d01b9e17 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Tue, 24 Nov 2020 15:37:03 -0800 Subject: [PATCH 370/388] build: port nodegl build to Windows --- libnodegl/meson.build | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/libnodegl/meson.build b/libnodegl/meson.build index 1138c683b9..76e83b306e 100644 --- a/libnodegl/meson.build +++ b/libnodegl/meson.build @@ -200,6 +200,9 @@ lib_deps = [ dependency('threads'), ] +lib_include_dirs = [] +lib_link_args = [] + host_cfg = hosts_cfg.get(host_system, {}) foreach dep : host_cfg.get('deps', []) lib_deps += dependency(dep) @@ -295,7 +298,7 @@ gbackends_cfg = { 'windows': { 'src': files('backends/gl/glcontext_wgl.c'), 'cfg': 'HAVE_GLPLATFORM_WGL', - 'libs': ['opengl32', 'gdi32'], + 'libs': ['OpenGL32', 'gdi32'], }, }, }, @@ -305,6 +308,19 @@ if host_system in ['darwin', 'iphone'] add_languages('objc') endif +cc_lib_dirs = [ get_option('prefix') / get_option('libdir') ] + +if host_system == 'windows' and cc.get_id() == 'msvc' + + #TODO: set correct path + windows_sdk_dir = 'C:\\Program Files (x86)\\Windows Kits\\10' + windows_sdk_library_directories = [ windows_sdk_dir + '\\Lib\\10.0.18362.0\\um\\x64' ] + + cc_lib_dirs += windows_sdk_library_directories + lib_include_dirs += include_directories('..\\external\\win64\\gl\\include') + +endif + foreach gbackend_name, gbackend_cfg : gbackends_cfg opt_gbackend = get_option('gbackend-' + gbackend_name) @@ -314,7 +330,11 @@ foreach gbackend_name, gbackend_cfg : gbackends_cfg gbackend_deps += dependency(dep, required: opt_gbackend) endforeach foreach dep : host_backend_cfg.get('libs', []) - gbackend_deps += cc.find_library(dep, required: opt_gbackend) + gbackend_dep = cc.find_library(dep, required: opt_gbackend) + if not gbackend_dep.found() + gbackend_dep = cc.find_library(dep, required: opt_gbackend, dirs: cc_lib_dirs) + endif + gbackend_deps += gbackend_dep endforeach if 'frameworks' in host_backend_cfg gbackend_deps += dependency('appleframeworks', modules: host_backend_cfg.get('frameworks'), required: opt_gbackend) @@ -348,11 +368,19 @@ configure_file(output: 'config.h', configuration: conf_data) # See https://github.com/mesonbuild/meson/pull/4747 to follow advancement on # the native support for symbol visibility -lib_link_args = cc.get_supported_link_arguments([ +lib_link_args += cc.get_supported_link_arguments([ '-Wl,--version-script,@0@'.format(meson.current_source_dir() / 'libnodegl.symexport'), # GNU ld '-Wl,-exported_symbols_list,@0@'.format(meson.current_source_dir() / 'libnodegl.darwin.symexport'), # Darwin ]) +lib_name_prefix = [] +lib_name_suffix = [] +if host_system == 'windows' + # Follow Windows library convention + lib_name_prefix = '' + lib_name_suffix = 'lib' +endif + libnodegl = library( 'nodegl', lib_src, @@ -361,6 +389,9 @@ libnodegl = library( install_rpath: install_rpath, version: meson.project_version(), link_args: lib_link_args, + include_directories: lib_include_dirs, + name_prefix: lib_name_prefix, + name_suffix: lib_name_suffix ) lib_header = configure_file( input: files('nodegl.h.in'), From 306564b52a73dba717df6428d585ec5f79c9ac4e Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Tue, 24 Nov 2020 15:38:03 -0800 Subject: [PATCH 371/388] build: port ngl-tools to Windows --- ngl-tools/meson.build | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/ngl-tools/meson.build b/ngl-tools/meson.build index 8292da9fa3..150cf602a1 100644 --- a/ngl-tools/meson.build +++ b/ngl-tools/meson.build @@ -42,9 +42,13 @@ add_project_arguments( # Main dependencies # tool_deps = [ - cc.find_library('m', required: false), dependency('libnodegl'), ] + +if host_system != 'windows' + tool_deps += cc.find_library('m', required: false) +endif + sxplayer_dep = dependency('libsxplayer') # for media probing threads_dep = dependency('threads', required: false) @@ -115,6 +119,12 @@ tools_specs = { }, } +if host_system == 'windows' + tool_install_dir = 'Scripts' +else + tool_install_dir = 'bin' +endif + foreach tool_name, tool_info : tools_specs all_dep_found = true foreach dep : tool_info.get('deps') @@ -127,6 +137,7 @@ foreach tool_name, tool_info : tools_specs tool_info.get('src') + files('common.c'), dependencies: tool_info.get('deps'), install: true, + install_dir: tool_install_dir, install_rpath: get_option('rpath') ? get_option('prefix') / 'lib' : '', ) endif From 76e5f90db08c5b23c07780fa6e8a6720be0a8ecc Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Wed, 25 Nov 2020 09:20:36 -0800 Subject: [PATCH 372/388] nodegl: port glincludes to windows Remove "WINAPI" declaration due to various compile errors --- libnodegl/backends/gl/glincludes.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/libnodegl/backends/gl/glincludes.h b/libnodegl/backends/gl/glincludes.h index 46206bd7c3..78d886dcdf 100644 --- a/libnodegl/backends/gl/glincludes.h +++ b/libnodegl/backends/gl/glincludes.h @@ -57,17 +57,14 @@ #endif #if _WIN32 -# include +# include +# include # include # include # define NGL_OGL3_COMPAT_INCLUDES 1 #endif -#ifdef _WIN32 -#define NGLI_GL_APIENTRY WINAPI -#else #define NGLI_GL_APIENTRY -#endif #ifndef GL_OES_EGL_image typedef void* GLeglImageOES; From 0fc7340b89a8e75f123071c601593ba74b112f85 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Wed, 25 Nov 2020 09:33:40 -0800 Subject: [PATCH 373/388] nodegl: use TARGET_WINDOWS instead of TARGET_MINGW --- libnodegl/memory.c | 4 ++-- ngl-tools/meson.build | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/libnodegl/memory.c b/libnodegl/memory.c index b763c7f598..b9c39c34bd 100644 --- a/libnodegl/memory.c +++ b/libnodegl/memory.c @@ -85,7 +85,7 @@ void *ngli_malloc_aligned(size_t size) return NULL; void *ptr; -#ifdef TARGET_MINGW_W64 +#ifdef TARGET_WINDOWS ptr = _aligned_malloc(size, NGLI_ALIGN_VAL); #else if (posix_memalign(&ptr, NGLI_ALIGN_VAL, size)) @@ -114,7 +114,7 @@ void ngli_freep(void *ptr) void ngli_free_aligned(void *ptr) { -#ifdef TARGET_MINGW_W64 +#ifdef TARGET_WINDOWS _aligned_free(ptr); #else free(ptr); diff --git a/ngl-tools/meson.build b/ngl-tools/meson.build index 150cf602a1..8d00edb867 100644 --- a/ngl-tools/meson.build +++ b/ngl-tools/meson.build @@ -120,6 +120,8 @@ tools_specs = { } if host_system == 'windows' + #Python -m venv on windows creates a Scripts folder instead of a bin folder + #TODO: add a bin folder and add it to path in activate script tool_install_dir = 'Scripts' else tool_install_dir = 'bin' From ca2b87741d03e68f7e3b9e4b65939b6d8fa9034d Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Fri, 11 Dec 2020 15:55:13 -0800 Subject: [PATCH 374/388] build: patch libnodegl.pc. TODO: remove --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 172b614ebe..a992713b1f 100644 --- a/Makefile +++ b/Makefile @@ -181,6 +181,8 @@ endif nodegl-install: nodegl-setup ifeq ($(TARGET_OS),Windows) (cmd.exe /C $(ACTIVATE) \&\& $(MESON_COMPILE) -C builddir\\libnodegl \&\& $(MESON_INSTALL) -C builddir\\libnodegl) + # patch libnodegl.pc TODO: remove + sed -i -e 's/Libs.private: .*/Libs.private: OpenGL32.lib gdi32.lib/' nodegl-env/Lib/pkgconfig/libnodegl.pc else (. $(ACTIVATE) && $(MESON_COMPILE) -C builddir/libnodegl && $(MESON_INSTALL) -C builddir/libnodegl) endif From 5209617c26cc8695339aef68919c9d9dd3512c05 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Wed, 25 Nov 2020 10:20:28 -0800 Subject: [PATCH 375/388] build: port tests target to windows --- Makefile | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Makefile b/Makefile index a992713b1f..d8825f0e4d 100644 --- a/Makefile +++ b/Makefile @@ -225,13 +225,25 @@ else endif tests: nodegl-tests tests-setup +ifeq ($(TARGET_OS),Windows) + (cmd.exe /C $(ACTIVATE) \&\& meson test $(MESON_TESTS_SUITE_OPTS) -C builddir\\tests) +else (. $(ACTIVATE) && meson test $(MESON_TESTS_SUITE_OPTS) -C builddir/tests) +endif tests-setup: ngl-tools-install pynodegl-utils-install +ifeq ($(TARGET_OS),Windows) + (cmd.exe /C $(ACTIVATE) \&\& $(MESON_SETUP) builddir\\tests tests) +else (. $(ACTIVATE) && $(MESON_SETUP) builddir/tests tests) +endif nodegl-tests: nodegl-install +ifeq ($(TARGET_OS),Windows) + (cmd.exe /C $(ACTIVATE) \&\& meson test -C builddir\\libnodegl) +else (. $(ACTIVATE) && meson test -C builddir/libnodegl) +endif nodegl-%: nodegl-setup (. $(ACTIVATE) && $(MESON_COMPILE) -C builddir/libnodegl $(subst nodegl-,,$@)) From d41b370189507e933ea32057532d0d420b605119 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Tue, 1 Dec 2020 08:35:34 -0800 Subject: [PATCH 376/388] pynodegl: update .gitignore --- pynodegl/.gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pynodegl/.gitignore b/pynodegl/.gitignore index c580ee5016..f786ea8ed5 100644 --- a/pynodegl/.gitignore +++ b/pynodegl/.gitignore @@ -4,3 +4,5 @@ pynodegl.c pynodegl.egg-info pynodegl.*.so .eggs +*.pyd +*.dll From 25889f775ba90ac5eb67983fcac7e5c92645f156 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Tue, 1 Dec 2020 08:35:48 -0800 Subject: [PATCH 377/388] pynodegl: update setup script --- pynodegl/setup.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pynodegl/setup.py b/pynodegl/setup.py index d24af826b6..6237f2f448 100644 --- a/pynodegl/setup.py +++ b/pynodegl/setup.py @@ -36,13 +36,18 @@ def __init__(self, pkg_config_bin='pkg-config'): self.version = subprocess.check_output([pkg_config_bin, '--modversion', self.PKG_LIB_NAME]).strip().decode() self.data_root_dir = subprocess.check_output([pkg_config_bin, '--variable=datarootdir', self.PKG_LIB_NAME]).strip().decode() - pkgcfg_libs_cflags = subprocess.check_output([pkg_config_bin, '--libs', '--cflags', self.PKG_LIB_NAME]).decode() - - flags = pkgcfg_libs_cflags.split() - self.include_dirs = [f[2:] for f in flags if f.startswith('-I')] - self.library_dirs = [f[2:] for f in flags if f.startswith('-L')] - self.libraries = [f[2:] for f in flags if f.startswith('-l')] - + pkgcfg_cflags = subprocess.check_output([pkg_config_bin, '--cflags', self.PKG_LIB_NAME]).decode() + pkgcfg_libs = subprocess.check_output([pkg_config_bin, '--libs', self.PKG_LIB_NAME]).decode() + cflags = pkgcfg_cflags.split() + ldflags = pkgcfg_libs.split() + self.include_dirs = [f[2:] for f in cflags if f.startswith('-I')] + self.library_dirs = [f[2:] for f in ldflags if f.startswith('-L')] + self.libraries = [] + for f in ldflags: + if f.startswith('-l'): + self.libraries.append(f[2:]) + elif f.endswith('.lib'): + self.libraries.append(f[:-4]) _LIB_CFG = LibNodeGLConfig() From cc85944e36fc18f406d784ed314c4d4bfba24b75 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Wed, 25 Nov 2020 15:07:36 -0800 Subject: [PATCH 378/388] build: add extra log messages to show if each backend is enabled --- libnodegl/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libnodegl/meson.build b/libnodegl/meson.build index 76e83b306e..cecf63a1f8 100644 --- a/libnodegl/meson.build +++ b/libnodegl/meson.build @@ -344,7 +344,7 @@ foreach gbackend_name, gbackend_cfg : gbackends_cfg foreach dep : gbackend_deps all_dep_found = all_dep_found and dep.found() endforeach - + message('backend: ' + gbackend_name + ' enabled: ' + all_dep_found.to_string()) if all_dep_found if gbackend_name == 'gl' and vaapi_enabled From fbffa7d86e892a19a74c615defb1af3399fc3710 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Wed, 25 Nov 2020 15:20:40 -0800 Subject: [PATCH 379/388] build: use ninja backend for unit tests, and copy dlls to search paths. TODO: remove duplicate dlls --- Makefile | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index d8825f0e4d..207ab8e966 100644 --- a/Makefile +++ b/Makefile @@ -63,6 +63,7 @@ RPATH_LDFLAGS ?= -Wl,-rpath,$(PREFIX)/lib ifeq ($(TARGET_OS),Windows) MESON_SETUP = meson setup --backend vs --prefix="$(W_PREFIX)" --pkg-config-path=$(PREFIX)\\Lib\\pkgconfig -Drpath=true +MESON_SETUP_NINJA = meson setup --backend ninja --prefix="$(W_PREFIX)" --pkg-config-path=$(PREFIX)\\Lib\\pkgconfig -Drpath=true else MESON_SETUP = meson setup --prefix=$(PREFIX) --pkg-config-path=$(PREFIX)/lib/pkgconfig -Drpath=true endif @@ -164,9 +165,12 @@ endif pynodegl-install: pynodegl-deps-install ifeq ($(TARGET_OS),Windows) (PKG_CONFIG_PATH="$(W_PREFIX)\Lib\pkgconfig" WSLENV=PKG_CONFIG_PATH/w cmd.exe /C $(ACTIVATE) \&\& pip -v install -e .\\pynodegl) - #Copy DLLs to runtime search path + #Copy DLLs and EXEs to runtime search path. TODO: optimize + (cp external/win64/ffmpeg_x64-windows/bin/*.exe $(PREFIX)/Scripts/.) (cp external/win64/ffmpeg_x64-windows/bin/*.dll pynodegl/.) - (cp external/win64/pthreads_x64-windows/bin/*.dll pynodegl/.) + (cp external/win64/pthreads_x64-windows/dll/x64/*.dll pynodegl/.) + (cp external/win64/ffmpeg_x64-windows/bin/*.dll $(PREFIX)/Scripts/.) + (cp external/win64/pthreads_x64-windows/dll/x64/*.dll $(PREFIX)/Scripts/.) else (. $(ACTIVATE) && PKG_CONFIG_PATH=$(PREFIX)/lib/pkgconfig LDFLAGS=$(RPATH_LDFLAGS) pip -v install -e ./pynodegl) endif @@ -233,7 +237,7 @@ endif tests-setup: ngl-tools-install pynodegl-utils-install ifeq ($(TARGET_OS),Windows) - (cmd.exe /C $(ACTIVATE) \&\& $(MESON_SETUP) builddir\\tests tests) + (cmd.exe /C $(ACTIVATE) \&\& $(MESON_SETUP_NINJA) builddir\\tests tests) else (. $(ACTIVATE) && $(MESON_SETUP) builddir/tests tests) endif From 02ce625452f9a176815bb06cf387818f3c715a27 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Fri, 11 Dec 2020 14:36:11 -0800 Subject: [PATCH 380/388] build: build sxplayer shared lib --- Makefile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 207ab8e966..3a6889c24e 100644 --- a/Makefile +++ b/Makefile @@ -169,8 +169,10 @@ ifeq ($(TARGET_OS),Windows) (cp external/win64/ffmpeg_x64-windows/bin/*.exe $(PREFIX)/Scripts/.) (cp external/win64/ffmpeg_x64-windows/bin/*.dll pynodegl/.) (cp external/win64/pthreads_x64-windows/dll/x64/*.dll pynodegl/.) + (cp builddir/sxplayer/*.dll pynodegl/.) (cp external/win64/ffmpeg_x64-windows/bin/*.dll $(PREFIX)/Scripts/.) (cp external/win64/pthreads_x64-windows/dll/x64/*.dll $(PREFIX)/Scripts/.) + (cp builddir/sxplayer/*.dll $(PREFIX)/Scripts/.) else (. $(ACTIVATE) && PKG_CONFIG_PATH=$(PREFIX)/lib/pkgconfig LDFLAGS=$(RPATH_LDFLAGS) pip -v install -e ./pynodegl) endif @@ -200,9 +202,9 @@ endif sxplayer-install: external-download $(PREFIX) ifeq ($(TARGET_OS),Windows) - (cmd.exe /C $(ACTIVATE) \&\& $(MESON_SETUP) --default-library static external\\sxplayer builddir\\sxplayer \&\& $(MESON_COMPILE) -C builddir\\sxplayer \&\& $(MESON_INSTALL) -C builddir\\sxplayer) + (cmd.exe /C $(ACTIVATE) \&\& $(MESON_SETUP) --default-library shared external\\sxplayer builddir\\sxplayer \&\& $(MESON_COMPILE) -C builddir\\sxplayer \&\& $(MESON_INSTALL) -C builddir\\sxplayer) else - (. $(ACTIVATE) && $(MESON_SETUP) external/sxplayer builddir/sxplayer && $(MESON_COMPILE) -C builddir/sxplayer && $(MESON_INSTALL) -C builddir/sxplayer) + (. $(ACTIVATE) && $(MESON_SETUP) --default-library shared external/sxplayer builddir/sxplayer && $(MESON_COMPILE) -C builddir/sxplayer && $(MESON_INSTALL) -C builddir/sxplayer) endif external-download: From 4a048e84bd515a8e6a8e389fc6874077d8116684 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Tue, 1 Dec 2020 16:39:56 -0800 Subject: [PATCH 381/388] build: update nodegl build --- libnodegl/meson.build | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/libnodegl/meson.build b/libnodegl/meson.build index cecf63a1f8..c39a5bb3d8 100644 --- a/libnodegl/meson.build +++ b/libnodegl/meson.build @@ -373,14 +373,6 @@ lib_link_args += cc.get_supported_link_arguments([ '-Wl,-exported_symbols_list,@0@'.format(meson.current_source_dir() / 'libnodegl.darwin.symexport'), # Darwin ]) -lib_name_prefix = [] -lib_name_suffix = [] -if host_system == 'windows' - # Follow Windows library convention - lib_name_prefix = '' - lib_name_suffix = 'lib' -endif - libnodegl = library( 'nodegl', lib_src, @@ -390,8 +382,6 @@ libnodegl = library( version: meson.project_version(), link_args: lib_link_args, include_directories: lib_include_dirs, - name_prefix: lib_name_prefix, - name_suffix: lib_name_suffix ) lib_header = configure_file( input: files('nodegl.h.in'), From 3ea91c98782531e42b2d462e4b291a670f4830ae Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Tue, 1 Dec 2020 16:40:14 -0800 Subject: [PATCH 382/388] build: rework public symbol exporting logic With MSVC the symbols are hidden by default, so we need a mechanism similar to what we do with GNU and Apple linkers to export them. The simplest way to achieve that is to use some ifdefery to set some specific import/export attributes. On Windows, this implies that users will be required to set a special compilation flag for their static build. To make this transparent for the users, the specific flag is exposed through pkg-config if the build is configured for static. pkg-config is arguably not well supported on Windows, but this is better than nothing. For DLL users, nothing specific is required since it's the preferred and default build configuration. As a side effect, the linker scripts for GNU and Apple are not required anymore. The meson gnu_symbol_visibility option also works with Apple Clang/LLVM toolchain. --- Makefile | 5 +++- libnodegl/meson.build | 29 ++++++++++++------- libnodegl/nodegl.h.in | 66 +++++++++++++++++++++++++------------------ 3 files changed, 62 insertions(+), 38 deletions(-) diff --git a/Makefile b/Makefile index 3a6889c24e..fd9ba0d177 100644 --- a/Makefile +++ b/Makefile @@ -170,9 +170,11 @@ ifeq ($(TARGET_OS),Windows) (cp external/win64/ffmpeg_x64-windows/bin/*.dll pynodegl/.) (cp external/win64/pthreads_x64-windows/dll/x64/*.dll pynodegl/.) (cp builddir/sxplayer/*.dll pynodegl/.) + (cp builddir/libnodegl/*.dll pynodegl/.) (cp external/win64/ffmpeg_x64-windows/bin/*.dll $(PREFIX)/Scripts/.) (cp external/win64/pthreads_x64-windows/dll/x64/*.dll $(PREFIX)/Scripts/.) (cp builddir/sxplayer/*.dll $(PREFIX)/Scripts/.) + (cp builddir/libnodegl/*.dll $(PREFIX)/Scripts/.) else (. $(ACTIVATE) && PKG_CONFIG_PATH=$(PREFIX)/lib/pkgconfig LDFLAGS=$(RPATH_LDFLAGS) pip -v install -e ./pynodegl) endif @@ -195,7 +197,7 @@ endif nodegl-setup: sxplayer-install ifeq ($(TARGET_OS),Windows) - (cmd.exe /C $(ACTIVATE) \&\& $(MESON_SETUP) $(NODEGL_DEBUG_OPTS) --default-library static libnodegl builddir\\libnodegl) + (cmd.exe /C $(ACTIVATE) \&\& $(MESON_SETUP) $(NODEGL_DEBUG_OPTS) --default-library shared libnodegl builddir\\libnodegl) else (. $(ACTIVATE) && $(MESON_SETUP) $(NODEGL_DEBUG_OPTS) libnodegl builddir/libnodegl) endif @@ -222,6 +224,7 @@ ifeq ($(TARGET_OS),Windows) (cmd.exe /C mkdir $(PREFIX)\\Lib\\pkgconfig) (cmd.exe /C pushd external\\win64\\ffmpeg_x64-windows \&\& python.exe scripts/install.py "$(W_PREFIX)" \& popd) (cmd.exe /C pushd external\\win64\\pthreads_x64-windows \&\& python.exe scripts/install.py "$(W_PREFIX)" \& popd) + (cmd.exe /C pushd external\\win64\\sdl2_x64-windows \&\& python.exe scripts/install.py "$(W_PREFIX)" \& popd) (cmd.exe /C $(ACTIVATE) \&\& pip install meson ninja) else ifeq ($(TARGET_OS),MinGW-w64) $(PYTHON) -m venv --system-site-packages $(PREFIX) diff --git a/libnodegl/meson.build b/libnodegl/meson.build index c39a5bb3d8..8fe14068a0 100644 --- a/libnodegl/meson.build +++ b/libnodegl/meson.build @@ -62,8 +62,12 @@ conf_data.set10('DEBUG_GL', 'gl' in debug_opts) conf_data.set10('DEBUG_MEM', 'mem' in debug_opts) conf_data.set10('DEBUG_SCENE', 'scene' in debug_opts) -if host_system == 'windows' and cc.get_id() != 'msvc' - conf_data.set10('TARGET_MINGW_W64', true) +if host_system == 'windows' + if cc.get_id() == 'msvc' + conf_data.set10('TARGET_MSVC', true) + else + conf_data.set10('TARGET_MINGW_W64', true) + endif endif # This trim prefix is used to make __FILE__ starts from the source dir with @@ -200,6 +204,7 @@ lib_deps = [ dependency('threads'), ] +lib_c_args = [] lib_include_dirs = [] lib_link_args = [] @@ -366,12 +371,10 @@ endforeach configure_file(output: 'config.h', configuration: conf_data) -# See https://github.com/mesonbuild/meson/pull/4747 to follow advancement on -# the native support for symbol visibility -lib_link_args += cc.get_supported_link_arguments([ - '-Wl,--version-script,@0@'.format(meson.current_source_dir() / 'libnodegl.symexport'), # GNU ld - '-Wl,-exported_symbols_list,@0@'.format(meson.current_source_dir() / 'libnodegl.darwin.symexport'), # Darwin -]) + +if get_option('default_library') == 'shared' + lib_c_args += '-DBUILD_NODEGL_SHARED_LIB' +endif libnodegl = library( 'nodegl', @@ -380,8 +383,8 @@ libnodegl = library( install: true, install_rpath: install_rpath, version: meson.project_version(), - link_args: lib_link_args, - include_directories: lib_include_dirs, + c_args: lib_c_args, + gnu_symbol_visibility: 'hidden', ) lib_header = configure_file( input: files('nodegl.h.in'), @@ -391,10 +394,16 @@ lib_header = configure_file( install_headers(lib_header) pkg = import('pkgconfig') +pkg_extra_cflags = [] +if get_option('default_library') == 'static' + pkg_extra_cflags += '-DUSE_NODEGL_STATIC_LIB' +endif + pkg.generate( libnodegl, name: 'libnodegl', # not specifying the name would fallback on "nodegl.pc" instead of "libnodegl.pc" description: 'Node/Graph based OpenGL engine', + extra_cflags: pkg_extra_cflags, variables: ['datarootdir=${prefix}/share'], ) diff --git a/libnodegl/nodegl.h.in b/libnodegl/nodegl.h.in index 417be12829..f361ef781f 100644 --- a/libnodegl/nodegl.h.in +++ b/libnodegl/nodegl.h.in @@ -32,6 +32,18 @@ NODEGL_VERSION_MINOR, \ NODEGL_VERSION_MICRO) +#if defined(_WIN32) +# if defined(BUILD_NODEGL_SHARED_LIB) +# define NGL_API __declspec(dllexport) /* On Windows, we need to export the symbols while building */ +# elif defined(USE_NODEGL_STATIC_LIB) /* Static library users don't need anything special */ +# define NGL_API +# else +# define NGL_API __declspec(dllimport) /* Dynamic library users (default) need a DLL import spec */ +# endif +#else +# define NGL_API __attribute__((visibility("default"))) /* This cancel the hidden GNU symbol visibility attribute */ +#endif + #include #include @@ -68,7 +80,7 @@ typedef void (*ngl_log_callback_type)(void *arg, int level, const char *filename * (typically a user context) * @param callback callback function to be called when logging a message */ -void ngl_log_set_callback(void *arg, ngl_log_callback_type callback); +NGL_API void ngl_log_set_callback(void *arg, ngl_log_callback_type callback); /** * Set the minimum global logging level. @@ -78,7 +90,7 @@ void ngl_log_set_callback(void *arg, ngl_log_callback_type callback); * * @param level log level (any of NGL_LOG_*) */ -void ngl_log_set_min_level(int level); +NGL_API void ngl_log_set_min_level(int level); /** * Opaque structure identifying a node @@ -255,7 +267,7 @@ struct ngl_node; * * @return a new allocated node or NULL on error */ -struct ngl_node *ngl_node_create(int type); +NGL_API struct ngl_node *ngl_node_create(int type); /** * Increment the reference counter of a given node by 1. @@ -268,7 +280,7 @@ struct ngl_node *ngl_node_create(int type); * * @return node with its reference counter incremented. */ -struct ngl_node *ngl_node_ref(struct ngl_node *node); +NGL_API struct ngl_node *ngl_node_ref(struct ngl_node *node); /** * Decrement the reference counter of a given node by 1, and destroy its @@ -277,7 +289,7 @@ struct ngl_node *ngl_node_ref(struct ngl_node *node); * * @param nodep pointer to the pointer to the target node */ -void ngl_node_unrefp(struct ngl_node **nodep); +NGL_API void ngl_node_unrefp(struct ngl_node **nodep); /** * Add entries to a list-based parameter of an allocated node. @@ -292,7 +304,7 @@ void ngl_node_unrefp(struct ngl_node **nodep); * * @return 0 on success, NGL_ERROR_* (< 0) on error */ -int ngl_node_param_add(struct ngl_node *node, const char *key, +NGL_API int ngl_node_param_add(struct ngl_node *node, const char *key, int nb_elems, void *elems); /** @@ -307,7 +319,7 @@ int ngl_node_param_add(struct ngl_node *node, const char *key, * * @return 0 on success, NGL_ERROR_* (< 0) on error */ -int ngl_node_param_set(struct ngl_node *node, const char *key, ...); +NGL_API int ngl_node_param_set(struct ngl_node *node, const char *key, ...); /** * Serialize in Graphviz format (.dot) a node graph. @@ -318,7 +330,7 @@ int ngl_node_param_set(struct ngl_node *node, const char *key, ...); * * @see ngl_dot() */ -char *ngl_node_dot(const struct ngl_node *node); +NGL_API char *ngl_node_dot(const struct ngl_node *node); /** * Serialize in node.gl format (.ngl). @@ -327,7 +339,7 @@ char *ngl_node_dot(const struct ngl_node *node); * * @return an allocated string in node.gl format or NULL on error */ -char *ngl_node_serialize(const struct ngl_node *node); +NGL_API char *ngl_node_serialize(const struct ngl_node *node); /** * De-serialize a scene. @@ -338,7 +350,7 @@ char *ngl_node_serialize(const struct ngl_node *node); * * @return a pointer to the de-serialized node graph or NULL on error */ -struct ngl_node *ngl_node_deserialize(const char *s); +NGL_API struct ngl_node *ngl_node_deserialize(const char *s); /** * Platform-specific identifiers @@ -477,9 +489,9 @@ struct ngl_backend { * * @return 0 on success, NGL_ERROR_* (< 0) on error */ -int ngl_backends_probe(const struct ngl_config *user_config, int *nb_backendsp, struct ngl_backend **backendsp); +NGL_API int ngl_backends_probe(const struct ngl_config *user_config, int *nb_backendsp, struct ngl_backend **backendsp); -void ngl_backends_freep(struct ngl_backend **backendsp); +NGL_API void ngl_backends_freep(struct ngl_backend **backendsp); /** * Opaque structure identifying a node.gl context @@ -495,7 +507,7 @@ struct ngl_ctx; * * @return a pointer to the context, or NULL on error */ -struct ngl_ctx *ngl_create(void); +NGL_API struct ngl_ctx *ngl_create(void); /** * Configure the node.gl context. @@ -514,7 +526,7 @@ struct ngl_ctx *ngl_create(void); * * @return 0 on success, NGL_ERROR_* (< 0) on error */ -int ngl_configure(struct ngl_ctx *s, struct ngl_config *config); +NGL_API int ngl_configure(struct ngl_ctx *s, struct ngl_config *config); /** * Update the swap chain buffers size. @@ -528,7 +540,7 @@ int ngl_configure(struct ngl_ctx *s, struct ngl_config *config); * dimensions of the swap chain buffers * @return 0 on success, NGL_ERROR_* (< 0) on error */ -int ngl_resize(struct ngl_ctx *s, int width, int height, const int *viewport); +NGL_API int ngl_resize(struct ngl_ctx *s, int width, int height, const int *viewport); /** * Set a new buffer for offscreen capture @@ -543,7 +555,7 @@ int ngl_resize(struct ngl_ctx *s, int width, int height, const int *viewport); * * @return 0 on success, NGL_ERROR_* (< 0) on error */ -int ngl_set_capture_buffer(struct ngl_ctx *s, void *capture_buffer); +NGL_API int ngl_set_capture_buffer(struct ngl_ctx *s, void *capture_buffer); /** * Associate a scene with a node.gl context. @@ -565,7 +577,7 @@ int ngl_set_capture_buffer(struct ngl_ctx *s, void *capture_buffer); * * @return 0 on success, NGL_ERROR_* (< 0) on error */ -int ngl_set_scene(struct ngl_ctx *s, struct ngl_node *scene); +NGL_API int ngl_set_scene(struct ngl_ctx *s, struct ngl_node *scene); /** * Draw at the specified time. @@ -577,7 +589,7 @@ int ngl_set_scene(struct ngl_ctx *s, struct ngl_node *scene); * * @return 0 on success, NGL_ERROR_* (< 0) on error */ -int ngl_draw(struct ngl_ctx *s, double t); +NGL_API int ngl_draw(struct ngl_ctx *s, double t); /** * Serialize the current scene in Graphviz format (.dot) a node graph at the @@ -589,7 +601,7 @@ int ngl_draw(struct ngl_ctx *s, double t); * * @see ngl_node_dot() */ -char *ngl_dot(struct ngl_ctx *s, double t); +NGL_API char *ngl_dot(struct ngl_ctx *s, double t); /** * Destroy a node.gl context. The passed context pointer will also be set to @@ -597,7 +609,7 @@ char *ngl_dot(struct ngl_ctx *s, double t); * * @param ss pointer to the pointer to the node.gl context */ -void ngl_freep(struct ngl_ctx **ss); +NGL_API void ngl_freep(struct ngl_ctx **ss); /** * Evaluate an animation at a given time t. @@ -613,7 +625,7 @@ void ngl_freep(struct ngl_ctx **ss); * * @return 0 on success, NGL_ERROR_* (< 0) on error */ -int ngl_anim_evaluate(struct ngl_node *anim, void *dst, double t); +NGL_API int ngl_anim_evaluate(struct ngl_node *anim, void *dst, double t); /** * Evaluate an easing at a given time t @@ -627,7 +639,7 @@ int ngl_anim_evaluate(struct ngl_node *anim, void *dst, double t); * * @return 0 on success, NGL_ERROR_* (< 0) on error */ -int ngl_easing_evaluate(const char *name, double *args, int nb_args, +NGL_API int ngl_easing_evaluate(const char *name, double *args, int nb_args, double *offsets, double t, double *v); /** @@ -644,7 +656,7 @@ int ngl_easing_evaluate(const char *name, double *args, int nb_args, * * @return 0 on success, NGL_ERROR_* (< 0) on error */ -int ngl_easing_solve(const char *name, double *args, int nb_args, +NGL_API int ngl_easing_solve(const char *name, double *args, int nb_args, double *offsets, double v, double *t); /** @@ -659,7 +671,7 @@ int ngl_easing_solve(const char *name, double *args, int nb_args, * * @return 0 on success, NGL_ERROR_* (< 0) on error */ -int ngl_jni_set_java_vm(void *vm); +NGL_API int ngl_jni_set_java_vm(void *vm); /** * Get the Java virtual machine pointer that has been set with @@ -667,7 +679,7 @@ int ngl_jni_set_java_vm(void *vm); * * @return a pointer to the Java virtual machine or NULL if none has been set */ -void *ngl_jni_get_java_vm(void); +NGL_API void *ngl_jni_get_java_vm(void); /** * Set the Android application context. @@ -675,7 +687,7 @@ void *ngl_jni_get_java_vm(void); * @param application_context JNI global reference of the Android application * context */ -int ngl_android_set_application_context(void *application_context); +NGL_API int ngl_android_set_application_context(void *application_context); /** * Get the Android application context that has been set with @@ -684,6 +696,6 @@ int ngl_android_set_application_context(void *application_context); * @return a pointer to the JNI global reference of the Android application * context or NULL if none has been set */ -void *ngl_android_get_application_context(void); +NGL_API void *ngl_android_get_application_context(void); #endif From 8d9871bb9708b3476cc656a0a4dc74350f4ec798 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Wed, 9 Dec 2020 17:04:24 -0800 Subject: [PATCH 383/388] disable automatic line ending conversion --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..625449502b --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* -text From c473d780905ad6ee00ac6b9d8662ede8e63fbc37 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Fri, 11 Dec 2020 16:27:02 -0800 Subject: [PATCH 384/388] build: misc improvements to Makefile Don't hardcode python.exe Update wslpath command --- Makefile | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index fd9ba0d177..0780ee7a0f 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ TARGET_OS ?= $(shell uname -s) ifeq ($(TARGET_OS),Windows) PYTHON ?= python.exe PREFIX ?= nodegl-env -W_PWD = $(shell wslpath -w .) +W_PWD = $(shell wslpath -wa .) W_PREFIX ?= $(W_PWD)\$(PREFIX) $(info PYTHON: $(PYTHON)) $(info PREFIX: $(PREFIX)) @@ -47,13 +47,19 @@ DEBUG_SCENE ?= no TESTS_SUITE ?= V ?= -ifneq ($(shell $(PYTHON) -c "import sys;print(sys.version_info.major)"),$(PYTHON_MAJOR)) -$(error "Python $(PYTHON_MAJOR) not found") +ifeq ($(TARGET_OS), Windows) + ifneq ($(shell cmd.exe /C $(PYTHON) -c "import sys;print(sys.version_info.major)"),$(PYTHON_MAJOR)) + $(error "Python $(PYTHON_MAJOR) not found") + endif +else + ifneq ($(shell $(PYTHON) -c "import sys;print(sys.version_info.major)"),$(PYTHON_MAJOR)) + $(error "Python $(PYTHON_MAJOR) not found") + endif endif ifeq ($(TARGET_OS), Windows) #TODO: identify correct path -VCVARS64 = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat" +VCVARS64 ?= "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat" ACTIVATE = $(VCVARS64) \&\& $(PREFIX)\\Scripts\\activate.bat else ACTIVATE = $(PREFIX)/bin/activate @@ -219,12 +225,12 @@ external-download: $(PREFIX): ifeq ($(TARGET_OS),Windows) (cd external && bash scripts/sync.sh win64) - $(PYTHON) -m venv $(PREFIX) + (cmd.exe /C $(PYTHON) -m venv $(PREFIX)) (cmd.exe /C copy external\\win64\\pkg-config.exe nodegl-env\\Scripts) (cmd.exe /C mkdir $(PREFIX)\\Lib\\pkgconfig) - (cmd.exe /C pushd external\\win64\\ffmpeg_x64-windows \&\& python.exe scripts/install.py "$(W_PREFIX)" \& popd) - (cmd.exe /C pushd external\\win64\\pthreads_x64-windows \&\& python.exe scripts/install.py "$(W_PREFIX)" \& popd) - (cmd.exe /C pushd external\\win64\\sdl2_x64-windows \&\& python.exe scripts/install.py "$(W_PREFIX)" \& popd) + (cmd.exe /C pushd external\\win64\\ffmpeg_x64-windows \&\& $(PYTHON) scripts/install.py "$(W_PREFIX)" \& popd) + (cmd.exe /C pushd external\\win64\\pthreads_x64-windows \&\& $(PYTHON) scripts/install.py "$(W_PREFIX)" \& popd) + (cmd.exe /C pushd external\\win64\\sdl2_x64-windows \&\& $(PYTHON) scripts/install.py "$(W_PREFIX)" \& popd) (cmd.exe /C $(ACTIVATE) \&\& pip install meson ninja) else ifeq ($(TARGET_OS),MinGW-w64) $(PYTHON) -m venv --system-site-packages $(PREFIX) From 2e9487674d08d3e021e5bbf0cc77538be8d06e52 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Fri, 11 Dec 2020 16:12:57 -0800 Subject: [PATCH 385/388] log: bugfix. config.h needs to be included before using config macros such as TARGET_WINDOWS --- libnodegl/log.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libnodegl/log.c b/libnodegl/log.c index 4b58e89611..ab55519530 100644 --- a/libnodegl/log.c +++ b/libnodegl/log.c @@ -22,11 +22,11 @@ #include #include #include +#include "config.h" #if !defined(TARGET_IPHONE) && !defined(TARGET_ANDROID) && !defined(TARGET_WINDOWS) #include #endif -#include "config.h" #include "log.h" static void default_callback(void *arg, int level, const char *filename, int ln, From d92a8798415541b15618c51443f6327711a8c12b Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Fri, 11 Dec 2020 17:12:30 -0800 Subject: [PATCH 386/388] utils: bugfix. Windows.h needs to be included first. Resolves compile error "No target architecture" --- libnodegl/utils.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/libnodegl/utils.c b/libnodegl/utils.c index ba6482fa14..cd620faa9d 100644 --- a/libnodegl/utils.c +++ b/libnodegl/utils.c @@ -23,11 +23,14 @@ #include #ifdef _WIN32 +#define POW10_9 1000000000 +#include #include #include #else #include #include +#include #include #include #endif @@ -37,12 +40,6 @@ #include #include #include -#ifdef _WIN32 -#define POW10_9 1000000000 -#include -#else -#include -#endif #include "log.h" #include "memory.h" From 27275cb82fdca11304d3cef6efa1e5e577f43f3a Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Sun, 13 Dec 2020 13:12:27 -0800 Subject: [PATCH 387/388] build: remove external sync script. Build external packages using vcpkg. --- Makefile | 96 +++++++++++++++++++++++-------------------- libnodegl/meson.build | 1 - 2 files changed, 52 insertions(+), 45 deletions(-) diff --git a/Makefile b/Makefile index 0780ee7a0f..5bfc5b1af7 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,18 @@ TESTS_SUITE ?= V ?= ifeq ($(TARGET_OS), Windows) - ifneq ($(shell cmd.exe /C $(PYTHON) -c "import sys;print(sys.version_info.major)"),$(PYTHON_MAJOR)) +VCVARS64 ?= "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat" +ACTIVATE = $(VCVARS64) \&\& $(PREFIX)\\Scripts\\activate.bat +VCPKG_DIR ?= C:\\vcpkg +PKG_CONFIG ?= C:\\msys64\\usr\\bin\\pkg-config.exe +SET_PKG_CONFIG_PATH = PKG_CONFIG_PATH="$(W_PREFIX)\Lib\pkgconfig" WSLENV=PKG_CONFIG_PATH/w +CMD = $(SET_PKG_CONFIG_PATH) cmd.exe /C +else +ACTIVATE = $(PREFIX)/bin/activate +endif + +ifeq ($(TARGET_OS), Windows) + ifneq ($(shell $(CMD) $(PYTHON) -c "import sys;print(sys.version_info.major)"),$(PYTHON_MAJOR)) $(error "Python $(PYTHON_MAJOR) not found") endif else @@ -57,19 +68,11 @@ else endif endif -ifeq ($(TARGET_OS), Windows) -#TODO: identify correct path -VCVARS64 ?= "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat" -ACTIVATE = $(VCVARS64) \&\& $(PREFIX)\\Scripts\\activate.bat -else -ACTIVATE = $(PREFIX)/bin/activate -endif - RPATH_LDFLAGS ?= -Wl,-rpath,$(PREFIX)/lib ifeq ($(TARGET_OS),Windows) -MESON_SETUP = meson setup --backend vs --prefix="$(W_PREFIX)" --pkg-config-path=$(PREFIX)\\Lib\\pkgconfig -Drpath=true -MESON_SETUP_NINJA = meson setup --backend ninja --prefix="$(W_PREFIX)" --pkg-config-path=$(PREFIX)\\Lib\\pkgconfig -Drpath=true +MESON_SETUP = meson setup --backend vs --prefix="$(W_PREFIX)" -Drpath=true +MESON_SETUP_NINJA = meson setup --backend ninja --prefix="$(W_PREFIX)" -Drpath=true else MESON_SETUP = meson setup --prefix=$(PREFIX) --pkg-config-path=$(PREFIX)/lib/pkgconfig -Drpath=true endif @@ -129,14 +132,14 @@ endif ngl-tools-install: nodegl-install ifeq ($(TARGET_OS),Windows) - (cmd.exe /C $(ACTIVATE) \&\& $(MESON_SETUP) ngl-tools builddir\\ngl-tools \&\& $(MESON_COMPILE) -C builddir\\ngl-tools \&\& $(MESON_INSTALL) -C builddir\\ngl-tools) + ($(CMD) $(ACTIVATE) \&\& $(MESON_SETUP) ngl-tools builddir\\ngl-tools \&\& $(MESON_COMPILE) -C builddir\\ngl-tools \&\& $(MESON_INSTALL) -C builddir\\ngl-tools) else (. $(ACTIVATE) && $(MESON_SETUP) ngl-tools builddir/ngl-tools && $(MESON_COMPILE) -C builddir/ngl-tools && $(MESON_INSTALL) -C builddir/ngl-tools) endif pynodegl-utils-install: pynodegl-utils-deps-install ifeq ($(TARGET_OS),Windows) - (cmd.exe /C $(ACTIVATE) \&\& pip -v install -e pynodegl-utils) + ($(CMD) $(ACTIVATE) \&\& pip -v install -e pynodegl-utils) else (. $(ACTIVATE) && pip -v install -e ./pynodegl-utils) endif @@ -163,38 +166,33 @@ endif # pynodegl-utils-deps-install: pynodegl-install ifeq ($(TARGET_OS),Windows) - (cmd.exe /C $(ACTIVATE) \&\& pip install -r pynodegl-utils\\requirements.txt) + ($(CMD) $(ACTIVATE) \&\& pip install -r pynodegl-utils\\requirements.txt) else ifneq ($(TARGET_OS),MinGW-w64) (. $(ACTIVATE) && pip install -r ./pynodegl-utils/requirements.txt) endif pynodegl-install: pynodegl-deps-install ifeq ($(TARGET_OS),Windows) - (PKG_CONFIG_PATH="$(W_PREFIX)\Lib\pkgconfig" WSLENV=PKG_CONFIG_PATH/w cmd.exe /C $(ACTIVATE) \&\& pip -v install -e .\\pynodegl) - #Copy DLLs and EXEs to runtime search path. TODO: optimize - (cp external/win64/ffmpeg_x64-windows/bin/*.exe $(PREFIX)/Scripts/.) - (cp external/win64/ffmpeg_x64-windows/bin/*.dll pynodegl/.) - (cp external/win64/pthreads_x64-windows/dll/x64/*.dll pynodegl/.) - (cp builddir/sxplayer/*.dll pynodegl/.) - (cp builddir/libnodegl/*.dll pynodegl/.) - (cp external/win64/ffmpeg_x64-windows/bin/*.dll $(PREFIX)/Scripts/.) - (cp external/win64/pthreads_x64-windows/dll/x64/*.dll $(PREFIX)/Scripts/.) - (cp builddir/sxplayer/*.dll $(PREFIX)/Scripts/.) - (cp builddir/libnodegl/*.dll $(PREFIX)/Scripts/.) + ($(CMD) $(ACTIVATE) \&\& pip -v install -e .\\pynodegl) + #Copy DLLs to prefix + ($(CMD) copy builddir\\sxplayer\\*.dll pynodegl\\.) + ($(CMD) copy builddir\\libnodegl\\*.dll pynodegl\\.) + ($(CMD) copy builddir\\sxplayer\\*.dll $(PREFIX)\\Scripts\\.) + ($(CMD) copy builddir\\libnodegl\\*.dll $(PREFIX)\\Scripts\\.) else (. $(ACTIVATE) && PKG_CONFIG_PATH=$(PREFIX)/lib/pkgconfig LDFLAGS=$(RPATH_LDFLAGS) pip -v install -e ./pynodegl) endif pynodegl-deps-install: $(PREFIX) nodegl-install ifeq ($(TARGET_OS),Windows) - (cmd.exe /C $(ACTIVATE) \&\& pip install -r pynodegl\\requirements.txt) + ($(CMD) $(ACTIVATE) \&\& pip install -r pynodegl\\requirements.txt) else (. $(ACTIVATE) && pip install -r ./pynodegl/requirements.txt) endif nodegl-install: nodegl-setup ifeq ($(TARGET_OS),Windows) - (cmd.exe /C $(ACTIVATE) \&\& $(MESON_COMPILE) -C builddir\\libnodegl \&\& $(MESON_INSTALL) -C builddir\\libnodegl) + ($(CMD) $(ACTIVATE) \&\& $(MESON_COMPILE) -C builddir\\libnodegl \&\& $(MESON_INSTALL) -C builddir\\libnodegl) # patch libnodegl.pc TODO: remove sed -i -e 's/Libs.private: .*/Libs.private: OpenGL32.lib gdi32.lib/' nodegl-env/Lib/pkgconfig/libnodegl.pc else @@ -203,14 +201,14 @@ endif nodegl-setup: sxplayer-install ifeq ($(TARGET_OS),Windows) - (cmd.exe /C $(ACTIVATE) \&\& $(MESON_SETUP) $(NODEGL_DEBUG_OPTS) --default-library shared libnodegl builddir\\libnodegl) + ($(CMD) $(ACTIVATE) \&\& $(MESON_SETUP) $(NODEGL_DEBUG_OPTS) --default-library shared libnodegl builddir\\libnodegl) else (. $(ACTIVATE) && $(MESON_SETUP) $(NODEGL_DEBUG_OPTS) libnodegl builddir/libnodegl) endif sxplayer-install: external-download $(PREFIX) ifeq ($(TARGET_OS),Windows) - (cmd.exe /C $(ACTIVATE) \&\& $(MESON_SETUP) --default-library shared external\\sxplayer builddir\\sxplayer \&\& $(MESON_COMPILE) -C builddir\\sxplayer \&\& $(MESON_INSTALL) -C builddir\\sxplayer) + ($(CMD) $(ACTIVATE) \&\& $(MESON_SETUP) --default-library shared external\\sxplayer builddir\\sxplayer \&\& $(MESON_COMPILE) -C builddir\\sxplayer \&\& $(MESON_INSTALL) -C builddir\\sxplayer) else (. $(ACTIVATE) && $(MESON_SETUP) --default-library shared external/sxplayer builddir/sxplayer && $(MESON_COMPILE) -C builddir/sxplayer && $(MESON_INSTALL) -C builddir/sxplayer) endif @@ -218,21 +216,31 @@ endif external-download: $(MAKE) -C external -# -# We do not pull meson from pip on Windows for the same reasons we don't pull -# Pillow and PySide2. We require the users to have it on their system. -# $(PREFIX): ifeq ($(TARGET_OS),Windows) - (cd external && bash scripts/sync.sh win64) - (cmd.exe /C $(PYTHON) -m venv $(PREFIX)) - (cmd.exe /C copy external\\win64\\pkg-config.exe nodegl-env\\Scripts) - (cmd.exe /C mkdir $(PREFIX)\\Lib\\pkgconfig) - (cmd.exe /C pushd external\\win64\\ffmpeg_x64-windows \&\& $(PYTHON) scripts/install.py "$(W_PREFIX)" \& popd) - (cmd.exe /C pushd external\\win64\\pthreads_x64-windows \&\& $(PYTHON) scripts/install.py "$(W_PREFIX)" \& popd) - (cmd.exe /C pushd external\\win64\\sdl2_x64-windows \&\& $(PYTHON) scripts/install.py "$(W_PREFIX)" \& popd) - (cmd.exe /C $(ACTIVATE) \&\& pip install meson ninja) + ($(CMD) $(PYTHON) -m venv $(PREFIX)) + ($(CMD) copy $(PKG_CONFIG) nodegl-env\\Scripts) + ($(CMD) copy $(VCPKG_DIR)\\packages\\ffmpeg_x64-windows\\tools\\ffmpeg\\*.exe $(PREFIX)\\Scripts\\.) + ($(CMD) copy $(VCPKG_DIR)\\packages\\ffmpeg_x64-windows\\bin\\*.dll pynodegl\\.) + ($(CMD) copy $(VCPKG_DIR)\\packages\\pthreads_x64-windows\\bin\\*.dll pynodegl\\.) + ($(CMD) copy $(VCPKG_DIR)\\packages\\ffmpeg_x64-windows\\bin\\*.dll $(PREFIX)\\Scripts\\.) + ($(CMD) copy $(VCPKG_DIR)\\packages\\pthreads_x64-windows\\bin\\*.dll $(PREFIX)\\Scripts\\.) + ($(CMD) mkdir $(PREFIX)\\Lib\\pkgconfig) + ($(CMD) copy ${VCPKG_DIR}\\packages\\ffmpeg_x64-windows\\lib\\pkgconfig\\*.pc $(PREFIX)\\Lib\\pkgconfig\\.) + #patch ffmpeg pkg-config files + (sed -i -e 's/\/cygdrive\/c/C:/g' $(PREFIX)/Lib/pkgconfig/*.pc) + (sed -i -e 's/\/cygdrive\/d/D:/g' $(PREFIX)/Lib/pkgconfig/*.pc) + ($(CMD) copy ${VCPKG_DIR}\\packages\\sdl2_x64-windows\\lib\\pkgconfig\\*.pc $(PREFIX)\\Lib\\pkgconfig\\.) + #patch SDL2 pkg-config file + (sed -i -e 's/prefix=.*/prefix=$(VCPKG_DIR)\/packages\/sdl2_x64-windows/' $(PREFIX)/Lib/pkgconfig/sdl2.pc) + (sed -i -e 's/Libs: .*/Libs: -L\${libdir} -lSDL2 -L\${libdir}/manual-link -lSDL2main/' $(PREFIX)/Lib/pkgconfig/sdl2.pc) + (sed -i -e 's/\\/\//g' $(PREFIX)/Lib/pkgconfig/sdl2.pc) + ($(CMD) $(ACTIVATE) \&\& pip install meson ninja) else ifeq ($(TARGET_OS),MinGW-w64) + # + # We do not pull meson from pip on mingw for the same reasons we don't pull + # Pillow and PySide2. We require the users to have it on their system. + # $(PYTHON) -m venv --system-site-packages $(PREFIX) else $(PYTHON) -m venv $(PREFIX) @@ -241,21 +249,21 @@ endif tests: nodegl-tests tests-setup ifeq ($(TARGET_OS),Windows) - (cmd.exe /C $(ACTIVATE) \&\& meson test $(MESON_TESTS_SUITE_OPTS) -C builddir\\tests) + ($(CMD) $(ACTIVATE) \&\& meson test $(MESON_TESTS_SUITE_OPTS) -C builddir\\tests) else (. $(ACTIVATE) && meson test $(MESON_TESTS_SUITE_OPTS) -C builddir/tests) endif tests-setup: ngl-tools-install pynodegl-utils-install ifeq ($(TARGET_OS),Windows) - (cmd.exe /C $(ACTIVATE) \&\& $(MESON_SETUP_NINJA) builddir\\tests tests) + ($(CMD) $(ACTIVATE) \&\& $(MESON_SETUP_NINJA) builddir\\tests tests) else (. $(ACTIVATE) && $(MESON_SETUP) builddir/tests tests) endif nodegl-tests: nodegl-install ifeq ($(TARGET_OS),Windows) - (cmd.exe /C $(ACTIVATE) \&\& meson test -C builddir\\libnodegl) + ($(CMD) $(ACTIVATE) \&\& meson test -C builddir\\libnodegl) else (. $(ACTIVATE) && meson test -C builddir/libnodegl) endif diff --git a/libnodegl/meson.build b/libnodegl/meson.build index 8fe14068a0..2544de7265 100644 --- a/libnodegl/meson.build +++ b/libnodegl/meson.build @@ -322,7 +322,6 @@ if host_system == 'windows' and cc.get_id() == 'msvc' windows_sdk_library_directories = [ windows_sdk_dir + '\\Lib\\10.0.18362.0\\um\\x64' ] cc_lib_dirs += windows_sdk_library_directories - lib_include_dirs += include_directories('..\\external\\win64\\gl\\include') endif From 89b3504bb0d91ee94a421d0fad8b45ba3c403c02 Mon Sep 17 00:00:00 2001 From: Jeff Moguillansky Date: Mon, 4 Jan 2021 10:55:24 -0800 Subject: [PATCH 388/388] ci: add windows/msvc build/run + debug --- .github/workflows/ci_win.yml | 66 ++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/.github/workflows/ci_win.yml b/.github/workflows/ci_win.yml index c753aa3355..2bf22da23f 100644 --- a/.github/workflows/ci_win.yml +++ b/.github/workflows/ci_win.yml @@ -30,3 +30,69 @@ jobs: run: | $env:CHERE_INVOKING = 'yes' # Preserve the current working directory C:\msys64\usr\bin\bash -lc "make -j$(($(nproc)+1)) TARGET_OS=MinGW-w64" + + win-msvc: + + runs-on: windows-latest + + defaults: + run: + shell: wsl-bash {0} + + steps: + - uses: Vampire/setup-wsl@v1 + with: + distribution: Ubuntu-20.04 + + - uses: actions/checkout@v2 + + - name: Install dependencies + run: | + sudo apt -y update + sudo apt -y install build-essential zip + pip.exe install meson + wget https://github.com/jmoguillansky-gpsw/gopro-pkg/blob/master/external/win64/pkg-config.exe?raw=true -O pkg-config.exe + + - name: Restore from cache and install vcpkg + uses: lukka/run-vcpkg@v6 + with: + vcpkgGitCommitId: 595777db2332a3442b73f9af9f656355f207aec9 + vcpkgTriplet: x64-windows + vcpkgArguments: pthreads opengl-registry ffmpeg[ffmpeg] sdl2 + cleanAfterBuild: false + + - name: vcpkg integrate install + run: | + cd vcpkg && ./vcpkg.exe integrate install + + - name: Build + run: | + make -j8 TARGET_OS=Windows PKG_CONFIG="D:\\\\a\\\\gopro-lib-node.gl\\\\gopro-lib-node.gl\\\\pkg-config.exe" VCPKG_DIR="D:\\\\a\\\\gopro-lib-node.gl\\\\gopro-lib-node.gl\\\\vcpkg" + +# - name: Setup debug session using tmate +# if: always() +# run: | +# sudo apt -y update +# #Install tmate +# sudo apt-get install -y tmate openssh-client +# # Generate ssh key if needed +# [ -e ~/.ssh/id_rsa ] || ssh-keygen -t rsa -f ~/.ssh/id_rsa -q -N "" +# # Run deamonized tmate +# echo Running tmate... +# tmate -S /tmp/tmate.sock new-session -d +# tmate -S /tmp/tmate.sock wait tmate-ready +# # Print connection info +# tmate -S /tmp/tmate.sock display -p '#{tmate_ssh}' +# +# - name: Wait for connection +# if: always() +# run: | +# # Wait for connection +# while [ -S /tmp/tmate.sock ]; do +# sleep 1 +# done + + +# - name: Run tests +# run: | +# make -j8 tests PYTHON="C:/hostedtoolcache/windows/Python/3.9.0/x64/python.exe" TARGET_OS=Windows