Skip to content
This repository was archived by the owner on Mar 3, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions libnopegl/src/ngpu/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ enum ngpu_format {
NGPU_FORMAT_R8_SNORM,
NGPU_FORMAT_R8_UINT,
NGPU_FORMAT_R8_SINT,
NGPU_FORMAT_R8_SRGB,
NGPU_FORMAT_R8G8_UNORM,
NGPU_FORMAT_R8G8_SNORM,
NGPU_FORMAT_R8G8_UINT,
NGPU_FORMAT_R8G8_SINT,
NGPU_FORMAT_R8G8_SRGB,
NGPU_FORMAT_R8G8B8_UNORM,
NGPU_FORMAT_R8G8B8_SNORM,
NGPU_FORMAT_R8G8B8_UINT,
Expand All @@ -49,6 +51,7 @@ enum ngpu_format {
NGPU_FORMAT_B8G8R8A8_SNORM,
NGPU_FORMAT_B8G8R8A8_UINT,
NGPU_FORMAT_B8G8R8A8_SINT,
NGPU_FORMAT_B8G8R8A8_SRGB,
NGPU_FORMAT_R16_UNORM,
NGPU_FORMAT_R16_SNORM,
NGPU_FORMAT_R16_UINT,
Expand Down
3 changes: 3 additions & 0 deletions libnopegl/src/ngpu/opengl/format_gl.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,13 @@ static const struct ngpu_format_gl formats[NGPU_FORMAT_NB] = {
[NGPU_FORMAT_R8_SNORM] = {GL_RED, GL_R8_SNORM, GL_BYTE, S|SL|B},
[NGPU_FORMAT_R8_UINT] = {GL_RED_INTEGER, GL_R8UI, GL_UNSIGNED_BYTE, S|C},
[NGPU_FORMAT_R8_SINT] = {GL_RED_INTEGER, GL_R8I, GL_BYTE, S|C},
[NGPU_FORMAT_R8_SRGB] = {GL_RED, GL_SR8, GL_BYTE, S|SL},

[NGPU_FORMAT_R8G8_UNORM] = {GL_RG, GL_RG8, GL_UNSIGNED_BYTE, S|SL|C|B},
[NGPU_FORMAT_R8G8_SNORM] = {GL_RG, GL_RG8_SNORM, GL_BYTE, S|SL|B},
[NGPU_FORMAT_R8G8_UINT] = {GL_RG_INTEGER, GL_RG8UI, GL_UNSIGNED_BYTE, S|C},
[NGPU_FORMAT_R8G8_SINT] = {GL_RG_INTEGER, GL_RG8I, GL_BYTE, S|C},
[NGPU_FORMAT_R8G8_SRGB] = {GL_RG, GL_SRG8, GL_BYTE, S|SL},

[NGPU_FORMAT_R8G8B8_UNORM] = {GL_RGB, GL_RGB8, GL_UNSIGNED_BYTE, S|SL|C|B},
[NGPU_FORMAT_R8G8B8_SNORM] = {GL_RGB, GL_RGB8_SNORM, GL_BYTE, S|SL},
Expand All @@ -63,6 +65,7 @@ static const struct ngpu_format_gl formats[NGPU_FORMAT_NB] = {
[NGPU_FORMAT_B8G8R8A8_SNORM] = {GL_BGRA, GL_RGBA8_SNORM, GL_BYTE, 0},
[NGPU_FORMAT_B8G8R8A8_UINT] = {GL_BGRA_INTEGER, GL_RGBA8UI, GL_UNSIGNED_BYTE, 0},
[NGPU_FORMAT_B8G8R8A8_SINT] = {GL_BGRA_INTEGER, GL_RGBA8I, GL_BYTE, 0},
[NGPU_FORMAT_B8G8R8A8_SRGB] = {GL_BGRA, GL_SRGB8_ALPHA8,GL_BYTE, 0},

[NGPU_FORMAT_R16_UNORM] = {GL_RED, GL_R16, GL_UNSIGNED_SHORT, 0},
[NGPU_FORMAT_R16_SNORM] = {GL_RED, GL_R16_SNORM, GL_SHORT, 0},
Expand Down
4 changes: 3 additions & 1 deletion libnopegl/src/ngpu/opengl/glcontext.c
Original file line number Diff line number Diff line change
Expand Up @@ -510,8 +510,10 @@ struct glcontext *ngli_glcontext_create(const struct glcontext_params *params)
if (ret < 0)
goto fail;

if (glcontext->backend == NGL_BACKEND_OPENGL)
if (glcontext->backend == NGL_BACKEND_OPENGL) {
glcontext->funcs.Enable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
glcontext->funcs.Enable(GL_FRAMEBUFFER_SRGB);
}

if (!glcontext->external && !glcontext->offscreen) {
ret = ngli_glcontext_resize(glcontext, glcontext->width, glcontext->height);
Expand Down
15 changes: 14 additions & 1 deletion libnopegl/src/ngpu/opengl/glcontext_egl.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@
#define EGL_PLATFORM_X11 0x31D5
#define EGL_PLATFORM_WAYLAND 0x31D8
#define EGL_PLATFORM_SURFACELESS_MESA 0x31DD
#define EGL_GL_COLORSPACE_KHR 0x309D
#define EGL_GL_COLORSPACE_SRGB_KHR 0x3089
#define EGL_GL_COLORSPACE_LINEAR_KHR 0x308A

struct egl_priv {
EGLNativeDisplayType native_display;
Expand All @@ -77,6 +80,7 @@ struct egl_priv {
int has_device_base_ext;
int has_create_context_ext;
int has_image_dma_buf_import_modifiers_ext;
int has_gl_colorspace_ext;
#if defined(HAVE_WAYLAND)
struct wl_egl_window *wl_egl_window;
#endif
Expand Down Expand Up @@ -196,6 +200,10 @@ static int egl_probe_extensions(struct glcontext *ctx)
egl->has_image_dma_buf_import_modifiers_ext = 1;
}

if (ngli_glcontext_check_extension("EGL_KHR_gl_colorspace", egl->extensions)) {
egl->has_gl_colorspace_ext = 1;
}

return 0;
}

Expand Down Expand Up @@ -507,7 +515,12 @@ try_again:;
LOG(ERROR, "could not retrieve EGL native window");
return NGL_ERROR_EXTERNAL;
}
egl->surface = eglCreateWindowSurface(egl->display, egl->config, egl->native_window, NULL);
const EGLint surface_attribs[] = {
EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_LINEAR_KHR,
EGL_NONE,
};
egl->surface = eglCreateWindowSurface(egl->display, egl->config, egl->native_window,
egl->has_surfaceless_context_ext ? surface_attribs : NULL);
if (!egl->surface) {
LOG(ERROR, "could not create EGL window surface: %s", ngli_eglGetErrorStr());
return NGL_ERROR_EXTERNAL;
Expand Down
14 changes: 14 additions & 0 deletions libnopegl/src/ngpu/opengl/glcontext_wgl.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
#include "nopegl.h"
#include "log.h"

#define WGL_COLORSPACE_EXT 0x309D
#define WGL_COLORSPACE_SRGB_EXT 0x3089
#define WGL_COLORSPACE_LINEAR_EXT 0x308A

struct wgl_priv {
HWND window;
HDC device_context;
Expand All @@ -42,6 +46,7 @@ struct wgl_priv {
PFNWGLCREATECONTEXTATTRIBSARBPROC CreateContextAttribsARB;
PFNWGLGETEXTENSIONSSTRINGARBPROC GetExtensionsStringARB;
PFNWGLSWAPINTERVALEXTPROC SwapIntervalEXT;
int has_colorspace_ext;
};

static int wgl_init_extensions(struct glcontext *ctx)
Expand Down Expand Up @@ -122,6 +127,11 @@ static int wgl_init_extensions(struct glcontext *ctx)
if (!wgl->SwapIntervalEXT)
LOG(WARNING, "context does not support any swap interval extension (%lu)", GetLastError());

const char *extensions_string = wgl->GetExtensionsStringARB(device_context);
if (ngli_glcontext_check_extension("WGL_EXT_colorspace", extensions_string)) {
wgl->has_colorspace_ext = 1;
}

ret = 0;

done:
Expand Down Expand Up @@ -166,6 +176,9 @@ static int wgl_init(struct glcontext *ctx, uintptr_t display, uintptr_t window,
return NGL_ERROR_EXTERNAL;
}

const int colorspace_attr = wgl->has_colorspace_ext ? WGL_COLORSPACE_EXT : 0;
const int colorspace_value = wgl->has_colorspace_ext ? WGL_COLORSPACE_LINEAR_EXT : 0;

const int pixel_format_attributes[] = {
WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
Expand All @@ -181,6 +194,7 @@ static int wgl_init(struct glcontext *ctx, uintptr_t display, uintptr_t window,
WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
WGL_SAMPLE_BUFFERS_ARB, ctx->offscreen ? 0 : (ctx->samples > 0),
WGL_SAMPLES_ARB, ctx->offscreen ? 0 : (int)ctx->samples,
colorspace_attr, colorspace_value,
0
};

Expand Down
5 changes: 5 additions & 0 deletions libnopegl/src/ngpu/opengl/glincludes.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,9 @@ typedef void (NGLI_GL_APIENTRY *GLDEBUGPROC)(GLenum source, GLenum type, GLuint
# define GL_DYNAMIC_STORAGE_BIT 0x0100
# define GL_CLIENT_STORAGE_BIT 0x0200

/* sRGB */
# define GL_FRAMEBUFFER_SRGB 0x8DB9
# define GL_SR8 0x8FBD
# define GL_SRG8 0x8FBE

#endif /* GLINCLUDES_H */
4 changes: 4 additions & 0 deletions libnopegl/src/ngpu/opengl/glstate.c
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ void ngpu_glstate_reset(const struct glcontext *gl, struct ngpu_glstate *glstate
{
memset(glstate, 0, sizeof(*glstate));

/* sRGB */
if (gl->backend == NGL_BACKEND_OPENGL)
gl->funcs.Enable(GL_FRAMEBUFFER_SRGB);

/* Blending */
gl->funcs.Disable(GL_BLEND);
glstate->blend = 0;
Expand Down
17 changes: 17 additions & 0 deletions libnopegl/src/ngpu/opengl/rendertarget_gl.c
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,17 @@ void ngpu_rendertarget_gl_begin_pass(struct ngpu_rendertarget *s)

ngpu_glstate_enable_scissor_test(gl, glstate, GL_FALSE);

#if defined(TARGET_DARWIN)
/*
* The default framebuffer on macOS is sRGB compatible and there is no way
* to request a non-sRGB format, thus we need to disable the linear -> sRGB
* conversion (that is enabled by GL_FRAMEBUFFER_SRGB).
*/
if (s_priv->id == ngli_glcontext_get_default_framebuffer(gl)) {
gl->funcs.Disable(GL_FRAMEBUFFER_SRGB);
}
#endif

gl->funcs.BindFramebuffer(GL_FRAMEBUFFER, s_priv->id);

const struct ngpu_viewport viewport = {
Expand Down Expand Up @@ -364,6 +375,12 @@ void ngpu_rendertarget_gl_end_pass(struct ngpu_rendertarget *s)
gl->funcs.BindFramebuffer(GL_FRAMEBUFFER, s_priv->id);
}

#if defined(TARGET_DARWIN)
if (s_priv->id == ngli_glcontext_get_default_framebuffer(gl)) {
gl->funcs.Enable(GL_FRAMEBUFFER_SRGB);
}
#endif

s_priv->invalidate(s);
}

Expand Down
3 changes: 3 additions & 0 deletions libnopegl/src/ngpu/vulkan/format_vk.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ VkFormat ngpu_format_ngl_to_vk(enum ngpu_format format)
[NGPU_FORMAT_R8_SNORM] = VK_FORMAT_R8_SNORM,
[NGPU_FORMAT_R8_UINT] = VK_FORMAT_R8_UINT,
[NGPU_FORMAT_R8_SINT] = VK_FORMAT_R8_SINT,
[NGPU_FORMAT_R8_SRGB] = VK_FORMAT_R8_SRGB,
[NGPU_FORMAT_R8G8_UNORM] = VK_FORMAT_R8G8_UNORM,
[NGPU_FORMAT_R8G8_SNORM] = VK_FORMAT_R8G8_SNORM,
[NGPU_FORMAT_R8G8_UINT] = VK_FORMAT_R8G8_UINT,
[NGPU_FORMAT_R8G8_SINT] = VK_FORMAT_R8G8_SINT,
[NGPU_FORMAT_R8G8_SRGB] = VK_FORMAT_R8G8_SRGB,
[NGPU_FORMAT_R8G8B8_UNORM] = VK_FORMAT_R8G8B8_UNORM,
[NGPU_FORMAT_R8G8B8_SNORM] = VK_FORMAT_R8G8B8_SNORM,
[NGPU_FORMAT_R8G8B8_UINT] = VK_FORMAT_R8G8B8_UINT,
Expand All @@ -50,6 +52,7 @@ VkFormat ngpu_format_ngl_to_vk(enum ngpu_format format)
[NGPU_FORMAT_B8G8R8A8_SNORM] = VK_FORMAT_B8G8R8A8_SNORM,
[NGPU_FORMAT_B8G8R8A8_UINT] = VK_FORMAT_B8G8R8A8_UINT,
[NGPU_FORMAT_B8G8R8A8_SINT] = VK_FORMAT_B8G8R8A8_SINT,
[NGPU_FORMAT_B8G8R8A8_SRGB] = VK_FORMAT_B8G8R8A8_SRGB,
[NGPU_FORMAT_R16_UNORM] = VK_FORMAT_R16_UNORM,
[NGPU_FORMAT_R16_SNORM] = VK_FORMAT_R16_SNORM,
[NGPU_FORMAT_R16_UINT] = VK_FORMAT_R16_UINT,
Expand Down
6 changes: 6 additions & 0 deletions python/pynopegl-utils/pynopegl_utils/qml/ngl_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
QML_IMPORT_MAJOR_VERSION = 1
QML_IMPORT_MINOR_VERSION = 0

_GL_FRAMEBUFFER_SRGB = 0x8DB9


@QmlElement
class NopeGLWidget(QQuickFramebufferObject):
Expand Down Expand Up @@ -142,6 +144,10 @@ def render(self):
self._context = None
self._request_stop = False

# endExternalCommands() does not reset GL_FRAMEBUFFER_SRGB state
gl_funcs = QOpenGLFunctions(QOpenGLContext.currentContext())
gl_funcs.glDisable(_GL_FRAMEBUFFER_SRGB)

self._window.endExternalCommands()

def synchronize(self, ngl_widget):
Expand Down
1 change: 1 addition & 0 deletions tests/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ foreach backend : backends
'clear_attachment_with_timeranges',
'resizable_with_timeranges',
'resizable_with_timeranges_implicit',
'srgb',
]

if max_samples >= 4
Expand Down
1 change: 1 addition & 0 deletions tests/refs/rtt_srgb.ref
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0:4F4F4FFF 1:7C7C7CFF 2:9B9B9BFF 3:B2B2B2FF 4:C8C8C8FF 5:DADADAFF 6:EBEBEBFF 7:FAFAFAFF
26 changes: 26 additions & 0 deletions tests/rtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from pynopegl_utils.misc import get_shader
from pynopegl_utils.tests.cmp_cuepoints import test_cuepoints
from pynopegl_utils.tests.cmp_fingerprint import test_fingerprint
from pynopegl_utils.tests.cuepoints_utils import get_grid_points, get_points_nodes
from pynopegl_utils.toolbox.colors import COLORS


Expand Down Expand Up @@ -427,3 +428,28 @@ def rtt_resizable_with_timeranges_implicit(cfg: ngl.SceneCfg):
group = ngl.Group(children=[time_range_filter1, time_range_filter2])

return group


@test_cuepoints(width=32, height=32, points=get_grid_points(8, 1), tolerance=1)
@ngl.scene(controls=dict(show_dbg_points=ngl.scene.Bool()))
def rtt_srgb(cfg: ngl.SceneCfg, show_dbg_points=False):
cfg.aspect_ratio = (1, 1)
cfg.duration = 1

gradient = ngl.DrawGradient(
color0=(0, 0, 0),
color1=(1, 1, 1),
)
texture = ngl.Texture2D(
format="r8g8b8a8_srgb",
width=32,
height=32,
data_src=gradient,
)
group = ngl.Group(children=[ngl.DrawTexture(texture)])

if show_dbg_points:
cuepoints = get_grid_points(8, 1)
group.add_children(get_points_nodes(cfg, cuepoints))

return group
Loading