git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commit6b60cedd0da6f7f11d2265ae3f86bf35673603e9
parent17c4a48015
authorLucas Galante <[email protected]>
date2026-07-14 18:03
fix(vk): retain staged text vertices across frames

write_frame_buffers took pending_vertices, wrote them into the frame's
buffer, put the Vec back — and then cleared it, so only the one frame
recorded right after a prepare() ever had glyph vertices; the other
frame in flight wrote vertex_count = 0. Invisible until now because
every app re-prepared text on effectively every rendered frame; frames
rendered WITHOUT a re-prepare (progressive RT refinement, pure
animation ticks) alternated to empty text and the designer's UI text
vanished. pending_vertices is now the retained staged-text state,
replaced only by the next prepare().

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01G5djCURa3LVnRU8WacanLC

 src/vk/text.rs | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/src/vk/text.rs b/src/vk/text.rs
index ecc5c63..433555f 100644
--- a/src/vk/text.rs
+++ b/src/vk/text.rs
@@ -597,18 +597,21 @@ impl TextStage {
         true
     }
 
-    /// Called after this frame's fence has been waited: move pending vertices into
-    /// the frame's buffer and refresh its staging copy if the atlas changed.
+    /// Called after this frame's fence has been waited: copy the current text
+    /// vertices into the frame's buffer and refresh its staging copy if the
+    /// atlas changed. `pending_vertices` is RETAINED — it is the staged text
+    /// state, replaced only by the next `prepare` — so frames rendered without
+    /// a re-prepare (progressive RT refinement, animation ticks) keep their
+    /// text instead of alternating to an empty buffer.
     pub(crate) fn write_frame_buffers(
         &mut self,
         device: &ash::Device,
         allocator: &mut Allocator,
         frame_index: usize,
     ) {
-        let vertices = std::mem::take(&mut self.pending_vertices);
         let frame = &mut self.frames[frame_index];
 
-        let bytes: &[u8] = bytemuck::cast_slice(&vertices);
+        let bytes: &[u8] = bytemuck::cast_slice(&self.pending_vertices);
         let needed = bytes.len() as vk::DeviceSize;
         if needed > frame.vertex.size {
             let mut old = std::mem::replace(&mut frame.vertex, AllocatedBuffer::null());
@@ -626,9 +629,7 @@ impl TextStage {
                 [..bytes.len()]
                 .copy_from_slice(bytes);
         }
-        frame.vertex_count = vertices.len() as u32;
-        self.pending_vertices = vertices;
-        self.pending_vertices.clear();
+        frame.vertex_count = self.pending_vertices.len() as u32;
 
         let frame = &mut self.frames[frame_index];
         if frame.uploaded_generation != self.generation {