From: Kostya Shishkov Date: Wed, 4 Feb 2026 21:07:03 +0000 (+0100) Subject: videoplayer: rework OSD X-Git-Url: https://git.nihav.org/?a=commitdiff_plain;h=dd3400cc063d49d44717c62d0634801ee4cb3fd3;p=nihav-player.git videoplayer: rework OSD A recent change uncovered why you should not try to modify texture data, thus requiring to implement OSD by blitting a separate texture. --- diff --git a/videoplayer/src/main.rs b/videoplayer/src/main.rs index ae86607..f89d865 100644 --- a/videoplayer/src/main.rs +++ b/videoplayer/src/main.rs @@ -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, 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, 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, osd: &mut OSD, ctime: &TimeKeep) -> Option { 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, 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(); } diff --git a/videoplayer/src/osd.rs b/videoplayer/src/osd.rs index 95fd4f5..b3ba52f 100644 --- a/videoplayer/src/osd.rs +++ b/videoplayer/src/osd.rs @@ -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] = &[