@@ -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
413413ssize_t
414414create_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) {
13491352static void
13501353configure_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
13661369static 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
13871386static 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
0 commit comments