Compare commits

...

53 Commits

Author SHA1 Message Date
Andri Yngvason 39976ab776 Allow the user to adjust the FPS limit 2020-07-07 14:22:38 +00:00
Andri Yngvason 0ed6ed9e3c util: latency_report: Update probes 2020-07-07 13:45:03 +00:00
Andri Yngvason a4c093bbdf Add dtrace probes for rendering and damage checking 2020-07-07 13:44:41 +00:00
Andri Yngvason 5d2dd100b7 Destroy wl_registry on exit 2020-07-07 12:58:19 +00:00
Andri Yngvason b47b3cf7c1 Use native pixel format 2020-07-07 11:53:19 +00:00
Andri Yngvason c40cb823d6 pixels: Add more formats and take into account endianness 2020-07-07 11:38:13 +00:00
Andri Yngvason 78c1616a46 Exit on mode change intead of crashing 2020-07-07 10:08:27 +00:00
Andri Yngvason f49791880f pixels: Add copyright notice 2020-07-07 10:06:44 +00:00
Andri Yngvason cc0c6246d2 buffer: Cleanup dmabuf params after use 2020-07-07 10:03:51 +00:00
Andri Yngvason 8a10ffb33f Clean up damage refinery on exit 2020-07-06 17:46:28 +00:00
Andri Yngvason 1786e0d549 util: Add valgrind suppressions and a helper script 2020-07-06 17:40:19 +00:00
Andri Yngvason cb2a5f543e Clean up gbm device on exit 2020-07-06 17:37:19 +00:00
Andri Yngvason d23c443e14 transform-util: Add copyright notice 2020-07-06 17:21:25 +00:00
Andri Yngvason 2ebbd86aec util: Add copyright notice 2020-07-06 17:15:07 +00:00
Andri Yngvason 82f7bd93ed pixman-renderer: Add copyright notice 2020-07-06 17:14:38 +00:00
Andri Yngvason cdd021b21c buffer: Add copyright notice 2020-07-06 17:14:10 +00:00
Andri Yngvason aa1bfb58e9 damage-refinery: Add copyright notice 2020-07-06 17:13:26 +00:00
Andri Yngvason c2d6a7daa6 Align with aml API changes 2020-07-06 16:45:15 +00:00
Andri Yngvason 63d15d9fe8 screencopy: Actually use y-inversion flag 2020-06-28 14:26:53 +00:00
Andri Yngvason dc568d14e8 screencopy: Set the rate limit to 30 Hz 2020-06-27 00:03:59 +00:00
Andri Yngvason 815b6ad52f Remove frame-capture abstraction 2020-06-26 21:44:58 +00:00
Andri Yngvason 023333a4d1 Move damage hints into buffer abstraction 2020-06-26 18:05:54 +00:00
Andri Yngvason f14eb5a813 buffer: Add damage field 2020-06-26 18:05:31 +00:00
Andri Yngvason 30c0909656 .gitignore: Add .pem files 2020-06-26 18:05:07 +00:00
Andri Yngvason ea10193747 README: Update dependencies 2020-06-25 21:40:04 +00:00
Andri Yngvason a67c9a6837 Remove shaders 2020-06-25 21:37:07 +00:00
Andri Yngvason 0ea4bd6646 Remove dead code 2020-06-25 21:28:30 +00:00
Andri Yngvason a7283e68fd buffer: Fix pool release/acquire 2020-06-24 20:45:33 +00:00
Andri Yngvason 0b15b465df buffer: Fix error check and use non-linear 2020-06-24 18:32:24 +00:00
Andri Yngvason 8c9211eed6 screencopy: map dmabuf after it's ready 2020-06-24 18:31:29 +00:00
Andri Yngvason c4ca264772 buffer: Unmap before release/destroy 2020-06-24 17:43:41 +00:00
Andri Yngvason 6a015d9dc0 screencopy: Use linux-dmabuf if available 2020-06-23 23:04:57 +00:00
Andri Yngvason 3facb3a58e main: Add gbm and linux-dmabuf 2020-06-23 23:04:01 +00:00
Andri Yngvason bea97623c9 protocols: Use screencopy v3 2020-06-23 22:50:32 +00:00
Andri Yngvason ef91f040f9 buffer: Use create_immed 2020-06-23 21:50:55 +00:00
Andri Yngvason a43fdd4779 buffer: Add DMA-BUFs 2020-06-23 18:30:08 +00:00
Andri Yngvason f546173bf7 protocols: add linux-dmabuf 2020-06-23 17:34:22 +00:00
Andri Yngvason 0f09581686 buffer: Add specific handling for different buffers 2020-06-23 17:29:48 +00:00
Andri Yngvason 473ce5eb23 buffer: Add buffer type to interface 2020-06-23 17:20:43 +00:00
Andri Yngvason 17ee85dfd0 main: Refine damage hints 2020-06-22 20:09:17 +00:00
Andri Yngvason ba23559283 damage-refinery: Use damage hint 2020-06-22 20:07:24 +00:00
Andri Yngvason 7fda124c69 damage-refinery: Hash directly instead of copying 2020-06-22 19:37:25 +00:00
Andri Yngvason a4e1a957c9 damage-refinery: Use negative stride to handle y-inversion 2020-06-21 19:34:49 +00:00
Andri Yngvason fe136fcd29 Create hash based damage checker 2020-06-21 17:53:13 +00:00
Andri Yngvason a48b7a66b0 Fix damage transform 2020-06-21 14:34:24 +00:00
Andri Yngvason d9ff6292dc pixman-renderer: Use negative stride for y-inversion 2020-06-21 14:25:23 +00:00
Andri Yngvason 197c165fa4 Transform damage coordinates 2020-06-21 14:03:00 +00:00
Andri Yngvason 1f229c3129 Add utilities for managing output transforms 2020-06-21 14:02:19 +00:00
Andri Yngvason 49a2d578d9 Create pixman renderer 2020-06-21 11:54:46 +00:00
Andri Yngvason 47a8dc8040 buffer: Leave pixman out of buffers 2020-06-20 21:43:35 +00:00
Andri Yngvason 4740967bfd Extract pixel format conversion into own file 2020-06-20 21:42:29 +00:00
Andri Yngvason 5e5806fcf6 Render using pixman 2020-06-20 21:07:58 +00:00
Andri Yngvason 03114c80e2 Create a buffer abstraction 2020-06-20 20:58:14 +00:00
36 changed files with 1919 additions and 2078 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ subprojects
.clang_complete .clang_complete
.ycm_extra_conf.py .ycm_extra_conf.py
perf.* perf.*
*.pem

View File

@ -36,10 +36,10 @@ dnf install wayvnc
## Building ## Building
### Runtime Dependencies ### Runtime Dependencies
* aml * aml
* EGL * drm
* gbm
* libxkbcommon * libxkbcommon
* neatvnc * neatvnc
* OpenGL ES V2.0
* pixman * pixman
### Build Dependencies ### Build Dependencies

82
include/buffer.h 100644
View File

@ -0,0 +1,82 @@
/*
* Copyright (c) 2020 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include "sys/queue.h"
#include <unistd.h>
#include <stdbool.h>
#include <stdint.h>
#include <pixman.h>
struct wl_buffer;
struct gbm_bo;
enum wv_buffer_type {
WV_BUFFER_UNSPEC = 0,
WV_BUFFER_SHM,
WV_BUFFER_DMABUF,
};
struct wv_buffer {
enum wv_buffer_type type;
TAILQ_ENTRY(wv_buffer) link;
struct wl_buffer* wl_buffer;
void* pixels;
size_t size;
int width, height, stride;
uint32_t format;
bool y_inverted;
struct pixman_region16 damage;
/* The following is only applicable to DMABUF */
struct gbm_bo* bo;
void* bo_map_handle;
};
TAILQ_HEAD(wv_buffer_queue, wv_buffer);
struct wv_buffer_pool {
struct wv_buffer_queue queue;
enum wv_buffer_type type;
int width, height, stride;
uint32_t format;
};
struct wv_buffer* wv_buffer_create(enum wv_buffer_type, int width, int height,
int stride, uint32_t fourcc);
void wv_buffer_destroy(struct wv_buffer* self);
int wv_buffer_map(struct wv_buffer* self);
void wv_buffer_unmap(struct wv_buffer* self);
void wv_buffer_damage_rect(struct wv_buffer* self, int x, int y, int width,
int height);
void wv_buffer_damage_whole(struct wv_buffer* self);
void wv_buffer_damage_clear(struct wv_buffer* self);
struct wv_buffer_pool* wv_buffer_pool_create(enum wv_buffer_type, int width,
int height, int stride, uint32_t format);
void wv_buffer_pool_destroy(struct wv_buffer_pool* pool);
void wv_buffer_pool_resize(struct wv_buffer_pool* pool, enum wv_buffer_type,
int width, int height, int stride, uint32_t format);
struct wv_buffer* wv_buffer_pool_acquire(struct wv_buffer_pool* pool);
void wv_buffer_pool_release(struct wv_buffer_pool* pool,
struct wv_buffer* buffer);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2019 - 2020 Andri Yngvason * Copyright (c) 2020 Andri Yngvason
* *
* Permission to use, copy, modify, and/or distribute this software for any * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -14,15 +14,24 @@
* PERFORMANCE OF THIS SOFTWARE. * PERFORMANCE OF THIS SOFTWARE.
*/ */
precision mediump float; #pragma once
uniform sampler2D u_tex0; #include <stdint.h>
uniform sampler2D u_tex1;
varying vec2 v_texture; struct pixman_region16;
struct wv_buffer;
void main() struct damage_refinery {
{ uint32_t* hashes;
float r = float(texture2D(u_tex0, v_texture).rgb != texture2D(u_tex1, v_texture).rgb); uint32_t width;
gl_FragColor = vec4(r); uint32_t height;
} };
int damage_refinery_init(struct damage_refinery* self, uint32_t width,
uint32_t height);
void damage_refinery_destroy(struct damage_refinery* self);
void damage_refine(struct damage_refinery* self,
struct pixman_region16* refined,
struct pixman_region16* hint,
const struct wv_buffer* buffer);

View File

@ -1,40 +0,0 @@
/*
* Copyright (c) 2020 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <stdint.h>
#include <stdio.h>
struct pixman_region16;
struct pixman_box16;
void damage_check_row(uint8_t* dst, const uint8_t* src, uint32_t width);
void damage_check_tile_row(struct pixman_region16* damage,
uint8_t* row_buffer, const uint8_t* buffer,
uint32_t y_start, uint32_t width, uint32_t height);
void damage_check(struct pixman_region16* damage, const uint8_t* buffer,
uint32_t width, uint32_t height, struct pixman_box16* hint);
int damage_check_async(uint8_t* buffer, uint32_t width, uint32_t height,
struct pixman_box16* hint,
void (*on_done)(struct pixman_region16*, void*),
void* userdata);
void damage_dump(FILE* stream, struct pixman_region16* damage,
uint32_t width, uint32_t height, uint32_t tile_size);

View File

@ -1,59 +0,0 @@
/*
* Copyright (c) 2019 - 2020 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <aml.h>
#include "frame-capture.h"
struct zwlr_export_dmabuf_manager_v1;
struct zwlr_export_dmabuf_frame_v1;
struct wl_output;
struct aml_timer;
struct dmabuf_plane {
int fd;
uint32_t offset;
uint32_t size;
uint32_t pitch;
uint64_t modifier;
};
struct dmabuf_frame {
uint32_t width;
uint32_t height;
uint32_t format;
uint32_t n_planes;
struct dmabuf_plane plane[4];
};
struct dmabuf_capture {
struct frame_capture fc;
struct zwlr_export_dmabuf_manager_v1* manager;
struct zwlr_export_dmabuf_frame_v1* zwlr_frame;
struct dmabuf_frame frame;
uint64_t render_finish_time;
uint64_t start_time;
struct aml_timer* timer;
};
void dmabuf_capture_init(struct dmabuf_capture* self);
void dmabuf_capture_destroy(struct dmabuf_capture* self);

View File

@ -1,84 +0,0 @@
/*
* Copyright (c) 2019 - 2020 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
struct wl_output;
struct nvnc_fb;
struct renderer;
enum frame_capture_status {
CAPTURE_STOPPED = 0,
CAPTURE_IN_PROGRESS,
CAPTURE_FAILED,
CAPTURE_FATAL,
CAPTURE_DONE,
};
enum frame_capture_options {
CAPTURE_NOW = 1 << 0,
};
struct frame_capture {
enum frame_capture_status status;
bool overlay_cursor;
struct wl_output* wl_output;
void* userdata;
void (*on_done)(struct frame_capture*);
struct {
uint32_t fourcc_format;
uint32_t width;
uint32_t height;
uint32_t stride;
} frame_info;
struct {
uint32_t x;
uint32_t y;
uint32_t width;
uint32_t height;
} damage_hint;
struct {
void (*render)(struct frame_capture*, struct renderer*,
struct nvnc_fb* fb);
int (*start)(struct frame_capture*, enum frame_capture_options);
void (*stop)(struct frame_capture*);
} backend;
};
static inline int frame_capture_start(struct frame_capture* self, enum frame_capture_options options)
{
return self->backend.start(self, options);
}
static inline void frame_capture_stop(struct frame_capture* self)
{
self->backend.stop(self);
}
static inline void frame_capture_render(struct frame_capture* self,
struct renderer* renderer,
struct nvnc_fb* fb)
{
return self->backend.render(self, renderer, fb);
}

View File

@ -0,0 +1,48 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2014 Joseph Werle
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef MURMURHASH_H
#define MURMURHASH_H 1
#include <stdint.h>
#define MURMURHASH_VERSION "0.0.3"
#ifdef __cplusplus
extern "C" {
#endif
/**
* Returns a murmur hash of `key' based on `seed'
* using the MurmurHash3 algorithm
*/
uint32_t
murmurhash (const char *, uint32_t, uint32_t);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2019 - 2020 Andri Yngvason * Copyright (c) 2020 Andri Yngvason
* *
* Permission to use, copy, modify, and/or distribute this software for any * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -14,15 +14,12 @@
* PERFORMANCE OF THIS SOFTWARE. * PERFORMANCE OF THIS SOFTWARE.
*/ */
#extension GL_OES_EGL_image_external: require #pragma once
precision mediump float; #include <pixman.h>
#include <wayland-client.h>
#include <libdrm/drm_fourcc.h>
#include <stdbool.h>
uniform samplerExternalOES u_tex0; enum wl_shm_format fourcc_to_wl_shm(uint32_t in);
bool fourcc_to_pixman_fmt(pixman_format_code_t* dst, uint32_t src);
varying vec2 v_texture;
void main()
{
gl_FragColor = texture2D(u_tex0, v_texture);
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2019 - 2020 Andri Yngvason * Copyright (c) 2020 Andri Yngvason
* *
* Permission to use, copy, modify, and/or distribute this software for any * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -14,15 +14,14 @@
* PERFORMANCE OF THIS SOFTWARE. * PERFORMANCE OF THIS SOFTWARE.
*/ */
uniform mat2 u_proj; #pragma once
attribute vec2 pos; #include <wayland-client.h>
attribute vec2 texture;
varying vec2 v_texture; struct nvnc_fb;
struct wv_buffer;
struct pixman_region16;
void main() void wv_pixman_render(struct nvnc_fb* dst, const struct wv_buffer* src,
{ enum wl_output_transform transform,
v_texture = vec2(texture.s, 1.0 - texture.t); struct pixman_region16* damage);
gl_Position = vec4(u_proj * pos, 0, 1);
}

View File

@ -1,82 +0,0 @@
/*
* Copyright (c) 2019 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <GLES2/gl2.h>
#include <EGL/egl.h>
#include <unistd.h>
#include <wayland-client.h>
struct dmabuf_frame;
struct output;
enum renderer_input_type {
RENDERER_INPUT_FB,
RENDERER_INPUT_DMABUF,
};
struct renderer_fbo {
GLuint rbo;
GLuint fbo;
GLuint tex;
};
struct renderer {
EGLDisplay display;
EGLContext context;
const struct output* output;
GLint read_format;
GLint read_type;
struct renderer_fbo frame_fbo[2];
int frame_index;
struct renderer_fbo damage_fbo;
struct {
GLuint program;
GLint u_tex0;
GLint u_proj;
} frame_shader;
struct {
GLuint program;
GLint u_tex0;
GLint u_tex1;
} damage_shader;
};
int renderer_init(struct renderer* self, const struct output* output,
enum renderer_input_type input_type);
void renderer_destroy(struct renderer* self);
int render_dmabuf(struct renderer* self, struct dmabuf_frame* frame);
int render_framebuffer(struct renderer* self, const void* addr, uint32_t format,
uint32_t width, uint32_t height, uint32_t stride);
/* Copy a horizontal stripe from the GL frame into a pixel buffer */
void renderer_read_frame(struct renderer* self, void* dst, uint32_t y,
uint32_t height);
void renderer_read_damage(struct renderer* self, void* dst, uint32_t y,
uint32_t height);
void renderer_swap_textures(struct renderer* self);
void render_damage(struct renderer* self);

View File

@ -19,8 +19,8 @@
#include <stdbool.h> #include <stdbool.h>
#include "wlr-screencopy-unstable-v1.h" #include "wlr-screencopy-unstable-v1.h"
#include "frame-capture.h"
#include "smooth.h" #include "smooth.h"
#include "buffer.h"
struct zwlr_screencopy_manager_v1; struct zwlr_screencopy_manager_v1;
struct zwlr_screencopy_frame_v1; struct zwlr_screencopy_frame_v1;
@ -31,23 +31,26 @@ struct aml_timer;
struct renderer; struct renderer;
enum screencopy_status { enum screencopy_status {
SCREENCOPY_STATUS_CAPTURING = 0, SCREENCOPY_STOPPED = 0,
SCREENCOPY_STATUS_FATAL, SCREENCOPY_IN_PROGRESS,
SCREENCOPY_STATUS_FAILED, SCREENCOPY_FAILED,
SCREENCOPY_STATUS_DONE, SCREENCOPY_FATAL,
SCREENCOPY_DONE,
}; };
struct screencopy { struct screencopy {
struct frame_capture frame_capture; enum screencopy_status status;
struct wl_shm* wl_shm; struct wv_buffer_pool* pool;
struct wl_buffer* buffer; struct wv_buffer* front;
struct wv_buffer* back;
void* pixels;
size_t bufsize;
struct zwlr_screencopy_manager_v1* manager; struct zwlr_screencopy_manager_v1* manager;
struct zwlr_screencopy_frame_v1* frame; struct zwlr_screencopy_frame_v1* frame;
int version;
void* userdata;
void (*on_done)(struct screencopy*);
uint64_t last_time; uint64_t last_time;
uint64_t start_time; uint64_t start_time;
@ -56,7 +59,23 @@ struct screencopy {
struct smooth delay_smoother; struct smooth delay_smoother;
double delay; double delay;
bool is_immediate_copy; bool is_immediate_copy;
bool overlay_cursor;
struct wl_output* wl_output;
uint32_t wl_shm_width, wl_shm_height, wl_shm_stride;
enum wl_shm_format wl_shm_format;
bool have_linux_dmabuf;
uint32_t dmabuf_width, dmabuf_height;
uint32_t fourcc;
double rate_limit;
}; };
void screencopy_init(struct screencopy* self); void screencopy_init(struct screencopy* self);
void screencopy_destroy(struct screencopy* self); void screencopy_destroy(struct screencopy* self);
int screencopy_start(struct screencopy* self);
int screencopy_start_immediate(struct screencopy* self);
void screencopy_stop(struct screencopy* self);

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2020 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <wayland-client.h>
#include <pixman.h>
void wv_region_transform(struct pixman_region16 *dst,
struct pixman_region16 *src, enum wl_output_transform transform,
int width, int height);
void wv_pixman_transform_from_wl_output_transform(pixman_transform_t* dst,
enum wl_output_transform src, int width, int height);
enum wl_output_transform wv_output_transform_invert(enum wl_output_transform tr);
enum wl_output_transform wv_output_transform_compose(
enum wl_output_transform tr_a, enum wl_output_transform tr_b);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2019 - 2020 Andri Yngvason * Copyright (c) 2020 Andri Yngvason
* *
* Permission to use, copy, modify, and/or distribute this software for any * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -14,13 +14,7 @@
* PERFORMANCE OF THIS SOFTWARE. * PERFORMANCE OF THIS SOFTWARE.
*/ */
precision mediump float; #pragma once
uniform sampler2D u_tex0; #define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
#define ALIGN_UP(n, a) (UDIV_UP(n, a) * a)
varying vec2 v_texture;
void main()
{
gl_FragColor = texture2D(u_tex0, v_texture);
}

View File

@ -28,8 +28,8 @@ libm = cc.find_library('m', required: false)
librt = cc.find_library('rt', required: false) librt = cc.find_library('rt', required: false)
pixman = dependency('pixman-1') pixman = dependency('pixman-1')
egl = dependency('egl') gbm = dependency('gbm')
glesv2 = dependency('glesv2') drm = dependency('libdrm')
xkbcommon = dependency('xkbcommon') xkbcommon = dependency('xkbcommon')
wayland_client = dependency('wayland-client') wayland_client = dependency('wayland-client')
@ -57,8 +57,6 @@ subdir('protocols')
sources = [ sources = [
'src/main.c', 'src/main.c',
'src/render.c',
'src/dmabuf.c',
'src/strlcpy.c', 'src/strlcpy.c',
'src/shm.c', 'src/shm.c',
'src/screencopy.c', 'src/screencopy.c',
@ -69,7 +67,12 @@ sources = [
'src/smooth.c', 'src/smooth.c',
'src/cfg.c', 'src/cfg.c',
'src/intset.c', 'src/intset.c',
'src/damage.c', 'src/buffer.c',
'src/pixels.c',
'src/pixman-renderer.c',
'src/transform-util.c',
'src/damage-refinery.c',
'src/murmurhash.c',
] ]
dependencies = [ dependencies = [
@ -77,8 +80,8 @@ dependencies = [
librt, librt,
pixman, pixman,
aml, aml,
egl, gbm,
glesv2, drm,
wayland_client, wayland_client,
neatvnc, neatvnc,
xkbcommon, xkbcommon,
@ -105,8 +108,6 @@ configure_file(
configuration: config, configuration: config,
) )
install_subdir('shaders', install_dir: 'share/wayvnc')
executable( executable(
'wayvnc', 'wayvnc',
sources, sources,
@ -114,5 +115,3 @@ executable(
include_directories: inc, include_directories: inc,
install: true, install: true,
) )
subdir('test')

View File

@ -0,0 +1,362 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="linux_dmabuf_unstable_v1">
<copyright>
Copyright © 2014, 2015 Collabora, Ltd.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<interface name="zwp_linux_dmabuf_v1" version="3">
<description summary="factory for creating dmabuf-based wl_buffers">
Following the interfaces from:
https://www.khronos.org/registry/egl/extensions/EXT/EGL_EXT_image_dma_buf_import.txt
https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_image_dma_buf_import_modifiers.txt
and the Linux DRM sub-system's AddFb2 ioctl.
This interface offers ways to create generic dmabuf-based
wl_buffers. Immediately after a client binds to this interface,
the set of supported formats and format modifiers is sent with
'format' and 'modifier' events.
The following are required from clients:
- Clients must ensure that either all data in the dma-buf is
coherent for all subsequent read access or that coherency is
correctly handled by the underlying kernel-side dma-buf
implementation.
- Don't make any more attachments after sending the buffer to the
compositor. Making more attachments later increases the risk of
the compositor not being able to use (re-import) an existing
dmabuf-based wl_buffer.
The underlying graphics stack must ensure the following:
- The dmabuf file descriptors relayed to the server will stay valid
for the whole lifetime of the wl_buffer. This means the server may
at any time use those fds to import the dmabuf into any kernel
sub-system that might accept it.
To create a wl_buffer from one or more dmabufs, a client creates a
zwp_linux_dmabuf_params_v1 object with a zwp_linux_dmabuf_v1.create_params
request. All planes required by the intended format are added with
the 'add' request. Finally, a 'create' or 'create_immed' request is
issued, which has the following outcome depending on the import success.
The 'create' request,
- on success, triggers a 'created' event which provides the final
wl_buffer to the client.
- on failure, triggers a 'failed' event to convey that the server
cannot use the dmabufs received from the client.
For the 'create_immed' request,
- on success, the server immediately imports the added dmabufs to
create a wl_buffer. No event is sent from the server in this case.
- on failure, the server can choose to either:
- terminate the client by raising a fatal error.
- mark the wl_buffer as failed, and send a 'failed' event to the
client. If the client uses a failed wl_buffer as an argument to any
request, the behaviour is compositor implementation-defined.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible changes
may be added together with the corresponding interface version bump.
Backward incompatible changes are done by bumping the version number in
the protocol and interface names and resetting the interface version.
Once the protocol is to be declared stable, the 'z' prefix and the
version number in the protocol and interface names are removed and the
interface version number is reset.
</description>
<request name="destroy" type="destructor">
<description summary="unbind the factory">
Objects created through this interface, especially wl_buffers, will
remain valid.
</description>
</request>
<request name="create_params">
<description summary="create a temporary object for buffer parameters">
This temporary object is used to collect multiple dmabuf handles into
a single batch to create a wl_buffer. It can only be used once and
should be destroyed after a 'created' or 'failed' event has been
received.
</description>
<arg name="params_id" type="new_id" interface="zwp_linux_buffer_params_v1"
summary="the new temporary"/>
</request>
<event name="format">
<description summary="supported buffer format">
This event advertises one buffer format that the server supports.
All the supported formats are advertised once when the client
binds to this interface. A roundtrip after binding guarantees
that the client has received all supported formats.
For the definition of the format codes, see the
zwp_linux_buffer_params_v1::create request.
Warning: the 'format' event is likely to be deprecated and replaced
with the 'modifier' event introduced in zwp_linux_dmabuf_v1
version 3, described below. Please refrain from using the information
received from this event.
</description>
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
</event>
<event name="modifier" since="3">
<description summary="supported buffer format modifier">
This event advertises the formats that the server supports, along with
the modifiers supported for each format. All the supported modifiers
for all the supported formats are advertised once when the client
binds to this interface. A roundtrip after binding guarantees that
the client has received all supported format-modifier pairs.
For legacy support, DRM_FORMAT_MOD_INVALID (that is, modifier_hi ==
0x00ffffff and modifier_lo == 0xffffffff) is allowed in this event.
It indicates that the server can support the format with an implicit
modifier. When a plane has DRM_FORMAT_MOD_INVALID as its modifier, it
is as if no explicit modifier is specified. The effective modifier
will be derived from the dmabuf.
For the definition of the format and modifier codes, see the
zwp_linux_buffer_params_v1::create and zwp_linux_buffer_params_v1::add
requests.
</description>
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
<arg name="modifier_hi" type="uint"
summary="high 32 bits of layout modifier"/>
<arg name="modifier_lo" type="uint"
summary="low 32 bits of layout modifier"/>
</event>
</interface>
<interface name="zwp_linux_buffer_params_v1" version="3">
<description summary="parameters for creating a dmabuf-based wl_buffer">
This temporary object is a collection of dmabufs and other
parameters that together form a single logical buffer. The temporary
object may eventually create one wl_buffer unless cancelled by
destroying it before requesting 'create'.
Single-planar formats only require one dmabuf, however
multi-planar formats may require more than one dmabuf. For all
formats, an 'add' request must be called once per plane (even if the
underlying dmabuf fd is identical).
You must use consecutive plane indices ('plane_idx' argument for 'add')
from zero to the number of planes used by the drm_fourcc format code.
All planes required by the format must be given exactly once, but can
be given in any order. Each plane index can be set only once.
</description>
<enum name="error">
<entry name="already_used" value="0"
summary="the dmabuf_batch object has already been used to create a wl_buffer"/>
<entry name="plane_idx" value="1"
summary="plane index out of bounds"/>
<entry name="plane_set" value="2"
summary="the plane index was already set"/>
<entry name="incomplete" value="3"
summary="missing or too many planes to create a buffer"/>
<entry name="invalid_format" value="4"
summary="format not supported"/>
<entry name="invalid_dimensions" value="5"
summary="invalid width or height"/>
<entry name="out_of_bounds" value="6"
summary="offset + stride * height goes out of dmabuf bounds"/>
<entry name="invalid_wl_buffer" value="7"
summary="invalid wl_buffer resulted from importing dmabufs via
the create_immed request on given buffer_params"/>
</enum>
<request name="destroy" type="destructor">
<description summary="delete this object, used or not">
Cleans up the temporary data sent to the server for dmabuf-based
wl_buffer creation.
</description>
</request>
<request name="add">
<description summary="add a dmabuf to the temporary set">
This request adds one dmabuf to the set in this
zwp_linux_buffer_params_v1.
The 64-bit unsigned value combined from modifier_hi and modifier_lo
is the dmabuf layout modifier. DRM AddFB2 ioctl calls this the
fb modifier, which is defined in drm_mode.h of Linux UAPI.
This is an opaque token. Drivers use this token to express tiling,
compression, etc. driver-specific modifications to the base format
defined by the DRM fourcc code.
Warning: It should be an error if the format/modifier pair was not
advertised with the modifier event. This is not enforced yet because
some implementations always accept DRM_FORMAT_MOD_INVALID. Also
version 2 of this protocol does not have the modifier event.
This request raises the PLANE_IDX error if plane_idx is too large.
The error PLANE_SET is raised if attempting to set a plane that
was already set.
</description>
<arg name="fd" type="fd" summary="dmabuf fd"/>
<arg name="plane_idx" type="uint" summary="plane index"/>
<arg name="offset" type="uint" summary="offset in bytes"/>
<arg name="stride" type="uint" summary="stride in bytes"/>
<arg name="modifier_hi" type="uint"
summary="high 32 bits of layout modifier"/>
<arg name="modifier_lo" type="uint"
summary="low 32 bits of layout modifier"/>
</request>
<enum name="flags">
<entry name="y_invert" value="1" summary="contents are y-inverted"/>
<entry name="interlaced" value="2" summary="content is interlaced"/>
<entry name="bottom_first" value="4" summary="bottom field first"/>
</enum>
<request name="create">
<description summary="create a wl_buffer from the given dmabufs">
This asks for creation of a wl_buffer from the added dmabuf
buffers. The wl_buffer is not created immediately but returned via
the 'created' event if the dmabuf sharing succeeds. The sharing
may fail at runtime for reasons a client cannot predict, in
which case the 'failed' event is triggered.
The 'format' argument is a DRM_FORMAT code, as defined by the
libdrm's drm_fourcc.h. The Linux kernel's DRM sub-system is the
authoritative source on how the format codes should work.
The 'flags' is a bitfield of the flags defined in enum "flags".
'y_invert' means the that the image needs to be y-flipped.
Flag 'interlaced' means that the frame in the buffer is not
progressive as usual, but interlaced. An interlaced buffer as
supported here must always contain both top and bottom fields.
The top field always begins on the first pixel row. The temporal
ordering between the two fields is top field first, unless
'bottom_first' is specified. It is undefined whether 'bottom_first'
is ignored if 'interlaced' is not set.
This protocol does not convey any information about field rate,
duration, or timing, other than the relative ordering between the
two fields in one buffer. A compositor may have to estimate the
intended field rate from the incoming buffer rate. It is undefined
whether the time of receiving wl_surface.commit with a new buffer
attached, applying the wl_surface state, wl_surface.frame callback
trigger, presentation, or any other point in the compositor cycle
is used to measure the frame or field times. There is no support
for detecting missed or late frames/fields/buffers either, and
there is no support whatsoever for cooperating with interlaced
compositor output.
The composited image quality resulting from the use of interlaced
buffers is explicitly undefined. A compositor may use elaborate
hardware features or software to deinterlace and create progressive
output frames from a sequence of interlaced input buffers, or it
may produce substandard image quality. However, compositors that
cannot guarantee reasonable image quality in all cases are recommended
to just reject all interlaced buffers.
Any argument errors, including non-positive width or height,
mismatch between the number of planes and the format, bad
format, bad offset or stride, may be indicated by fatal protocol
errors: INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS,
OUT_OF_BOUNDS.
Dmabuf import errors in the server that are not obvious client
bugs are returned via the 'failed' event as non-fatal. This
allows attempting dmabuf sharing and falling back in the client
if it fails.
This request can be sent only once in the object's lifetime, after
which the only legal request is destroy. This object should be
destroyed after issuing a 'create' request. Attempting to use this
object after issuing 'create' raises ALREADY_USED protocol error.
It is not mandatory to issue 'create'. If a client wants to
cancel the buffer creation, it can just destroy this object.
</description>
<arg name="width" type="int" summary="base plane width in pixels"/>
<arg name="height" type="int" summary="base plane height in pixels"/>
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
<arg name="flags" type="uint" summary="see enum flags"/>
</request>
<event name="created">
<description summary="buffer creation succeeded">
This event indicates that the attempted buffer creation was
successful. It provides the new wl_buffer referencing the dmabuf(s).
Upon receiving this event, the client should destroy the
zlinux_dmabuf_params object.
</description>
<arg name="buffer" type="new_id" interface="wl_buffer"
summary="the newly created wl_buffer"/>
</event>
<event name="failed">
<description summary="buffer creation failed">
This event indicates that the attempted buffer creation has
failed. It usually means that one of the dmabuf constraints
has not been fulfilled.
Upon receiving this event, the client should destroy the
zlinux_buffer_params object.
</description>
</event>
<request name="create_immed" since="2">
<description summary="immediately create a wl_buffer from the given
dmabufs">
This asks for immediate creation of a wl_buffer by importing the
added dmabufs.
In case of import success, no event is sent from the server, and the
wl_buffer is ready to be used by the client.
Upon import failure, either of the following may happen, as seen fit
by the implementation:
- the client is terminated with one of the following fatal protocol
errors:
- INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS, OUT_OF_BOUNDS,
in case of argument errors such as mismatch between the number
of planes and the format, bad format, non-positive width or
height, or bad offset or stride.
- INVALID_WL_BUFFER, in case the cause for failure is unknown or
plaform specific.
- the server creates an invalid wl_buffer, marks it as failed and
sends a 'failed' event to the client. The result of using this
invalid wl_buffer as an argument in any request by the client is
defined by the compositor implementation.
This takes the same arguments as a 'create' request, and obeys the
same restrictions.
</description>
<arg name="buffer_id" type="new_id" interface="wl_buffer"
summary="id for the newly created wl_buffer"/>
<arg name="width" type="int" summary="base plane width in pixels"/>
<arg name="height" type="int" summary="base plane height in pixels"/>
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
<arg name="flags" type="uint" summary="see enum flags"/>
</request>
</interface>
</protocol>

View File

@ -19,6 +19,7 @@ client_protocols = [
'wlr-virtual-pointer-unstable-v1.xml', 'wlr-virtual-pointer-unstable-v1.xml',
'virtual-keyboard-unstable-v1.xml', 'virtual-keyboard-unstable-v1.xml',
'xdg-output-unstable-v1.xml', 'xdg-output-unstable-v1.xml',
'linux-dmabuf-unstable-v1.xml',
] ]
client_protos_src = [] client_protos_src = []

View File

@ -38,7 +38,7 @@
interface version number is reset. interface version number is reset.
</description> </description>
<interface name="zwlr_screencopy_manager_v1" version="2"> <interface name="zwlr_screencopy_manager_v1" version="3">
<description summary="manager to inform clients and begin capturing"> <description summary="manager to inform clients and begin capturing">
This object is a manager which offers requests to start capturing from a This object is a manager which offers requests to start capturing from a
source. source.
@ -80,13 +80,18 @@
</request> </request>
</interface> </interface>
<interface name="zwlr_screencopy_frame_v1" version="2"> <interface name="zwlr_screencopy_frame_v1" version="3">
<description summary="a frame ready for copy"> <description summary="a frame ready for copy">
This object represents a single frame. This object represents a single frame.
When created, a "buffer" event will be sent. The client will then be able When created, a series of buffer events will be sent, each representing a
to send a "copy" request. If the capture is successful, the compositor supported buffer type. The "buffer_done" event is sent afterwards to
will send a "flags" followed by a "ready" event. indicate that all supported buffer types have been enumerated. The client
will then be able to send a "copy" request. If the capture is successful,
the compositor will send a "flags" followed by a "ready" event.
For objects version 2 or lower, wl_shm buffers are always supported, ie.
the "buffer" event is guaranteed to be sent.
If the capture failed, the "failed" event is sent. This can happen anytime If the capture failed, the "failed" event is sent. This can happen anytime
before the "ready" event. before the "ready" event.
@ -96,14 +101,12 @@
</description> </description>
<event name="buffer"> <event name="buffer">
<description summary="buffer information"> <description summary="wl_shm buffer information">
Provides information about the frame's buffer. This event is sent once Provides information about wl_shm buffer parameters that need to be
as soon as the frame is created. used for this frame. This event is sent once after the frame is created
if wl_shm buffers are supported.
The client should then create a buffer with the provided attributes, and
send a "copy" request.
</description> </description>
<arg name="format" type="uint" summary="buffer format"/> <arg name="format" type="uint" enum="wl_shm.format" summary="buffer format"/>
<arg name="width" type="uint" summary="buffer width"/> <arg name="width" type="uint" summary="buffer width"/>
<arg name="height" type="uint" summary="buffer height"/> <arg name="height" type="uint" summary="buffer height"/>
<arg name="stride" type="uint" summary="buffer stride"/> <arg name="stride" type="uint" summary="buffer stride"/>
@ -112,8 +115,9 @@
<request name="copy"> <request name="copy">
<description summary="copy the frame"> <description summary="copy the frame">
Copy the frame to the supplied buffer. The buffer must have a the Copy the frame to the supplied buffer. The buffer must have a the
correct size, see zwlr_screencopy_frame_v1.buffer. The buffer needs to correct size, see zwlr_screencopy_frame_v1.buffer and
have a supported format. zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a
supported format.
If the frame is successfully copied, a "flags" and a "ready" events are If the frame is successfully copied, a "flags" and a "ready" events are
sent. Otherwise, a "failed" event is sent. sent. Otherwise, a "failed" event is sent.
@ -203,5 +207,26 @@
<arg name="width" type="uint" summary="current width"/> <arg name="width" type="uint" summary="current width"/>
<arg name="height" type="uint" summary="current height"/> <arg name="height" type="uint" summary="current height"/>
</event> </event>
<!-- Version 3 additions -->
<event name="linux_dmabuf" since="3">
<description summary="linux-dmabuf buffer information">
Provides information about linux-dmabuf buffer parameters that need to
be used for this frame. This event is sent once after the frame is
created if linux-dmabuf buffers are supported.
</description>
<arg name="format" type="uint" summary="fourcc pixel format"/>
<arg name="width" type="uint" summary="buffer width"/>
<arg name="height" type="uint" summary="buffer height"/>
</event>
<event name="buffer_done" since="3">
<description summary="all buffer types reported">
This event is sent once after all buffer events have been sent.
The client should proceed to create a buffer of one of the supported
types, and send a "copy" request.
</description>
</event>
</interface> </interface>
</protocol> </protocol>

View File

@ -1,26 +0,0 @@
/*
* Copyright (c) 2019 - 2020 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
attribute vec2 pos;
attribute vec2 texture;
varying vec2 v_texture;
void main()
{
v_texture = vec2(texture.s, 1.0 - texture.t);
gl_Position = vec4(pos, 0, 1);
}

View File

@ -1,28 +0,0 @@
/*
* Copyright (c) 2019 - 2020 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
uniform mat2 u_proj;
attribute vec2 pos;
attribute vec2 texture;
varying vec2 v_texture;
void main()
{
v_texture = texture;
gl_Position = vec4(u_proj * pos, 0, 1);
}

354
src/buffer.c 100644
View File

@ -0,0 +1,354 @@
/*
* Copyright (c) 2020 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <stdbool.h>
#include <assert.h>
#include <sys/mman.h>
#include <libdrm/drm_fourcc.h>
#include <wayland-client.h>
#include <gbm.h>
#include <pixman.h>
#include "linux-dmabuf-unstable-v1.h"
#include "shm.h"
#include "sys/queue.h"
#include "buffer.h"
#include "pixels.h"
extern struct wl_shm* wl_shm;
extern struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf;
extern struct gbm_device* gbm_device;
struct wv_buffer* wv_buffer_create_shm(int width,
int height, int stride, uint32_t fourcc)
{
assert(wl_shm);
enum wl_shm_format wl_fmt = fourcc_to_wl_shm(fourcc);
struct wv_buffer* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->type = WV_BUFFER_SHM;
self->width = width;
self->height = height;
self->stride = stride;
self->format = fourcc;
self->size = height * stride;
int fd = shm_alloc_fd(self->size);
if (fd < 0)
goto failure;
self->pixels = mmap(NULL, self->size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (!self->pixels)
goto mmap_failure;
struct wl_shm_pool* pool = wl_shm_create_pool(wl_shm, fd, self->size);
if (!pool)
goto pool_failure;
self->wl_buffer = wl_shm_pool_create_buffer(pool, 0, width, height,
stride, wl_fmt);
wl_shm_pool_destroy(pool);
if (!self->wl_buffer)
goto shm_failure;
pixman_region_init(&self->damage);
close(fd);
return self;
shm_failure:
pool_failure:
munmap(self->pixels, self->size);
mmap_failure:
close(fd);
failure:
free(self);
return NULL;
}
static struct wv_buffer* wv_buffer_create_dmabuf(int width, int height,
uint32_t fourcc)
{
assert(zwp_linux_dmabuf);
struct wv_buffer* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
self->type = WV_BUFFER_DMABUF;
self->width = width;
self->height = height;
self->format = fourcc;
self->bo = gbm_bo_create(gbm_device, width, height, fourcc,
GBM_BO_USE_RENDERING);
if (!self->bo)
goto bo_failure;
struct zwp_linux_buffer_params_v1* params;
params = zwp_linux_dmabuf_v1_create_params(zwp_linux_dmabuf);
if (!params)
goto params_failure;
uint32_t offset = gbm_bo_get_offset(self->bo, 0);
uint32_t stride = gbm_bo_get_stride(self->bo);
uint64_t mod = gbm_bo_get_modifier(self->bo);
int fd = gbm_bo_get_fd(self->bo);
if (fd < 0)
goto fd_failure;
zwp_linux_buffer_params_v1_add(params, fd, 0, offset, stride,
mod >> 32, mod & 0xffffffff);
self->wl_buffer = zwp_linux_buffer_params_v1_create_immed(params, width,
height, fourcc, /* flags */ 0);
zwp_linux_buffer_params_v1_destroy(params);
close(fd);
if (!self->wl_buffer)
goto buffer_failure;
return self;
buffer_failure:
fd_failure:
zwp_linux_buffer_params_v1_destroy(params);
params_failure:
gbm_bo_destroy(self->bo);
bo_failure:
free(self);
return NULL;
}
struct wv_buffer* wv_buffer_create(enum wv_buffer_type type, int width,
int height, int stride, uint32_t fourcc)
{
switch (type) {
case WV_BUFFER_SHM:
return wv_buffer_create_shm(width, height, stride, fourcc);
case WV_BUFFER_DMABUF:
return wv_buffer_create_dmabuf(width, height, fourcc);
case WV_BUFFER_UNSPEC:;
}
abort();
return NULL;
}
static void wv_buffer_destroy_shm(struct wv_buffer* self)
{
wl_buffer_destroy(self->wl_buffer);
munmap(self->pixels, self->size);
free(self);
}
static void wv_buffer_destroy_dmabuf(struct wv_buffer* self)
{
wl_buffer_destroy(self->wl_buffer);
gbm_bo_destroy(self->bo);
free(self);
}
void wv_buffer_destroy(struct wv_buffer* self)
{
pixman_region_fini(&self->damage);
wv_buffer_unmap(self);
switch (self->type) {
case WV_BUFFER_SHM:
wv_buffer_destroy_shm(self);
return;
case WV_BUFFER_DMABUF:
wv_buffer_destroy_dmabuf(self);
return;
case WV_BUFFER_UNSPEC:;
}
abort();
}
static int wv_buffer_map_dmabuf(struct wv_buffer* self)
{
if (self->bo_map_handle)
return 0;
uint32_t stride = 0;
self->pixels = gbm_bo_map(self->bo, 0, 0, self->width, self->height,
GBM_BO_TRANSFER_READ, &stride, &self->bo_map_handle);
self->stride = stride;
if (self->pixels)
return 0;
self->bo_map_handle = NULL;
return -1;
}
int wv_buffer_map(struct wv_buffer* self)
{
switch (self->type) {
case WV_BUFFER_SHM:
return 0;
case WV_BUFFER_DMABUF:
return wv_buffer_map_dmabuf(self);
case WV_BUFFER_UNSPEC:;
}
abort();
}
static void wv_buffer_unmap_dmabuf(struct wv_buffer* self)
{
if (self->bo_map_handle)
gbm_bo_unmap(self->bo, self->bo_map_handle);
self->bo_map_handle = NULL;
}
void wv_buffer_unmap(struct wv_buffer* self)
{
switch (self->type) {
case WV_BUFFER_SHM:
return;
case WV_BUFFER_DMABUF:
return wv_buffer_unmap_dmabuf(self);
case WV_BUFFER_UNSPEC:;
}
abort();
}
void wv_buffer_damage_rect(struct wv_buffer* self, int x, int y, int width,
int height)
{
pixman_region_union_rect(&self->damage, &self->damage, x, y, width,
height);
}
void wv_buffer_damage_whole(struct wv_buffer* self)
{
wv_buffer_damage_rect(self, 0, 0, self->width, self->height);
}
void wv_buffer_damage_clear(struct wv_buffer* self)
{
pixman_region_clear(&self->damage);
}
struct wv_buffer_pool* wv_buffer_pool_create(enum wv_buffer_type type,
int width, int height, int stride, uint32_t format)
{
struct wv_buffer_pool* self = calloc(1, sizeof(*self));
if (!self)
return NULL;
TAILQ_INIT(&self->queue);
self->type = type;
self->width = width;
self->height = height;
self->stride = stride;
self->format = format;
return self;
}
static void wv_buffer_pool_clear(struct wv_buffer_pool* pool)
{
while (!TAILQ_EMPTY(&pool->queue)) {
struct wv_buffer* buffer = TAILQ_FIRST(&pool->queue);
TAILQ_REMOVE(&pool->queue, buffer, link);
wv_buffer_destroy(buffer);
}
}
void wv_buffer_pool_destroy(struct wv_buffer_pool* pool)
{
wv_buffer_pool_clear(pool);
free(pool);
}
void wv_buffer_pool_resize(struct wv_buffer_pool* pool,
enum wv_buffer_type type, int width, int height, int stride,
uint32_t format)
{
if (pool->type != type || pool->width != width || pool->height != height
|| pool->stride != stride || pool->format != format) {
wv_buffer_pool_clear(pool);
}
pool->type = type;
pool->width = width;
pool->height = height;
pool->stride = stride;
pool->format = format;
}
static bool wv_buffer_pool_match_buffer(struct wv_buffer_pool* pool,
struct wv_buffer* buffer)
{
if (pool->type != buffer->type)
return false;
switch (pool->type) {
case WV_BUFFER_SHM:
if (pool->stride != buffer->stride)
return false;
/* fall-through */
case WV_BUFFER_DMABUF:
if (pool->width != buffer->width
|| pool->height != buffer->height
|| pool->format != buffer->format)
return false;
return true;
case WV_BUFFER_UNSPEC:
abort();
}
return false;
}
struct wv_buffer* wv_buffer_pool_acquire(struct wv_buffer_pool* pool)
{
struct wv_buffer* buffer = TAILQ_FIRST(&pool->queue);
if (buffer) {
assert(wv_buffer_pool_match_buffer(pool, buffer));
TAILQ_REMOVE(&pool->queue, buffer, link);
return buffer;
}
return wv_buffer_create(pool->type, pool->width, pool->height,
pool->stride, pool->format);
}
void wv_buffer_pool_release(struct wv_buffer_pool* pool,
struct wv_buffer* buffer)
{
wv_buffer_damage_clear(buffer);
wv_buffer_unmap(buffer);
if (wv_buffer_pool_match_buffer(pool, buffer)) {
TAILQ_INSERT_TAIL(&pool->queue, buffer, link);
} else {
wv_buffer_destroy(buffer);
}
}

View File

@ -0,0 +1,139 @@
/*
* Copyright (c) 2020 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <assert.h>
#include <pixman.h>
#include <sys/param.h>
#include "damage-refinery.h"
#include "buffer.h"
#include "util.h"
#include "murmurhash.h"
#define HASH_SEED 0
int damage_refinery_init(struct damage_refinery* self, uint32_t width,
uint32_t height)
{
self->width = width;
self->height = height;
uint32_t twidth = UDIV_UP(width, 32);
uint32_t theight = UDIV_UP(height, 32);
self->hashes = calloc(twidth * theight, sizeof(*self->hashes));
if (!self->hashes)
return -1;
return 0;
}
void damage_refinery_destroy(struct damage_refinery* self)
{
free(self->hashes);
}
static uint32_t damage_hash_tile(struct damage_refinery* self, uint32_t tx,
uint32_t ty, const struct wv_buffer* buffer)
{
// TODO: Support different pixel sizes
uint32_t* pixels = buffer->pixels;
int pixel_stride = buffer->stride / 4;
if (buffer->y_inverted) {
pixels += (buffer->height - 1) * pixel_stride;
pixel_stride *= -1;
}
int x_start = tx * 32;
int x_stop = MIN((tx + 1) * 32, self->width);
int y_start = ty * 32;
int y_stop = MIN((ty + 1) * 32, self->height);
uint32_t hash = 0;
for (int y = y_start; y < y_stop; ++y)
hash = murmurhash((void*)&(pixels[x_start + y * pixel_stride]),
4 * (x_stop - x_start), hash);
return hash;
}
static uint32_t* damage_tile_hash_ptr(struct damage_refinery* self,
uint32_t tx, uint32_t ty)
{
uint32_t twidth = UDIV_UP(self->width, 32);
return &self->hashes[tx + ty * twidth];
}
static void damage_refine_tile(struct damage_refinery* self,
struct pixman_region16* refined, uint32_t tx, uint32_t ty,
const struct wv_buffer* buffer)
{
uint32_t hash = damage_hash_tile(self, tx, ty, buffer);
uint32_t* old_hash_ptr = damage_tile_hash_ptr(self, tx, ty);
int is_damaged = hash != *old_hash_ptr;
*old_hash_ptr = hash;
if (is_damaged)
pixman_region_union_rect(refined, refined, tx * 32, ty * 32, 32,
32);
}
static void tile_region_from_region(struct pixman_region16* dst,
struct pixman_region16* src)
{
int n_rects = 0;
struct pixman_box16* rects = pixman_region_rectangles(src, &n_rects);
for (int i = 0; i < n_rects; ++i) {
int x1 = rects[i].x1 / 32;
int y1 = rects[i].y1 / 32;
int x2 = UDIV_UP(rects[i].x2, 32);
int y2 = UDIV_UP(rects[i].y2, 32);
pixman_region_union_rect(dst, dst, x1, y1, x2 - x1, y2 - y1);
}
}
void damage_refine(struct damage_refinery* self,
struct pixman_region16* refined,
struct pixman_region16* hint,
const struct wv_buffer* buffer)
{
assert(self->width == (uint32_t)buffer->width &&
self->height == (uint32_t)buffer->height);
struct pixman_region16 tile_region;
pixman_region_init(&tile_region);
tile_region_from_region(&tile_region, hint);
int n_rects = 0;
struct pixman_box16* rects = pixman_region_rectangles(&tile_region,
&n_rects);
for (int i = 0; i < n_rects; ++i)
for (int ty = rects[i].y1; ty < rects[i].y2; ++ty)
for (int tx = rects[i].x1; tx < rects[i].x2; ++tx)
damage_refine_tile(self, refined, tx, ty, buffer);
pixman_region_fini(&tile_region);
pixman_region_intersect_rect(refined, refined, 0, 0, self->width,
self->height);
}

View File

@ -1,180 +0,0 @@
/*
* Copyright (c) 2020 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <sys/param.h>
#include <pixman.h>
#include <aml.h>
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
struct damage_work {
void (*on_done)(struct pixman_region16*, void*);
void* userdata;
uint8_t* buffer;
uint32_t width, height;
struct pixman_region16 damage;
};
struct damage_work* damage_work_new(void)
{
struct damage_work* work = calloc(1, sizeof(*work));
if (!work)
return NULL;
pixman_region_init(&work->damage);
return work;
}
void damage_work_free(void* ud)
{
struct damage_work* work = ud;
free(work->buffer);
pixman_region_fini(&work->damage);
free(work);
}
bool damage_check_32_byte_block(const void* block)
{
const uint64_t* a = block;
return a[0] || a[1] || a[2] || a[3];
}
void damage_check_row(uint8_t* dst, const uint8_t* src, uint32_t width)
{
uint32_t aligned_width = (width / 32) * 32;
for (uint32_t x = 0; x < aligned_width; x += 32)
dst[x / 32] |= damage_check_32_byte_block(&src[x]);
for (uint32_t x = aligned_width; x < width; ++x)
dst[x / 32] |= src[x];
}
void damage_check_tile_row(struct pixman_region16* damage,
uint8_t* row_buffer, const uint8_t* buffer,
uint32_t y_start, uint32_t width, uint32_t height)
{
uint32_t tiled_width = UDIV_UP(width, 32);
memset(row_buffer, 0, tiled_width);
for (uint32_t y = y_start; y < y_start + height; ++y)
damage_check_row(row_buffer, buffer + y * width, width);
for (uint32_t x = 0; x < tiled_width; ++x)
if (row_buffer[x])
pixman_region_union_rect(damage, damage,
x * 32, y_start, 32, 32);
}
void damage_check(struct pixman_region16* damage, const uint8_t* buffer,
uint32_t width, uint32_t height, struct pixman_box16* hint)
{
uint32_t tiled_width = UDIV_UP(width, 32);
uint8_t* row_buffer = malloc(tiled_width);
assert(row_buffer);
for (uint32_t y = 0; y < height; y += 32) {
uint32_t current_height = MIN(32, height - y);
damage_check_tile_row(damage, row_buffer, buffer, y, width,
current_height);
}
pixman_region_intersect_rect(damage, damage, 0, 0, width, height);
free(row_buffer);
}
static void do_damage_check(void* aml_obj)
{
struct damage_work* priv = aml_get_userdata(aml_obj);
damage_check(&priv->damage, priv->buffer, priv->width, priv->height, NULL);
}
static void on_damage_check_done(void* aml_obj)
{
struct damage_work* priv = aml_get_userdata(aml_obj);
priv->on_done(&priv->damage, priv->userdata);
}
// TODO: Use the hint
// TODO: Split rows between jobs
// XXX: This takes ownership of the damage buffer
int damage_check_async(uint8_t* buffer, uint32_t width, uint32_t height,
struct pixman_box16* hint,
void (*on_done)(struct pixman_region16*, void*),
void* userdata)
{
struct damage_work* priv = damage_work_new();
if (!priv)
return -1;
priv->buffer = buffer;
priv->width = width;
priv->height = height;
priv->on_done = on_done;
priv->userdata = userdata;
struct aml_work* work;
work = aml_work_new(do_damage_check, on_damage_check_done, priv,
damage_work_free);
if (!work)
goto oom;
int rc = aml_start(aml_get_default(), work);
aml_unref(work);
return rc;
oom:
damage_work_free(priv);
return -1;
}
void damage_dump(FILE* stream, struct pixman_region16* damage,
uint32_t width, uint32_t height, uint32_t tile_size)
{
uint32_t tiled_width = UDIV_UP(width, tile_size);
uint32_t tiled_height = UDIV_UP(height, tile_size);
fprintf(stream, "\033[2J");
for (uint32_t y = 0; y < tiled_height; ++y) {
for (uint32_t x = 0; x < tiled_width; ++x) {
struct pixman_box16 box = {
.x1 = x * tile_size,
.x2 = ((x + 1) * tile_size) - 1,
.y1 = y * tile_size,
.y2 = ((y + 1) * tile_size) - 1,
};
pixman_region_overlap_t overlap =
pixman_region_contains_rectangle(damage, &box);
putc(overlap == PIXMAN_REGION_IN ? 'x' : ' ', stream);
}
putc('\n', stream);
}
}

View File

@ -1,243 +0,0 @@
/*
* Copyright (c) 2019 - 2020 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <inttypes.h>
#include <wayland-client.h>
#include <wayland-util.h>
#include <time-util.h>
#include "frame-capture.h"
#include "logging.h"
#include "dmabuf.h"
#include "wlr-export-dmabuf-unstable-v1.h"
#include "render.h"
#include "usdt.h"
#define RATE_LIMIT 30.0
#define MIN_PERIOD ((uint64_t)(1000.0 / RATE_LIMIT))
#define PROCESSING_DURATION_LIMIT 16 /* ms */
static int dmabuf_capture_start_now(struct frame_capture* fc,
enum frame_capture_options options);
static void dmabuf_close_fds(struct dmabuf_capture* self)
{
for (size_t i = 0; i < self->frame.n_planes; ++i)
close(self->frame.plane[i].fd);
self->frame.n_planes = 0;
}
static void dmabuf_capture_stop(struct frame_capture* fc)
{
struct dmabuf_capture* self = (void*)fc;
aml_stop(aml_get_default(), self->timer);
fc->status = CAPTURE_STOPPED;
if (self->zwlr_frame) {
zwlr_export_dmabuf_frame_v1_destroy(self->zwlr_frame);
self->zwlr_frame = NULL;
}
}
static void dmabuf_frame_start(void* data,
struct zwlr_export_dmabuf_frame_v1* frame,
uint32_t width, uint32_t height,
uint32_t offset_x, uint32_t offset_y,
uint32_t buffer_flags, uint32_t flags,
uint32_t format,
uint32_t mod_high, uint32_t mod_low,
uint32_t num_objects)
{
struct dmabuf_capture* self = data;
struct frame_capture* fc = data;
dmabuf_close_fds(self);
uint64_t mod = ((uint64_t)mod_high << 32) | (uint64_t)mod_low;
self->frame.width = width;
self->frame.height = height;
self->frame.n_planes = num_objects;
self->frame.format = format;
self->frame.plane[0].modifier = mod;
self->frame.plane[1].modifier = mod;
self->frame.plane[2].modifier = mod;
self->frame.plane[3].modifier = mod;
fc->damage_hint.x = 0;
fc->damage_hint.y = 0;
fc->damage_hint.width = width;
fc->damage_hint.height = height;
}
static void dmabuf_frame_object(void* data,
struct zwlr_export_dmabuf_frame_v1* frame,
uint32_t index, int32_t fd, uint32_t size,
uint32_t offset, uint32_t stride,
uint32_t plane_index)
{
struct dmabuf_capture* self = data;
self->frame.plane[plane_index].fd = fd;
self->frame.plane[plane_index].size = size;
self->frame.plane[plane_index].offset = offset;
self->frame.plane[plane_index].pitch = stride;
}
static void dmabuf_frame_ready(void* data,
struct zwlr_export_dmabuf_frame_v1* frame,
uint32_t tv_sec_hi, uint32_t tv_sec_lo,
uint32_t tv_nsec)
{
struct dmabuf_capture* self = data;
struct frame_capture* fc = data;
struct timespec ts = {
.tv_sec = ((uint64_t)tv_sec_hi << 32) | tv_sec_lo,
.tv_nsec = tv_nsec
};
uint64_t precommit_time = timespec_to_ms(&ts);
DTRACE_PROBE2(wayvnc, dmabuf_frame_ready, self, precommit_time);
dmabuf_capture_stop(fc);
fc->status = CAPTURE_DONE;
fc->on_done(fc);
dmabuf_close_fds(self);
uint64_t processing_duration =
self->render_finish_time - precommit_time;
DTRACE_PROBE3(wayvnc, dmabuf_frame_release, self,
self->render_finish_time, processing_duration);
if (processing_duration > PROCESSING_DURATION_LIMIT)
log_debug("Processing dmabuf took %"PRIu64" ms.\n",
processing_duration);
}
static void dmabuf_frame_cancel(void* data,
struct zwlr_export_dmabuf_frame_v1* frame,
uint32_t reason)
{
struct dmabuf_capture* self = data;
struct frame_capture* fc = data;
DTRACE_PROBE1(wayvnc, dmabuf_frame_cancel, self);
dmabuf_capture_stop(fc);
fc->status = reason == ZWLR_EXPORT_DMABUF_FRAME_V1_CANCEL_REASON_PERMANENT
? CAPTURE_FATAL : CAPTURE_FAILED;
fc->on_done(fc);
dmabuf_close_fds(self);
}
static void dmabuf__poll(void* obj)
{
struct dmabuf_capture* self = aml_get_userdata(obj);
struct frame_capture* fc = (struct frame_capture*)self;
dmabuf_capture_start_now(fc, 0);
}
static int dmabuf_capture_start(struct frame_capture* fc,
enum frame_capture_options options)
{
struct dmabuf_capture* self = (void*)fc;
if (fc->status == CAPTURE_IN_PROGRESS)
return -1;
uint64_t now = gettime_ms();
uint64_t dt = now - self->start_time;
fc->status = CAPTURE_IN_PROGRESS;
if (dt < MIN_PERIOD) {
uint64_t time_left = MIN_PERIOD - dt;
aml_set_duration(self->timer, time_left);
return aml_start(aml_get_default(), self->timer);
}
return dmabuf_capture_start_now(fc, options);
}
static int dmabuf_capture_start_now(struct frame_capture* fc,
enum frame_capture_options options)
{
struct dmabuf_capture* self = (void*)fc;
static const struct zwlr_export_dmabuf_frame_v1_listener
dmabuf_frame_listener = {
.frame = dmabuf_frame_start,
.object = dmabuf_frame_object,
.ready = dmabuf_frame_ready,
.cancel = dmabuf_frame_cancel,
};
DTRACE_PROBE1(wayvnc, dmabuf_capture_start, self);
self->zwlr_frame =
zwlr_export_dmabuf_manager_v1_capture_output(self->manager,
fc->overlay_cursor,
fc->wl_output);
if (!self->zwlr_frame)
return -1;
self->start_time = gettime_ms();
zwlr_export_dmabuf_frame_v1_add_listener(self->zwlr_frame,
&dmabuf_frame_listener, self);
return 0;
}
static void dmabuf_capture_render(struct frame_capture* fc,
struct renderer* render, struct nvnc_fb* fb)
{
struct dmabuf_capture* self = (void*)fc;
render_dmabuf(render, &self->frame);
self->render_finish_time = gettime_ms();
}
void dmabuf_capture_init(struct dmabuf_capture* self)
{
self->timer = aml_timer_new(0, dmabuf__poll, self, NULL);
assert(self->timer);
self->fc.backend.start = dmabuf_capture_start;
self->fc.backend.stop = dmabuf_capture_stop;
self->fc.backend.render = dmabuf_capture_render;
}
void dmabuf_capture_destroy(struct dmabuf_capture* self)
{
aml_stop(aml_get_default(), self->timer);
aml_unref(self->timer);
}

View File

@ -28,21 +28,20 @@
#include <aml.h> #include <aml.h>
#include <signal.h> #include <signal.h>
#include <libdrm/drm_fourcc.h> #include <libdrm/drm_fourcc.h>
#include <wayland-client-protocol.h>
#include <wayland-client.h> #include <wayland-client.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <pixman.h> #include <pixman.h>
#include <sys/param.h> #include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <gbm.h>
#include <xf86drm.h>
#include "frame-capture.h"
#include "wlr-export-dmabuf-unstable-v1.h"
#include "wlr-screencopy-unstable-v1.h" #include "wlr-screencopy-unstable-v1.h"
#include "wlr-virtual-pointer-unstable-v1.h" #include "wlr-virtual-pointer-unstable-v1.h"
#include "virtual-keyboard-unstable-v1.h" #include "virtual-keyboard-unstable-v1.h"
#include "xdg-output-unstable-v1.h" #include "xdg-output-unstable-v1.h"
#include "render.h" #include "linux-dmabuf-unstable-v1.h"
#include "dmabuf.h"
#include "screencopy.h" #include "screencopy.h"
#include "strlcpy.h" #include "strlcpy.h"
#include "logging.h" #include "logging.h"
@ -51,20 +50,14 @@
#include "keyboard.h" #include "keyboard.h"
#include "seat.h" #include "seat.h"
#include "cfg.h" #include "cfg.h"
#include "damage.h" #include "pixman-renderer.h"
#include "transform-util.h"
#include "damage-refinery.h"
#include "usdt.h"
#define DEFAULT_ADDRESS "127.0.0.1" #define DEFAULT_ADDRESS "127.0.0.1"
#define DEFAULT_PORT 5900 #define DEFAULT_PORT 5900
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
#define ALIGN_UP(n, a) (UDIV_UP(n, a) * a)
enum frame_capture_backend_type {
FRAME_CAPTURE_BACKEND_NONE = 0,
FRAME_CAPTURE_BACKEND_SCREENCOPY,
FRAME_CAPTURE_BACKEND_DMABUF,
};
struct wayvnc { struct wayvnc {
bool do_exit; bool do_exit;
@ -80,13 +73,10 @@ struct wayvnc {
int pointer_manager_version; int pointer_manager_version;
struct renderer renderer;
const struct output* selected_output; const struct output* selected_output;
const struct seat* selected_seat; const struct seat* selected_seat;
struct dmabuf_capture dmabuf_backend; struct screencopy screencopy;
struct screencopy screencopy_backend;
struct frame_capture* capture_backend;
struct pointer pointer_backend; struct pointer pointer_backend;
struct keyboard keyboard_backend; struct keyboard keyboard_backend;
@ -97,26 +87,19 @@ struct wayvnc {
struct nvnc_display* nvnc_display; struct nvnc_display* nvnc_display;
struct nvnc_fb* buffer; struct nvnc_fb* buffer;
struct damage_refinery damage_refinery;
struct pixman_region16 current_damage; struct pixman_region16 current_damage;
const char* kb_layout; const char* kb_layout;
}; };
void wayvnc_exit(struct wayvnc* self); void wayvnc_exit(struct wayvnc* self);
void on_capture_done(struct frame_capture* capture); void on_capture_done(struct screencopy* sc);
static void on_render(struct nvnc_display* display, struct nvnc_fb* fb); static void on_render(struct nvnc_display* display, struct nvnc_fb* fb);
static enum frame_capture_backend_type struct wl_shm* wl_shm = NULL;
frame_capture_backend_from_string(const char* str) struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf = NULL;
{ struct gbm_device* gbm_device = NULL;
if (strcmp(str, "screencopy") == 0)
return FRAME_CAPTURE_BACKEND_SCREENCOPY;
if (strcmp(str, "dmabuf") == 0)
return FRAME_CAPTURE_BACKEND_DMABUF;
return FRAME_CAPTURE_BACKEND_NONE;
}
static void registry_add(void* data, struct wl_registry* registry, static void registry_add(void* data, struct wl_registry* registry,
uint32_t id, const char* interface, uint32_t id, const char* interface,
@ -147,24 +130,16 @@ static void registry_add(void* data, struct wl_registry* registry,
} }
if (strcmp(interface, wl_shm_interface.name) == 0) { if (strcmp(interface, wl_shm_interface.name) == 0) {
self->screencopy_backend.wl_shm wl_shm = wl_registry_bind(registry, id, &wl_shm_interface, 1);
= wl_registry_bind(registry, id, &wl_shm_interface, 1);
return;
}
if (strcmp(interface, zwlr_export_dmabuf_manager_v1_interface.name) == 0) {
self->dmabuf_backend.manager =
wl_registry_bind(registry, id,
&zwlr_export_dmabuf_manager_v1_interface,
1);
return; return;
} }
if (strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == 0) { if (strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == 0) {
self->screencopy_backend.manager = self->screencopy.manager =
wl_registry_bind(registry, id, wl_registry_bind(registry, id,
&zwlr_screencopy_manager_v1_interface, &zwlr_screencopy_manager_v1_interface,
2); version);
self->screencopy.version = version;
return; return;
} }
@ -200,6 +175,12 @@ static void registry_add(void* data, struct wl_registry* registry,
1); 1);
return; return;
} }
if (strcmp(interface, zwp_linux_dmabuf_v1_interface.name) == 0) {
zwp_linux_dmabuf = wl_registry_bind(registry, id,
&zwp_linux_dmabuf_v1_interface, 3);
return;
}
} }
static void registry_remove(void* data, struct wl_registry* registry, static void registry_remove(void* data, struct wl_registry* registry,
@ -234,6 +215,25 @@ static void registry_remove(void* data, struct wl_registry* registry,
} }
} }
static int find_render_node(char *node, size_t maxlen) {
bool r = -1;
drmDevice *devices[64];
int n = drmGetDevices2(0, devices, sizeof(devices) / sizeof(devices[0]));
for (int i = 0; i < n; ++i) {
drmDevice *dev = devices[i];
if (!(dev->available_nodes & (1 << DRM_NODE_RENDER)))
continue;
strlcpy(node, dev->nodes[DRM_NODE_RENDER], maxlen);
r = 0;
break;
}
drmFreeDevices(devices, n);
return r;
}
void wayvnc_destroy(struct wayvnc* self) void wayvnc_destroy(struct wayvnc* self)
{ {
output_list_destroy(&self->outputs); output_list_destroy(&self->outputs);
@ -241,7 +241,7 @@ void wayvnc_destroy(struct wayvnc* self)
zxdg_output_manager_v1_destroy(self->xdg_output_manager); zxdg_output_manager_v1_destroy(self->xdg_output_manager);
wl_shm_destroy(self->screencopy_backend.wl_shm); wl_shm_destroy(wl_shm);
zwp_virtual_keyboard_v1_destroy(self->keyboard_backend.virtual_keyboard); zwp_virtual_keyboard_v1_destroy(self->keyboard_backend.virtual_keyboard);
zwp_virtual_keyboard_manager_v1_destroy(self->keyboard_manager); zwp_virtual_keyboard_manager_v1_destroy(self->keyboard_manager);
@ -250,12 +250,10 @@ void wayvnc_destroy(struct wayvnc* self)
zwlr_virtual_pointer_manager_v1_destroy(self->pointer_manager); zwlr_virtual_pointer_manager_v1_destroy(self->pointer_manager);
pointer_destroy(&self->pointer_backend); pointer_destroy(&self->pointer_backend);
if (self->screencopy_backend.manager) if (self->screencopy.manager)
zwlr_screencopy_manager_v1_destroy(self->screencopy_backend.manager); zwlr_screencopy_manager_v1_destroy(self->screencopy.manager);
if (self->dmabuf_backend.manager)
zwlr_export_dmabuf_manager_v1_destroy(self->dmabuf_backend.manager);
wl_registry_destroy(self->registry);
wl_display_disconnect(self->display); wl_display_disconnect(self->display);
} }
@ -309,16 +307,13 @@ static int init_wayland(struct wayvnc* self)
wl_display_dispatch(self->display); wl_display_dispatch(self->display);
wl_display_roundtrip(self->display); wl_display_roundtrip(self->display);
if (!self->dmabuf_backend.manager && !self->screencopy_backend.manager) { if (!self->screencopy.manager) {
log_error("Compositor supports neither screencopy nor export-dmabuf! Exiting.\n"); log_error("Compositor doesn't support screencopy! Exiting.\n");
goto failure; goto failure;
} }
self->dmabuf_backend.fc.on_done = on_capture_done; self->screencopy.on_done = on_capture_done;
self->dmabuf_backend.fc.userdata = self; self->screencopy.userdata = self;
self->screencopy_backend.frame_capture.on_done = on_capture_done;
self->screencopy_backend.frame_capture.userdata = self;
return 0; return 0;
@ -386,16 +381,6 @@ int init_main_loop(struct wayvnc* self)
return 0; return 0;
} }
uint32_t fourcc_from_gl_format(uint32_t format)
{
switch (format) {
case GL_BGRA_EXT: return DRM_FORMAT_XRGB8888;
case GL_RGBA: return DRM_FORMAT_XBGR8888;
}
return DRM_FORMAT_INVALID;
}
static void on_pointer_event(struct nvnc_client* client, uint16_t x, uint16_t y, static void on_pointer_event(struct nvnc_client* client, uint16_t x, uint16_t y,
enum nvnc_button_mask button_mask) enum nvnc_button_mask button_mask)
{ {
@ -469,9 +454,14 @@ failure:
return -1; return -1;
} }
int wayvnc_start_capture(struct wayvnc* self, enum frame_capture_options opt) int wayvnc_start_capture(struct wayvnc* self)
{ {
return frame_capture_start(self->capture_backend, opt); return screencopy_start(&self->screencopy);
}
int wayvnc_start_capture_immediate(struct wayvnc* self)
{
return screencopy_start_immediate(&self->screencopy);
} }
static void on_render(struct nvnc_display* display, struct nvnc_fb* fb) static void on_render(struct nvnc_display* display, struct nvnc_fb* fb)
@ -479,16 +469,17 @@ static void on_render(struct nvnc_display* display, struct nvnc_fb* fb)
struct nvnc* nvnc = nvnc_display_get_server(display); struct nvnc* nvnc = nvnc_display_get_server(display);
struct wayvnc* self = nvnc_get_userdata(nvnc); struct wayvnc* self = nvnc_get_userdata(nvnc);
struct pixman_box16* ext = pixman_region_extents(&self->current_damage); if (!self->screencopy.back)
uint32_t y = ext->y1; return;
uint32_t damage_height = ext->y2 - ext->y1;
uint32_t* addr = nvnc_fb_get_addr(fb); DTRACE_PROBE(wayvnc, render_start);
uint32_t width = nvnc_fb_get_width(fb);
renderer_read_frame(&self->renderer, addr + y * width, y, damage_height);
enum wl_output_transform transform = self->selected_output->transform;
wv_pixman_render(fb, self->screencopy.back, transform,
&self->current_damage);
pixman_region_clear(&self->current_damage); pixman_region_clear(&self->current_damage);
DTRACE_PROBE(wayvnc, render_end);
} }
static void wayvnc_damage_region(struct wayvnc* self, static void wayvnc_damage_region(struct wayvnc* self,
@ -498,109 +489,73 @@ static void wayvnc_damage_region(struct wayvnc* self,
nvnc_display_damage_region(self->nvnc_display, damage); nvnc_display_damage_region(self->nvnc_display, damage);
} }
static void wayvnc_damage_whole(struct wayvnc* self)
{
uint16_t width = nvnc_fb_get_width(self->buffer);
uint16_t height = nvnc_fb_get_height(self->buffer);
struct pixman_region16 damage;
pixman_region_init_rect(&damage, 0, 0, width, height);
wayvnc_damage_region(self, &damage);
pixman_region_fini(&damage);
}
static void on_damage_check_done(struct pixman_region16* damage, void* userdata)
{
struct wayvnc* self = userdata;
wayvnc_damage_region(self, damage);
if (wayvnc_start_capture(self, 0) < 0) {
log_error("Failed to start capture. Exiting...\n");
wayvnc_exit(self);
}
}
void wayvnc_process_frame(struct wayvnc* self) void wayvnc_process_frame(struct wayvnc* self)
{ {
uint32_t format = fourcc_from_gl_format(self->renderer.read_format);
uint32_t width = output_get_transformed_width(self->selected_output); uint32_t width = output_get_transformed_width(self->selected_output);
uint32_t height = output_get_transformed_height(self->selected_output); uint32_t height = output_get_transformed_height(self->selected_output);
bool is_first_frame = false; uint32_t format = self->screencopy.back->format;
if (!self->buffer) { if (!self->buffer) {
self->buffer = nvnc_fb_new(width, height, format); self->buffer = nvnc_fb_new(width, height, format);
nvnc_display_set_buffer(self->nvnc_display, self->buffer); nvnc_display_set_buffer(self->nvnc_display, self->buffer);
is_first_frame = true;
damage_refinery_init(&self->damage_refinery,
self->screencopy.back->width,
self->screencopy.back->height);
} else { } else {
// TODO: Reallocate // TODO: Reallocate
assert(width == nvnc_fb_get_width(self->buffer)); if (width != nvnc_fb_get_width(self->buffer) ||
assert(height == nvnc_fb_get_height(self->buffer)); height != nvnc_fb_get_height(self->buffer)) {
} wayvnc_exit(self);
struct nvnc_fb* fb = self->buffer;
// TODO: Fix constness on fb in this function
frame_capture_render(self->capture_backend, &self->renderer, fb);
if (!is_first_frame) {
uint32_t hint_x = self->capture_backend->damage_hint.x;
uint32_t hint_y = self->capture_backend->damage_hint.y;
uint32_t hint_width = self->capture_backend->damage_hint.width;
uint32_t hint_height = self->capture_backend->damage_hint.height;
uint32_t tfx0, tfy0, tfx1, tfy1;
output_transform_box_coord(self->selected_output,
hint_x, hint_y,
hint_x + hint_width,
hint_y + hint_height,
&tfx0, &tfy0, &tfx1, &tfy1);
struct pixman_box16 hint = {
.x1 = tfx0, .y1 = tfy0,
.x2 = tfx1, .y2 = tfy1,
};
size_t alignment = MAX(4, sizeof(void*));
size_t damage_buffer_size = ALIGN_UP(width * height, alignment);
uint8_t* damage_buffer =
aligned_alloc(alignment, damage_buffer_size);
render_damage(&self->renderer);
renderer_read_damage(&self->renderer, damage_buffer, 0, height);
damage_check_async(damage_buffer, width, height, &hint,
on_damage_check_done, self);
return; return;
} }
}
wayvnc_damage_whole(self); DTRACE_PROBE(wayvnc, refine_damage_start);
if (wayvnc_start_capture(self, 0) < 0) { struct pixman_region16 txdamage, refined;
pixman_region_init(&txdamage);
pixman_region_init(&refined);
damage_refine(&self->damage_refinery, &refined,
&self->screencopy.back->damage,
self->screencopy.back);
wv_region_transform(&txdamage, &refined,
self->selected_output->transform,
self->selected_output->width,
self->selected_output->height);
// damage_dump(stdout, &txdamage, width, height, 32);
wayvnc_damage_region(self, &txdamage);
pixman_region_fini(&refined);
pixman_region_fini(&txdamage);
DTRACE_PROBE(wayvnc, refine_damage_end);
if (wayvnc_start_capture(self) < 0) {
log_error("Failed to start capture. Exiting...\n"); log_error("Failed to start capture. Exiting...\n");
wayvnc_exit(self); wayvnc_exit(self);
} }
} }
void on_capture_done(struct frame_capture* capture) void on_capture_done(struct screencopy* sc)
{ {
struct wayvnc* self = capture->userdata; struct wayvnc* self = sc->userdata;
switch (capture->status) { switch (sc->status) {
case CAPTURE_STOPPED: case SCREENCOPY_STOPPED:
break; break;
case CAPTURE_IN_PROGRESS: case SCREENCOPY_IN_PROGRESS:
break; break;
case CAPTURE_FATAL: case SCREENCOPY_FATAL:
log_error("Fatal error while capturing. Exiting...\n"); log_error("Fatal error while capturing. Exiting...\n");
wayvnc_exit(self); wayvnc_exit(self);
break; break;
case CAPTURE_FAILED: case SCREENCOPY_FAILED:
if (wayvnc_start_capture(self, CAPTURE_NOW) < 0) { if (wayvnc_start_capture_immediate(self) < 0) {
log_error("Failed to start capture. Exiting...\n"); log_error("Failed to start capture. Exiting...\n");
wayvnc_exit(self); wayvnc_exit(self);
} }
break; break;
case CAPTURE_DONE: case SCREENCOPY_DONE:
wayvnc_process_frame(self); wayvnc_process_frame(self);
break; break;
} }
@ -612,11 +567,11 @@ int wayvnc_usage(FILE* stream, int rc)
"Usage: wayvnc [options] [address [port]]\n" "Usage: wayvnc [options] [address [port]]\n"
"\n" "\n"
" -C,--config=<path> Select a config file.\n" " -C,--config=<path> Select a config file.\n"
" -c,--frame-capturing=screencopy|dmabuf Select frame capturing backend.\n"
" -o,--output=<name> Select output to capture.\n" " -o,--output=<name> Select output to capture.\n"
" -k,--keyboard=<layout> Select keyboard layout.\n" " -k,--keyboard=<layout> Select keyboard layout.\n"
" -s,--seat=<name> Select seat by name.\n" " -s,--seat=<name> Select seat by name.\n"
" -r,--render-cursor Enable overlay cursor rendering.\n" " -r,--render-cursor Enable overlay cursor rendering.\n"
" -f,--max-fps=<fps> Set the rate limit (default 30).\n"
" -h,--help Get help (this text).\n" " -h,--help Get help (this text).\n"
"\n"; "\n";
@ -671,20 +626,21 @@ int main(int argc, char* argv[])
int port = 0; int port = 0;
const char* output_name = NULL; const char* output_name = NULL;
enum frame_capture_backend_type fcbackend = FRAME_CAPTURE_BACKEND_NONE;
const char* seat_name = NULL; const char* seat_name = NULL;
bool overlay_cursor = false; bool overlay_cursor = false;
int max_rate = 30;
static const char* shortopts = "C:c:o:k:s:rh"; static const char* shortopts = "C:o:k:s:rf:h";
int drm_fd = -1;
static const struct option longopts[] = { static const struct option longopts[] = {
{ "config", required_argument, NULL, 'C' }, { "config", required_argument, NULL, 'C' },
{ "frame-capturing", required_argument, NULL, 'c' },
{ "output", required_argument, NULL, 'o' }, { "output", required_argument, NULL, 'o' },
{ "keyboard", required_argument, NULL, 'k' }, { "keyboard", required_argument, NULL, 'k' },
{ "seat", required_argument, NULL, 's' }, { "seat", required_argument, NULL, 's' },
{ "render-cursor", no_argument, NULL, 'r' }, { "render-cursor", no_argument, NULL, 'r' },
{ "max-fps", required_argument, NULL, 'f' },
{ "help", no_argument, NULL, 'h' }, { "help", no_argument, NULL, 'h' },
{ NULL, 0, NULL, 0 } { NULL, 0, NULL, 0 }
}; };
@ -698,15 +654,6 @@ int main(int argc, char* argv[])
case 'C': case 'C':
cfg_file = optarg; cfg_file = optarg;
break; break;
case 'c':
fcbackend = frame_capture_backend_from_string(optarg);
if (fcbackend == FRAME_CAPTURE_BACKEND_NONE) {
fprintf(stderr, "Invalid backend: %s\n\n",
optarg);
return wayvnc_usage(stderr, 1);
}
break;
case 'o': case 'o':
output_name = optarg; output_name = optarg;
break; break;
@ -719,6 +666,9 @@ int main(int argc, char* argv[])
case 'r': case 'r':
overlay_cursor = true; overlay_cursor = true;
break; break;
case 'f':
max_rate = atoi(optarg);
break;
case 'h': case 'h':
return wayvnc_usage(stdout, 0); return wayvnc_usage(stdout, 0);
default: default:
@ -795,8 +745,8 @@ int main(int argc, char* argv[])
self.selected_output = out; self.selected_output = out;
self.selected_seat = seat; self.selected_seat = seat;
self.dmabuf_backend.fc.wl_output = out->wl_output; self.screencopy.wl_output = out->wl_output;
self.screencopy_backend.frame_capture.wl_output = out->wl_output; self.screencopy.rate_limit = max_rate;
self.keyboard_backend.virtual_keyboard = self.keyboard_backend.virtual_keyboard =
zwp_virtual_keyboard_manager_v1_create_virtual_keyboard( zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(
@ -816,16 +766,19 @@ int main(int argc, char* argv[])
pointer_init(&self.pointer_backend); pointer_init(&self.pointer_backend);
enum renderer_input_type renderer_input_type = char render_node[256];
fcbackend == FRAME_CAPTURE_BACKEND_DMABUF ? if (find_render_node(render_node, sizeof(render_node)) < 0)
RENDERER_INPUT_DMABUF : RENDERER_INPUT_FB;
if (renderer_init(&self.renderer, self.selected_output,
renderer_input_type) < 0) {
log_error("Failed to initialise renderer\n");
goto failure; goto failure;
}
struct aml* aml = aml_new(NULL, 0); drm_fd = open(render_node, O_RDWR);
if (drm_fd < 0)
goto failure;
gbm_device = gbm_create_device(drm_fd);
if (!gbm_device)
goto failure;
struct aml* aml = aml_new();
if (!aml) if (!aml)
goto main_loop_failure; goto main_loop_failure;
@ -837,44 +790,19 @@ int main(int argc, char* argv[])
if (init_nvnc(&self, address, port) < 0) if (init_nvnc(&self, address, port) < 0)
goto nvnc_failure; goto nvnc_failure;
if (self.screencopy_backend.manager) if (self.screencopy.manager)
screencopy_init(&self.screencopy_backend); screencopy_init(&self.screencopy);
if (self.dmabuf_backend.manager) if (!self.screencopy.manager) {
dmabuf_capture_init(&self.dmabuf_backend);
switch (fcbackend) {
case FRAME_CAPTURE_BACKEND_SCREENCOPY:
if (!self.screencopy_backend.manager) {
log_error("screencopy is not supported by compositor\n"); log_error("screencopy is not supported by compositor\n");
goto capture_failure; goto capture_failure;
} }
self.capture_backend = &self.screencopy_backend.frame_capture;
break;
case FRAME_CAPTURE_BACKEND_DMABUF:
if (!self.screencopy_backend.manager) {
log_error("export-dmabuf is not supported by compositor\n");
goto capture_failure;
}
self.capture_backend = &self.dmabuf_backend.fc;
break;
case FRAME_CAPTURE_BACKEND_NONE:
if (self.screencopy_backend.manager)
self.capture_backend = &self.screencopy_backend.frame_capture;
else if (self.dmabuf_backend.manager)
self.capture_backend = &self.dmabuf_backend.fc;
else
goto capture_failure;
break;
}
pixman_region_init(&self.current_damage); pixman_region_init(&self.current_damage);
self.capture_backend->overlay_cursor = overlay_cursor; self.screencopy.overlay_cursor = overlay_cursor;
if (wayvnc_start_capture(&self, 0) < 0) if (wayvnc_start_capture(&self) < 0)
goto capture_failure; goto capture_failure;
wl_display_dispatch(self.display); wl_display_dispatch(self.display);
@ -885,19 +813,22 @@ int main(int argc, char* argv[])
aml_dispatch(aml); aml_dispatch(aml);
} }
frame_capture_stop(self.capture_backend); screencopy_stop(&self.screencopy);
if (self.buffer) nvnc_fb_unref(self.buffer); if (self.buffer) {
damage_refinery_destroy(&self.damage_refinery);
nvnc_fb_unref(self.buffer);
}
pixman_region_fini(&self.current_damage); pixman_region_fini(&self.current_damage);
nvnc_display_unref(self.nvnc_display); nvnc_display_unref(self.nvnc_display);
nvnc_close(self.nvnc); nvnc_close(self.nvnc);
renderer_destroy(&self.renderer); if (zwp_linux_dmabuf)
if (self.screencopy_backend.manager) zwp_linux_dmabuf_v1_destroy(zwp_linux_dmabuf);
screencopy_destroy(&self.screencopy_backend); if (self.screencopy.manager)
if (self.dmabuf_backend.manager) screencopy_destroy(&self.screencopy);
dmabuf_capture_destroy(&self.dmabuf_backend); gbm_device_destroy(gbm_device);
wayvnc_destroy(&self); wayvnc_destroy(&self);
aml_unref(aml); aml_unref(aml);
@ -908,8 +839,10 @@ capture_failure:
nvnc_close(self.nvnc); nvnc_close(self.nvnc);
nvnc_failure: nvnc_failure:
main_loop_failure: main_loop_failure:
renderer_destroy(&self.renderer);
failure: failure:
gbm_device_destroy(gbm_device);
if (drm_fd >= 0)
close(drm_fd);
wayvnc_destroy(&self); wayvnc_destroy(&self);
return 1; return 1;
} }

91
src/murmurhash.c 100644
View File

@ -0,0 +1,91 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2014 Joseph Werle
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include "murmurhash.h"
uint32_t
murmurhash (const char *key, uint32_t len, uint32_t seed) {
uint32_t c1 = 0xcc9e2d51;
uint32_t c2 = 0x1b873593;
uint32_t r1 = 15;
uint32_t r2 = 13;
uint32_t m = 5;
uint32_t n = 0xe6546b64;
uint32_t h = 0;
uint32_t k = 0;
uint8_t *d = (uint8_t *) key; // 32 bit extract from `key'
const uint32_t *chunks = NULL;
const uint8_t *tail = NULL; // tail - last 8 bytes
int i = 0;
int l = len / 4; // chunk length
h = seed;
chunks = (const uint32_t *) (d + l * 4); // body
tail = (const uint8_t *) (d + l * 4); // last 8 byte chunk of `key'
// for each 4 byte chunk of `key'
for (i = -l; i != 0; ++i) {
// next 4 byte chunk of `key'
k = chunks[i];
// encode next 4 byte chunk of `key'
k *= c1;
k = (k << r1) | (k >> (32 - r1));
k *= c2;
// append to hash
h ^= k;
h = (h << r2) | (h >> (32 - r2));
h = h * m + n;
}
k = 0;
// remainder
switch (len & 3) { // `len % 4'
case 3: k ^= (tail[2] << 16);
case 2: k ^= (tail[1] << 8);
case 1:
k ^= tail[0];
k *= c1;
k = (k << r1) | (k >> (32 - r1));
k *= c2;
h ^= k;
}
h ^= len;
h ^= (h >> 16);
h *= 0x85ebca6b;
h ^= (h >> 13);
h *= 0xc2b2ae35;
h ^= (h >> 16);
return h;
}

106
src/pixels.c 100644
View File

@ -0,0 +1,106 @@
/*
* Copyright (c) 2020 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "pixels.h"
#include <pixman.h>
#include <wayland-client.h>
#include <assert.h>
#include <libdrm/drm_fourcc.h>
#include <stdbool.h>
#include <endian.h>
enum wl_shm_format fourcc_to_wl_shm(uint32_t in)
{
assert(!(in & DRM_FORMAT_BIG_ENDIAN));
switch (in) {
case DRM_FORMAT_ARGB8888: return WL_SHM_FORMAT_ARGB8888;
case DRM_FORMAT_XRGB8888: return WL_SHM_FORMAT_XRGB8888;
}
return in;
}
bool fourcc_to_pixman_fmt(pixman_format_code_t* dst, uint32_t src)
{
assert(!(src & DRM_FORMAT_BIG_ENDIAN));
#define LOWER_R r
#define LOWER_G g
#define LOWER_B b
#define LOWER_A a
#define LOWER_X x
#define LOWER_
#define LOWER(x) LOWER_##x
#define CONCAT_(a, b) a ## b
#define CONCAT(a, b) CONCAT_(a, b)
#define FMT_DRM(x, y, z, v, a, b, c, d) DRM_FORMAT_##x##y##z##v##a##b##c##d
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define FMT_PIXMAN(x, y, z, v, a, b, c, d) \
CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(\
PIXMAN_, LOWER(x)), a), LOWER(y)), b), LOWER(z)), c), LOWER(v)), d)
#elif
#define FMT_PIXMAN(x, y, z, v, a, b, c, d) \
CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(CONCAT(\
PIXMAN_, LOWER(v)), d), LOWER(z)), c), LOWER(y)), b), LOWER(x)), a)
#endif
switch (src) {
#define X(...) \
case FMT_DRM(__VA_ARGS__): *dst = FMT_PIXMAN(__VA_ARGS__); break
/* 32 bits */
X(A,R,G,B,8,8,8,8);
X(A,B,G,R,8,8,8,8);
X(X,R,G,B,8,8,8,8);
X(X,B,G,R,8,8,8,8);
X(R,G,B,A,8,8,8,8);
X(B,G,R,A,8,8,8,8);
X(R,G,B,X,8,8,8,8);
X(B,G,R,X,8,8,8,8);
/* 24 bits */
X(R,G,B,,8,8,8,);
X(B,G,R,,8,8,8,);
/* 16 bits */
X(R,G,B,,5,6,5,);
X(B,G,R,,5,6,5,);
/* These are incompatible on big endian */
#if __BYTE_ORDER == __LITTLE_ENDIAN
X(A,R,G,B,1,5,5,5);
X(A,B,G,R,1,5,5,5);
X(X,R,G,B,1,5,5,5);
X(X,B,G,R,1,5,5,5);
X(A,R,G,B,4,4,4,4);
X(A,B,G,R,4,4,4,4);
X(X,R,G,B,4,4,4,4);
X(X,B,G,R,4,4,4,4);
#endif
#undef X
default: return false;
}
return true;
}

View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2020 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdlib.h>
#include <pixman.h>
#include <wayland-client.h>
#include <neatvnc.h>
#include <assert.h>
#include "buffer.h"
#include "pixels.h"
#include "transform-util.h"
void wv_pixman_render(struct nvnc_fb* dst, const struct wv_buffer* src,
enum wl_output_transform transform,
struct pixman_region16* damage)
{
uint32_t* dst_pixels = nvnc_fb_get_addr(dst);
uint32_t dst_width = nvnc_fb_get_width(dst);
uint32_t dst_height = nvnc_fb_get_height(dst);
bool ok __attribute__((unused));
// TODO: Check that both buffers have the same dimensions after applying
// transform
pixman_format_code_t dst_fmt = 0;
ok = fourcc_to_pixman_fmt(&dst_fmt, nvnc_fb_get_fourcc_format(dst));
assert(ok);
pixman_image_t* dstimg = pixman_image_create_bits_no_clear(
dst_fmt, dst_width, dst_height, dst_pixels,
4 * dst_width);
intptr_t src_offset = src->y_inverted ?
src->stride * (src->height - 1) : 0;
void* src_pixels = (void*)((intptr_t)src->pixels + src_offset);
int src_stride = src->y_inverted ? -src->stride : src->stride;
pixman_format_code_t src_fmt = 0;
ok = fourcc_to_pixman_fmt(&src_fmt, src->format);
assert(ok);
pixman_image_t* srcimg = pixman_image_create_bits_no_clear(
src_fmt, src->width, src->height, src_pixels,
src_stride);
pixman_transform_t pxform;
wv_pixman_transform_from_wl_output_transform(&pxform, transform,
src->width, src->height);
pixman_image_set_transform(srcimg, &pxform);
pixman_image_set_clip_region(dstimg, damage);
pixman_image_composite(PIXMAN_OP_OVER, srcimg, NULL, dstimg,
0, 0,
0, 0,
0, 0,
dst_width, dst_height);
pixman_image_unref(srcimg);
pixman_image_unref(dstimg);
}

View File

@ -1,795 +0,0 @@
/*
* Copyright (c) 2019 - 2020 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include <wayland-client.h>
#include <libdrm/drm_fourcc.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include "logging.h"
#include "render.h"
#include "dmabuf.h"
#include "output.h"
#include "config.h"
#include "usdt.h"
#define MAYBE_UNUSED __attribute__((unused))
#define SHADER_PATH PREFIX "/share/wayvnc/shaders"
enum {
ATTR_INDEX_POS = 0,
ATTR_INDEX_TEXTURE,
};
#define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
#define XSTR(s) STR(s)
#define STR(s) #s
#define X_GL_EARLY_EXTENSIONS \
X(PFNEGLGETPLATFORMDISPLAYEXTPROC, eglGetPlatformDisplayEXT) \
X(PFNEGLDEBUGMESSAGECONTROLKHRPROC, eglDebugMessageControlKHR) \
X(PFNGLDEBUGMESSAGECALLBACKKHRPROC, glDebugMessageCallbackKHR) \
#define X_GL_LATE_EXTENSIONS \
X(PFNEGLCREATEIMAGEKHRPROC, eglCreateImageKHR) \
X(PFNEGLDESTROYIMAGEKHRPROC, eglDestroyImageKHR) \
X(PFNGLEGLIMAGETARGETTEXTURE2DOESPROC, glEGLImageTargetTexture2DOES) \
#define X_GL_EXTENSIONS \
X_GL_EARLY_EXTENSIONS \
X_GL_LATE_EXTENSIONS \
#define X(type, name) type name;
X_GL_EXTENSIONS
#undef X
static const float transforms[][4] = {
[WL_OUTPUT_TRANSFORM_NORMAL] = {
1.0f, 0.0f,
0.0f, 1.0f,
},
[WL_OUTPUT_TRANSFORM_90] = {
0.0f, 1.0f,
-1.0f, 0.0f,
},
[WL_OUTPUT_TRANSFORM_180] = {
-1.0f, 0.0f,
0.0f, -1.0f,
},
[WL_OUTPUT_TRANSFORM_270] = {
0.0f, -1.0f,
1.0f, 0.0f,
},
[WL_OUTPUT_TRANSFORM_FLIPPED] = {
-1.0f, 0.0f,
0.0f, 1.0f,
},
[WL_OUTPUT_TRANSFORM_FLIPPED_90] = {
0.0f, 1.0f,
1.0f, 0.0f,
},
[WL_OUTPUT_TRANSFORM_FLIPPED_180] = {
1.0f, 0.0f,
0.0f, -1.0f,
},
[WL_OUTPUT_TRANSFORM_FLIPPED_270] = {
0.0f, -1.0f,
-1.0f, 0.0f,
},
};
int gl_format_from_fourcc(GLenum* result, uint32_t format)
{
switch (format) {
case DRM_FORMAT_XRGB8888:
case DRM_FORMAT_ARGB8888:
*result = GL_BGRA_EXT;
return 0;
case DRM_FORMAT_XBGR8888:
case DRM_FORMAT_ABGR8888:
*result = GL_RGBA;
return 0;
}
return -1;
}
static inline void* gl_load_single_extension(const char* name)
{
void* ext = eglGetProcAddress(name);
if (!ext)
log_debug("GL: Failed to load procedure: %s\n", name);
return ext;
}
static int gl_load_early_extensions(void)
{
#define X(type, name) \
name = gl_load_single_extension(XSTR(name)); \
if (!name) \
return -1;
X_GL_EARLY_EXTENSIONS
#undef X
return 0;
}
static int gl_load_late_extensions(void)
{
#define X(type, name) \
name = gl_load_single_extension(XSTR(name)); \
if (!name) \
return -1;
X_GL_LATE_EXTENSIONS
#undef X
return 0;
}
MAYBE_UNUSED
static void egl_log(EGLenum error, const char* command, EGLint msg_type,
EGLLabelKHR thread, EGLLabelKHR obj, const char *msg)
{
(void)error;
(void)msg_type;
(void)thread;
(void)obj;
log_debug("EGL: %s: %s\n", command, msg);
}
MAYBE_UNUSED
static void gles2_log(GLenum src, GLenum type, GLuint id, GLenum severity,
GLsizei len, const GLchar *msg, const void *user)
{
(void)src;
(void)type;
(void)id;
(void)severity;
(void)len;
(void)user;
log_debug("GLES2: %s\n", msg);
}
static void gl_debug_init()
{
#ifndef NDEBUG
static const EGLAttrib debug_attribs[] = {
EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE,
EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE,
EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE,
EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE,
EGL_NONE,
};
eglDebugMessageControlKHR(egl_log, debug_attribs);
glEnable(GL_DEBUG_OUTPUT_KHR);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR);
glDebugMessageCallbackKHR(gles2_log, NULL);
#endif
}
static void gl_shader_log(GLuint shader)
{
GLint len = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);
char* log = malloc(len);
assert(log);
glGetShaderInfoLog(shader, len, &len, log);
fwrite(log, 1, len, stderr);
free(log);
}
static int gl_load_shader(GLuint* dst, const char* path, const char* source,
GLenum type)
{
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &source, NULL);
glCompileShader(shader);
GLint is_compiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &is_compiled);
if (!is_compiled) {
log_error("Failed to compile shader: %s\n", path);
gl_shader_log(shader);
glDeleteShader(shader);
return -1;
}
*dst = shader;
return 0;
}
static char* read_file(const char* path)
{
FILE* stream = fopen(path, "r");
if (!stream)
return NULL;
size_t size = 4096;
size_t rsize = 0;
char* contents = malloc(size);
if (!contents)
goto alloc_failure;
while (1) {
rsize += fread(contents + rsize, 1, size - rsize, stream);
if (rsize < size)
break;
size *= 2;
contents = realloc(contents, size);
if (!contents)
goto read_failure;
}
if (ferror(stream))
goto read_failure;
if (rsize == size) {
contents = realloc(contents, size + 1);
if (!contents)
goto read_failure;
}
contents[rsize] = '\0';
fclose(stream);
return contents;
read_failure:
free(contents);
alloc_failure:
fclose(stream);
return NULL;
}
static char* read_file_at_path(const char* prefix, const char* file)
{
char path[256];
snprintf(path, sizeof(path), "%s/%s", prefix, file);
path[sizeof(path) - 1] = '\0';
return read_file(path);
}
static int gl_load_shader_from_file(GLuint* dst, const char* file, GLenum type)
{
char* source = read_file_at_path("shaders", file);
if (!source)
source = read_file_at_path(SHADER_PATH, file);
if (!source)
return -1;
int rc = gl_load_shader(dst, file, source, type);
free(source);
return rc;
}
static int gl_compile_shader_program(GLuint* dst, const char* vertex_path,
const char* fragment_path)
{
int rc = -1;
GLuint vertex, fragment;
if (gl_load_shader_from_file(&vertex, vertex_path, GL_VERTEX_SHADER) < 0)
return -1;
if (gl_load_shader_from_file(&fragment, fragment_path,
GL_FRAGMENT_SHADER) < 0)
goto fragment_failure;
GLuint program = glCreateProgram();
glAttachShader(program, vertex);
glAttachShader(program, fragment);
glBindAttribLocation(program, ATTR_INDEX_POS, "pos");
glBindAttribLocation(program, ATTR_INDEX_TEXTURE, "texture");
glLinkProgram(program);
glDeleteShader(vertex);
glDeleteShader(fragment);
GLint is_linked = 0;
glGetProgramiv(program, GL_LINK_STATUS, &is_linked);
if (!is_linked) {
log_error("Failed to link shaders %s and %s\n", vertex_path,
fragment_path);
gl_shader_log(program);
glDeleteProgram(program);
goto program_failure;
}
*dst = program;
rc = 0;
program_failure:
glDeleteShader(fragment);
fragment_failure:
glDeleteShader(vertex);
return rc;
}
void gl_clear(void)
{
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
}
void gl_draw(void)
{
static const GLfloat s_vertices[4][2] = {
{ -1.0, 1.0 },
{ 1.0, 1.0 },
{ -1.0, -1.0 },
{ 1.0, -1.0 },
};
static const GLfloat s_positions[4][2] = {
{ 0, 0 },
{ 1, 0 },
{ 0, 1 },
{ 1, 1 },
};
gl_clear();
glVertexAttribPointer(ATTR_INDEX_POS, 2, GL_FLOAT, GL_FALSE, 0,
s_vertices);
glVertexAttribPointer(ATTR_INDEX_TEXTURE, 2, GL_FLOAT, GL_FALSE, 0,
s_positions);
glEnableVertexAttribArray(ATTR_INDEX_POS);
glEnableVertexAttribArray(ATTR_INDEX_TEXTURE);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableVertexAttribArray(ATTR_INDEX_TEXTURE);
glDisableVertexAttribArray(ATTR_INDEX_POS);
}
void renderer_swap(struct renderer* self)
{
self->frame_index ^= 1;
}
void render_frame(struct renderer* self)
{
renderer_swap(self);
glBindFramebuffer(GL_FRAMEBUFFER, self->frame_fbo[self->frame_index].fbo);
glUseProgram(self->frame_shader.program);
glUniform1i(self->frame_shader.u_tex0, 0);
const float* proj = transforms[self->output->transform];
glUniformMatrix2fv(self->frame_shader.u_proj, 1, GL_FALSE, proj);
gl_draw();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void render_damage(struct renderer* self)
{
glBindFramebuffer(GL_FRAMEBUFFER, self->damage_fbo.fbo);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, self->frame_fbo[self->frame_index].tex);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, self->frame_fbo[!self->frame_index].tex);
glUseProgram(self->damage_shader.program);
glUniform1i(self->damage_shader.u_tex0, 0);
glUniform1i(self->damage_shader.u_tex1, 1);
gl_draw();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void destroy_fbo(struct renderer_fbo* fbo)
{
glDeleteFramebuffers(1, &fbo->fbo);
glDeleteRenderbuffers(1, &fbo->rbo);
if (fbo->tex)
glDeleteTextures(1, &fbo->tex);
}
int create_fbo(struct renderer_fbo* dst, GLint format, uint32_t width,
uint32_t height)
{
GLuint rbo = 0;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, format, width, height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
GLuint fbo = 0;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, rbo);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if (status != GL_FRAMEBUFFER_COMPLETE) {
log_error("Framebuffer incomplete\n");
return -1;
}
dst->fbo = fbo;
dst->rbo = rbo;
dst->tex = 0;
return 0;
}
int create_textured_fbo(struct renderer_fbo* dst, GLint format, uint32_t width,
uint32_t height)
{
GLuint tex = 0;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format,
GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
GLuint fbo = 0;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, tex, 0);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if (status != GL_FRAMEBUFFER_COMPLETE) {
log_error("Framebuffer incomplete\n");
return -1;
}
dst->fbo = fbo;
dst->rbo = 0;
dst->tex = tex;
return 0;
}
void renderer_destroy(struct renderer* self)
{
glDeleteProgram(self->frame_shader.program);
eglMakeCurrent(self->display, EGL_NO_SURFACE, EGL_NO_SURFACE,
EGL_NO_CONTEXT);
destroy_fbo(&self->frame_fbo[1]);
destroy_fbo(&self->frame_fbo[0]);
eglDestroyContext(self->display, self->context);
eglTerminate(self->display);
}
int renderer_init(struct renderer* self, const struct output* output,
enum renderer_input_type input_type)
{
if (!eglBindAPI(EGL_OPENGL_ES_API)) {
log_error("Failed to bind EGL API\n");
return -1;
}
if (gl_load_early_extensions() < 0) {
log_error("Failed to load early GL extensions\n");
return -1;
}
gl_debug_init();
self->display =
eglGetPlatformDisplayEXT(EGL_PLATFORM_SURFACELESS_MESA,
EGL_DEFAULT_DISPLAY, NULL);
if (!self->display) {
log_error("Failed to get EGL display\n");
return -1;
}
if (!eglInitialize(self->display, NULL, NULL)) {
log_error("Failed to get initialize EGL\n");
return -1;
}
static const EGLint cfg_attr[] = {
EGL_SURFACE_TYPE, 0,
EGL_ALPHA_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_NONE
};
EGLConfig cfg;
EGLint cfg_count;
if (!eglChooseConfig(self->display, cfg_attr, &cfg, 1, &cfg_count)) {
log_error("Could not choose EGL config\n");
return -1;
}
static const EGLint ctx_attr[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
self->context = eglCreateContext(self->display, cfg, EGL_NO_CONTEXT,
ctx_attr);
if (!self->context) {
log_error("Failed to create EGL context\n");
return -1;
}
if (!eglMakeCurrent(self->display, EGL_NO_SURFACE, EGL_NO_SURFACE,
self->context)) {
log_error("Failed to make EGL context current\n");
goto make_current_failure;
}
log_debug("%s\n", glGetString(GL_VERSION));
if (gl_load_late_extensions() < 0) {
log_error("Failed to load late GL extensions\n");
goto late_extension_failure;
}
uint32_t tf_width = output_get_transformed_width(output);
uint32_t tf_height = output_get_transformed_height(output);
if (create_textured_fbo(&self->frame_fbo[0], GL_RGBA, tf_width,
tf_height) < 0) {
log_error("Failed to create frame FBO 0\n");
goto frame_fbo_failure_0;
}
if (create_textured_fbo(&self->frame_fbo[1], GL_RGBA, tf_width,
tf_height) < 0) {
log_error("Failed to create frame FBO 1\n");
goto frame_fbo_failure_1;
}
if (create_fbo(&self->damage_fbo, GL_R8_EXT, tf_width, tf_height) < 0) {
log_error("Failed to create damage FBO\n");
goto damage_fbo_failure;
}
glBindFramebuffer(GL_FRAMEBUFFER, self->frame_fbo[0].fbo);
switch (input_type) {
case RENDERER_INPUT_DMABUF:
if (gl_compile_shader_program(&self->frame_shader.program,
"dmabuf-vertex.glsl",
"dmabuf-fragment.glsl") < 0)
goto frame_shader_failure;
break;
case RENDERER_INPUT_FB:
if (gl_compile_shader_program(&self->frame_shader.program,
"texture-vertex.glsl",
"texture-fragment.glsl") < 0)
goto frame_shader_failure;
break;
}
if (gl_compile_shader_program(&self->damage_shader.program,
"damage-vertex.glsl",
"damage-fragment.glsl") < 0)
goto damage_shader_failure;
self->frame_shader.u_tex0 =
glGetUniformLocation(self->frame_shader.program, "u_tex0");
self->frame_shader.u_proj =
glGetUniformLocation(self->frame_shader.program, "u_proj");
self->damage_shader.u_tex0 =
glGetUniformLocation(self->damage_shader.program, "u_tex0");
self->damage_shader.u_tex1 =
glGetUniformLocation(self->damage_shader.program, "u_tex1");
self->output = output;
glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &self->read_format);
glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &self->read_type);
glViewport(0, 0,
output_get_transformed_width(output),
output_get_transformed_height(output));
gl_clear();
return 0;
damage_shader_failure:
glDeleteShader(self->frame_shader.program);
frame_shader_failure:
destroy_fbo(&self->damage_fbo);
damage_fbo_failure:
destroy_fbo(&self->frame_fbo[1]);
frame_fbo_failure_1:
destroy_fbo(&self->frame_fbo[0]);
frame_fbo_failure_0:
late_extension_failure:
make_current_failure:
eglDestroyContext(self->display, self->context);
return -1;
}
static inline void append_attr(EGLint* dst, int* i, EGLint name, EGLint value)
{
dst[*i] = name;
i[0] += 1;
dst[*i] = value;
i[0] += 1;
}
static void dmabuf_attr_append_planes(EGLint* dst, int* i,
struct dmabuf_frame* frame)
{
#define APPEND_PLANE_ATTR(n) \
if (frame->n_planes <= n) \
return; \
\
append_attr(dst, i, EGL_DMA_BUF_PLANE##n##_FD_EXT, frame->plane[n].fd); \
append_attr(dst, i, EGL_DMA_BUF_PLANE##n##_OFFSET_EXT, frame->plane[n].offset); \
append_attr(dst, i, EGL_DMA_BUF_PLANE##n##_PITCH_EXT, frame->plane[n].pitch); \
append_attr(dst, i, EGL_DMA_BUF_PLANE##n##_MODIFIER_LO_EXT, frame->plane[n].modifier); \
append_attr(dst, i, EGL_DMA_BUF_PLANE##n##_MODIFIER_HI_EXT, frame->plane[n].modifier >> 32); \
APPEND_PLANE_ATTR(0);
APPEND_PLANE_ATTR(1);
APPEND_PLANE_ATTR(2);
APPEND_PLANE_ATTR(3);
#undef APPEND_PLANE_ATTR
}
int render_dmabuf(struct renderer* self, struct dmabuf_frame* frame)
{
DTRACE_PROBE1(wayvnc, render_dmabuf_start, self);
int index = 0;
EGLint attr[6 + 10 * 4 + 1];
if (frame->n_planes == 0)
return -1;
append_attr(attr, &index, EGL_WIDTH, frame->width);
append_attr(attr, &index, EGL_HEIGHT, frame->height);
append_attr(attr, &index, EGL_LINUX_DRM_FOURCC_EXT, frame->format);
dmabuf_attr_append_planes(attr, &index, frame);
attr[index++] = EGL_NONE;
EGLImageKHR image =
eglCreateImageKHR(self->display, EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT, NULL, attr);
if (!image)
return -1;
GLuint tex = 0;
glGenTextures(1, &tex);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex);
glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image);
eglDestroyImageKHR(self->display, image);
render_frame(self);
glFinish();
glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
glDeleteTextures(1, &tex);
DTRACE_PROBE(wayvnc, render_dmabuf_end);
return 0;
}
int render_framebuffer(struct renderer* self, const void* addr, uint32_t format,
uint32_t width, uint32_t height, uint32_t stride)
{
DTRACE_PROBE1(wayvnc, render_framebuffer_start, self);
GLuint tex = 0;
glGenTextures(1, &tex);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex);
GLenum gl_format;
if (gl_format_from_fourcc(&gl_format, format) < 0)
return -1;
glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / 4);
glTexImage2D(GL_TEXTURE_2D, 0, self->read_format, width, height, 0,
gl_format, GL_UNSIGNED_BYTE, addr);
glGenerateMipmap(GL_TEXTURE_2D);
glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);
render_frame(self);
glBindTexture(GL_TEXTURE_2D, 0);
glDeleteTextures(1, &tex);
DTRACE_PROBE(wayvnc, render_framebuffer_end);
return 0;
}
void renderer_read_pixels(struct renderer* self, void* dst, uint32_t y,
uint32_t height)
{
assert(y + height <= output_get_transformed_height(self->output));
uint32_t width = output_get_transformed_width(self->output);
GLint read_format, read_type;
glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &read_format);
glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &read_type);
glFinish();
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(0, y, width, height, read_format, read_type, dst);
}
void renderer_read_frame(struct renderer* self, void* dst, uint32_t y,
uint32_t height)
{
DTRACE_PROBE3(wayvnc, render_read_frame_start, self, y, height);
glBindFramebuffer(GL_FRAMEBUFFER, self->frame_fbo[self->frame_index].fbo);
renderer_read_pixels(self, dst, y, height);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
DTRACE_PROBE(wayvnc, render_read_frame_end);
}
void renderer_read_damage(struct renderer* self, void* dst, uint32_t y,
uint32_t height)
{
DTRACE_PROBE1(wayvnc, render_read_damage_start, self);
glBindFramebuffer(GL_FRAMEBUFFER, self->damage_fbo.fbo);
renderer_read_pixels(self, dst, y, height);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
DTRACE_PROBE(wayvnc, render_read_damage_end);
}

View File

@ -24,75 +24,21 @@
#include <libdrm/drm_fourcc.h> #include <libdrm/drm_fourcc.h>
#include <aml.h> #include <aml.h>
#include "frame-capture.h" #include "wlr-screencopy-unstable-v1.h"
#include "buffer.h"
#include "shm.h" #include "shm.h"
#include "screencopy.h" #include "screencopy.h"
#include "smooth.h" #include "smooth.h"
#include "time-util.h" #include "time-util.h"
#include "render.h"
#include "usdt.h" #include "usdt.h"
#define RATE_LIMIT 20.0 // Hz
#define DELAY_SMOOTHER_TIME_CONSTANT 0.5 // s #define DELAY_SMOOTHER_TIME_CONSTANT 0.5 // s
static uint32_t fourcc_from_wl_shm(enum wl_shm_format in) void screencopy_stop(struct screencopy* self)
{ {
switch (in) {
case WL_SHM_FORMAT_ARGB8888: return DRM_FORMAT_ARGB8888;
case WL_SHM_FORMAT_XRGB8888: return DRM_FORMAT_XRGB8888;
default: return in;
}
}
static int screencopy_buffer_init(struct screencopy* self,
enum wl_shm_format format, uint32_t width,
uint32_t height, uint32_t stride)
{
if (self->buffer)
return 0;
size_t size = stride * height;
int fd = shm_alloc_fd(size);
if (fd < 0)
return -1;
void* addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (!addr)
goto mmap_failure;
struct wl_shm_pool* pool = wl_shm_create_pool(self->wl_shm, fd, size);
if (!pool)
goto shm_failure;
struct wl_buffer* buffer =
wl_shm_pool_create_buffer(pool, 0, width, height, stride,
format);
wl_shm_pool_destroy(pool);
if (!buffer)
goto shm_failure;
self->buffer = buffer;
self->pixels = addr;
self->bufsize = size;
close(fd);
return 0;
shm_failure:
munmap(addr, size);
mmap_failure:
close(fd);
return -1;
}
static void screencopy_stop(struct frame_capture* fc)
{
struct screencopy* self = (void*)fc;
aml_stop(aml_get_default(), self->timer); aml_stop(aml_get_default(), self->timer);
self->frame_capture.status = CAPTURE_STOPPED; self->status = SCREENCOPY_STOPPED;
if (self->frame) { if (self->frame) {
zwlr_screencopy_frame_v1_destroy(self->frame); zwlr_screencopy_frame_v1_destroy(self->frame);
@ -100,6 +46,58 @@ static void screencopy_stop(struct frame_capture* fc)
} }
} }
static void screencopy_linux_dmabuf(void* data,
struct zwlr_screencopy_frame_v1* frame,
uint32_t format, uint32_t width, uint32_t height)
{
struct screencopy* self = data;
self->have_linux_dmabuf = true;
self->dmabuf_width = width;
self->dmabuf_height = height;
self->fourcc = format;
}
static void screencopy_buffer_done(void* data,
struct zwlr_screencopy_frame_v1* frame)
{
struct screencopy* self = data;
uint32_t width, height, stride, fourcc;
if (self->have_linux_dmabuf) {
width = self->dmabuf_width;
height = self->dmabuf_height;
stride = 0;
fourcc = self->fourcc;
wv_buffer_pool_resize(self->pool, WV_BUFFER_DMABUF, width,
height, stride, fourcc);
} else {
width = self->wl_shm_width;
height = self->wl_shm_height;
stride = self->wl_shm_stride;
fourcc = self->fourcc;
wv_buffer_pool_resize(self->pool, WV_BUFFER_SHM, width,
height, stride, self->wl_shm_format);
}
struct wv_buffer* buffer = wv_buffer_pool_acquire(self->pool);
if (!buffer) {
screencopy_stop(self);
self->status = SCREENCOPY_FATAL;
self->on_done(self);
return;
}
assert(!self->front);
self->front = buffer;
if (self->is_immediate_copy)
zwlr_screencopy_frame_v1_copy(self->frame, buffer->wl_buffer);
else
zwlr_screencopy_frame_v1_copy_with_damage(self->frame,
buffer->wl_buffer);
}
static void screencopy_buffer(void* data, static void screencopy_buffer(void* data,
struct zwlr_screencopy_frame_v1* frame, struct zwlr_screencopy_frame_v1* frame,
enum wl_shm_format format, uint32_t width, enum wl_shm_format format, uint32_t width,
@ -107,34 +105,28 @@ static void screencopy_buffer(void* data,
{ {
struct screencopy* self = data; struct screencopy* self = data;
if (screencopy_buffer_init(self, format, width, height, stride) < 0) { self->wl_shm_format = format;
self->frame_capture.status = CAPTURE_FATAL; self->wl_shm_width = width;
screencopy_stop(&self->frame_capture); self->wl_shm_height = height;
self->frame_capture.on_done(&self->frame_capture); self->wl_shm_stride = stride;
if (self->version < 3) {
self->have_linux_dmabuf = false;
screencopy_buffer_done(data, frame);
return;
} }
self->frame_capture.frame_info.fourcc_format =
fourcc_from_wl_shm(format);
self->frame_capture.frame_info.width = width;
self->frame_capture.frame_info.height = height;
self->frame_capture.frame_info.stride = stride;
if (self->is_immediate_copy)
zwlr_screencopy_frame_v1_copy(self->frame, self->buffer);
else
zwlr_screencopy_frame_v1_copy_with_damage(self->frame,
self->buffer);
} }
static void screencopy_flags(void* data, static void screencopy_flags(void* data,
struct zwlr_screencopy_frame_v1* frame, struct zwlr_screencopy_frame_v1* frame,
uint32_t flags) uint32_t flags)
{ {
(void)data;
(void)frame; (void)frame;
(void)flags;
/* TODO. Assume y-invert for now */ struct screencopy* self = data;
self->front->y_inverted =
!!(flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT);
} }
static void screencopy_ready(void* data, static void screencopy_ready(void* data,
@ -149,24 +141,25 @@ static void screencopy_ready(void* data,
DTRACE_PROBE1(wayvnc, screencopy_ready, self); DTRACE_PROBE1(wayvnc, screencopy_ready, self);
screencopy_stop(&self->frame_capture); screencopy_stop(self);
self->last_time = gettime_us(); self->last_time = gettime_us();
double delay = (self->last_time - self->start_time) * 1.0e-6; double delay = (self->last_time - self->start_time) * 1.0e-6;
self->delay = smooth(&self->delay_smoother, delay); self->delay = smooth(&self->delay_smoother, delay);
if (self->is_immediate_copy) { if (self->is_immediate_copy)
self->frame_capture.damage_hint.x = 0; wv_buffer_damage_whole(self->front);
self->frame_capture.damage_hint.y = 0;
self->frame_capture.damage_hint.width =
self->frame_capture.frame_info.width;
self->frame_capture.damage_hint.height =
self->frame_capture.frame_info.height;
}
self->frame_capture.status = CAPTURE_DONE; if (self->back)
self->frame_capture.on_done(&self->frame_capture); wv_buffer_pool_release(self->pool, self->back);
self->back = self->front;
self->front = NULL;
wv_buffer_map(self->back);
self->status = SCREENCOPY_DONE;
self->on_done(self);
} }
static void screencopy_failed(void* data, static void screencopy_failed(void* data,
@ -176,9 +169,9 @@ static void screencopy_failed(void* data,
DTRACE_PROBE1(wayvnc, screencopy_failed, self); DTRACE_PROBE1(wayvnc, screencopy_failed, self);
screencopy_stop(&self->frame_capture); screencopy_stop(self);
self->frame_capture.status = CAPTURE_FAILED; self->status = SCREENCOPY_FAILED;
self->frame_capture.on_done(&self->frame_capture); self->on_done(self);
} }
static void screencopy_damage(void* data, static void screencopy_damage(void* data,
@ -190,20 +183,17 @@ static void screencopy_damage(void* data,
DTRACE_PROBE1(wayvnc, screencopy_damage, self); DTRACE_PROBE1(wayvnc, screencopy_damage, self);
self->frame_capture.damage_hint.x = x; wv_buffer_damage_rect(self->front, x, y, width, height);
self->frame_capture.damage_hint.y = y;
self->frame_capture.damage_hint.width = width;
self->frame_capture.damage_hint.height = height;
} }
static int screencopy__start_capture(struct frame_capture* fc) static int screencopy__start_capture(struct screencopy* self)
{ {
struct screencopy* self = (void*)fc;
DTRACE_PROBE1(wayvnc, screencopy_start, self); DTRACE_PROBE1(wayvnc, screencopy_start, self);
static const struct zwlr_screencopy_frame_v1_listener frame_listener = { static const struct zwlr_screencopy_frame_v1_listener frame_listener = {
.buffer = screencopy_buffer, .buffer = screencopy_buffer,
.linux_dmabuf = screencopy_linux_dmabuf,
.buffer_done = screencopy_buffer_done,
.flags = screencopy_flags, .flags = screencopy_flags,
.ready = screencopy_ready, .ready = screencopy_ready,
.failed = screencopy_failed, .failed = screencopy_failed,
@ -212,10 +202,8 @@ static int screencopy__start_capture(struct frame_capture* fc)
self->start_time = gettime_us(); self->start_time = gettime_us();
self->frame = self->frame = zwlr_screencopy_manager_v1_capture_output(self->manager,
zwlr_screencopy_manager_v1_capture_output(self->manager, self->overlay_cursor, self->wl_output);
fc->overlay_cursor,
fc->wl_output);
if (!self->frame) if (!self->frame)
return -1; return -1;
@ -228,58 +216,51 @@ static int screencopy__start_capture(struct frame_capture* fc)
static void screencopy__poll(void* obj) static void screencopy__poll(void* obj)
{ {
struct screencopy* self = aml_get_userdata(obj); struct screencopy* self = aml_get_userdata(obj);
struct frame_capture* fc = (struct frame_capture*)self;
screencopy__start_capture(fc); screencopy__start_capture(self);
} }
static int screencopy_start(struct frame_capture* fc, static int screencopy__start(struct screencopy* self, bool is_immediate_copy)
enum frame_capture_options options)
{ {
struct screencopy* self = (void*)fc;
if (fc->status == CAPTURE_IN_PROGRESS) if (self->status == SCREENCOPY_IN_PROGRESS)
return -1; return -1;
self->is_immediate_copy = !!(options & CAPTURE_NOW); self->is_immediate_copy = is_immediate_copy;
uint64_t now = gettime_us(); uint64_t now = gettime_us();
double dt = (now - self->last_time) * 1.0e-6; double dt = (now - self->last_time) * 1.0e-6;
double time_left = (1.0 / RATE_LIMIT - dt - self->delay) * 1.0e3; double time_left = (1.0 / self->rate_limit - dt - self->delay) * 1.0e3;
fc->status = CAPTURE_IN_PROGRESS; self->status = SCREENCOPY_IN_PROGRESS;
if (time_left > 0) { if (time_left > 0) {
aml_set_duration(self->timer, time_left); aml_set_duration(self->timer, time_left);
return aml_start(aml_get_default(), self->timer); return aml_start(aml_get_default(), self->timer);
} }
return screencopy__start_capture(fc); return screencopy__start_capture(self);
} }
static void screencopy_render(struct frame_capture* fc, int screencopy_start(struct screencopy* self)
struct renderer* renderer, struct nvnc_fb* fb)
{ {
struct screencopy* self = (void*)fc; return screencopy__start(self, false);
}
uint32_t width = fc->frame_info.width; int screencopy_start_immediate(struct screencopy* self)
uint32_t height = fc->frame_info.height; {
uint32_t stride = fc->frame_info.stride; return screencopy__start(self, true);
uint32_t format = fc->frame_info.fourcc_format;
render_framebuffer(renderer, self->pixels, format, width, height, stride);
} }
void screencopy_init(struct screencopy* self) void screencopy_init(struct screencopy* self)
{ {
self->pool = wv_buffer_pool_create(0, 0, 0, 0, 0);
assert(self->pool);
self->timer = aml_timer_new(0, screencopy__poll, self, NULL); self->timer = aml_timer_new(0, screencopy__poll, self, NULL);
assert(self->timer); assert(self->timer);
self->delay_smoother.time_constant = DELAY_SMOOTHER_TIME_CONSTANT; self->delay_smoother.time_constant = DELAY_SMOOTHER_TIME_CONSTANT;
self->frame_capture.backend.start = screencopy_start;
self->frame_capture.backend.stop = screencopy_stop;
self->frame_capture.backend.render = screencopy_render;
} }
void screencopy_destroy(struct screencopy* self) void screencopy_destroy(struct screencopy* self)
@ -287,6 +268,10 @@ void screencopy_destroy(struct screencopy* self)
aml_stop(aml_get_default(), self->timer); aml_stop(aml_get_default(), self->timer);
aml_unref(self->timer); aml_unref(self->timer);
if (self->buffer) if (self->back)
wl_buffer_destroy(self->buffer); wv_buffer_pool_release(self->pool, self->back);
if (self->front)
wv_buffer_pool_release(self->pool, self->front);
wv_buffer_pool_destroy(self->pool);
} }

View File

@ -0,0 +1,236 @@
/*
* Copyright (c) 2020 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*
* For code borrowed from wlroots:
* Copyright (c) 2017, 2018 Drew DeVault
* Copyright (c) 2014 Jari Vetoniemi
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <stdlib.h>
#include <wayland-client.h>
#include <pixman.h>
/* Note: This function yields the inverse pixman transform of the
* wl_output_transform.
*/
void wv_pixman_transform_from_wl_output_transform(pixman_transform_t* dst,
enum wl_output_transform src, int width, int height)
{
#define F1 pixman_fixed_1
switch (src) {
case WL_OUTPUT_TRANSFORM_NORMAL:
{
pixman_transform_t t = {{
{ F1, 0, 0 },
{ 0, F1, 0 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
case WL_OUTPUT_TRANSFORM_90:
{
pixman_transform_t t = {{
{ 0, F1, 0 },
{ -F1, 0, height * F1 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
case WL_OUTPUT_TRANSFORM_180:
{
pixman_transform_t t = {{
{ -F1, 0, width * F1 },
{ 0, -F1, height * F1 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
case WL_OUTPUT_TRANSFORM_270:
{
pixman_transform_t t = {{
{ 0, -F1, width * F1 },
{ F1, 0, 0 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
case WL_OUTPUT_TRANSFORM_FLIPPED:
{
pixman_transform_t t = {{
{ -F1, 0, width * F1 },
{ 0, F1, 0 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
{
pixman_transform_t t = {{
{ 0, F1, 0 },
{ F1, 0, 0 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
case WL_OUTPUT_TRANSFORM_FLIPPED_180:
{
pixman_transform_t t = {{
{ F1, 0, 0 },
{ 0, -F1, height * F1 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
case WL_OUTPUT_TRANSFORM_FLIPPED_270:
{
pixman_transform_t t = {{
{ 0, -F1, width * F1 },
{ -F1, 0, height * F1 },
{ 0, 0, F1 },
}};
*dst = t;
}
return;
}
#undef F1
abort();
}
/* Borrowed these from wlroots */
void wv_region_transform(struct pixman_region16* dst,
struct pixman_region16* src, enum wl_output_transform transform,
int width, int height)
{
if (transform == WL_OUTPUT_TRANSFORM_NORMAL) {
pixman_region_copy(dst, src);
return;
}
int nrects = 0;
pixman_box16_t* src_rects = pixman_region_rectangles(src, &nrects);
pixman_box16_t* dst_rects = malloc(nrects * sizeof(*dst_rects));
if (dst_rects == NULL) {
return;
}
for (int i = 0; i < nrects; ++i) {
switch (transform) {
case WL_OUTPUT_TRANSFORM_NORMAL:
dst_rects[i].x1 = src_rects[i].x1;
dst_rects[i].y1 = src_rects[i].y1;
dst_rects[i].x2 = src_rects[i].x2;
dst_rects[i].y2 = src_rects[i].y2;
break;
case WL_OUTPUT_TRANSFORM_90:
dst_rects[i].x1 = height - src_rects[i].y2;
dst_rects[i].y1 = src_rects[i].x1;
dst_rects[i].x2 = height - src_rects[i].y1;
dst_rects[i].y2 = src_rects[i].x2;
break;
case WL_OUTPUT_TRANSFORM_180:
dst_rects[i].x1 = width - src_rects[i].x2;
dst_rects[i].y1 = height - src_rects[i].y2;
dst_rects[i].x2 = width - src_rects[i].x1;
dst_rects[i].y2 = height - src_rects[i].y1;
break;
case WL_OUTPUT_TRANSFORM_270:
dst_rects[i].x1 = src_rects[i].y1;
dst_rects[i].y1 = width - src_rects[i].x2;
dst_rects[i].x2 = src_rects[i].y2;
dst_rects[i].y2 = width - src_rects[i].x1;
break;
case WL_OUTPUT_TRANSFORM_FLIPPED:
dst_rects[i].x1 = width - src_rects[i].x2;
dst_rects[i].y1 = src_rects[i].y1;
dst_rects[i].x2 = width - src_rects[i].x1;
dst_rects[i].y2 = src_rects[i].y2;
break;
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
dst_rects[i].x1 = src_rects[i].y1;
dst_rects[i].y1 = src_rects[i].x1;
dst_rects[i].x2 = src_rects[i].y2;
dst_rects[i].y2 = src_rects[i].x2;
break;
case WL_OUTPUT_TRANSFORM_FLIPPED_180:
dst_rects[i].x1 = src_rects[i].x1;
dst_rects[i].y1 = height - src_rects[i].y2;
dst_rects[i].x2 = src_rects[i].x2;
dst_rects[i].y2 = height - src_rects[i].y1;
break;
case WL_OUTPUT_TRANSFORM_FLIPPED_270:
dst_rects[i].x1 = height - src_rects[i].y2;
dst_rects[i].y1 = width - src_rects[i].x2;
dst_rects[i].x2 = height - src_rects[i].y1;
dst_rects[i].y2 = width - src_rects[i].x1;
break;
}
}
pixman_region_fini(dst);
pixman_region_init_rects(dst, dst_rects, nrects);
free(dst_rects);
}
enum wl_output_transform wv_output_transform_invert(enum wl_output_transform tr)
{
if ((tr & WL_OUTPUT_TRANSFORM_90) && !(tr & WL_OUTPUT_TRANSFORM_FLIPPED)) {
tr ^= WL_OUTPUT_TRANSFORM_180;
}
return tr;
}
enum wl_output_transform wv_output_transform_compose(
enum wl_output_transform tr_a, enum wl_output_transform tr_b)
{
uint32_t flipped = (tr_a ^ tr_b) & WL_OUTPUT_TRANSFORM_FLIPPED;
uint32_t rotation_mask = WL_OUTPUT_TRANSFORM_90 | WL_OUTPUT_TRANSFORM_180;
uint32_t rotated;
if (tr_b & WL_OUTPUT_TRANSFORM_FLIPPED) {
// When a rotation of k degrees is followed by a flip, the
// equivalent transform is a flip followed by a rotation of
// -k degrees.
rotated = (tr_b - tr_a) & rotation_mask;
} else {
rotated = (tr_a + tr_b) & rotation_mask;
}
return flipped | rotated;
}

View File

@ -1,15 +0,0 @@
test_inc = include_directories('../include')
test('damage',
executable('test-damage',
[
'test-damage.c',
'../src/damage.c',
],
include_directories: test_inc,
dependencies: [
pixman,
aml,
]
)
)

View File

@ -1,104 +0,0 @@
#include "damage.h"
#include "tst.h"
#include <pixman.h>
static int test_damage_check_row_aligned(void)
{
uint32_t width = 32;
uint8_t row[width];
uint8_t r = 0;
memset(row, 0, width);
damage_check_row(&r, row, width);
ASSERT_FALSE(r);
row[0] = 1;
r = 0;
damage_check_row(&r, row, width);
ASSERT_TRUE(r);
return 0;
}
static int test_damage_check_row_unaligned(void)
{
uint32_t width = 33;
uint8_t row[width];
uint8_t r[2] = { 0 };
memset(row, 0, width);
damage_check_row(r, row, width);
ASSERT_FALSE(r[0]);
ASSERT_FALSE(r[1]);
row[32] = 1;
r[0] = 0;
r[1] = 0;
damage_check_row(r, row, width);
ASSERT_FALSE(r[0]);
ASSERT_TRUE(r[1]);
return 0;
}
static int test_damage_check_tile_row_aligned(void)
{
uint32_t width = 32;
uint32_t height = 32;
uint8_t tile[width * height];
struct pixman_region16 damage;
uint8_t row = 0;
pixman_region_init(&damage);
memset(tile, 0, sizeof(tile));
damage_check_tile_row(&damage, &row, tile, 0, width, height);
ASSERT_FALSE(pixman_region_not_empty(&damage));
row = 0;
pixman_region_clear(&damage);
tile[0] = 1;
damage_check_tile_row(&damage, &row, tile, 0, width, height);
ASSERT_TRUE(pixman_region_not_empty(&damage));
pixman_region_fini(&damage);
return 0;
}
static int test_damage_check_tile_row_unaligned(void)
{
uint32_t width = 33;
uint32_t height = 32;
uint8_t tile[width * height];
struct pixman_region16 damage;
uint8_t row[2] = { 0 };
pixman_region_init(&damage);
memset(tile, 0, sizeof(tile));
damage_check_tile_row(&damage, row, tile, 0, width, height);
ASSERT_FALSE(pixman_region_not_empty(&damage));
row[0] = 0;
row[1] = 0;
pixman_region_clear(&damage);
tile[32] = 1;
damage_check_tile_row(&damage, row, tile, 0, width, height);
ASSERT_TRUE(pixman_region_not_empty(&damage));
pixman_region_fini(&damage);
return 0;
}
int main()
{
int r = 0;
RUN_TEST(test_damage_check_row_aligned);
RUN_TEST(test_damage_check_row_unaligned);
RUN_TEST(test_damage_check_tile_row_aligned);
RUN_TEST(test_damage_check_tile_row_unaligned);
return r;
}

View File

@ -58,10 +58,8 @@ trackers = [
StateTracker('Framebuffer update', 'sdt_neatvnc', 'update_fb_start', 'update_fb_done'), StateTracker('Framebuffer update', 'sdt_neatvnc', 'update_fb_start', 'update_fb_done'),
StateTracker('Framebuffer update (only sending)', 'sdt_neatvnc', 'send_fb_start', 'send_fb_done'), StateTracker('Framebuffer update (only sending)', 'sdt_neatvnc', 'send_fb_start', 'send_fb_done'),
StateTracker('Screencopy', 'sdt_wayvnc', 'screencopy_start', 'screencopy_ready'), StateTracker('Screencopy', 'sdt_wayvnc', 'screencopy_start', 'screencopy_ready'),
StateTracker('Dmabuf', 'sdt_wayvnc', 'dmabuf_capture_start', 'dmabuf_frame_ready'), StateTracker('Refine damage', 'sdt_wayvnc', 'refine_damage_start', 'refine_damage_end'),
StateTracker('Render', 'sdt_wayvnc', 'render_framebuffer_start', 'render_framebuffer_end'), StateTracker('Render', 'sdt_wayvnc', 'render_start', 'render_end'),
StateTracker('Copy damage map', 'sdt_wayvnc', 'render_read_damage_start', 'render_read_damage_end'),
StateTracker('Copy frame pixels', 'sdt_wayvnc', 'render_read_frame_start', 'render_read_frame_end'),
] ]
for line in stream: for line in stream:

6
util/valgrind.sh 100755
View File

@ -0,0 +1,6 @@
#!/bin/sh
valgrind --leak-check=full \
--show-leak-kinds=all \
--suppressions=util/valgrind.supp \
$@

View File

@ -0,0 +1,7 @@
{
Ignore dlopen bug.
Memcheck:Leak
...
fun:_dl_*
...
}