diff --git a/Makefile b/Makefile index fdaf933..d2ed0c1 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ SOURCES := \ src/vec.c \ src/zrle.c \ src/pixels.c \ + src/damage.c \ include common.mk diff --git a/include/neatvnc.h b/include/neatvnc.h index c799992..3919aad 100644 --- a/include/neatvnc.h +++ b/include/neatvnc.h @@ -53,6 +53,7 @@ typedef void (*nvnc_fb_req_fn)(struct nvnc_client*, bool is_incremental, uint16_t x, uint16_t y, uint16_t width, uint16_t height); typedef void (*nvnc_client_fn)(struct nvnc_client*); +typedef void (*nvnc_damage_fn)(struct pixman_region16 *damage, void *userdata); struct nvnc *nvnc_open(const char *addr, uint16_t port); void nvnc_close(struct nvnc *self); @@ -80,3 +81,13 @@ void nvnc_set_client_cleanup_fn(struct nvnc_client *self, nvnc_client_fn fn); */ int nvnc_update_fb(struct nvnc *self, const struct nvnc_fb* fb, const struct pixman_region16* region); + +/* + * Find the regions that differ between fb0 and fb1. Regions outside the hinted + * rectangle region are not guaranteed to be checked. + * + * This is a utility function that may be used to reduce network traffic. + */ +int nvnc_check_damage(const struct nvnc_fb *fb0, const struct nvnc_fb *fb1, + int x_hint, int y_hint, int width_hint, int height_hint, + nvnc_damage_fn on_check_done, void *userdata); diff --git a/src/damage.c b/src/damage.c new file mode 100644 index 0000000..82b2801 --- /dev/null +++ b/src/damage.c @@ -0,0 +1,156 @@ +#include "neatvnc.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define EXPORT __attribute__((visibility("default"))) +#define ALIGN_DOWN(a, b) (((a) / (b)) * (b)) + +struct damage_check { + uv_work_t work; + const struct nvnc_fb *fb0; + const struct nvnc_fb *fb1; + int x_hint; + int y_hint; + int width_hint; + int height_hint; + nvnc_damage_fn on_done; + struct pixman_region16 damage; + void *userdata; +}; + +static bool fbs_are_compatible(const struct nvnc_fb *fb0, + const struct nvnc_fb *fb1) +{ + return fb0->fourcc_format == fb1->fourcc_format + && fb0->fourcc_modifier == fb1->fourcc_modifier + && fb0->nvnc_modifier == fb1->nvnc_modifier + && fb0->width == fb1->width + && fb0->height == fb1->height; +} + +static inline bool are_tiles_equal(const uint32_t* a, const uint32_t* b, + int stride, int width, int height) +{ + for (int y = 0; y < height; ++y) + if (memcmp(a + y * stride, b + y * stride, width * 4) != 0) + return false; + + return true; +} + +#define TILE_SIDE_LENGTH 32 +int check_damage_linear(struct pixman_region16 *damage, + const struct nvnc_fb *fb0, const struct nvnc_fb *fb1, + int x_hint, int y_hint, int width_hint, int height_hint) +{ + uint32_t *b0 = fb0->addr; + uint32_t *b1 = fb1->addr; + + int width = fb0->width; + int height = fb0->height; + + assert(x_hint + width_hint <= width); + assert(y_hint + height_hint <= height); + + int x_start = ALIGN_DOWN(x_hint, TILE_SIDE_LENGTH); + int y_start = ALIGN_DOWN(y_hint, TILE_SIDE_LENGTH); + + for (int y = y_start; y < y_start + height_hint; + y += TILE_SIDE_LENGTH) { + int tile_height = MIN(TILE_SIDE_LENGTH, height - y); + + for (int x = x_start; x < x_start + width_hint; + x += TILE_SIDE_LENGTH) { + int tile_width = MIN(TILE_SIDE_LENGTH, width - x); + + if (are_tiles_equal(b0 + x + y * width, + b1 + x + y * width, + width, tile_width, tile_height)) + continue; + + pixman_region_union_rect(damage, damage, x, y, + tile_width, tile_height); + } + } + + return 0; +} +#undef TILE_SIDE_LENGTH + +void do_damage_check_linear(uv_work_t *work) +{ + struct damage_check *check = (void*)work; + + check_damage_linear(&check->damage, check->fb0, check->fb1, + check->x_hint, check->y_hint, + check->width_hint, check->height_hint); +} + +void on_damage_check_done_linear(uv_work_t *work, int status) +{ + (void)status; + + struct damage_check *check = (void*)work; + + check->on_done(&check->damage, check->userdata); + + pixman_region_fini(&check->damage); + free(check); +} + +int check_damage_linear_threaded(const struct nvnc_fb *fb0, + const struct nvnc_fb *fb1, + int x_hint, int y_hint, + int width_hint, int height_hint, + nvnc_damage_fn on_check_done, + void *userdata) +{ + struct damage_check *work = calloc(1, sizeof(*work)); + if (!work) + return -1; + + work->on_done = on_check_done; + work->userdata = userdata; + work->fb0 = fb0; + work->fb1 = fb1; + work->x_hint = x_hint; + work->y_hint = y_hint; + work->width_hint = width_hint; + work->height_hint = height_hint; + pixman_region_init(&work->damage); + + /* TODO: Spread the work into more tasks */ + int rc = uv_queue_work(uv_default_loop(), &work->work, + do_damage_check_linear, + on_damage_check_done_linear); + if (rc < 0) + free(work); + + return rc; +} + +EXPORT +int nvnc_check_damage(const struct nvnc_fb *fb0, const struct nvnc_fb *fb1, + int x_hint, int y_hint, int width_hint, int height_hint, + nvnc_damage_fn on_check_done, void *userdata) +{ + if (!fbs_are_compatible(fb0, fb1)) + return -1; + + switch (fb0->fourcc_modifier) { + case DRM_FORMAT_MOD_LINEAR: + return check_damage_linear_threaded(fb0, fb1, x_hint, y_hint, + width_hint, height_hint, + on_check_done, userdata); + } + + return -1; +}