diff options
author | Mikhail Burakov <mburakov@mailbox.org> | 2023-03-19 12:30:24 +0100 |
---|---|---|
committer | Mikhail Burakov <mburakov@mailbox.org> | 2023-03-19 12:30:24 +0100 |
commit | 1b00a18b7c50e54928dcd273d2b6800f0c0a24f0 (patch) | |
tree | f35e56f52bb76cbd847c758353eacd382ca9079b | |
parent | 85c81156a37e161ca08831b9ac9af022c72ebdea (diff) |
Add colorspace and ranges handling to streamer
-rw-r--r-- | chroma.glsl | 33 | ||||
-rw-r--r-- | encode.c | 31 | ||||
-rw-r--r-- | encode.h | 6 | ||||
-rw-r--r-- | gpu.c | 101 | ||||
-rw-r--r-- | gpu.h | 5 | ||||
-rw-r--r-- | luma.glsl | 11 | ||||
-rw-r--r-- | main.c | 13 | ||||
-rw-r--r-- | util.h | 1 |
8 files changed, 152 insertions, 49 deletions
diff --git a/chroma.glsl b/chroma.glsl index 9004793..dd103b1 100644 --- a/chroma.glsl +++ b/chroma.glsl @@ -16,27 +16,26 @@ */ uniform sampler2D img_input; -uniform mediump vec2 chroma_offsets; +uniform mediump vec2 sample_offsets[4]; +uniform mediump mat3 colorspace; +uniform mediump vec3 ranges[2]; varying mediump vec2 texcoord; -mediump vec2 rgb2chroma(in mediump vec4 rgb) { - // mburakov: This hardcodes BT.709 full-range. - mediump float y = rgb.r * 0.2126 + rgb.g * 0.7152 + rgb.b * 0.0722; - mediump float u = (rgb.b - y) / (2.0 * (1.0 - 0.0722)); - mediump float v = (rgb.r - y) / (2.0 * (1.0 - 0.2126)); - return vec2(u + 0.5, v + 0.5); +mediump vec4 supersample() { + return texture2D(img_input, texcoord + sample_offsets[0]) + + texture2D(img_input, texcoord + sample_offsets[1]) + + texture2D(img_input, texcoord + sample_offsets[2]) + + texture2D(img_input, texcoord + sample_offsets[3]); +} + +mediump vec3 rgb2yuv(in mediump vec3 rgb) { + mediump vec3 yuv = colorspace * rgb.rgb + vec3(0.0, 0.5, 0.5); + return ranges[0] + yuv * ranges[1]; } void main() { - mediump vec2 sample_points[4]; - sample_points[0] = texcoord; - sample_points[1] = texcoord + vec2(chroma_offsets.x, 0.0); - sample_points[2] = texcoord + vec2(0.0, chroma_offsets.y); - sample_points[3] = texcoord + chroma_offsets; - mediump vec4 rgb = texture2D(img_input, sample_points[0]) + - texture2D(img_input, sample_points[1]) + - texture2D(img_input, sample_points[2]) + - texture2D(img_input, sample_points[3]); - gl_FragColor = vec4(rgb2chroma(rgb / 4.0), 0.0, 1.0); + mediump vec4 rgb = supersample() / 4.0; + mediump vec3 yuv = rgb2yuv(rgb.rgb); + gl_FragColor = vec4(yuv.yz, 0.0, 1.0); } @@ -77,8 +77,33 @@ static bool SetHwFramesContext(struct EncodeContext* encode_context, int width, return true; } +static enum AVColorSpace ConvertColorspace(enum YuvColorspace colorspace) { + switch (colorspace) { + case kItuRec601: + // TODO(mburakov): No dedicated definition for BT601? + return AVCOL_SPC_SMPTE170M; + case kItuRec709: + return AVCOL_SPC_BT709; + default: + __builtin_unreachable(); + } +} + +static enum AVColorRange ConvertRange(enum YuvRange range) { + switch (range) { + case kNarrowRange: + return AVCOL_RANGE_MPEG; + case kFullRange: + return AVCOL_RANGE_JPEG; + default: + __builtin_unreachable(); + } +} + struct EncodeContext* EncodeContextCreate(struct GpuContext* gpu_context, - uint32_t width, uint32_t height) { + uint32_t width, uint32_t height, + enum YuvColorspace colrospace, + enum YuvRange range) { struct AUTO(EncodeContext)* encode_context = malloc(sizeof(struct EncodeContext)); if (!encode_context) { @@ -119,8 +144,8 @@ struct EncodeContext* EncodeContextCreate(struct GpuContext* gpu_context, encode_context->codec_context->max_b_frames = 0; encode_context->codec_context->refs = 1; encode_context->codec_context->global_quality = 18; - encode_context->codec_context->colorspace = AVCOL_SPC_BT709; - encode_context->codec_context->color_range = AVCOL_RANGE_JPEG; + encode_context->codec_context->colorspace = ConvertColorspace(colrospace); + encode_context->codec_context->color_range = ConvertRange(range); if (!SetHwFramesContext(encode_context, (int)width, (int)height)) { LOG("Failed to set hwframes context"); @@ -21,12 +21,16 @@ #include <stdbool.h> #include <stdint.h> +#include "colorspace.h" + struct EncodeContext; struct GpuContext; struct GpuFrame; struct EncodeContext* EncodeContextCreate(struct GpuContext* gpu_context, - uint32_t width, uint32_t height); + uint32_t width, uint32_t height, + enum YuvColorspace colorspace, + enum YuvRange range); const struct GpuFrame* EncodeContextGetFrame( struct EncodeContext* encode_context); bool EncodeContextEncodeFrame(struct EncodeContext* encode_context, int fd); @@ -56,7 +56,7 @@ struct GpuContext { PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; GLuint program_luma; GLuint program_chroma; - GLint chroma_offsets; + GLint sample_offsets; GLuint framebuffer; GLuint vertices; }; @@ -179,15 +179,71 @@ bail_out: return program; } -static bool SetupImgInputUniform(GLuint program) { - GLint img_input = glGetUniformLocation(program, "img_input"); - if (img_input == -1) { - LOG("Failed to find img_input uniform (%s)", GlErrorString(glGetError())); - return false; +static const GLfloat* GetColorspaceMatrix(enum YuvColorspace colorspace) { + static const GLfloat rec601[] = { + _(0.299f, 0.587f, 0.114f), + _(-0.168736f, -0.331264f, 0.5f), + _(0.5f, -0.418688f, -0.081312f), + }; + static const GLfloat rec709[] = { + _(0.2126f, 0.7152f, 0.0722f), + _(-0.1146f, -0.3854f, 0.5f), + _(0.5f, -0.4542f, -0.0458f), + }; + switch (colorspace) { + case kItuRec601: + return rec601; + case kItuRec709: + return rec709; + default: + __builtin_unreachable(); + } +} + +static const GLfloat* GetRangeVectors(enum YuvRange range) { + static const GLfloat narrow[] = { + _(16.f / 255.f, 16.f / 255.f, 16.f / 255.f), + _((235.f - 16.f) / 255.f, (240.f - 16.f) / 255.f, (240.f - 16.f) / 255.f), + }; + static const GLfloat full[] = { + _(0.f, 0.f, 0.f), + _(1.f, 1.f, 1.f), + }; + switch (range) { + case kNarrowRange: + return narrow; + case kFullRange: + return full; + default: + __builtin_unreachable(); + } +} + +static bool SetupCommonUniforms(GLuint program, enum YuvColorspace colorspace, + enum YuvRange range) { + struct { + const char* name; + GLint location; + } uniforms[] = { + {.name = "img_input"}, + {.name = "colorspace"}, + {.name = "ranges"}, + }; + + for (size_t i = 0; i < LENGTH(uniforms); i++) { + uniforms[i].location = glGetUniformLocation(program, uniforms[i].name); + if (uniforms[i].location == -1) { + LOG("Failed to locate %s uniform (%s)", uniforms[i].name, + GlErrorString(glGetError())); + return false; + } } glUseProgram(program); - glUniform1i(img_input, 0); + glUniform1i(uniforms[0].location, 0); + glUniformMatrix3fv(uniforms[1].location, 1, GL_TRUE, + GetColorspaceMatrix(colorspace)); + glUniform3fv(uniforms[2].location, 2, GetRangeVectors(range)); GLenum error = glGetError(); if (error != GL_NO_ERROR) { LOG("Failed to set img_input uniform (%s)", GlErrorString(glGetError())); @@ -196,7 +252,8 @@ static bool SetupImgInputUniform(GLuint program) { return true; } -struct GpuContext* GpuContextCreate(void) { +struct GpuContext* GpuContextCreate(enum YuvColorspace colorspace, + enum YuvRange range) { struct AUTO(GpuContext)* gpu_context = malloc(sizeof(struct GpuContext)); if (!gpu_context) { LOG("Failed to allocate gpu context (%s)", strerror(errno)); @@ -208,7 +265,7 @@ struct GpuContext* GpuContextCreate(void) { .glEGLImageTargetTexture2DOES = NULL, .program_luma = 0, .program_chroma = 0, - .chroma_offsets = -1, + .sample_offsets = -1, .framebuffer = 0, .vertices = 0, }; @@ -255,11 +312,9 @@ struct GpuContext* GpuContextCreate(void) { } static const EGLint context_attribs[] = { -#define _(...) __VA_ARGS__ _(EGL_CONTEXT_MAJOR_VERSION, 3), _(EGL_CONTEXT_MINOR_VERSION, 1), EGL_NONE, -#undef _ }; gpu_context->context = eglCreateContext( gpu_context->display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, context_attribs); @@ -295,7 +350,7 @@ struct GpuContext* GpuContextCreate(void) { CreateGlProgram(_binary_vertex_glsl_start, _binary_vertex_glsl_end, _binary_luma_glsl_start, _binary_luma_glsl_end); if (!gpu_context->program_luma || - !SetupImgInputUniform(gpu_context->program_luma)) { + !SetupCommonUniforms(gpu_context->program_luma, colorspace, range)) { LOG("Failed to create luma program"); return NULL; } @@ -304,14 +359,14 @@ struct GpuContext* GpuContextCreate(void) { CreateGlProgram(_binary_vertex_glsl_start, _binary_vertex_glsl_end, _binary_chroma_glsl_start, _binary_chroma_glsl_end); if (!gpu_context->program_chroma || - !SetupImgInputUniform(gpu_context->program_luma)) { + !SetupCommonUniforms(gpu_context->program_chroma, colorspace, range)) { LOG("Failed to create chroma program"); return NULL; } - gpu_context->chroma_offsets = - glGetUniformLocation(gpu_context->program_chroma, "chroma_offsets"); - if (gpu_context->chroma_offsets == -1) { - LOG("Failed to find chroma_offsets uniform (%s)", + gpu_context->sample_offsets = + glGetUniformLocation(gpu_context->program_chroma, "sample_offsets"); + if (gpu_context->sample_offsets == -1) { + LOG("Failed to find sample_offsets uniform (%s)", GlErrorString(glGetError())); return NULL; } @@ -380,11 +435,9 @@ static EGLImage CreateEglImage(struct GpuContext* gpu_context, uint32_t width, }; EGLAttrib attrib_list[7 + LENGTH(attrib_keys) * 2] = { -#define _(...) __VA_ARGS__ _(EGL_WIDTH, width), _(EGL_HEIGHT, height), _(EGL_LINUX_DRM_FOURCC_EXT, fourcc), -#undef _ }; EGLAttrib* pairs = &attrib_list[6]; @@ -533,9 +586,15 @@ bool GpuFrameConvert(const struct GpuFrame* from, const struct GpuFrame* to) { return false; } + const GLfloat sample_offsets[] = { + _(0.f, 0.f), + _(1.f / (GLfloat)from->width, 0.f), + _(0.f, 1.f / (GLfloat)from->height), + _(1.f / (GLfloat)from->width, 1.f / (GLfloat)from->height), + }; + glUseProgram(from->gpu_context->program_chroma); - glUniform2f(from->gpu_context->chroma_offsets, 1.f / (GLfloat)from->width, - 1.f / (GLfloat)from->height); + glUniform2fv(from->gpu_context->sample_offsets, 4, sample_offsets); glViewport(0, 0, (GLsizei)to->width / 2, (GLsizei)to->height / 2); if (!GpuFrameConvertImpl(from->textures[0], to->textures[1])) { LOG("Failed to convert chroma plane"); @@ -22,6 +22,8 @@ #include <stddef.h> #include <stdint.h> +#include "colorspace.h" + struct GpuFramePlane { int dmabuf_fd; uint32_t pitch; @@ -29,7 +31,8 @@ struct GpuFramePlane { uint64_t modifier; }; -struct GpuContext* GpuContextCreate(void); +struct GpuContext* GpuContextCreate(enum YuvColorspace colorspace, + enum YuvRange range); bool GpuContextSync(struct GpuContext* gpu_context); void GpuContextDestroy(struct GpuContext** gpu_context); @@ -16,15 +16,18 @@ */ uniform sampler2D img_input; +uniform mediump mat3 colorspace; +uniform mediump vec3 ranges[2]; varying mediump vec2 texcoord; -mediump float rgb2luma(in mediump vec4 rgb) { - // mburakov: This hardcodes BT.709 full-range. - return rgb.r * 0.2126 + rgb.g * 0.7152 + rgb.b * 0.0722; +mediump vec3 rgb2yuv(in mediump vec3 rgb) { + mediump vec3 yuv = colorspace * rgb.rgb + vec3(0.0, 0.5, 0.5); + return ranges[0] + yuv * ranges[1]; } void main() { mediump vec4 rgb = texture2D(img_input, texcoord); - gl_FragColor = vec4(rgb2luma(rgb), 0.0, 0.0, 1.0); + mediump vec3 yuv = rgb2yuv(rgb.rgb); + gl_FragColor = vec4(yuv.x, 0.0, 0.0, 1.0); } @@ -25,10 +25,18 @@ #include <unistd.h> #include "capture.h" +#include "colorspace.h" #include "encode.h" #include "gpu.h" #include "util.h" +// TODO(mburakov): Currently zwp_linux_dmabuf_v1 has no way to provide +// colorspace and range information to the compositor. Maybe this would change +// in the future, i.e keep an eye on color-representation Wayland protocol: +// https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/183 +static const enum YuvColorspace colorspace = kItuRec601; +static const enum YuvRange range = kNarrowRange; + static volatile sig_atomic_t g_signal; static void OnSignal(int status) { g_signal = status; } @@ -69,7 +77,7 @@ int main(int argc, char* argv[]) { return EXIT_FAILURE; } - struct AUTO(GpuContext)* gpu_context = GpuContextCreate(); + struct AUTO(GpuContext)* gpu_context = GpuContextCreate(colorspace, range); if (!gpu_context) { LOG("Failed to create gpu context"); return EXIT_FAILURE; @@ -104,7 +112,8 @@ int main(int argc, char* argv[]) { if (!encode_context) { uint32_t width, height; GpuFrameGetSize(captured_frame, &width, &height); - encode_context = EncodeContextCreate(gpu_context, width, height); + encode_context = + EncodeContextCreate(gpu_context, width, height, colorspace, range); if (!encode_context) { LOG("Failed to create encode context"); return EXIT_FAILURE; @@ -27,6 +27,7 @@ #define LENGTH(x) (sizeof(x) / sizeof *(x)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define _(...) __VA_ARGS__ static inline void* Release(void** x) { void* result = *x; |