WiP: Tight encoder v2
parent
9b54f6d936
commit
c908c713a6
|
@ -0,0 +1,380 @@
|
|||
#include "neatvnc.h"
|
||||
#include "rfb-proto.h"
|
||||
#include "common.h"
|
||||
#include "pixels.h"
|
||||
#include "vec.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <zlib.h>
|
||||
#include <pixels.h>
|
||||
#include <pthread.h>
|
||||
#include <aml.h>
|
||||
|
||||
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
|
||||
|
||||
#define TIGHT_FILL 0x80
|
||||
#define TIGHT_JPEG 0x90
|
||||
#define TIGHT_PNG 0xA0
|
||||
#define TIGHT_BASIC 0x00
|
||||
|
||||
#define TIGHT_STREAM(n) ((n) << 4)
|
||||
#define TIGHT_RESET(n) (1 << (n))
|
||||
|
||||
#define TSL 64 /* Tile Side Length */
|
||||
|
||||
#define MAX_TILE_SIZE (2 * (TSL * TSL * 4 + sizeof(struct rfb_server_fb_rect)))
|
||||
|
||||
struct tight_encoder_v2;
|
||||
|
||||
enum tight_tile_state {
|
||||
TIGHT_TILE_READY = 0,
|
||||
TIGHT_TILE_DAMAGED,
|
||||
TIGHT_TILE_ENCODED,
|
||||
};
|
||||
|
||||
struct tight_tile {
|
||||
enum tight_tile_state state;
|
||||
struct tight_encoder_v2* parent;
|
||||
size_t size;
|
||||
char buffer[MAX_TILE_SIZE];
|
||||
};
|
||||
|
||||
struct tight_encoder_v2 {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t grid_width;
|
||||
uint32_t grid_height;
|
||||
|
||||
struct tight_tile* grid;
|
||||
|
||||
z_stream zs[4];
|
||||
uint8_t zs_mask;
|
||||
pthread_mutex_t zs_mutex;
|
||||
pthread_cond_t zs_cond;
|
||||
|
||||
const struct rfb_pixel_format* dfmt;
|
||||
const struct rfb_pixel_format* sfmt;
|
||||
const struct nvnc_fb* fb;
|
||||
|
||||
uint32_t n_rects;
|
||||
uint32_t n_jobs;
|
||||
|
||||
struct vec* dst;
|
||||
};
|
||||
|
||||
static int tight_encoder_v2_init_stream(z_stream* zs)
|
||||
{
|
||||
int rc = deflateInit2(zs,
|
||||
/* compression level: */ 1,
|
||||
/* method: */ Z_DEFLATED,
|
||||
/* window bits: */ 15,
|
||||
/* mem level: */ 9,
|
||||
/* strategy: */ Z_DEFAULT_STRATEGY);
|
||||
return rc == Z_OK ? 0 : -1;
|
||||
}
|
||||
|
||||
static inline struct tight_tile* tight_tile(struct tight_encoder_v2* self,
|
||||
uint32_t x, uint32_t y)
|
||||
{
|
||||
return &self->grid[x + y * self->grid_width];
|
||||
}
|
||||
|
||||
int tight_encoder_v2_init(struct tight_encoder_v2* self, uint32_t width,
|
||||
uint32_t height)
|
||||
{
|
||||
memset(self, 0, sizeof(*self));
|
||||
|
||||
self->width = width;
|
||||
self->height = height;
|
||||
|
||||
self->grid_width = UDIV_UP(width, 64);
|
||||
self->grid_height = UDIV_UP(height, 64);
|
||||
|
||||
self->grid = calloc(self->grid_width * self->grid_height,
|
||||
sizeof(*self->grid));
|
||||
if (!self->grid)
|
||||
return -1;
|
||||
|
||||
for (uint32_t y = 0; y < self->grid_height; ++y)
|
||||
for (uint32_t x = 0; x < self->grid_width; ++x)
|
||||
tight_tile(self, x, y)->parent = self;
|
||||
|
||||
tight_encoder_v2_init_stream(&self->zs[0]);
|
||||
tight_encoder_v2_init_stream(&self->zs[1]);
|
||||
tight_encoder_v2_init_stream(&self->zs[2]);
|
||||
tight_encoder_v2_init_stream(&self->zs[3]);
|
||||
|
||||
pthread_mutex_init(&self->zs_mutex, NULL);
|
||||
pthread_cond_init(&self->zs_cond, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tight_apply_damage(struct tight_encoder_v2* self,
|
||||
struct pixman_region16* damage)
|
||||
{
|
||||
int n_damaged = 0;
|
||||
|
||||
/* Align damage to tile grid */
|
||||
for (uint32_t y = 0; y < self->grid_height; ++y)
|
||||
for (uint32_t x = 0; x < self->grid_height; ++x) {
|
||||
struct pixman_box16 box = {
|
||||
.x1 = x * 64,
|
||||
.y1 = y * 64,
|
||||
.x2 = ((x + 1) * 64) - 1,
|
||||
.y2 = ((y + 1) * 64) - 1,
|
||||
};
|
||||
|
||||
pixman_region_overlap_t overlap
|
||||
= pixman_region_contains_rectangle(damage, &box);
|
||||
|
||||
if (overlap == PIXMAN_REGION_IN) {
|
||||
++n_damaged;
|
||||
tight_tile(self, x, y)->state = TIGHT_TILE_DAMAGED;
|
||||
} else {
|
||||
tight_tile(self, x, y)->state = TIGHT_TILE_READY;
|
||||
}
|
||||
}
|
||||
|
||||
return n_damaged;
|
||||
}
|
||||
|
||||
static void tight_encode_size(struct tight_tile* tile)
|
||||
{
|
||||
size_t size = tile->size;
|
||||
|
||||
tile->buffer[tile->size++] = (size & 0x7f) | ((size >= 128) << 7);
|
||||
|
||||
if (size >= 128)
|
||||
tile->buffer[tile->size++] =
|
||||
((size >> 7) & 0x7f) | ((size >= 16384) << 7);
|
||||
|
||||
if (size >= 16384)
|
||||
tile->buffer[tile->size++] = (size >> 14) & 0xff;
|
||||
}
|
||||
|
||||
static int calc_bytes_per_cpixel(const struct rfb_pixel_format* fmt)
|
||||
{
|
||||
return fmt->bits_per_pixel == 32 ? fmt->depth / 8
|
||||
: fmt->bits_per_pixel / 8;
|
||||
}
|
||||
|
||||
static int tight_acquire_zstream_unlocked(struct tight_encoder_v2* self)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < 4; ++i)
|
||||
if (!(self->zs_mask & (1 << i)))
|
||||
break;
|
||||
|
||||
if (i >= 4)
|
||||
return -1;
|
||||
|
||||
self->zs_mask |= (1 << i);
|
||||
return i;
|
||||
}
|
||||
|
||||
static int tight_acquire_zstream(struct tight_encoder_v2* self)
|
||||
{
|
||||
pthread_mutex_lock(&self->zs_mutex);
|
||||
|
||||
int i;
|
||||
while ((i = tight_acquire_zstream_unlocked(self)) < 0)
|
||||
pthread_cond_wait(&self->zs_cond, &self->zs_mutex);
|
||||
|
||||
pthread_mutex_unlock(&self->zs_mutex);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static void tight_release_zstream(struct tight_encoder_v2* self, int index)
|
||||
{
|
||||
pthread_mutex_lock(&self->zs_mutex);
|
||||
self->zs_mask &= ~(1 << index);
|
||||
pthread_cond_signal(&self->zs_cond);
|
||||
pthread_mutex_unlock(&self->zs_mutex);
|
||||
}
|
||||
|
||||
static int tight_deflate(struct tight_tile* tile, void* src,
|
||||
size_t len, z_stream* zs, bool flush)
|
||||
{
|
||||
zs->next_in = src;
|
||||
zs->avail_in = len;
|
||||
|
||||
do {
|
||||
if (tile->size >= MAX_TILE_SIZE)
|
||||
return -1;
|
||||
|
||||
zs->next_out = ((Bytef*)tile->buffer) + tile->size;
|
||||
zs->avail_out = MAX_TILE_SIZE - tile->size;
|
||||
|
||||
int r = deflate(zs, flush ? Z_SYNC_FLUSH : Z_NO_FLUSH);
|
||||
if (r == Z_STREAM_ERROR)
|
||||
return -1;
|
||||
|
||||
tile->size = zs->next_out - (Bytef*)tile->buffer;
|
||||
} while (zs->avail_out == 0);
|
||||
|
||||
assert(zs->avail_in == 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tight_encode_tile_basic(struct tight_encoder_v2* self,
|
||||
struct tight_tile* tile)
|
||||
{
|
||||
intptr_t index = ((intptr_t)tile - (intptr_t)self->grid) / sizeof(*tile);
|
||||
uint32_t gx = index % self->width;
|
||||
uint32_t gy = index / self->width;
|
||||
|
||||
uint32_t x = gx * TSL;
|
||||
uint32_t y_start = gy * TSL;
|
||||
|
||||
struct rfb_server_fb_rect rect = {
|
||||
.encoding = htonl(RFB_ENCODING_TIGHT),
|
||||
.x = htons(x),
|
||||
.y = htons(y_start),
|
||||
.width = htons(TSL),
|
||||
.height = htons(TSL),
|
||||
};
|
||||
|
||||
tile->size = sizeof(rect);
|
||||
memcpy(tile->buffer, &rect, sizeof(rect));
|
||||
|
||||
// TODO Figure out of way to postpone job instead of blocking
|
||||
int zs_index = tight_acquire_zstream(self);
|
||||
z_stream* zs = &self->zs[zs_index];
|
||||
|
||||
tile->buffer[tile->size++] = TIGHT_BASIC | TIGHT_STREAM(zs_index);
|
||||
|
||||
int bytes_per_cpixel = calc_bytes_per_cpixel(self->dfmt);
|
||||
assert(bytes_per_cpixel <= 4);
|
||||
uint8_t row[TSL * 4];
|
||||
|
||||
uint32_t* addr = nvnc_fb_get_addr(self->fb);
|
||||
uint32_t stride = nvnc_fb_get_width(self->fb);
|
||||
|
||||
for (uint32_t y = y_start; y < y_start + TSL; ++y) {
|
||||
void* img = addr + x + y * stride;
|
||||
pixel32_to_cpixel(row, self->dfmt, img, self->sfmt,
|
||||
bytes_per_cpixel, TSL);
|
||||
|
||||
// TODO What to do if the buffer fills up?
|
||||
if (tight_deflate(tile, row, bytes_per_cpixel * TSL,
|
||||
zs, y == y_start + TSL - 1) < 0)
|
||||
abort();
|
||||
}
|
||||
|
||||
tight_encode_size(tile);
|
||||
|
||||
tight_release_zstream(self, zs_index);
|
||||
}
|
||||
|
||||
static void tight_encode_tile(struct tight_encoder_v2* self,
|
||||
struct tight_tile* tile)
|
||||
{
|
||||
tight_encode_tile_basic(self, tile);
|
||||
//TODO Jpeg
|
||||
}
|
||||
|
||||
static int tight_encode_rect_count(struct tight_encoder_v2* self)
|
||||
{
|
||||
struct rfb_server_fb_update_msg msg = {
|
||||
.type = RFB_SERVER_TO_CLIENT_FRAMEBUFFER_UPDATE,
|
||||
.n_rects = htons(self->n_rects),
|
||||
};
|
||||
|
||||
return vec_append(self->dst, &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
static void tight_finish_tile(struct tight_encoder_v2* self, uint32_t x,
|
||||
uint32_t y)
|
||||
{
|
||||
struct tight_tile* tile = tight_tile(self, x, y);
|
||||
vec_append(self->dst, tile->buffer, tile->size);
|
||||
tile->state = TIGHT_TILE_READY;
|
||||
}
|
||||
|
||||
static void tight_finish_frame(struct tight_encoder_v2* self)
|
||||
{
|
||||
tight_encode_rect_count(self);
|
||||
|
||||
for (uint32_t y = 0; y < self->grid_height; ++y)
|
||||
for (uint32_t x = 0; x < self->grid_height; ++x)
|
||||
if (tight_tile(self, x, y)->state == TIGHT_TILE_ENCODED)
|
||||
tight_finish_tile(self, x, y);
|
||||
|
||||
// TODO Notify that encoding is done
|
||||
}
|
||||
|
||||
static void do_encode_tile(void* obj)
|
||||
{
|
||||
struct tight_tile* tile = aml_get_userdata(obj);
|
||||
struct tight_encoder_v2* self = tile->parent;
|
||||
tight_encode_tile(self, tile);
|
||||
}
|
||||
|
||||
static void on_encode_tile_done(void* obj)
|
||||
{
|
||||
struct tight_tile* tile = aml_get_userdata(obj);
|
||||
struct tight_encoder_v2* self = tile->parent;
|
||||
|
||||
tile->state = TIGHT_TILE_ENCODED;
|
||||
|
||||
if (--self->n_jobs == 0)
|
||||
tight_finish_frame(self);
|
||||
}
|
||||
|
||||
static int tight_schedule_encode_tile(struct tight_encoder_v2* self,
|
||||
uint32_t x, uint32_t y)
|
||||
{
|
||||
struct tight_tile* tile = tight_tile(self, x, y);
|
||||
|
||||
struct aml_work* work = aml_work_new(do_encode_tile,
|
||||
on_encode_tile_done, tile, NULL);
|
||||
if (!work)
|
||||
return -1;
|
||||
|
||||
int rc = aml_start(aml_get_default(), work);
|
||||
if (rc >= 0)
|
||||
++self->n_jobs;
|
||||
|
||||
aml_unref(work);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int tight_schedule_encoding_jobs(struct tight_encoder_v2* self)
|
||||
{
|
||||
for (uint32_t y = 0; y < self->grid_height; ++y)
|
||||
for (uint32_t x = 0; x < self->grid_height; ++x)
|
||||
if (tight_schedule_encode_tile(self, x, y) < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tight_encode_frame_v2(struct tight_encoder_v2* self, struct vec* dst,
|
||||
const struct rfb_pixel_format* dfmt,
|
||||
const struct nvnc_fb* src,
|
||||
const struct rfb_pixel_format* sfmt,
|
||||
struct pixman_region16* damage)
|
||||
{
|
||||
self->dfmt = dfmt;
|
||||
self->sfmt = sfmt;
|
||||
self->fb = src;
|
||||
self->dst = dst;
|
||||
|
||||
self->n_rects = tight_apply_damage(self, damage);
|
||||
if (self->n_rects == 0)
|
||||
return 0;
|
||||
|
||||
if (tight_schedule_encoding_jobs(self) < 0)
|
||||
return -1;
|
||||
|
||||
// TODO Wait for encoding to finish or change architecture of caller
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue