glterm: single-pass disjoint copies, fringe bitmap cache, small wins
copy_region: when source and destination do not overlap (page scrolls, large jumps) one direct blit within the FBO replaces the bounce through the scratch texture, halving the copied bandwidth; only overlapping moves (single-line scrolls) still need the intermediate hop. draw_bitmap: fringe bitmaps are uploaded once and cached by an FNV-1a hash of their visible rows (the coverage is color-independent, the color rides on the vertices), instead of creating and destroying a GL texture on every call. Also: cache the last frame-data lookup (it runs per drawing op and scanned the whole slot table each time, and a freed frame now also drops a stale batch target), skip the explicit glFlush when the frame is presented immediately (the swap flushes; the deferred path keeps it so GPU work overlaps the wait), and declare the atlas texture coordinates highp (2048 texels exceed fp16's exact range on strict-mediump GPUs; desktop Mesa promotes anyway, mobile would not). Parity bytes unchanged across the text, scroll and image batteries. Page scroll reaches 0.99x of the cairo backend, line scroll 0.93x, image scroll 0.86x on the AMD Renoir test machine.
This commit is contained in:
parent
51d51ace75
commit
b3e19f2d78
1 changed files with 103 additions and 44 deletions
147
src/glterm.c
147
src/glterm.c
|
|
@ -118,14 +118,16 @@ static const char *VS_GLYPH =
|
|||
"layout(location=1) in vec2 a_uv;\n"
|
||||
"layout(location=2) in vec4 a_color;\n"
|
||||
"uniform vec2 u_size;\n"
|
||||
"out vec2 v_uv; out vec4 v_color;\n"
|
||||
/* highp UVs: a 2048px atlas needs more than fp16's ~10-bit mantissa to
|
||||
address its texels exactly on strict-mediump GPUs. */
|
||||
"out highp vec2 v_uv; out vec4 v_color;\n"
|
||||
"void main(){\n"
|
||||
" vec2 n = vec2((a_pos.x/u_size.x)*2.0-1.0, 1.0-(a_pos.y/u_size.y)*2.0);\n"
|
||||
" gl_Position = vec4(n,0.0,1.0); v_uv=a_uv; v_color=a_color; }\n";
|
||||
static const char *FS_GLYPH =
|
||||
"#version 300 es\n"
|
||||
"precision mediump float;\n"
|
||||
"in vec2 v_uv; in vec4 v_color; out vec4 o;\n"
|
||||
"in highp vec2 v_uv; in vec4 v_color; out vec4 o;\n"
|
||||
"uniform sampler2D u_atlas;\n"
|
||||
"void main(){\n"
|
||||
" float cov = texture(u_atlas, v_uv).r;\n"
|
||||
|
|
@ -138,7 +140,7 @@ static const char *VS_IMAGE =
|
|||
"layout(location=0) in vec2 a_pos;\n"
|
||||
"layout(location=1) in vec2 a_uv;\n"
|
||||
"uniform vec2 u_size;\n"
|
||||
"out vec2 v_uv;\n"
|
||||
"out highp vec2 v_uv;\n"
|
||||
"void main(){\n"
|
||||
" vec2 n = vec2((a_pos.x/u_size.x)*2.0-1.0, 1.0-(a_pos.y/u_size.y)*2.0);\n"
|
||||
" gl_Position = vec4(n,0.0,1.0); v_uv=a_uv; }\n";
|
||||
|
|
@ -149,7 +151,7 @@ static const char *VS_IMAGE =
|
|||
static const char *FS_IMAGE =
|
||||
"#version 300 es\n"
|
||||
"precision mediump float;\n"
|
||||
"in vec2 v_uv; out vec4 o;\n"
|
||||
"in highp vec2 v_uv; out vec4 o;\n"
|
||||
"uniform sampler2D u_tex; uniform float u_alpha;\n"
|
||||
"void main(){ o = texture(u_tex,v_uv) * u_alpha; }\n";
|
||||
|
||||
|
|
@ -510,12 +512,19 @@ struct gl_frame_data
|
|||
static struct gl_frame_data *g_frames[GL_MAX_FRAMES];
|
||||
static struct gl_frame_data *g_cur; /* frame of the open cycle */
|
||||
|
||||
/* The lookup runs once per drawing op (per glyph at the worst), so keep
|
||||
the last hit: redisplay works one frame at a time and the slot scan
|
||||
only happens on a frame switch. */
|
||||
static struct gl_frame_data *g_fd_mru;
|
||||
|
||||
static struct gl_frame_data *
|
||||
gl_get_frame_data (struct frame *f)
|
||||
{
|
||||
if (g_fd_mru && g_fd_mru->f == f)
|
||||
return g_fd_mru;
|
||||
for (int i = 0; i < GL_MAX_FRAMES; i++)
|
||||
if (g_frames[i] && g_frames[i]->f == f)
|
||||
return g_frames[i];
|
||||
return g_fd_mru = g_frames[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -1134,17 +1143,22 @@ gl_drv_end_frame (struct frame *f, bool present_p)
|
|||
{
|
||||
struct gl_frame_data *fd = gl_get_frame_data (f);
|
||||
if (!fd) return;
|
||||
gl_flush_glyph_batch (); /* drain any glyphs left from the last string */
|
||||
glFlush ();
|
||||
gl_flush_glyph_batch (); /* drain any quads left from the last string */
|
||||
fd->in_cycle = false;
|
||||
g_cur = NULL;
|
||||
if (present_p)
|
||||
{
|
||||
/* The swap in the present flushes; no explicit glFlush needed. */
|
||||
gl_present_to_window (fd);
|
||||
fd->needs_present = false;
|
||||
}
|
||||
else
|
||||
fd->needs_present = true; /* deferred: flush_display presents */
|
||||
{
|
||||
/* Deferred: start the GPU on the frame now so the work overlaps
|
||||
the wait until flush_display presents it. */
|
||||
glFlush ();
|
||||
fd->needs_present = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -1228,19 +1242,34 @@ gl_drv_copy_region (struct frame *f, int x, int y, int w, int h,
|
|||
int sx = (int) (x * s), sy = (int) (y * s);
|
||||
int dx = (int) (dst_x * s), dy = (int) (dst_y * s);
|
||||
int sw = (int) (w * s), sh = (int) (h * s);
|
||||
/* Bounce through the scratch target so overlapping moves are safe. */
|
||||
int sy0 = fd->h - (sy + sh), dy0 = fd->h - (dy + sh);
|
||||
glBindFramebuffer (GL_READ_FRAMEBUFFER, fd->fbo);
|
||||
glBindFramebuffer (GL_DRAW_FRAMEBUFFER, fd->scratch_fbo);
|
||||
glDisable (GL_SCISSOR_TEST);
|
||||
glBlitFramebuffer (sx, sy0, sx + sw, sy0 + sh,
|
||||
sx, sy0, sx + sw, sy0 + sh,
|
||||
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
glBindFramebuffer (GL_READ_FRAMEBUFFER, fd->scratch_fbo);
|
||||
glBindFramebuffer (GL_DRAW_FRAMEBUFFER, fd->fbo);
|
||||
glBlitFramebuffer (sx, sy0, sx + sw, sy0 + sh,
|
||||
dx, dy0, dx + sw, dy0 + sh,
|
||||
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
if (dx >= sx + sw || sx >= dx + sw || dy0 >= sy0 + sh || sy0 >= dy0 + sh)
|
||||
{
|
||||
/* Source and destination are disjoint (page scrolls, large jumps):
|
||||
one direct blit within the FBO is legal and halves the bandwidth.
|
||||
Only OVERLAPPING blits are undefined in ES 3. */
|
||||
glBindFramebuffer (GL_READ_FRAMEBUFFER, fd->fbo);
|
||||
glBindFramebuffer (GL_DRAW_FRAMEBUFFER, fd->fbo);
|
||||
glBlitFramebuffer (sx, sy0, sx + sw, sy0 + sh,
|
||||
dx, dy0, dx + sw, dy0 + sh,
|
||||
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Overlapping move (single-line scrolls): bounce through the
|
||||
scratch target so the copy is safe. */
|
||||
glBindFramebuffer (GL_READ_FRAMEBUFFER, fd->fbo);
|
||||
glBindFramebuffer (GL_DRAW_FRAMEBUFFER, fd->scratch_fbo);
|
||||
glBlitFramebuffer (sx, sy0, sx + sw, sy0 + sh,
|
||||
sx, sy0, sx + sw, sy0 + sh,
|
||||
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
glBindFramebuffer (GL_READ_FRAMEBUFFER, fd->scratch_fbo);
|
||||
glBindFramebuffer (GL_DRAW_FRAMEBUFFER, fd->fbo);
|
||||
glBlitFramebuffer (sx, sy0, sx + sw, sy0 + sh,
|
||||
dx, dy0, dx + sw, dy0 + sh,
|
||||
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
}
|
||||
glBindFramebuffer (GL_FRAMEBUFFER, fd->fbo);
|
||||
}
|
||||
|
||||
|
|
@ -1744,33 +1773,61 @@ gl_drv_draw_bitmap (struct frame *f, unsigned short *bits, int dh,
|
|||
if (wd > 16) wd = 16; /* a fringe bitmap row is an unsigned short */
|
||||
gl_mark_dirty (fd, x, y, wd, h);
|
||||
|
||||
/* Expand the bitmap rows into an R8 coverage buffer, then draw it with
|
||||
the glyph program (1-bit coverage, so the gamma is a no-op) using
|
||||
NEAREST sampling for hard edges. Bit order: the X/cairo backend
|
||||
writes the row straight into a CAIRO_FORMAT_A1 surface, where on a
|
||||
little-endian host pixel x is bit x (LSB-first) -- so we map pixel c
|
||||
to bit c, the mirror of the macOS/NS driver's MSB-first order. */
|
||||
unsigned char *buf = calloc ((size_t) wd * h, 1);
|
||||
if (!buf) return;
|
||||
/* The bitmap as an R8 coverage texture, cached: a fringe indicator is
|
||||
redrawn every time its row updates, with the same handful of
|
||||
patterns over and over, so re-uploading per call is pure waste.
|
||||
Keyed by an FNV-1a hash of the visible rows plus the box size (a
|
||||
64-bit hash over a couple dozen distinct patterns; collisions are
|
||||
not a practical concern). The coverage is color-independent (the
|
||||
color rides on the vertices), so one texture serves every face.
|
||||
|
||||
Bit order: the X/cairo backend writes the row straight into a
|
||||
CAIRO_FORMAT_A1 surface, where on a little-endian host pixel x is
|
||||
bit x (LSB-first) -- the mirror of the macOS/NS driver's MSB-first
|
||||
order. Drawn with the glyph program (1-bit coverage, so the gamma
|
||||
is a no-op) and NEAREST sampling for hard edges. */
|
||||
unsigned long long hash = 1469598103934665603ULL;
|
||||
for (int r = 0; r < h; r++)
|
||||
{
|
||||
unsigned short row = bits[dh + r];
|
||||
for (int c = 0; c < wd; c++)
|
||||
if ((row >> c) & 1)
|
||||
buf[r * wd + c] = 0xFF;
|
||||
hash ^= (unsigned long long) bits[dh + r];
|
||||
hash *= 1099511628211ULL;
|
||||
}
|
||||
hash ^= ((unsigned long long) wd << 32) ^ (unsigned long long) h;
|
||||
|
||||
GLuint tex;
|
||||
glGenTextures (1, &tex);
|
||||
glBindTexture (GL_TEXTURE_2D, tex);
|
||||
glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexImage2D (GL_TEXTURE_2D, 0, GL_R8, wd, h, 0, GL_RED,
|
||||
GL_UNSIGNED_BYTE, buf);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
free (buf);
|
||||
#define GL_BITMAP_CAP 64
|
||||
static struct { unsigned long long hash; GLuint tex; } cache[GL_BITMAP_CAP];
|
||||
static int cache_next;
|
||||
GLuint tex = 0;
|
||||
for (int i = 0; i < GL_BITMAP_CAP; i++)
|
||||
if (cache[i].tex && cache[i].hash == hash)
|
||||
{ tex = cache[i].tex; break; }
|
||||
if (!tex)
|
||||
{
|
||||
unsigned char *buf = calloc ((size_t) wd * h, 1);
|
||||
if (!buf) return;
|
||||
for (int r = 0; r < h; r++)
|
||||
{
|
||||
unsigned short row = bits[dh + r];
|
||||
for (int c = 0; c < wd; c++)
|
||||
if ((row >> c) & 1)
|
||||
buf[r * wd + c] = 0xFF;
|
||||
}
|
||||
glGenTextures (1, &tex);
|
||||
glBindTexture (GL_TEXTURE_2D, tex);
|
||||
glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexImage2D (GL_TEXTURE_2D, 0, GL_R8, wd, h, 0, GL_RED,
|
||||
GL_UNSIGNED_BYTE, buf);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
free (buf);
|
||||
if (cache[cache_next].tex)
|
||||
glDeleteTextures (1, &cache[cache_next].tex);
|
||||
cache[cache_next].hash = hash;
|
||||
cache[cache_next].tex = tex;
|
||||
cache_next = (cache_next + 1) % GL_BITMAP_CAP;
|
||||
}
|
||||
|
||||
float s = (float) fd->scale;
|
||||
float x0 = x * s, y0 = y * s, x1 = (x + wd) * s, y1 = (y + h) * s;
|
||||
|
|
@ -1795,8 +1852,6 @@ gl_drv_draw_bitmap (struct frame *f, unsigned short *bits, int dh,
|
|||
glEnableVertexAttribArray (2);
|
||||
glVertexAttribPointer (2, 4, GL_FLOAT, GL_FALSE, 32, (void *) 16);
|
||||
glDrawArrays (GL_TRIANGLES, 0, 6);
|
||||
|
||||
glDeleteTextures (1, &tex);
|
||||
}
|
||||
|
||||
/* Relief shading. Port of x_alloc_lighter_color (xterm.c): scale the
|
||||
|
|
@ -2436,6 +2491,10 @@ gl_free_frame_data (struct frame *f)
|
|||
if (g_frames[i] && g_frames[i]->f == f)
|
||||
{
|
||||
struct gl_frame_data *fd = g_frames[i];
|
||||
if (g_fd_mru == fd)
|
||||
g_fd_mru = NULL;
|
||||
if (g_glyph_batch_fd == fd)
|
||||
{ g_glyph_batch_fd = NULL; g_glyph_batch_verts = 0; }
|
||||
if (fd->surf != EGL_NO_SURFACE)
|
||||
{
|
||||
if (g_bound_known && g_bound_surf == fd->surf)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue