aboutsummaryrefslogtreecommitdiff
path: root/src/utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/utils.c')
-rw-r--r--src/utils.c518
1 files changed, 436 insertions, 82 deletions
diff --git a/src/utils.c b/src/utils.c
index 94b0037..9b4a4b6 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -1,12 +1,23 @@
#include "../include/utils.h"
+#include "../include/window/window.h"
+
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/random.h>
+#include <errno.h>
+#include <assert.h>
+
#include <xf86drmMode.h>
#include <xf86drm.h>
-#include <stdlib.h>
+#include <X11/Xatom.h>
+#include <X11/extensions/Xrandr.h>
+#include <libavcodec/avcodec.h>
+#include <libavutil/hwcontext_vaapi.h>
double clock_get_monotonic_seconds(void) {
struct timespec ts;
@@ -16,6 +27,25 @@ double clock_get_monotonic_seconds(void) {
return (double)ts.tv_sec + (double)ts.tv_nsec * 0.000000001;
}
+bool generate_random_characters(char *buffer, int buffer_size, const char *alphabet, size_t alphabet_size) {
+ /* TODO: Use other functions on other platforms than linux */
+ if(getrandom(buffer, buffer_size, 0) < buffer_size) {
+ fprintf(stderr, "Failed to get random bytes, error: %s\n", strerror(errno));
+ return false;
+ }
+
+ for(int i = 0; i < buffer_size; ++i) {
+ unsigned char c = *(unsigned char*)&buffer[i];
+ buffer[i] = alphabet[c % alphabet_size];
+ }
+
+ return true;
+}
+
+bool generate_random_characters_standard_alphabet(char *buffer, int buffer_size) {
+ return generate_random_characters(buffer, buffer_size, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 62);
+}
+
static const XRRModeInfo* get_mode_info(const XRRScreenResources *sr, RRMode id) {
for(int i = 0; i < sr->nmode; ++i) {
if(sr->modes[i].id == id)
@@ -24,29 +54,73 @@ static const XRRModeInfo* get_mode_info(const XRRScreenResources *sr, RRMode id)
return NULL;
}
-static void for_each_active_monitor_output_x11(Display *display, active_monitor_callback callback, void *userdata) {
+static gsr_monitor_rotation x11_rotation_to_gsr_rotation(int rot) {
+ switch(rot) {
+ case RR_Rotate_0: return GSR_MONITOR_ROT_0;
+ case RR_Rotate_90: return GSR_MONITOR_ROT_90;
+ case RR_Rotate_180: return GSR_MONITOR_ROT_180;
+ case RR_Rotate_270: return GSR_MONITOR_ROT_270;
+ }
+ return GSR_MONITOR_ROT_0;
+}
+
+static uint32_t x11_output_get_connector_id(Display *dpy, RROutput output, Atom randr_connector_id_atom) {
+ Atom type = 0;
+ int format = 0;
+ unsigned long bytes_after = 0;
+ unsigned long nitems = 0;
+ unsigned char *prop = NULL;
+ XRRGetOutputProperty(dpy, output, randr_connector_id_atom, 0, 128, false, false, AnyPropertyType, &type, &format, &nitems, &bytes_after, &prop);
+
+ long result = 0;
+ if(type == XA_INTEGER && format == 32)
+ result = *(long*)prop;
+
+ free(prop);
+ return result;
+}
+
+static vec2i get_monitor_size_rotated(int width, int height, gsr_monitor_rotation rotation) {
+ vec2i size = { .x = width, .y = height };
+ if(rotation == GSR_MONITOR_ROT_90 || rotation == GSR_MONITOR_ROT_270) {
+ int tmp_x = size.x;
+ size.x = size.y;
+ size.y = tmp_x;
+ }
+ return size;
+}
+
+void for_each_active_monitor_output_x11_not_cached(Display *display, active_monitor_callback callback, void *userdata) {
XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display));
if(!screen_res)
return;
+ const Atom randr_connector_id_atom = XInternAtom(display, "CONNECTOR_ID", False);
+
char display_name[256];
for(int i = 0; i < screen_res->noutput; ++i) {
XRROutputInfo *out_info = XRRGetOutputInfo(display, screen_res, screen_res->outputs[i]);
if(out_info && out_info->crtc && out_info->connection == RR_Connected) {
XRRCrtcInfo *crt_info = XRRGetCrtcInfo(display, screen_res, out_info->crtc);
if(crt_info && crt_info->mode) {
+ // We want to use the current mode info width/height (mode_info->width/height) instead of crtc info width/height (crt_info->width/height) because crtc info
+ // is scaled if the monitor is scaled (xrandr --output DP-1 --scale 1.5). Normally this is not an issue for x11 applications,
+ // but gpu screen recorder captures the drm framebuffer instead of x11 api. This drm framebuffer which doesn't increase in size when using xrandr scaling.
+ // Maybe a better option would be to get the drm crtc size instead.
const XRRModeInfo *mode_info = get_mode_info(screen_res, crt_info->mode);
if(mode_info && out_info->nameLen < (int)sizeof(display_name)) {
- memcpy(display_name, out_info->name, out_info->nameLen);
- display_name[out_info->nameLen] = '\0';
+ snprintf(display_name, sizeof(display_name), "%.*s", (int)out_info->nameLen, out_info->name);
+ const gsr_monitor_rotation rotation = x11_rotation_to_gsr_rotation(crt_info->rotation);
+ const vec2i monitor_size = get_monitor_size_rotated(mode_info->width, mode_info->height, rotation);
- gsr_monitor monitor = {
+ const gsr_monitor monitor = {
.name = display_name,
.name_len = out_info->nameLen,
.pos = { .x = crt_info->x, .y = crt_info->y },
- .size = { .x = (int)crt_info->width, .y = (int)crt_info->height },
- .crt_info = crt_info,
- .connector_id = 0 // TODO: Get connector id
+ .size = monitor_size,
+ .connector_id = x11_output_get_connector_id(display, screen_res->outputs[i], randr_connector_id_atom),
+ .rotation = rotation,
+ .monitor_identifier = out_info->crtc
};
callback(&monitor, userdata);
}
@@ -61,14 +135,22 @@ static void for_each_active_monitor_output_x11(Display *display, active_monitor_
XRRFreeScreenResources(screen_res);
}
-typedef struct {
- int type;
- int count;
-} drm_connector_type_count;
-
-#define CONNECTOR_TYPE_COUNTS 32
+/* TODO: Support more connector types */
+int get_connector_type_by_name(const char *name) {
+ int len = strlen(name);
+ if(len >= 5 && strncmp(name, "HDMI-", 5) == 0)
+ return 1;
+ else if(len >= 3 && strncmp(name, "DP-", 3) == 0)
+ return 2;
+ else if(len >= 12 && strncmp(name, "DisplayPort-", 12) == 0)
+ return 3;
+ else if(len >= 4 && strncmp(name, "eDP-", 4) == 0)
+ return 4;
+ else
+ return -1;
+}
-static drm_connector_type_count* drm_connector_types_get_index(drm_connector_type_count *type_counts, int *num_type_counts, int connector_type) {
+drm_connector_type_count* drm_connector_types_get_index(drm_connector_type_count *type_counts, int *num_type_counts, int connector_type) {
for(int i = 0; i < *num_type_counts; ++i) {
if(type_counts[i].type == connector_type)
return &type_counts[i];
@@ -80,10 +162,15 @@ static drm_connector_type_count* drm_connector_types_get_index(drm_connector_typ
const int index = *num_type_counts;
type_counts[index].type = connector_type;
type_counts[index].count = 0;
+ type_counts[index].count_active = 0;
++*num_type_counts;
return &type_counts[index];
}
+uint32_t monitor_identifier_from_type_and_count(int monitor_type_index, int monitor_type_count) {
+ return ((uint32_t)monitor_type_index << 16) | ((uint32_t)monitor_type_count);
+}
+
static bool connector_get_property_by_name(int drmfd, drmModeConnectorPtr props, const char *name, uint64_t *result) {
for(int i = 0; i < props->count_props; ++i) {
drmModePropertyPtr prop = drmModeGetProperty(drmfd, props->props[i]);
@@ -99,30 +186,12 @@ static bool connector_get_property_by_name(int drmfd, drmModeConnectorPtr props,
return false;
}
-static void for_each_active_monitor_output_wayland(gsr_egl *egl, active_monitor_callback callback, void *userdata) {
- if(!gsr_egl_supports_wayland_capture(egl))
+static void for_each_active_monitor_output_drm(const char *card_path, active_monitor_callback callback, void *userdata) {
+ int fd = open(card_path, O_RDONLY);
+ if(fd == -1) {
+ fprintf(stderr, "gsr error: for_each_active_monitor_output_drm failed, failed to open \"%s\", error: %s\n", card_path, strerror(errno));
return;
-
- for(int i = 0; i < egl->wayland.num_outputs; ++i) {
- if(!egl->wayland.outputs[i].name)
- continue;
-
- gsr_monitor monitor = {
- .name = egl->wayland.outputs[i].name,
- .name_len = strlen(egl->wayland.outputs[i].name),
- .pos = { .x = egl->wayland.outputs[i].pos.x, .y = egl->wayland.outputs[i].pos.y },
- .size = { .x = egl->wayland.outputs[i].size.x, .y = egl->wayland.outputs[i].size.y },
- .crt_info = NULL,
- .connector_id = 0
- };
- callback(&monitor, userdata);
}
-}
-
-static void for_each_active_monitor_output_drm(const char *drm_card_path, active_monitor_callback callback, void *userdata) {
- int fd = open(drm_card_path, O_RDONLY);
- if(fd == -1)
- return;
drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1);
@@ -148,19 +217,24 @@ static void for_each_active_monitor_output_drm(const char *drm_card_path, active
continue;
}
+ if(connector_type)
+ ++connector_type->count_active;
+
uint64_t crtc_id = 0;
connector_get_property_by_name(fd, connector, "CRTC_ID", &crtc_id);
drmModeCrtcPtr crtc = drmModeGetCrtc(fd, crtc_id);
if(connector_type && crtc_id > 0 && crtc && connection_name_len + 5 < (int)sizeof(display_name)) {
const int display_name_len = snprintf(display_name, sizeof(display_name), "%s-%d", connection_name, connector_type->count);
+ const int connector_type_index_name = get_connector_type_by_name(display_name);
gsr_monitor monitor = {
.name = display_name,
.name_len = display_name_len,
.pos = { .x = crtc->x, .y = crtc->y },
.size = { .x = (int)crtc->width, .y = (int)crtc->height },
- .crt_info = NULL,
- .connector_id = connector->connector_id
+ .connector_id = connector->connector_id,
+ .rotation = GSR_MONITOR_ROT_0,
+ .monitor_identifier = connector_type_index_name != -1 ? monitor_identifier_from_type_and_count(connector_type_index_name, connector_type->count_active) : 0
};
callback(&monitor, userdata);
}
@@ -176,16 +250,14 @@ static void for_each_active_monitor_output_drm(const char *drm_card_path, active
close(fd);
}
-void for_each_active_monitor_output(void *connection, gsr_connection_type connection_type, active_monitor_callback callback, void *userdata) {
+void for_each_active_monitor_output(const gsr_window *window, const char *card_path, gsr_connection_type connection_type, active_monitor_callback callback, void *userdata) {
switch(connection_type) {
case GSR_CONNECTION_X11:
- for_each_active_monitor_output_x11(connection, callback, userdata);
- break;
case GSR_CONNECTION_WAYLAND:
- for_each_active_monitor_output_wayland(connection, callback, userdata);
+ gsr_window_for_each_active_monitor_output_cached(window, callback, userdata);
break;
case GSR_CONNECTION_DRM:
- for_each_active_monitor_output_drm(connection, callback, userdata);
+ for_each_active_monitor_output_drm(card_path, callback, userdata);
break;
}
}
@@ -195,20 +267,96 @@ static void get_monitor_by_name_callback(const gsr_monitor *monitor, void *userd
if(!data->found_monitor && strcmp(data->name, monitor->name) == 0) {
data->monitor->pos = monitor->pos;
data->monitor->size = monitor->size;
+ data->monitor->connector_id = monitor->connector_id;
+ data->monitor->rotation = monitor->rotation;
+ data->monitor->monitor_identifier = monitor->monitor_identifier;
data->found_monitor = true;
}
}
-bool get_monitor_by_name(void *connection, gsr_connection_type connection_type, const char *name, gsr_monitor *monitor) {
+bool get_monitor_by_name(const gsr_egl *egl, gsr_connection_type connection_type, const char *name, gsr_monitor *monitor) {
get_monitor_by_name_userdata userdata;
userdata.name = name;
userdata.name_len = strlen(name);
userdata.monitor = monitor;
userdata.found_monitor = false;
- for_each_active_monitor_output(connection, connection_type, get_monitor_by_name_callback, &userdata);
+ for_each_active_monitor_output(egl->window, egl->card_path, connection_type, get_monitor_by_name_callback, &userdata);
return userdata.found_monitor;
}
+typedef struct {
+ const gsr_monitor *monitor;
+ gsr_monitor_rotation rotation;
+ vec2i position;
+ bool match_found;
+} get_monitor_by_connector_id_userdata;
+
+static bool vec2i_eql(vec2i a, vec2i b) {
+ return a.x == b.x && a.y == b.y;
+}
+
+static void get_monitor_by_name_and_size_callback(const gsr_monitor *monitor, void *userdata) {
+ get_monitor_by_connector_id_userdata *data = (get_monitor_by_connector_id_userdata*)userdata;
+ if(monitor->name && data->monitor->name && strcmp(monitor->name, data->monitor->name) == 0 && vec2i_eql(monitor->size, data->monitor->size)) {
+ data->rotation = monitor->rotation;
+ data->position = monitor->pos;
+ data->match_found = true;
+ }
+}
+
+static void get_monitor_by_connector_id_callback(const gsr_monitor *monitor, void *userdata) {
+ get_monitor_by_connector_id_userdata *data = (get_monitor_by_connector_id_userdata*)userdata;
+ if(monitor->connector_id == data->monitor->connector_id ||
+ (!monitor->connector_id && monitor->monitor_identifier == data->monitor->monitor_identifier))
+ {
+ data->rotation = monitor->rotation;
+ data->position = monitor->pos;
+ data->match_found = true;
+ }
+}
+
+bool drm_monitor_get_display_server_data(const gsr_window *window, const gsr_monitor *monitor, gsr_monitor_rotation *monitor_rotation, vec2i *monitor_position) {
+ *monitor_rotation = GSR_MONITOR_ROT_0;
+ *monitor_position = (vec2i){0, 0};
+
+ if(gsr_window_get_display_server(window) == GSR_DISPLAY_SERVER_WAYLAND) {
+ {
+ get_monitor_by_connector_id_userdata userdata;
+ userdata.monitor = monitor;
+ userdata.rotation = GSR_MONITOR_ROT_0;
+ userdata.position = (vec2i){0, 0};
+ userdata.match_found = false;
+ gsr_window_for_each_active_monitor_output_cached(window, get_monitor_by_name_and_size_callback, &userdata);
+ if(userdata.match_found) {
+ *monitor_rotation = userdata.rotation;
+ *monitor_position = userdata.position;
+ return true;
+ }
+ }
+ {
+ get_monitor_by_connector_id_userdata userdata;
+ userdata.monitor = monitor;
+ userdata.rotation = GSR_MONITOR_ROT_0;
+ userdata.position = (vec2i){0, 0};
+ userdata.match_found = false;
+ gsr_window_for_each_active_monitor_output_cached(window, get_monitor_by_connector_id_callback, &userdata);
+ *monitor_rotation = userdata.rotation;
+ *monitor_position = userdata.position;
+ return userdata.match_found;
+ }
+ } else {
+ get_monitor_by_connector_id_userdata userdata;
+ userdata.monitor = monitor;
+ userdata.rotation = GSR_MONITOR_ROT_0;
+ userdata.position = (vec2i){0, 0};
+ userdata.match_found = false;
+ gsr_window_for_each_active_monitor_output_cached(window, get_monitor_by_connector_id_callback, &userdata);
+ *monitor_rotation = userdata.rotation;
+ *monitor_position = userdata.position;
+ return userdata.match_found;
+ }
+}
+
bool gl_get_gpu_info(gsr_egl *egl, gsr_gpu_info *info) {
const char *software_renderers[] = { "llvmpipe", "SWR", "softpipe", NULL };
bool supported = true;
@@ -216,6 +364,7 @@ bool gl_get_gpu_info(gsr_egl *egl, gsr_gpu_info *info) {
const unsigned char *gl_renderer = egl->glGetString(GL_RENDERER);
info->gpu_version = 0;
+ info->is_steam_deck = false;
if(!gl_vendor) {
fprintf(stderr, "gsr error: failed to get gpu vendor\n");
@@ -235,10 +384,14 @@ bool gl_get_gpu_info(gsr_egl *egl, gsr_gpu_info *info) {
if(strstr((const char*)gl_vendor, "AMD"))
info->vendor = GSR_GPU_VENDOR_AMD;
+ else if(strstr((const char*)gl_vendor, "Mesa") && gl_renderer && strstr((const char*)gl_renderer, "AMD"))
+ info->vendor = GSR_GPU_VENDOR_AMD;
else if(strstr((const char*)gl_vendor, "Intel"))
info->vendor = GSR_GPU_VENDOR_INTEL;
else if(strstr((const char*)gl_vendor, "NVIDIA"))
info->vendor = GSR_GPU_VENDOR_NVIDIA;
+ else if(strstr((const char*)gl_vendor, "Broadcom"))
+ info->vendor = GSR_GPU_VENDOR_BROADCOM;
else {
fprintf(stderr, "gsr error: unknown gpu vendor: %s\n", gl_vendor);
supported = false;
@@ -248,53 +401,66 @@ bool gl_get_gpu_info(gsr_egl *egl, gsr_gpu_info *info) {
if(gl_renderer) {
if(info->vendor == GSR_GPU_VENDOR_NVIDIA)
sscanf((const char*)gl_renderer, "%*s %*s %*s %d", &info->gpu_version);
+ info->is_steam_deck = strstr((const char*)gl_renderer, "vangogh") != NULL;
}
end:
return supported;
}
-bool gsr_get_valid_card_path(char *output) {
- for(int i = 0; i < 10; ++i) {
- drmVersion *ver = NULL;
- drmModePlaneResPtr planes = NULL;
- bool found_screen_card = false;
+bool try_card_has_valid_plane(const char *card_path) {
+ drmVersion *ver = NULL;
+ drmModePlaneResPtr planes = NULL;
+ bool found_screen_card = false;
- sprintf(output, DRM_DEV_NAME, DRM_DIR_NAME, i);
- int fd = open(output, O_RDONLY);
- if(fd == -1)
- continue;
-
- ver = drmGetVersion(fd);
- if(!ver || strstr(ver->name, "nouveau"))
- goto next;
+ int fd = open(card_path, O_RDONLY);
+ if(fd == -1)
+ return false;
- drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
+ ver = drmGetVersion(fd);
+ if(!ver || strstr(ver->name, "nouveau"))
+ goto next;
- planes = drmModeGetPlaneResources(fd);
- if(!planes)
- goto next;
+ drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
- for(uint32_t j = 0; j < planes->count_planes; ++j) {
- drmModePlanePtr plane = drmModeGetPlane(fd, planes->planes[j]);
- if(!plane)
- continue;
+ planes = drmModeGetPlaneResources(fd);
+ if(!planes)
+ goto next;
- if(plane->fb_id)
- found_screen_card = true;
+ for(uint32_t j = 0; j < planes->count_planes; ++j) {
+ drmModePlanePtr plane = drmModeGetPlane(fd, planes->planes[j]);
+ if(!plane)
+ continue;
- drmModeFreePlane(plane);
- if(found_screen_card)
- break;
- }
+ if(plane->fb_id)
+ found_screen_card = true;
- next:
- if(planes)
- drmModeFreePlaneResources(planes);
- if(ver)
- drmFreeVersion(ver);
- close(fd);
+ drmModeFreePlane(plane);
if(found_screen_card)
+ break;
+ }
+
+ next:
+ if(planes)
+ drmModeFreePlaneResources(planes);
+ if(ver)
+ drmFreeVersion(ver);
+ close(fd);
+ if(found_screen_card)
+ return true;
+
+ return false;
+}
+
+bool gsr_get_valid_card_path(gsr_egl *egl, char *output, bool is_monitor_capture) {
+ if(egl->dri_card_path) {
+ snprintf(output, 128, "%s", egl->dri_card_path);
+ return is_monitor_capture ? try_card_has_valid_plane(output) : true;
+ }
+
+ for(int i = 0; i < 10; ++i) {
+ snprintf(output, 128, DRM_DEV_NAME, DRM_DIR_NAME, i);
+ if(try_card_has_valid_plane(output))
return true;
}
return false;
@@ -307,7 +473,7 @@ bool gsr_card_path_get_render_path(const char *card_path, char *render_path) {
char *render_path_tmp = drmGetRenderDeviceNameFromFd(fd);
if(render_path_tmp) {
- strncpy(render_path, render_path_tmp, 128);
+ snprintf(render_path, 128, "%s", render_path_tmp);
free(render_path_tmp);
close(fd);
return true;
@@ -317,6 +483,194 @@ bool gsr_card_path_get_render_path(const char *card_path, char *render_path) {
return false;
}
-int even_number_ceil(int value) {
- return value + (value & 1);
+int create_directory_recursive(char *path) {
+ int path_len = strlen(path);
+ char *p = path;
+ char *end = path + path_len;
+ for(;;) {
+ char *slash_p = strchr(p, '/');
+
+ // Skips first '/', we don't want to try and create the root directory
+ if(slash_p == path) {
+ ++p;
+ continue;
+ }
+
+ if(!slash_p)
+ slash_p = end;
+
+ char prev_char = *slash_p;
+ *slash_p = '\0';
+ int err = mkdir(path, S_IRWXU);
+ *slash_p = prev_char;
+
+ if(err == -1 && errno != EEXIST)
+ return err;
+
+ if(slash_p == end)
+ break;
+ else
+ p = slash_p + 1;
+ }
+ return 0;
+}
+
+void setup_dma_buf_attrs(intptr_t *img_attr, uint32_t format, uint32_t width, uint32_t height, const int *fds, const uint32_t *offsets, const uint32_t *pitches, const uint64_t *modifiers, int num_planes, bool use_modifier) {
+ size_t img_attr_index = 0;
+
+ img_attr[img_attr_index++] = EGL_LINUX_DRM_FOURCC_EXT;
+ img_attr[img_attr_index++] = format;
+
+ img_attr[img_attr_index++] = EGL_WIDTH;
+ img_attr[img_attr_index++] = width;
+
+ img_attr[img_attr_index++] = EGL_HEIGHT;
+ img_attr[img_attr_index++] = height;
+
+ if(num_planes >= 1) {
+ img_attr[img_attr_index++] = EGL_DMA_BUF_PLANE0_FD_EXT;
+ img_attr[img_attr_index++] = fds[0];
+
+ img_attr[img_attr_index++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
+ img_attr[img_attr_index++] = offsets[0];
+
+ img_attr[img_attr_index++] = EGL_DMA_BUF_PLANE0_PITCH_EXT;
+ img_attr[img_attr_index++] = pitches[0];
+
+ if(use_modifier) {
+ img_attr[img_attr_index++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT;
+ img_attr[img_attr_index++] = modifiers[0] & 0xFFFFFFFFULL;
+
+ img_attr[img_attr_index++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT;
+ img_attr[img_attr_index++] = modifiers[0] >> 32ULL;
+ }
+ }
+
+ if(num_planes >= 2) {
+ img_attr[img_attr_index++] = EGL_DMA_BUF_PLANE1_FD_EXT;
+ img_attr[img_attr_index++] = fds[1];
+
+ img_attr[img_attr_index++] = EGL_DMA_BUF_PLANE1_OFFSET_EXT;
+ img_attr[img_attr_index++] = offsets[1];
+
+ img_attr[img_attr_index++] = EGL_DMA_BUF_PLANE1_PITCH_EXT;
+ img_attr[img_attr_index++] = pitches[1];
+
+ if(use_modifier) {
+ img_attr[img_attr_index++] = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT;
+ img_attr[img_attr_index++] = modifiers[1] & 0xFFFFFFFFULL;
+
+ img_attr[img_attr_index++] = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT;
+ img_attr[img_attr_index++] = modifiers[1] >> 32ULL;
+ }
+ }
+
+ if(num_planes >= 3) {
+ img_attr[img_attr_index++] = EGL_DMA_BUF_PLANE2_FD_EXT;
+ img_attr[img_attr_index++] = fds[2];
+
+ img_attr[img_attr_index++] = EGL_DMA_BUF_PLANE2_OFFSET_EXT;
+ img_attr[img_attr_index++] = offsets[2];
+
+ img_attr[img_attr_index++] = EGL_DMA_BUF_PLANE2_PITCH_EXT;
+ img_attr[img_attr_index++] = pitches[2];
+
+ if(use_modifier) {
+ img_attr[img_attr_index++] = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT;
+ img_attr[img_attr_index++] = modifiers[2] & 0xFFFFFFFFULL;
+
+ img_attr[img_attr_index++] = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT;
+ img_attr[img_attr_index++] = modifiers[2] >> 32ULL;
+ }
+ }
+
+ if(num_planes >= 4) {
+ img_attr[img_attr_index++] = EGL_DMA_BUF_PLANE3_FD_EXT;
+ img_attr[img_attr_index++] = fds[3];
+
+ img_attr[img_attr_index++] = EGL_DMA_BUF_PLANE3_OFFSET_EXT;
+ img_attr[img_attr_index++] = offsets[3];
+
+ img_attr[img_attr_index++] = EGL_DMA_BUF_PLANE3_PITCH_EXT;
+ img_attr[img_attr_index++] = pitches[3];
+
+ if(use_modifier) {
+ img_attr[img_attr_index++] = EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT;
+ img_attr[img_attr_index++] = modifiers[3] & 0xFFFFFFFFULL;
+
+ img_attr[img_attr_index++] = EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT;
+ img_attr[img_attr_index++] = modifiers[3] >> 32ULL;
+ }
+ }
+
+ img_attr[img_attr_index++] = EGL_NONE;
+ assert(img_attr_index <= 44);
+}
+
+static VADisplay video_codec_context_get_vaapi_display(AVCodecContext *video_codec_context) {
+ AVBufferRef *hw_frames_ctx = video_codec_context->hw_frames_ctx;
+ if(!hw_frames_ctx)
+ return NULL;
+
+ AVHWFramesContext *hw_frame_context = (AVHWFramesContext*)hw_frames_ctx->data;
+ AVHWDeviceContext *device_context = (AVHWDeviceContext*)hw_frame_context->device_ctx;
+ if(device_context->type != AV_HWDEVICE_TYPE_VAAPI)
+ return NULL;
+
+ AVVAAPIDeviceContext *vactx = device_context->hwctx;
+ return vactx->display;
+}
+
+bool video_codec_context_is_vaapi(AVCodecContext *video_codec_context) {
+ if(!video_codec_context)
+ return false;
+
+ AVBufferRef *hw_frames_ctx = video_codec_context->hw_frames_ctx;
+ if(!hw_frames_ctx)
+ return false;
+
+ AVHWFramesContext *hw_frame_context = (AVHWFramesContext*)hw_frames_ctx->data;
+ AVHWDeviceContext *device_context = (AVHWDeviceContext*)hw_frame_context->device_ctx;
+ return device_context->type == AV_HWDEVICE_TYPE_VAAPI;
+}
+
+vec2i scale_keep_aspect_ratio(vec2i from, vec2i to) {
+ if(from.x == 0 || from.y == 0)
+ return (vec2i){0, 0};
+
+ const double height_to_width_ratio = (double)from.y / (double)from.x;
+ from.x = to.x;
+ from.y = from.x * height_to_width_ratio;
+
+ if(from.y > to.y) {
+ const double width_height_ratio = (double)from.x / (double)from.y;
+ from.y = to.y;
+ from.x = from.y * width_height_ratio;
+ }
+
+ return from;
+}
+
+unsigned int gl_create_texture(gsr_egl *egl, int width, int height, int internal_format, unsigned int format, int filter) {
+ float border_color[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
+ if(format == GL_RG) { // UV
+ border_color[0] = 0.5f;
+ border_color[1] = 0.5f;
+ border_color[2] = 0.0f;
+ border_color[3] = 1.0f;
+ }
+
+ unsigned int texture_id = 0;
+ egl->glGenTextures(1, &texture_id);
+ egl->glBindTexture(GL_TEXTURE_2D, texture_id);
+ egl->glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, GL_UNSIGNED_BYTE, NULL);
+
+ egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
+ egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
+ egl->glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
+ egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
+ egl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
+
+ egl->glBindTexture(GL_TEXTURE_2D, 0);
+ return texture_id;
}