]> git.nihav.org Git - nihav-player.git/commitdiff
videoplayer: rework OSD
authorKostya Shishkov <kostya.shishkov@gmail.com>
Wed, 4 Feb 2026 21:07:03 +0000 (22:07 +0100)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Wed, 4 Feb 2026 21:07:03 +0000 (22:07 +0100)
A recent change uncovered why you should not try to modify texture data, thus
requiring to implement OSD by blitting a separate texture.

videoplayer/src/main.rs
videoplayer/src/osd.rs

index ae866075e70d54e783f635baacf2ce9ce8e98122..f89d865b5570ff493210fbd78ee4d6f02ae6eadd 100644 (file)
@@ -16,7 +16,7 @@ use std::sync::atomic::{AtomicU8, Ordering};
 use sdl2::event::{Event, WindowEvent};
 use sdl2::keyboard::{Keycode, Mod};
 use sdl2::mouse::{MouseButton, MouseWheelDirection};
-use sdl2::render::{Canvas, Texture, TextureCreator};
+use sdl2::render::{BlendMode, Canvas, Texture, TextureCreator};
 use sdl2::pixels::PixelFormatEnum;
 use sdl2::video::{Window, WindowContext};
 
@@ -312,14 +312,17 @@ pub struct DispQueue<'a> {
     pub len:        usize,
     pub width:      usize,
     pub height:     usize,
+    pub osd_tex:    Texture<'a>,
 }
 
 impl<'a> DispQueue<'a> {
     fn new(texture_creator: &'a TextureCreator<WindowContext>, width: usize, height: usize, len: usize) -> Self {
         let mut pool = Vec::with_capacity(len);
         for _ in 0..len + 1 {
-            let rgb_tex = texture_creator.create_texture_streaming(PixelFormatEnum::RGB24, width as u32, height as u32).expect("failed to create RGB texture");
-            let yuv_tex = texture_creator.create_texture_streaming(PixelFormatEnum::IYUV, ((width + 1) & !1) as u32, ((height + 1) & !1) as u32).expect("failed to create YUV texture");
+            let mut rgb_tex = texture_creator.create_texture_streaming(PixelFormatEnum::RGB24, width as u32, height as u32).expect("failed to create RGB texture");
+            let mut yuv_tex = texture_creator.create_texture_streaming(PixelFormatEnum::IYUV, ((width + 1) & !1) as u32, ((height + 1) & !1) as u32).expect("failed to create YUV texture");
+            rgb_tex.set_blend_mode(BlendMode::None);
+            yuv_tex.set_blend_mode(BlendMode::None);
             pool.push(DispFrame{ ts: 0, is_yuv: false, valid: false, rgb_tex, yuv_tex });
         }
         pool[len].is_yuv = false;
@@ -327,7 +330,10 @@ impl<'a> DispQueue<'a> {
                 for el in buffer.iter_mut() { *el = 0; }
             }).expect("RGB texture could not be locked");
 
-        Self { pool, first_ts: 0, last_ts: 0, start: 0, end: 0, len, width, height }
+        let mut osd_tex = texture_creator.create_texture_streaming(PixelFormatEnum::RGBA8888, width as u32, OSD_HEIGHT as u32).expect("failed to create RGBA texture");
+        osd_tex.set_blend_mode(BlendMode::Blend);
+
+        Self { pool, osd_tex, first_ts: 0, last_ts: 0, start: 0, end: 0, len, width, height }
     }
 
     fn flush(&mut self) {
@@ -340,20 +346,10 @@ impl<'a> DispQueue<'a> {
         }
     }
 
-    fn get_last_texture(&mut self, osd: &OSD) -> &Texture<'a> {
+    fn get_last_texture(&mut self) -> &Texture<'a> {
         if self.pool[self.len].is_yuv {
-            if osd.is_active() {
-                self.pool[self.len].yuv_tex.with_lock(None, |buffer: &mut [u8], pitch: usize| {
-                        osd.draw_yuv(buffer, pitch);
-                    }).expect("YUV texture locking failure");
-            }
             &self.pool[self.len].yuv_tex
         } else {
-            if osd.is_active() {
-                self.pool[self.len].rgb_tex.with_lock(None, |buffer: &mut [u8], pitch: usize| {
-                        osd.draw_rgb(buffer, pitch);
-                    }).expect("RGB texture locking failure");
-            }
             &self.pool[self.len].rgb_tex
         }
     }
@@ -377,6 +373,16 @@ impl<'a> DispQueue<'a> {
     }
 }
 
+fn draw_osd(disp_queue: &mut DispQueue, canvas: &mut Canvas<Window>, osd: &mut OSD) {
+    disp_queue.osd_tex.with_lock(None, |buffer: &mut [u8], pitch: usize| {
+        osd.draw_tex(buffer, pitch);
+    }).expect("RGBA texture locking failure");
+    let (ow, oh) = osd.get_dimensions();
+    let srect = sdl2::rect::Rect::new(0, 0, ow as u32, oh as u32);
+    let drect = sdl2::rect::Rect::new(OSD_XOFF as i32, OSD_YOFF as i32, ow as u32, oh as u32);
+    canvas.copy(&disp_queue.osd_tex, srect, drect).expect("OSD blitting failure");
+}
+
 fn try_display(disp_queue: &mut DispQueue, canvas: &mut Canvas<Window>, osd: &mut OSD, ctime: &TimeKeep) -> Option<u64> {
     while !disp_queue.is_empty() {
         let disp_time = disp_queue.first_ts;
@@ -391,22 +397,15 @@ fn try_display(disp_queue: &mut DispQueue, canvas: &mut Canvas<Window>, osd: &mu
             }
             let frm = &mut disp_queue.pool[disp_queue.start];
             let texture = if frm.is_yuv {
-                    if osd.is_active() {
-                        frm.yuv_tex.with_lock(None, |buffer: &mut [u8], pitch: usize| {
-                                osd.draw_yuv(buffer, pitch);
-                            }).expect("YUV texture locking failure");
-                    }
                     &frm.yuv_tex
                 } else {
-                    if osd.is_active() {
-                        frm.rgb_tex.with_lock(None, |buffer: &mut [u8], pitch: usize| {
-                                osd.draw_rgb(buffer, pitch);
-                            }).expect("RGB texture locking failure");
-                    }
                     &frm.rgb_tex
                 };
             canvas.clear();
             canvas.copy(texture, None, None).expect("canvas blit failure");
+            if osd.is_active() {
+                draw_osd(disp_queue, canvas, osd);
+            }
             canvas.present();
 
             disp_queue.move_start();
@@ -620,7 +619,10 @@ impl Player {
             }
             if let Event::Window {win_event: WindowEvent::Exposed, ..} = event {
                 canvas.clear();
-                canvas.copy(disp_queue.get_last_texture(&self.osd), None, None).expect("blitting failure");
+                canvas.copy(disp_queue.get_last_texture(), None, None).expect("blitting failure");
+                if self.osd.is_active() {
+                    draw_osd(disp_queue, canvas, &mut self.osd);
+                }
                 canvas.present();
             }
             if let Event::MouseButtonDown {mouse_btn, ..} = event {
@@ -969,7 +971,7 @@ impl Player {
         let mut disp_q = DispQueue::new(&texture_creator, width, height, if self.has_video { FRAME_QUEUE_LEN } else { 0 });
         if !self.has_video {
             canvas.clear();
-            canvas.copy(disp_q.get_last_texture(&self.osd), None, None).expect("blit failure");
+            canvas.copy(disp_q.get_last_texture(), None, None).expect("blit failure");
             canvas.present();
         }
 
index 95fd4f5ca3a29f27dba8e1a98a235a5e96133b61..b3ba52ff735dc6257b7861dbc5223ddf3ebd02c9 100644 (file)
@@ -1,7 +1,8 @@
 use std::time::Instant;
 
-const XOFF: usize = 10;
-const YOFF: usize = 4;
+pub const OSD_XOFF: usize = 10;
+pub const OSD_YOFF: usize = 4;
+pub const OSD_HEIGHT: usize = 16;
 
 #[derive(Default)]
 pub struct OSD {
@@ -73,7 +74,7 @@ impl OSD {
         }
         self.text_stride = w;
         if w > 0 {
-            self.text.resize(self.text_stride * 16, 0);
+            self.text.resize(self.text_stride * OSD_HEIGHT, 0);
             let mut pos = 0;
             for &sym_idx in syms.iter() {
                 let glyph = &OSD_GLYPHS[sym_idx];
@@ -92,33 +93,22 @@ impl OSD {
             self.text.clear();
         }
     }
-    pub fn draw_yuv(&self, buffer: &mut [u8], pitch: usize) {
+    pub fn draw_tex(&self, buffer: &mut [u8], pitch: usize) {
         if !self.is_active() || self.text_stride == 0 {
             return;
         }
-        for (dline, sline) in buffer.chunks_exact_mut(pitch).skip(YOFF).zip(self.text.chunks_exact(self.text_stride)) {
-            for (dst, &src) in dline.iter_mut().skip(XOFF).zip(sline.iter()) {
+        for (dline, sline) in buffer.chunks_exact_mut(pitch).zip(self.text.chunks_exact(self.text_stride)) {
+            for (dst, &src) in dline.chunks_exact_mut(4).zip(sline.iter()) {
                 match src {
-                    2 => *dst = 0xFF,
-                    1 => *dst = 0x00,
-                    _ => {},
+                    2 => dst.copy_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]),
+                    1 => dst.copy_from_slice(&[0xFF, 0x00, 0x00, 0x00]),
+                    _ => dst.copy_from_slice(&[0x00, 0x00, 0x00, 0x00]),
                 };
             }
         }
     }
-    pub fn draw_rgb(&self, buffer: &mut [u8], pitch: usize) {
-        if !self.is_active() || self.text_stride == 0 {
-            return;
-        }
-        for (dline, sline) in buffer.chunks_exact_mut(pitch).skip(YOFF).zip(self.text.chunks_exact(self.text_stride)) {
-            for (dst, &src) in dline.chunks_exact_mut(3).skip(XOFF).zip(sline.iter()) {
-                match src {
-                    2 => dst.copy_from_slice(&[0xFF, 0xFF, 0xFF]),
-                    1 => dst.copy_from_slice(&[0x00, 0x00, 0x00]),
-                    _ => {},
-                };
-            }
-        }
+    pub fn get_dimensions(&self) -> (usize, usize) {
+        (self.text_stride, OSD_HEIGHT)
     }
 }
 
@@ -140,8 +130,8 @@ fn format_time(ms: u64) -> String {
 struct Glyph {
     width:  usize,
     sym:    char,
-    bits:   [u16; 16],
-    mask:   [u16; 16],
+    bits:   [u16; OSD_HEIGHT],
+    mask:   [u16; OSD_HEIGHT],
 }
 
 const OSD_GLYPHS: &[Glyph] = &[