diff --git a/kitty/gl.c b/kitty/gl.c index 1384ab6ac..134891114 100644 --- a/kitty/gl.c +++ b/kitty/gl.c @@ -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); +} + // }}} diff --git a/kitty/gl.h b/kitty/gl.h index a5e4cd5c0..43c6bd2bf 100644 --- a/kitty/gl.h +++ b/kitty/gl.h @@ -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); diff --git a/kitty/shaders.c b/kitty/shaders.c index a5ebb50e4..a9d2f5751 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -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 diff --git a/kitty/shaders/padding.slang b/kitty/shaders/padding.slang index 889f711fd..3a9670b8d 100644 --- a/kitty/shaders/padding.slang +++ b/kitty/shaders/padding.slang @@ -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);