mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-05 04:06:08 -05:00
Foot likes it when compositor releases buffer immediately, as that means we only have to re-render the cells that have changed since the last frame. For various reasons, not all compositors do this. In this case, foot is typically forced to switch between two buffers, i.e. double-buffer. In this case, each frame starts with copying over the damage from the previous frame, to the new frame. Then we start rendering the updated cells. Bringing over the previous frame's damage can be slow, if the changed area was large (e.g. when scrolling one or a few lines, or on full screen updates). It's also done single-threaded. Thus it not only slows down frame rendering, but pauses everything else (i.e. input processing). All in all, it reduces performance and increases input latency. But we don't have to wait until it's time to render a frame to copy over the previous frame's damage. We can do that as soon as the compositor has released the buffer (for the frame _before_ the previous frame). And we can do this in a thread. This frees up foot to continue processing input, and reduces frame rendering time since we can now start rendering the modified cells immediately, without first doing a large memcpy(3). In worst case scenarios (or perhaps we should consider them best case scenarios...), I've seen up to a 10x performance increase in frame rendering times (this obviously does *not* include the time it takes to copy over the previous frame's damage, since that doesn't affect neither input processing nor frame rendering). Implemented by adding a callback mechanism to the shm abstraction layer. Use it for the grid buffers, and kick off a thread that copies the previous frame's damage, and resets the buffers age to 0 (so that foot understands it can start render to it immediately when it later needs to render a frame). Since we have certain way of knowing if a compositor releases buffers immediately or not, use a bit of heuristics; if we see 10 consecutive non-immediate releases (that is, we reset the counter as soon as we do see an immediate release), this new "pre-apply damage" logic is enabled. It can be force-disabled with tweak.pre-apply-damage=no. We also need to take care to wait for the thread before resetting the render's "last_buf" pointer (or we'll SEGFAULT in the thread...). We must also ensure we wait for the thread to finish before we start rendering a new frame. Under normal circumstances, the wait time is always 0, the thread has almost always finished long before we need to render the next frame. But it _can_ happen. Closes #2188
99 lines
2.8 KiB
C
99 lines
2.8 KiB
C
#pragma once
|
|
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <pixman.h>
|
|
#include <wayland-client.h>
|
|
|
|
#include <tllist.h>
|
|
|
|
#include "config.h"
|
|
#include "wayland.h"
|
|
|
|
struct damage;
|
|
|
|
struct buffer {
|
|
int width;
|
|
int height;
|
|
int stride;
|
|
|
|
void *data;
|
|
|
|
struct wl_buffer *wl_buf;
|
|
pixman_image_t **pix;
|
|
size_t pix_instances;
|
|
|
|
unsigned age;
|
|
|
|
/*
|
|
* First item in the array is used to track frame-to-frame
|
|
* damage. This is used when re-applying damage from the last
|
|
* frame, when the compositor doesn't release buffers immediately
|
|
* (forcing us to double buffer)
|
|
*
|
|
* The remaining items are used to track surface damage. Each
|
|
* worker thread adds its own cell damage to "its" region. When
|
|
* the frame is done, all damage is converted to a single region,
|
|
* which is then used in calls to wl_surface_damage_buffer().
|
|
*/
|
|
pixman_region32_t *dirty;
|
|
};
|
|
|
|
void shm_fini(void);
|
|
|
|
/* TODO: combine into shm_init() */
|
|
void shm_set_max_pool_size(off_t max_pool_size);
|
|
void shm_set_min_stride_alignment(size_t min_stride_alignment);
|
|
|
|
struct buffer_chain;
|
|
struct buffer_chain *shm_chain_new(
|
|
struct wayland *wayl, bool scrollable, size_t pix_instances,
|
|
enum shm_bit_depth desired_bit_depth,
|
|
void (*release_cb)(struct buffer *buf, void *data), void *cb_data);
|
|
void shm_chain_free(struct buffer_chain *chain);
|
|
|
|
enum shm_bit_depth shm_chain_bit_depth(const struct buffer_chain *chain);
|
|
|
|
/*
|
|
* Returns a single buffer.
|
|
*
|
|
* May returned a cached buffer. If so, the buffer's age indicates how
|
|
* many shm_get_buffer() calls have been made for the same
|
|
* width/height while the buffer was still busy.
|
|
*
|
|
* A newly allocated buffer has an age of 1234.
|
|
*/
|
|
struct buffer *shm_get_buffer(
|
|
struct buffer_chain *chain, int width, int height, bool with_alpha);
|
|
/*
|
|
* Returns many buffers, described by 'info', all sharing the same SHM
|
|
* buffer pool.
|
|
*
|
|
* Never returns cached buffers. However, the newly created buffers
|
|
* are all inserted into the regular buffer cache, and are treated
|
|
* just like buffers created by shm_get_buffer().
|
|
*
|
|
* This function is useful when allocating many small buffers, with
|
|
* (roughly) the same life time.
|
|
*
|
|
* Buffers are tagged for immediate purging, and will be destroyed as
|
|
* soon as the compositor releases them.
|
|
*/
|
|
void shm_get_many(
|
|
struct buffer_chain *chain, size_t count,
|
|
int widths[static count], int heights[static count],
|
|
struct buffer *bufs[static count], bool with_alpha);
|
|
|
|
void shm_did_not_use_buf(struct buffer *buf);
|
|
|
|
bool shm_can_scroll(const struct buffer *buf);
|
|
bool shm_scroll(struct buffer *buf, int rows,
|
|
int top_margin, int top_keep_rows,
|
|
int bottom_margin, int bottom_keep_rows);
|
|
|
|
void shm_addref(struct buffer *buf);
|
|
void shm_unref(struct buffer *buf);
|
|
|
|
void shm_purge(struct buffer_chain *chain);
|