Use only 2 drawcalls for filling compensatory padding

This commit is contained in:
Kovid Goyal 2026-07-27 13:11:14 +05:30
parent e44a7b9583
commit ae33c422ff
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
4 changed files with 174 additions and 59 deletions

View file

@ -676,4 +676,23 @@ unmap_vao_buffer(ssize_t vao_idx, size_t bufnum) {
unbind_buffer(buf_idx);
}
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) {
ssize_t src_buf = vaos[vao_idx].buffers[src_bufnum];
ssize_t dst_buf = vaos[vao_idx].buffers[dst_bufnum];
glBindBuffer(GL_COPY_READ_BUFFER, buffers[src_buf].id);
glBindBuffer(GL_COPY_WRITE_BUFFER, buffers[dst_buf].id);
glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, src_off, dst_off, size);
glBindBuffer(GL_COPY_READ_BUFFER, 0);
glBindBuffer(GL_COPY_WRITE_BUFFER, 0);
}
const void*
map_vao_buffer_for_reading(ssize_t vao_idx, size_t bufnum) {
ssize_t buf_idx = vaos[vao_idx].buffers[bufnum];
bind_buffer(buf_idx);
return map_buffer(buf_idx, GL_READ_ONLY);
}
// }}}

View file

@ -75,6 +75,8 @@ void* map_vao_buffer_for_write_only(ssize_t vao_idx, size_t bufnum, int offset,
void bind_program(int program);
void bind_vertex_array(ssize_t vao_idx);
void bind_vao_uniform_buffer(ssize_t vao_idx, size_t bufnum, GLuint block_index);
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);
const void* map_vao_buffer_for_reading(ssize_t vao_idx, size_t bufnum);
void unbind_vertex_array(void);
void unbind_program(void);
GLuint compile_shaders(GLenum shader_type, GLsizei count, const GLchar * const * string);

View file

@ -408,7 +408,7 @@ bind_shader_globals_to_current_context(void) {
bind_vao_uniform_buffer(shader_globals_vao_idx, BORDER_COLORS_GLOBAL_BUFFER, BORDER_COLORS_BINDING_POINT);
}
#define CELL_BUFFERS enum { cell_data_buffer, selection_buffer, uniform_buffer, color_table_buffer };
#define CELL_BUFFERS enum { cell_data_buffer, selection_buffer, uniform_buffer, color_table_buffer, padding_cell_data_buffer, padding_selection_buffer };
ssize_t
create_cell_vao(void) {
@ -431,6 +431,9 @@ create_cell_vao(void) {
size_t ctbufnum = add_buffer_to_vao(vao_idx, GL_UNIFORM_BUFFER);
alloc_vao_buffer(vao_idx, program_uniform_block(CELL_PROGRAM, "ColorTable").size, ctbufnum, GL_STATIC_DRAW);
add_buffer_to_vao(vao_idx, GL_ARRAY_BUFFER); // padding_cell_data_buffer (slot 4)
add_buffer_to_vao(vao_idx, GL_ARRAY_BUFFER); // padding_selection_buffer (slot 5)
return vao_idx;
#undef A
#undef A1
@ -1349,9 +1352,9 @@ draw_cells_without_layers(const UIRenderData *ui, ssize_t vao_idx) {
static void
configure_cell_vao_attributes(ssize_t vao_idx, unsigned int base_cell, unsigned int cell_step) {
// (Re)point the instanced cell attributes so that instance i corresponds to
// the cell at (base_cell + i*cell_step). Used to draw a single padding strip
// from the shared cell VAO, and to restore the canonical layout afterwards
// (base_cell=0, cell_step=1). The VAO must be bound before calling this.
// the cell at (base_cell + i*cell_step). Used to restore the canonical
// layout after padding draws (base_cell=0, cell_step=1). The VAO must be
// bound before calling this.
CELL_BUFFERS;
const GLsizei cell_stride = (GLsizei)(cell_step * sizeof(GPUCell));
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
}
static void
draw_padding_strip(
ssize_t vao_idx, bool for_final_output, unsigned int is_horizontal, unsigned int count,
unsigned int base_cell, unsigned int cell_step, float across0, float across1,
float along_start, float along_step, float clamp_lo, float clamp_hi
) {
if (!count) return;
configure_cell_vao_attributes(vao_idx, base_cell, cell_step);
#define L(x) program_uniform_location(PADDING_PROGRAM, #x)
glUniform1ui(L(is_horizontal), is_horizontal);
glUniform1ui(L(along_count), count);
glUniform1ui(L(base_instance), base_cell);
glUniform1ui(L(instance_step), cell_step);
glUniform2f(L(across), across0, across1);
glUniform1f(L(along_start), along_start);
glUniform1f(L(along_step), along_step);
glUniform2f(L(along_clamp), clamp_lo, clamp_hi);
#undef L
draw_quad(!for_final_output, count);
configure_padding_vao_attributes(ssize_t vao_idx) {
// Point the instanced cell attributes at the dedicated padding buffers
// (base=0, stride=sizeof(GPUCell)), which are pre-packed before each
// combined draw. The VAO must be bound before calling this.
CELL_BUFFERS;
set_vao_attribute(vao_idx, padding_cell_data_buffer,
program_attribute_location(CELL_PROGRAM, "sprite_idx"),
2, GL_UNSIGNED_INT, sizeof(GPUCell), (void*)offsetof(GPUCell, sprite_idx), 1);
set_vao_attribute(vao_idx, padding_cell_data_buffer,
program_attribute_location(CELL_PROGRAM, "colors"),
3, GL_UNSIGNED_INT, sizeof(GPUCell), (void*)offsetof(GPUCell, fg), 1);
set_vao_attribute(vao_idx, padding_selection_buffer,
program_attribute_location(CELL_PROGRAM, "is_selected"),
1, GL_UNSIGNED_BYTE, sizeof(GLubyte), NULL, 1);
}
static void
@ -1390,6 +1389,11 @@ draw_window_padding(const UIRenderData *ui, Window *window, ssize_t vao_idx, boo
// padding, arising from the window size not being an exact multiple of the
// cell size) to match their neighboring cell. The strips lie outside the
// per-window cell viewport, so this runs with the full framebuffer viewport.
//
// Two instanced draws are issued: one covers the horizontal pair (top+bottom)
// and one the vertical pair (left+right). Each draw packs its strip cells into
// a dedicated buffer so the VAO needs no per-strip reconfiguration. The shader
// selects per-strip geometry and cell indices branch-free via lerp.
if (!window || OPT(padding_fill_strategy) != PADDING_FILL_NEIGHBORING_CELL) return;
const unsigned int cl = window->size_mismatch_padding.left, ct = window->size_mismatch_padding.top,
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
const float fbw = (float)ui->full_framebuffer_width, fbh = (float)ui->full_framebuffer_height;
const float cw = (float)ui->cell_width, ch = (float)ui->cell_height;
const float L = (float)ui->screen_left, T = (float)ui->screen_top;
const float R = L + (float)ui->screen_width, B = T + (float)ui->screen_height;
const float fL = (float)ui->screen_left, T = (float)ui->screen_top;
const float R = fL + (float)ui->screen_width, B = T + (float)ui->screen_height;
#define NX(px) (2.f * (px) / fbw - 1.f)
#define NY(px) (1.f - 2.f * (px) / fbh)
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
bind_vao_uniform_buffer(vao_idx, color_table_buffer, COLOR_TABLE_BINDING_POINT);
if (for_final_output) glEnable(GL_FRAMEBUFFER_SRGB);
// Top strip: spans content width, per top-row cell. across selected by cell
// corner: top corner -> outer edge (T-ct), bottom corner -> content edge (T).
if (ct) draw_padding_strip(vao_idx, for_final_output, 1u, columns, top_row * columns, 1u,
NY(T - ct), NY(T), NX(L), dx, NX(L), NX(R));
// Bottom strip: top corner -> content edge (B), bottom corner -> outer (B+cb).
if (cb) draw_padding_strip(vao_idx, for_final_output, 1u, columns, bottom_row * columns, 1u,
NY(B), NY(B + cb), NX(L), dx, NX(L), NX(R));
// Left strip: full comp-frame height (corners via along_clamp), per left-column
// cell. left corner -> outer (L-cl), right corner -> content edge (L).
if (cl) draw_padding_strip(vao_idx, for_final_output, 0u, lines, top_row * columns, columns,
NX(L - cl), NX(L), NY(T), -dy, NY(T - ct), NY(B + cb));
// Right strip: left corner -> content edge (R), right corner -> outer (R+cr).
if (cr) draw_padding_strip(vao_idx, for_final_output, 0u, lines, top_row * columns + (columns - 1u), columns,
NX(R), NX(R + cr), NY(T), -dy, NY(T - ct), NY(B + cb));
// Point instanced attributes at the padding-specific buffers once for both draws.
configure_padding_vao_attributes(vao_idx);
#define PL(x) program_uniform_location(PADDING_PROGRAM, #x)
// Horizontal combined draw: top strip (strip 0) + bottom strip (strip 1).
// Cell data is packed contiguously via GPU-side copies (no CPU round-trip).
if (ct || cb) {
const unsigned int nH = (ct ? 1u : 0u) + (cb ? 1u : 0u);
alloc_vao_buffer(vao_idx, (GLsizeiptr)(nH * columns * sizeof(GPUCell)), padding_cell_data_buffer, GL_DYNAMIC_DRAW);
alloc_vao_buffer(vao_idx, (GLsizeiptr)(nH * columns * sizeof(GLubyte)), padding_selection_buffer, GL_DYNAMIC_DRAW);
unsigned int sidx = 0u;
if (ct) {
copy_vao_buffer_region(vao_idx, cell_data_buffer, (GLintptr)(top_row * columns * sizeof(GPUCell)),
padding_cell_data_buffer, (GLintptr)(sidx * columns * sizeof(GPUCell)), (GLsizeiptr)(columns * sizeof(GPUCell)));
copy_vao_buffer_region(vao_idx, selection_buffer, (GLintptr)(top_row * columns * sizeof(GLubyte)),
padding_selection_buffer, (GLintptr)(sidx * columns * sizeof(GLubyte)), (GLsizeiptr)(columns * sizeof(GLubyte)));
sidx++;
}
if (cb) {
copy_vao_buffer_region(vao_idx, cell_data_buffer, (GLintptr)(bottom_row * columns * sizeof(GPUCell)),
padding_cell_data_buffer, (GLintptr)(sidx * columns * sizeof(GPUCell)), (GLsizeiptr)(columns * sizeof(GPUCell)));
copy_vao_buffer_region(vao_idx, selection_buffer, (GLintptr)(bottom_row * columns * sizeof(GLubyte)),
padding_selection_buffer, (GLintptr)(sidx * columns * sizeof(GLubyte)), (GLsizeiptr)(columns * sizeof(GLubyte)));
}
// Strip-0 is top (or bottom when only bottom exists); strip-1 is bottom.
// For a single-strip draw base_instance2/across2 equal strip-0 values so
// the shader lerp is a no-op for all instances (strip_f is always 0.0).
const unsigned int base0 = (ct ? top_row : bottom_row) * columns;
const unsigned int base1 = bottom_row * columns;
glUniform1ui(PL(is_horizontal), 1u);
glUniform1ui(PL(along_count), columns);
glUniform1ui(PL(instance_step), 1u);
glUniform1ui(PL(base_instance), base0);
glUniform1ui(PL(base_instance2), base1);
glUniform2f(PL(across), ct ? NY(T - ct) : NY(B), ct ? NY(T) : NY(B + cb));
glUniform2f(PL(across2), NY(B), NY(B + cb));
glUniform1f(PL(along_start), NX(fL));
glUniform1f(PL(along_step), dx);
glUniform2f(PL(along_clamp), NX(fL), NX(R));
draw_quad(!for_final_output, nH * columns);
}
// Vertical combined draw: left strip (strip 0) + right strip (strip 1).
// Column cells are non-contiguous in the cell buffer so we gather them
// CPU-side by mapping the buffer for reading, then upload in one shot.
if (cl || cr) {
const unsigned int nV = (cl ? 1u : 0u) + (cr ? 1u : 0u);
// VLA sizes: lines is bounded by the window height / cell height (~<500).
GPUCell gathered_cells[2 * lines];
GLubyte gathered_sel[2 * lines];
const unsigned int strip0_col = cl ? 0u : (columns - 1u);
const unsigned int strip1_col = columns - 1u;
const GPUCell *cells = (const GPUCell*)map_vao_buffer_for_reading(vao_idx, cell_data_buffer);
for (unsigned int i = 0; i < lines; i++) {
gathered_cells[i] = cells[(top_row + i) * columns + strip0_col];
if (nV == 2u) gathered_cells[lines + i] = cells[(top_row + i) * columns + strip1_col];
}
unmap_vao_buffer(vao_idx, cell_data_buffer);
const GLubyte *sel = (const GLubyte*)map_vao_buffer_for_reading(vao_idx, selection_buffer);
for (unsigned int i = 0; i < lines; i++) {
gathered_sel[i] = sel[(top_row + i) * columns + strip0_col];
if (nV == 2u) gathered_sel[lines + i] = sel[(top_row + i) * columns + strip1_col];
}
unmap_vao_buffer(vao_idx, selection_buffer);
void *dst = alloc_and_map_vao_buffer(vao_idx, (GLsizeiptr)(nV * lines * sizeof(GPUCell)), padding_cell_data_buffer, false);
memcpy(dst, gathered_cells, nV * lines * sizeof(GPUCell));
unmap_vao_buffer(vao_idx, padding_cell_data_buffer);
dst = alloc_and_map_vao_buffer(vao_idx, (GLsizeiptr)(nV * lines * sizeof(GLubyte)), padding_selection_buffer, false);
memcpy(dst, gathered_sel, nV * lines * sizeof(GLubyte));
unmap_vao_buffer(vao_idx, padding_selection_buffer);
const unsigned int base0 = top_row * columns + (cl ? 0u : (columns - 1u));
const unsigned int base1 = top_row * columns + (columns - 1u);
glUniform1ui(PL(is_horizontal), 0u);
glUniform1ui(PL(along_count), lines);
glUniform1ui(PL(instance_step), columns);
glUniform1ui(PL(base_instance), base0);
glUniform1ui(PL(base_instance2), base1);
glUniform2f(PL(across), cl ? NX(fL - cl) : NX(R), cl ? NX(fL) : NX(R + cr));
glUniform2f(PL(across2), NX(R), NX(R + cr));
glUniform1f(PL(along_start), NY(T));
glUniform1f(PL(along_step), -dy);
glUniform2f(PL(along_clamp), NY(T - ct), NY(B + cb));
draw_quad(!for_final_output, nV * lines);
}
#undef PL
if (for_final_output) glDisable(GL_FRAMEBUFFER_SRGB);
// Restore the canonical cell attribute layout so cell rendering is unaffected.
// Restore the canonical cell attribute layout so subsequent cell rendering is unaffected.
configure_cell_vao_attributes(vao_idx, 0u, 1u);
unbind_program();
#undef NX

View file

@ -14,11 +14,11 @@ import background;
// the cell VAO (colors/sprite_idx/is_selected instanced attributes) and the
// background module's padding_background_premul() to compute the color.
//
// It is invoked once per edge (a "strip") with the cell VAO attributes
// re-pointed so that instance i corresponds to the i'th cell along that strip.
// The geometry of the quad is computed entirely from uniforms in
// framebuffer-NDC, since the padding lies outside the per-window cell viewport
// and is therefore drawn with the full framebuffer viewport.
// Each draw call covers one or two parallel strips (e.g. top+bottom or
// left+right). The cell VAO attributes are pre-packed into a dedicated
// padding buffer so instance i reads strip-0 cells for i < along_count and
// strip-1 cells for i >= along_count. All per-strip selection (geometry and
// cell index) is done branch-free via lerp on strip_f ∈ {0.0, 1.0}.
// Corner indicator per quad vertex, matching cell_pos_map in background.slang.
// .x: 0 -> left, 1 -> right ; .y: 0 -> top, 1 -> bottom
@ -41,19 +41,15 @@ VertexOutput vertex_main(
[[vk::location(2)]] uint is_selected,
uint vertex_id : SV_VertexID,
uint instance_id : SV_InstanceID,
// The true cell index (into the cell grid) of this strip cell, used only to
// reconstruct the row/column for the shared background computation. It is
// base_instance + instance_id * instance_step and matches the offset/stride
// used to re-point the VAO attributes on the C side.
// The true cell index (into the cell grid) for strip 0's first instance.
uniform uint base_instance,
uniform uint instance_step,
// 1 for the top/bottom strips (the strip runs along x), 0 for left/right.
uniform uint is_horizontal,
// Number of cells along the strip (== instance count).
// Number of cells per strip (== instance count for a single-strip draw;
// total instances == along_count * num_strips).
uniform uint along_count,
// The two NDC values of the thin (across) dimension of the strip, selected
// by the quad corner: .x for the near-content corner mapping, .y for the
// other. The C side assigns these per edge.
// The two NDC values of the thin (across) dimension for strip 0.
uniform float2 across,
// NDC of the along dimension for the first cell's near corner, and the step
// per cell (signed; negative for the vertical strips as NDC y decreases
@ -61,15 +57,29 @@ VertexOutput vertex_main(
uniform float along_start,
uniform float along_step,
// NDC bounds used to extend the first/last cell so the strip fills the
// corners (a no-op when set to the natural strip extent).
// corners (shared by both strips of a combined draw).
uniform float2 along_clamp,
// Strip-1 parameters. Equal to strip-0 values for single-strip draws so
// the lerp below is always correct regardless of strip count.
uniform uint base_instance2,
uniform float2 across2,
) {
VertexOutput vo;
uint real_id = base_instance + instance_id * instance_step;
// Branchless strip selector: 0.0 for strip 0, 1.0 for strip 1.
float strip_f = float(instance_id / along_count);
uint strip_i = instance_id % along_count;
// Select the base cell and across extents for whichever strip this
// instance belongs to, using lerp so no divergent branch is emitted.
float fbase = lerp(float(base_instance), float(base_instance2), strip_f);
uint real_id = uint(fbase) + strip_i * instance_step;
float2 chosen_across = lerp(across, across2, strip_f);
vo.color_premul = padding_background_premul(colors, sprite_idx, is_selected, real_id);
uint2 p = pad_pos_map[vertex_id];
float along_lo = along_start + float(instance_id) * along_step;
float along_lo = along_start + float(strip_i) * along_step;
float along_hi = along_lo + along_step;
// Branch-free selection via lerp(). h selects the strip orientation, s is the
@ -77,16 +87,16 @@ VertexOutput vertex_main(
float h = float(is_horizontal);
float s = lerp(float(p.y), float(p.x), h);
float a = lerp(float(p.x), float(p.y), h);
// 1 when this is the first/last cell along the strip, else 0.
float is_first = 1.0 - min(float(instance_id), 1.0);
float is_last = 1.0 - min(float(along_count - 1u - instance_id), 1.0);
// 1 when this is the first/last instance within its strip, else 0.
float is_first = 1.0 - min(float(strip_i), 1.0);
float is_last = 1.0 - min(float(along_count - 1u - strip_i), 1.0);
float along = lerp(along_lo, along_hi, s);
// Extend the first cell's near corner and the last cell's far corner out to
// along_clamp so the strip fills the corners.
along = lerp(along, along_clamp.x, is_first * (1.0 - s));
along = lerp(along, along_clamp.y, is_last * s);
float across_v = lerp(across.x, across.y, a);
float across_v = lerp(chosen_across.x, chosen_across.y, a);
// Horizontal strip -> position is (along, across); vertical -> (across, along).
vo.position = float4(lerp(across_v, along, h), lerp(along, across_v, h), 0.0, 1.0);