Skip to content

Commit 5df588a

Browse files
committed
Use only 2 drawcalls for filling compensatory padding
1 parent 752a2a9 commit 5df588a

4 files changed

Lines changed: 174 additions & 59 deletions

File tree

kitty/gl.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,4 +676,23 @@ unmap_vao_buffer(ssize_t vao_idx, size_t bufnum) {
676676
unbind_buffer(buf_idx);
677677
}
678678

679+
void
680+
copy_vao_buffer_region(ssize_t vao_idx, size_t src_bufnum, GLintptr src_off,
681+
size_t dst_bufnum, GLintptr dst_off, GLsizeiptr size) {
682+
ssize_t src_buf = vaos[vao_idx].buffers[src_bufnum];
683+
ssize_t dst_buf = vaos[vao_idx].buffers[dst_bufnum];
684+
glBindBuffer(GL_COPY_READ_BUFFER, buffers[src_buf].id);
685+
glBindBuffer(GL_COPY_WRITE_BUFFER, buffers[dst_buf].id);
686+
glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, src_off, dst_off, size);
687+
glBindBuffer(GL_COPY_READ_BUFFER, 0);
688+
glBindBuffer(GL_COPY_WRITE_BUFFER, 0);
689+
}
690+
691+
const void*
692+
map_vao_buffer_for_reading(ssize_t vao_idx, size_t bufnum) {
693+
ssize_t buf_idx = vaos[vao_idx].buffers[bufnum];
694+
bind_buffer(buf_idx);
695+
return map_buffer(buf_idx, GL_READ_ONLY);
696+
}
697+
679698
// }}}

kitty/gl.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ void* map_vao_buffer_for_write_only(ssize_t vao_idx, size_t bufnum, int offset,
7575
void bind_program(int program);
7676
void bind_vertex_array(ssize_t vao_idx);
7777
void bind_vao_uniform_buffer(ssize_t vao_idx, size_t bufnum, GLuint block_index);
78+
void copy_vao_buffer_region(ssize_t vao_idx, size_t src_bufnum, GLintptr src_off, size_t dst_bufnum, GLintptr dst_off, GLsizeiptr size);
79+
const void* map_vao_buffer_for_reading(ssize_t vao_idx, size_t bufnum);
7880
void unbind_vertex_array(void);
7981
void unbind_program(void);
8082
GLuint compile_shaders(GLenum shader_type, GLsizei count, const GLchar * const * string);

kitty/shaders.c

Lines changed: 123 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ bind_shader_globals_to_current_context(void) {
408408
bind_vao_uniform_buffer(shader_globals_vao_idx, BORDER_COLORS_GLOBAL_BUFFER, BORDER_COLORS_BINDING_POINT);
409409
}
410410

411-
#define CELL_BUFFERS enum { cell_data_buffer, selection_buffer, uniform_buffer, color_table_buffer };
411+
#define CELL_BUFFERS enum { cell_data_buffer, selection_buffer, uniform_buffer, color_table_buffer, padding_cell_data_buffer, padding_selection_buffer };
412412

413413
ssize_t
414414
create_cell_vao(void) {
@@ -431,6 +431,9 @@ create_cell_vao(void) {
431431
size_t ctbufnum = add_buffer_to_vao(vao_idx, GL_UNIFORM_BUFFER);
432432
alloc_vao_buffer(vao_idx, program_uniform_block(CELL_PROGRAM, "ColorTable").size, ctbufnum, GL_STATIC_DRAW);
433433

434+
add_buffer_to_vao(vao_idx, GL_ARRAY_BUFFER); // padding_cell_data_buffer (slot 4)
435+
add_buffer_to_vao(vao_idx, GL_ARRAY_BUFFER); // padding_selection_buffer (slot 5)
436+
434437
return vao_idx;
435438
#undef A
436439
#undef A1
@@ -1349,9 +1352,9 @@ draw_cells_without_layers(const UIRenderData *ui, ssize_t vao_idx) {
13491352
static void
13501353
configure_cell_vao_attributes(ssize_t vao_idx, unsigned int base_cell, unsigned int cell_step) {
13511354
// (Re)point the instanced cell attributes so that instance i corresponds to
1352-
// the cell at (base_cell + i*cell_step). Used to draw a single padding strip
1353-
// from the shared cell VAO, and to restore the canonical layout afterwards
1354-
// (base_cell=0, cell_step=1). The VAO must be bound before calling this.
1355+
// the cell at (base_cell + i*cell_step). Used to restore the canonical
1356+
// layout after padding draws (base_cell=0, cell_step=1). The VAO must be
1357+
// bound before calling this.
13551358
CELL_BUFFERS;
13561359
const GLsizei cell_stride = (GLsizei)(cell_step * sizeof(GPUCell));
13571360
const uintptr_t cell_base = (uintptr_t)base_cell * sizeof(GPUCell);
@@ -1364,24 +1367,20 @@ configure_cell_vao_attributes(ssize_t vao_idx, unsigned int base_cell, unsigned
13641367
}
13651368

13661369
static void
1367-
draw_padding_strip(
1368-
ssize_t vao_idx, bool for_final_output, unsigned int is_horizontal, unsigned int count,
1369-
unsigned int base_cell, unsigned int cell_step, float across0, float across1,
1370-
float along_start, float along_step, float clamp_lo, float clamp_hi
1371-
) {
1372-
if (!count) return;
1373-
configure_cell_vao_attributes(vao_idx, base_cell, cell_step);
1374-
#define L(x) program_uniform_location(PADDING_PROGRAM, #x)
1375-
glUniform1ui(L(is_horizontal), is_horizontal);
1376-
glUniform1ui(L(along_count), count);
1377-
glUniform1ui(L(base_instance), base_cell);
1378-
glUniform1ui(L(instance_step), cell_step);
1379-
glUniform2f(L(across), across0, across1);
1380-
glUniform1f(L(along_start), along_start);
1381-
glUniform1f(L(along_step), along_step);
1382-
glUniform2f(L(along_clamp), clamp_lo, clamp_hi);
1383-
#undef L
1384-
draw_quad(!for_final_output, count);
1370+
configure_padding_vao_attributes(ssize_t vao_idx) {
1371+
// Point the instanced cell attributes at the dedicated padding buffers
1372+
// (base=0, stride=sizeof(GPUCell)), which are pre-packed before each
1373+
// combined draw. The VAO must be bound before calling this.
1374+
CELL_BUFFERS;
1375+
set_vao_attribute(vao_idx, padding_cell_data_buffer,
1376+
program_attribute_location(CELL_PROGRAM, "sprite_idx"),
1377+
2, GL_UNSIGNED_INT, sizeof(GPUCell), (void*)offsetof(GPUCell, sprite_idx), 1);
1378+
set_vao_attribute(vao_idx, padding_cell_data_buffer,
1379+
program_attribute_location(CELL_PROGRAM, "colors"),
1380+
3, GL_UNSIGNED_INT, sizeof(GPUCell), (void*)offsetof(GPUCell, fg), 1);
1381+
set_vao_attribute(vao_idx, padding_selection_buffer,
1382+
program_attribute_location(CELL_PROGRAM, "is_selected"),
1383+
1, GL_UNSIGNED_BYTE, sizeof(GLubyte), NULL, 1);
13851384
}
13861385

13871386
static void
@@ -1390,6 +1389,11 @@ draw_window_padding(const UIRenderData *ui, Window *window, ssize_t vao_idx, boo
13901389
// padding, arising from the window size not being an exact multiple of the
13911390
// cell size) to match their neighboring cell. The strips lie outside the
13921391
// per-window cell viewport, so this runs with the full framebuffer viewport.
1392+
//
1393+
// Two instanced draws are issued: one covers the horizontal pair (top+bottom)
1394+
// and one the vertical pair (left+right). Each draw packs its strip cells into
1395+
// a dedicated buffer so the VAO needs no per-strip reconfiguration. The shader
1396+
// selects per-strip geometry and cell indices branch-free via lerp.
13931397
if (!window || OPT(padding_fill_strategy) != PADDING_FILL_NEIGHBORING_CELL) return;
13941398
const unsigned int cl = window->size_mismatch_padding.left, ct = window->size_mismatch_padding.top,
13951399
cr = window->size_mismatch_padding.right, cb = window->size_mismatch_padding.bottom;
@@ -1402,8 +1406,8 @@ draw_window_padding(const UIRenderData *ui, Window *window, ssize_t vao_idx, boo
14021406

14031407
const float fbw = (float)ui->full_framebuffer_width, fbh = (float)ui->full_framebuffer_height;
14041408
const float cw = (float)ui->cell_width, ch = (float)ui->cell_height;
1405-
const float L = (float)ui->screen_left, T = (float)ui->screen_top;
1406-
const float R = L + (float)ui->screen_width, B = T + (float)ui->screen_height;
1409+
const float fL = (float)ui->screen_left, T = (float)ui->screen_top;
1410+
const float R = fL + (float)ui->screen_width, B = T + (float)ui->screen_height;
14071411
#define NX(px) (2.f * (px) / fbw - 1.f)
14081412
#define NY(px) (1.f - 2.f * (px) / fbh)
14091413
const float dx = 2.f * cw / fbw, dy = 2.f * ch / fbh;
@@ -1415,23 +1419,103 @@ draw_window_padding(const UIRenderData *ui, Window *window, ssize_t vao_idx, boo
14151419
bind_vao_uniform_buffer(vao_idx, color_table_buffer, COLOR_TABLE_BINDING_POINT);
14161420
if (for_final_output) glEnable(GL_FRAMEBUFFER_SRGB);
14171421

1418-
// Top strip: spans content width, per top-row cell. across selected by cell
1419-
// corner: top corner -> outer edge (T-ct), bottom corner -> content edge (T).
1420-
if (ct) draw_padding_strip(vao_idx, for_final_output, 1u, columns, top_row * columns, 1u,
1421-
NY(T - ct), NY(T), NX(L), dx, NX(L), NX(R));
1422-
// Bottom strip: top corner -> content edge (B), bottom corner -> outer (B+cb).
1423-
if (cb) draw_padding_strip(vao_idx, for_final_output, 1u, columns, bottom_row * columns, 1u,
1424-
NY(B), NY(B + cb), NX(L), dx, NX(L), NX(R));
1425-
// Left strip: full comp-frame height (corners via along_clamp), per left-column
1426-
// cell. left corner -> outer (L-cl), right corner -> content edge (L).
1427-
if (cl) draw_padding_strip(vao_idx, for_final_output, 0u, lines, top_row * columns, columns,
1428-
NX(L - cl), NX(L), NY(T), -dy, NY(T - ct), NY(B + cb));
1429-
// Right strip: left corner -> content edge (R), right corner -> outer (R+cr).
1430-
if (cr) draw_padding_strip(vao_idx, for_final_output, 0u, lines, top_row * columns + (columns - 1u), columns,
1431-
NX(R), NX(R + cr), NY(T), -dy, NY(T - ct), NY(B + cb));
1422+
// Point instanced attributes at the padding-specific buffers once for both draws.
1423+
configure_padding_vao_attributes(vao_idx);
1424+
1425+
#define PL(x) program_uniform_location(PADDING_PROGRAM, #x)
1426+
1427+
// Horizontal combined draw: top strip (strip 0) + bottom strip (strip 1).
1428+
// Cell data is packed contiguously via GPU-side copies (no CPU round-trip).
1429+
if (ct || cb) {
1430+
const unsigned int nH = (ct ? 1u : 0u) + (cb ? 1u : 0u);
1431+
alloc_vao_buffer(vao_idx, (GLsizeiptr)(nH * columns * sizeof(GPUCell)), padding_cell_data_buffer, GL_DYNAMIC_DRAW);
1432+
alloc_vao_buffer(vao_idx, (GLsizeiptr)(nH * columns * sizeof(GLubyte)), padding_selection_buffer, GL_DYNAMIC_DRAW);
1433+
1434+
unsigned int sidx = 0u;
1435+
if (ct) {
1436+
copy_vao_buffer_region(vao_idx, cell_data_buffer, (GLintptr)(top_row * columns * sizeof(GPUCell)),
1437+
padding_cell_data_buffer, (GLintptr)(sidx * columns * sizeof(GPUCell)), (GLsizeiptr)(columns * sizeof(GPUCell)));
1438+
copy_vao_buffer_region(vao_idx, selection_buffer, (GLintptr)(top_row * columns * sizeof(GLubyte)),
1439+
padding_selection_buffer, (GLintptr)(sidx * columns * sizeof(GLubyte)), (GLsizeiptr)(columns * sizeof(GLubyte)));
1440+
sidx++;
1441+
}
1442+
if (cb) {
1443+
copy_vao_buffer_region(vao_idx, cell_data_buffer, (GLintptr)(bottom_row * columns * sizeof(GPUCell)),
1444+
padding_cell_data_buffer, (GLintptr)(sidx * columns * sizeof(GPUCell)), (GLsizeiptr)(columns * sizeof(GPUCell)));
1445+
copy_vao_buffer_region(vao_idx, selection_buffer, (GLintptr)(bottom_row * columns * sizeof(GLubyte)),
1446+
padding_selection_buffer, (GLintptr)(sidx * columns * sizeof(GLubyte)), (GLsizeiptr)(columns * sizeof(GLubyte)));
1447+
}
1448+
1449+
// Strip-0 is top (or bottom when only bottom exists); strip-1 is bottom.
1450+
// For a single-strip draw base_instance2/across2 equal strip-0 values so
1451+
// the shader lerp is a no-op for all instances (strip_f is always 0.0).
1452+
const unsigned int base0 = (ct ? top_row : bottom_row) * columns;
1453+
const unsigned int base1 = bottom_row * columns;
1454+
glUniform1ui(PL(is_horizontal), 1u);
1455+
glUniform1ui(PL(along_count), columns);
1456+
glUniform1ui(PL(instance_step), 1u);
1457+
glUniform1ui(PL(base_instance), base0);
1458+
glUniform1ui(PL(base_instance2), base1);
1459+
glUniform2f(PL(across), ct ? NY(T - ct) : NY(B), ct ? NY(T) : NY(B + cb));
1460+
glUniform2f(PL(across2), NY(B), NY(B + cb));
1461+
glUniform1f(PL(along_start), NX(fL));
1462+
glUniform1f(PL(along_step), dx);
1463+
glUniform2f(PL(along_clamp), NX(fL), NX(R));
1464+
draw_quad(!for_final_output, nH * columns);
1465+
}
1466+
1467+
// Vertical combined draw: left strip (strip 0) + right strip (strip 1).
1468+
// Column cells are non-contiguous in the cell buffer so we gather them
1469+
// CPU-side by mapping the buffer for reading, then upload in one shot.
1470+
if (cl || cr) {
1471+
const unsigned int nV = (cl ? 1u : 0u) + (cr ? 1u : 0u);
1472+
// VLA sizes: lines is bounded by the window height / cell height (~<500).
1473+
GPUCell gathered_cells[2 * lines];
1474+
GLubyte gathered_sel[2 * lines];
1475+
1476+
const unsigned int strip0_col = cl ? 0u : (columns - 1u);
1477+
const unsigned int strip1_col = columns - 1u;
1478+
1479+
const GPUCell *cells = (const GPUCell*)map_vao_buffer_for_reading(vao_idx, cell_data_buffer);
1480+
for (unsigned int i = 0; i < lines; i++) {
1481+
gathered_cells[i] = cells[(top_row + i) * columns + strip0_col];
1482+
if (nV == 2u) gathered_cells[lines + i] = cells[(top_row + i) * columns + strip1_col];
1483+
}
1484+
unmap_vao_buffer(vao_idx, cell_data_buffer);
1485+
1486+
const GLubyte *sel = (const GLubyte*)map_vao_buffer_for_reading(vao_idx, selection_buffer);
1487+
for (unsigned int i = 0; i < lines; i++) {
1488+
gathered_sel[i] = sel[(top_row + i) * columns + strip0_col];
1489+
if (nV == 2u) gathered_sel[lines + i] = sel[(top_row + i) * columns + strip1_col];
1490+
}
1491+
unmap_vao_buffer(vao_idx, selection_buffer);
1492+
1493+
void *dst = alloc_and_map_vao_buffer(vao_idx, (GLsizeiptr)(nV * lines * sizeof(GPUCell)), padding_cell_data_buffer, false);
1494+
memcpy(dst, gathered_cells, nV * lines * sizeof(GPUCell));
1495+
unmap_vao_buffer(vao_idx, padding_cell_data_buffer);
1496+
1497+
dst = alloc_and_map_vao_buffer(vao_idx, (GLsizeiptr)(nV * lines * sizeof(GLubyte)), padding_selection_buffer, false);
1498+
memcpy(dst, gathered_sel, nV * lines * sizeof(GLubyte));
1499+
unmap_vao_buffer(vao_idx, padding_selection_buffer);
1500+
1501+
const unsigned int base0 = top_row * columns + (cl ? 0u : (columns - 1u));
1502+
const unsigned int base1 = top_row * columns + (columns - 1u);
1503+
glUniform1ui(PL(is_horizontal), 0u);
1504+
glUniform1ui(PL(along_count), lines);
1505+
glUniform1ui(PL(instance_step), columns);
1506+
glUniform1ui(PL(base_instance), base0);
1507+
glUniform1ui(PL(base_instance2), base1);
1508+
glUniform2f(PL(across), cl ? NX(fL - cl) : NX(R), cl ? NX(fL) : NX(R + cr));
1509+
glUniform2f(PL(across2), NX(R), NX(R + cr));
1510+
glUniform1f(PL(along_start), NY(T));
1511+
glUniform1f(PL(along_step), -dy);
1512+
glUniform2f(PL(along_clamp), NY(T - ct), NY(B + cb));
1513+
draw_quad(!for_final_output, nV * lines);
1514+
}
14321515

1516+
#undef PL
14331517
if (for_final_output) glDisable(GL_FRAMEBUFFER_SRGB);
1434-
// Restore the canonical cell attribute layout so cell rendering is unaffected.
1518+
// Restore the canonical cell attribute layout so subsequent cell rendering is unaffected.
14351519
configure_cell_vao_attributes(vao_idx, 0u, 1u);
14361520
unbind_program();
14371521
#undef NX

kitty/shaders/padding.slang

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ import background;
1414
// the cell VAO (colors/sprite_idx/is_selected instanced attributes) and the
1515
// background module's padding_background_premul() to compute the color.
1616
//
17-
// It is invoked once per edge (a "strip") with the cell VAO attributes
18-
// re-pointed so that instance i corresponds to the i'th cell along that strip.
19-
// The geometry of the quad is computed entirely from uniforms in
20-
// framebuffer-NDC, since the padding lies outside the per-window cell viewport
21-
// and is therefore drawn with the full framebuffer viewport.
17+
// Each draw call covers one or two parallel strips (e.g. top+bottom or
18+
// left+right). The cell VAO attributes are pre-packed into a dedicated
19+
// padding buffer so instance i reads strip-0 cells for i < along_count and
20+
// strip-1 cells for i >= along_count. All per-strip selection (geometry and
21+
// cell index) is done branch-free via lerp on strip_f ∈ {0.0, 1.0}.
2222

2323
// Corner indicator per quad vertex, matching cell_pos_map in background.slang.
2424
// .x: 0 -> left, 1 -> right ; .y: 0 -> top, 1 -> bottom
@@ -41,52 +41,62 @@ VertexOutput vertex_main(
4141
[[vk::location(2)]] uint is_selected,
4242
uint vertex_id : SV_VertexID,
4343
uint instance_id : SV_InstanceID,
44-
// The true cell index (into the cell grid) of this strip cell, used only to
45-
// reconstruct the row/column for the shared background computation. It is
46-
// base_instance + instance_id * instance_step and matches the offset/stride
47-
// used to re-point the VAO attributes on the C side.
44+
// The true cell index (into the cell grid) for strip 0's first instance.
4845
uniform uint base_instance,
4946
uniform uint instance_step,
5047
// 1 for the top/bottom strips (the strip runs along x), 0 for left/right.
5148
uniform uint is_horizontal,
52-
// Number of cells along the strip (== instance count).
49+
// Number of cells per strip (== instance count for a single-strip draw;
50+
// total instances == along_count * num_strips).
5351
uniform uint along_count,
54-
// The two NDC values of the thin (across) dimension of the strip, selected
55-
// by the quad corner: .x for the near-content corner mapping, .y for the
56-
// other. The C side assigns these per edge.
52+
// The two NDC values of the thin (across) dimension for strip 0.
5753
uniform float2 across,
5854
// NDC of the along dimension for the first cell's near corner, and the step
5955
// per cell (signed; negative for the vertical strips as NDC y decreases
6056
// downwards).
6157
uniform float along_start,
6258
uniform float along_step,
6359
// NDC bounds used to extend the first/last cell so the strip fills the
64-
// corners (a no-op when set to the natural strip extent).
60+
// corners (shared by both strips of a combined draw).
6561
uniform float2 along_clamp,
62+
// Strip-1 parameters. Equal to strip-0 values for single-strip draws so
63+
// the lerp below is always correct regardless of strip count.
64+
uniform uint base_instance2,
65+
uniform float2 across2,
6666
) {
6767
VertexOutput vo;
68-
uint real_id = base_instance + instance_id * instance_step;
68+
69+
// Branchless strip selector: 0.0 for strip 0, 1.0 for strip 1.
70+
float strip_f = float(instance_id / along_count);
71+
uint strip_i = instance_id % along_count;
72+
73+
// Select the base cell and across extents for whichever strip this
74+
// instance belongs to, using lerp so no divergent branch is emitted.
75+
float fbase = lerp(float(base_instance), float(base_instance2), strip_f);
76+
uint real_id = uint(fbase) + strip_i * instance_step;
77+
float2 chosen_across = lerp(across, across2, strip_f);
78+
6979
vo.color_premul = padding_background_premul(colors, sprite_idx, is_selected, real_id);
7080

7181
uint2 p = pad_pos_map[vertex_id];
72-
float along_lo = along_start + float(instance_id) * along_step;
82+
float along_lo = along_start + float(strip_i) * along_step;
7383
float along_hi = along_lo + along_step;
7484

7585
// Branch-free selection via lerp(). h selects the strip orientation, s is the
7686
// corner component along the strip axis and a the component across it.
7787
float h = float(is_horizontal);
7888
float s = lerp(float(p.y), float(p.x), h);
7989
float a = lerp(float(p.x), float(p.y), h);
80-
// 1 when this is the first/last cell along the strip, else 0.
81-
float is_first = 1.0 - min(float(instance_id), 1.0);
82-
float is_last = 1.0 - min(float(along_count - 1u - instance_id), 1.0);
90+
// 1 when this is the first/last instance within its strip, else 0.
91+
float is_first = 1.0 - min(float(strip_i), 1.0);
92+
float is_last = 1.0 - min(float(along_count - 1u - strip_i), 1.0);
8393

8494
float along = lerp(along_lo, along_hi, s);
8595
// Extend the first cell's near corner and the last cell's far corner out to
8696
// along_clamp so the strip fills the corners.
8797
along = lerp(along, along_clamp.x, is_first * (1.0 - s));
8898
along = lerp(along, along_clamp.y, is_last * s);
89-
float across_v = lerp(across.x, across.y, a);
99+
float across_v = lerp(chosen_across.x, chosen_across.y, a);
90100

91101
// Horizontal strip -> position is (along, across); vertical -> (across, along).
92102
vo.position = float4(lerp(across_v, along, h), lerp(along, across_v, h), 0.0, 1.0);

0 commit comments

Comments
 (0)