#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <math.h>
#include <pthread.h>
#include "drmtap.h"
#define INTEL_X_TILE_WIDTH 512
#define INTEL_X_TILE_HEIGHT 8
static int deswizzle_intel_x_tiled(const void *src, void *dst,
uint32_t width, uint32_t height,
uint32_t src_stride, uint32_t dst_stride) {
uint32_t bpp = 4;
uint32_t tile_w_bytes = INTEL_X_TILE_WIDTH;
uint32_t tile_h = INTEL_X_TILE_HEIGHT;
uint32_t tiles_x = (src_stride + tile_w_bytes - 1) / tile_w_bytes;
const uint8_t *s = (const uint8_t *)src;
uint8_t *d = (uint8_t *)dst;
for (uint32_t y = 0; y < height; y++) {
uint32_t tile_row = y / tile_h;
uint32_t tile_y = y % tile_h;
for (uint32_t x = 0; x < width; x++) {
uint32_t x_bytes = x * bpp;
uint32_t tile_col = x_bytes / tile_w_bytes;
uint32_t tile_x_bytes = x_bytes % tile_w_bytes;
uint32_t tile_idx = tile_row * tiles_x + tile_col;
uint32_t tile_size = tile_w_bytes * tile_h;
uint32_t src_off = tile_idx * tile_size +
tile_y * tile_w_bytes + tile_x_bytes;
memcpy(d + y * dst_stride + x * bpp,
s + src_off, bpp);
}
}
return 0;
}
#define INTEL_Y_TILE_WIDTH 128
#define INTEL_Y_TILE_HEIGHT 32
#define INTEL_Y_OWORD 16
static int deswizzle_intel_y_tiled(const void *src, void *dst,
uint32_t width, uint32_t height,
uint32_t src_stride, uint32_t dst_stride) {
uint32_t bpp = 4;
uint32_t tile_w_bytes = INTEL_Y_TILE_WIDTH;
uint32_t tile_h = INTEL_Y_TILE_HEIGHT;
uint32_t tiles_x = (src_stride + tile_w_bytes - 1) / tile_w_bytes;
uint32_t oword = INTEL_Y_OWORD;
uint32_t columns_per_tile = tile_w_bytes / oword;
const uint8_t *s = (const uint8_t *)src;
uint8_t *d = (uint8_t *)dst;
for (uint32_t y = 0; y < height; y++) {
uint32_t tile_row = y / tile_h;
uint32_t tile_y = y % tile_h;
for (uint32_t x = 0; x < width; x++) {
uint32_t x_bytes = x * bpp;
uint32_t tile_col = x_bytes / tile_w_bytes;
uint32_t in_tile_x = x_bytes % tile_w_bytes;
uint32_t column = in_tile_x / oword;
uint32_t column_offset = in_tile_x % oword;
uint32_t tile_idx = tile_row * tiles_x + tile_col;
uint32_t tile_size = tile_w_bytes * tile_h;
uint32_t src_off = tile_idx * tile_size +
column * (oword * tile_h) +
tile_y * oword +
column_offset;
memcpy(d + y * dst_stride + x * bpp,
s + src_off, bpp);
}
}
(void)columns_per_tile;
return 0;
}
#define NV_TILE_WIDTH 16
#define NV_TILE_HEIGHT 128
static int deswizzle_nvidia_x_tiled(const void *src, void *dst,
uint32_t width, uint32_t height,
uint32_t src_stride, uint32_t dst_stride) {
uint32_t bpp = 4;
uint32_t tile_w = NV_TILE_WIDTH;
uint32_t tile_h = NV_TILE_HEIGHT;
uint32_t tile_w_bytes = tile_w * bpp;
uint32_t tiles_x = (width + tile_w - 1) / tile_w;
(void)src_stride;
const uint8_t *s = (const uint8_t *)src;
uint8_t *d = (uint8_t *)dst;
for (uint32_t y = 0; y < height; y++) {
uint32_t tile_row = y / tile_h;
uint32_t tile_y = y % tile_h;
for (uint32_t tx = 0; tx < tiles_x; tx++) {
uint32_t tile_idx = tile_row * tiles_x + tx;
uint32_t tile_size = tile_w_bytes * tile_h;
uint32_t src_off = tile_idx * tile_size + tile_y * tile_w_bytes;
uint32_t dst_x = tx * tile_w;
uint32_t copy_w = (dst_x + tile_w > width)
? width - dst_x : tile_w;
memcpy(d + y * dst_stride + dst_x * bpp,
s + src_off, copy_w * bpp);
}
}
return 0;
}
static void convert_ar30_to_xrgb8888(const void *src, void *dst,
uint32_t width, uint32_t height,
uint32_t src_stride,
uint32_t dst_stride) {
for (uint32_t y = 0; y < height; y++) {
const uint32_t *s = (const uint32_t *)
((const uint8_t *)src + y * src_stride);
uint32_t *d = (uint32_t *)
((uint8_t *)dst + y * dst_stride);
for (uint32_t x = 0; x < width; x++) {
uint32_t pixel = s[x];
uint8_t r = (uint8_t)((pixel >> 22) & 0xFF);
uint8_t g = (uint8_t)((pixel >> 12) & 0xFF);
uint8_t b = (uint8_t)((pixel >> 2) & 0xFF);
d[x] = (0xFFu << 24) | ((uint32_t)r << 16) |
((uint32_t)g << 8) | b;
}
}
}
static void convert_abgr_to_argb(const void *src, void *dst,
uint32_t width, uint32_t height,
uint32_t src_stride, uint32_t dst_stride) {
for (uint32_t y = 0; y < height; y++) {
const uint32_t *s = (const uint32_t *)
((const uint8_t *)src + y * src_stride);
uint32_t *d = (uint32_t *)
((uint8_t *)dst + y * dst_stride);
for (uint32_t x = 0; x < width; x++) {
uint32_t pixel = s[x];
uint8_t a = (uint8_t)(pixel >> 24);
uint8_t b_ch = (uint8_t)(pixel >> 16);
uint8_t g = (uint8_t)(pixel >> 8);
uint8_t r = (uint8_t)(pixel);
d[x] = ((uint32_t)a << 24) | ((uint32_t)r << 16) |
((uint32_t)g << 8) | b_ch;
}
}
}
int drmtap_deswizzle(const void *src, void *dst,
uint32_t width, uint32_t height,
uint32_t src_stride, uint32_t dst_stride,
uint64_t modifier) {
if (!src || !dst || width == 0 || height == 0) {
return -EINVAL;
}
if (modifier == 0 ) {
for (uint32_t y = 0; y < height; y++) {
memcpy((uint8_t *)dst + y * dst_stride,
(const uint8_t *)src + y * src_stride,
width * 4);
}
return 0;
}
uint8_t vendor = (uint8_t)(modifier >> 56);
uint8_t mod_type = (uint8_t)(modifier & 0xFF);
if (vendor == 0x01) {
if (mod_type == 0x01) {
return deswizzle_intel_x_tiled(src, dst, width, height,
src_stride, dst_stride);
} else if (mod_type == 0x02 || mod_type == 0x03) {
return deswizzle_intel_y_tiled(src, dst, width, height,
src_stride, dst_stride);
} else if (mod_type == 0x05 || mod_type == 0x06 ||
mod_type == 0x07 || mod_type == 0x08) {
return -ENOTSUP;
}
}
if (vendor == 0x10) {
return deswizzle_nvidia_x_tiled(src, dst, width, height,
src_stride, dst_stride);
}
for (uint32_t y = 0; y < height; y++) {
memcpy((uint8_t *)dst + y * dst_stride,
(const uint8_t *)src + y * src_stride,
width * 4);
}
return 0;
}
#define DRMTAP_SDR_WHITE_NITS 203.0
#define DRMTAP_PQ_LUT_N 1024
#define DRMTAP_SRGB_LUT_N 4096
static float g_pq_lut[DRMTAP_PQ_LUT_N];
static uint8_t g_srgb_lut[DRMTAP_SRGB_LUT_N];
static pthread_once_t g_hdr_once = PTHREAD_ONCE_INIT;
static double pq_eotf_nits(double e) {
const double m1 = 2610.0 / 16384.0;
const double m2 = 2523.0 / 4096.0 * 128.0;
const double c1 = 3424.0 / 4096.0;
const double c2 = 2413.0 / 4096.0 * 32.0;
const double c3 = 2392.0 / 4096.0 * 32.0;
if (e <= 0.0) return 0.0;
double ep = pow(e, 1.0 / m2);
double num = ep - c1;
if (num < 0.0) num = 0.0;
double den = c2 - c3 * ep;
if (den <= 0.0) return 10000.0;
return 10000.0 * pow(num / den, 1.0 / m1);
}
static double srgb_oetf(double c) {
if (c <= 0.0) return 0.0;
if (c >= 1.0) return 1.0;
if (c <= 0.0031308) return 12.92 * c;
return 1.055 * pow(c, 1.0 / 2.4) - 0.055;
}
static void hdr_lut_init(void) {
for (int i = 0; i < DRMTAP_PQ_LUT_N; i++) {
g_pq_lut[i] = (float)pq_eotf_nits((double)i / (DRMTAP_PQ_LUT_N - 1));
}
for (int i = 0; i < DRMTAP_SRGB_LUT_N; i++) {
double s = srgb_oetf((double)i / (DRMTAP_SRGB_LUT_N - 1));
int v = (int)(s * 255.0 + 0.5);
if (v < 0) v = 0;
if (v > 255) v = 255;
g_srgb_lut[i] = (uint8_t)v;
}
}
static double tonemap_softknee(double x, double peak_n) {
const double knee = 0.90;
if (x <= knee) return x;
if (peak_n < knee + 0.5) peak_n = knee + 0.5;
if (x >= peak_n) return 1.0;
double t = (x - knee) / (peak_n - knee);
return knee + (1.0 - knee) * (t * (2.0 - t));
}
static uint8_t to_srgb8(double linear) {
if (linear <= 0.0) return g_srgb_lut[0];
if (linear >= 1.0) return g_srgb_lut[DRMTAP_SRGB_LUT_N - 1];
int idx = (int)(linear * (DRMTAP_SRGB_LUT_N - 1) + 0.5);
return g_srgb_lut[idx];
}
static void tonemap_rgb_linear(double r_lin, double g_lin, double b_lin,
double peak_n,
uint8_t *r8, uint8_t *g8, uint8_t *b8) {
double r = 1.660491 * r_lin - 0.587641 * g_lin - 0.072850 * b_lin;
double g = -0.124550 * r_lin + 1.132900 * g_lin - 0.008349 * b_lin;
double b = -0.018151 * r_lin - 0.100579 * g_lin + 1.118730 * b_lin;
if (r < 0.0) r = 0.0;
if (g < 0.0) g = 0.0;
if (b < 0.0) b = 0.0;
*r8 = to_srgb8(tonemap_softknee(r / DRMTAP_SDR_WHITE_NITS, peak_n));
*g8 = to_srgb8(tonemap_softknee(g / DRMTAP_SDR_WHITE_NITS, peak_n));
*b8 = to_srgb8(tonemap_softknee(b / DRMTAP_SDR_WHITE_NITS, peak_n));
}
static double hdr_peak_units(uint32_t max_nits) {
double peak = (max_nits > 0) ? (double)max_nits : 1000.0;
return peak / DRMTAP_SDR_WHITE_NITS;
}
int drmtap_tonemap_hdr10(const void *src, void *dst,
uint32_t width, uint32_t height,
uint32_t src_stride, uint32_t dst_stride,
uint32_t src_format, uint32_t max_nits) {
if (!src || !dst || width == 0 || height == 0) {
return -EINVAL;
}
if (src_format != 0x30335258u && src_format != 0x30335241u) {
return -ENOTSUP;
}
size_t row_bytes = (size_t)width * 4u;
if (row_bytes > UINT32_MAX || src_stride < row_bytes ||
dst_stride < row_bytes) {
return -EINVAL;
}
pthread_once(&g_hdr_once, hdr_lut_init);
double peak_n = hdr_peak_units(max_nits);
for (uint32_t y = 0; y < height; y++) {
const uint32_t *s = (const uint32_t *)
((const uint8_t *)src + (size_t)y * src_stride);
uint32_t *d = (uint32_t *)((uint8_t *)dst + (size_t)y * dst_stride);
for (uint32_t x = 0; x < width; x++) {
uint32_t pixel = s[x];
uint8_t r, g, b;
tonemap_rgb_linear(g_pq_lut[(pixel >> 20) & 0x3FF],
g_pq_lut[(pixel >> 10) & 0x3FF],
g_pq_lut[(pixel) & 0x3FF],
peak_n, &r, &g, &b);
d[x] = (0xFFu << 24) | ((uint32_t)r << 16) |
((uint32_t)g << 8) | b;
}
}
return 0;
}
int drmtap_convert_rgb16(const void *src, void *dst,
uint32_t width, uint32_t height,
uint32_t src_stride, uint32_t dst_stride,
int bgr, uint32_t eotf, uint32_t max_nits) {
if (!src || !dst || width == 0 || height == 0) {
return -EINVAL;
}
size_t src_row = (size_t)width * 8u;
size_t dst_row = (size_t)width * 4u;
if (src_row > UINT32_MAX || dst_row > UINT32_MAX ||
src_stride < src_row || dst_stride < dst_row) {
return -EINVAL;
}
int hdr = (eotf == 2 );
if (hdr) {
pthread_once(&g_hdr_once, hdr_lut_init);
}
double peak_n = hdr_peak_units(max_nits);
int ri = bgr ? 0 : 2;
int bi = bgr ? 2 : 0;
for (uint32_t y = 0; y < height; y++) {
const uint16_t *s = (const uint16_t *)
((const uint8_t *)src + (size_t)y * src_stride);
uint32_t *d = (uint32_t *)((uint8_t *)dst + (size_t)y * dst_stride);
for (uint32_t x = 0; x < width; x++) {
const uint16_t *px = s + (size_t)x * 4;
uint16_t cr = px[ri], cg = px[1], cb = px[bi];
uint8_t r8, g8, b8;
if (hdr) {
tonemap_rgb_linear(pq_eotf_nits((double)cr / 65535.0),
pq_eotf_nits((double)cg / 65535.0),
pq_eotf_nits((double)cb / 65535.0),
peak_n, &r8, &g8, &b8);
} else {
r8 = (uint8_t)(cr >> 8);
g8 = (uint8_t)(cg >> 8);
b8 = (uint8_t)(cb >> 8);
}
d[x] = (0xFFu << 24) | ((uint32_t)r8 << 16) |
((uint32_t)g8 << 8) | b8;
}
}
return 0;
}
int drmtap_convert_format(const void *src, void *dst,
uint32_t width, uint32_t height,
uint32_t src_stride, uint32_t dst_stride,
uint32_t src_format, uint32_t dst_format) {
if (!src || !dst || width == 0 || height == 0) {
return -EINVAL;
}
if (src_format == dst_format) {
for (uint32_t y = 0; y < height; y++) {
memcpy((uint8_t *)dst + y * dst_stride,
(const uint8_t *)src + y * src_stride,
width * 4);
}
return 0;
}
#define DRM_FMT_XRGB8888 0x34325258u
#define DRM_FMT_ARGB8888 0x34325241u
#define DRM_FMT_ABGR8888 0x34324241u
#define DRM_FMT_XRGB2101010 0x30335258u
#define DRM_FMT_ARGB2101010 0x30335241u
if ((src_format == DRM_FMT_XRGB2101010 ||
src_format == DRM_FMT_ARGB2101010) &&
(dst_format == DRM_FMT_XRGB8888 ||
dst_format == DRM_FMT_ARGB8888)) {
convert_ar30_to_xrgb8888(src, dst, width, height,
src_stride, dst_stride);
return 0;
}
if (src_format == DRM_FMT_ABGR8888 &&
(dst_format == DRM_FMT_ARGB8888 ||
dst_format == DRM_FMT_XRGB8888)) {
convert_abgr_to_argb(src, dst, width, height,
src_stride, dst_stride);
return 0;
}
return -ENOTSUP;
}
int drmtap_diff_frames(const void *frame_a, const void *frame_b,
uint32_t width, uint32_t height, uint32_t stride,
drmtap_rect *rects_out, int max_rects,
int tile_size) {
if (!frame_a || !frame_b || !rects_out || max_rects <= 0 || tile_size <= 0) {
return -EINVAL;
}
int dirty_count = 0;
uint32_t tiles_x = (width + (uint32_t)tile_size - 1) / (uint32_t)tile_size;
uint32_t tiles_y = (height + (uint32_t)tile_size - 1) / (uint32_t)tile_size;
for (uint32_t ty = 0; ty < tiles_y; ty++) {
for (uint32_t tx = 0; tx < tiles_x; tx++) {
uint32_t rx = tx * (uint32_t)tile_size;
uint32_t ry = ty * (uint32_t)tile_size;
uint32_t rw = (rx + (uint32_t)tile_size > width)
? width - rx : (uint32_t)tile_size;
uint32_t rh = (ry + (uint32_t)tile_size > height)
? height - ry : (uint32_t)tile_size;
int dirty = 0;
for (uint32_t row = ry; row < ry + rh && !dirty; row++) {
const uint8_t *a = (const uint8_t *)frame_a +
row * stride + rx * 4;
const uint8_t *b = (const uint8_t *)frame_b +
row * stride + rx * 4;
if (memcmp(a, b, rw * 4) != 0) {
dirty = 1;
}
}
if (dirty) {
if (dirty_count < max_rects) {
rects_out[dirty_count].x = rx;
rects_out[dirty_count].y = ry;
rects_out[dirty_count].w = rw;
rects_out[dirty_count].h = rh;
}
dirty_count++;
}
}
}
return dirty_count;
}