Rust, SDL2 and raw textures help

Usually, the back buffer is also in video card memory, so that present() is just updating an internal video card pointer.

The default assumption for accelerated 2D graphics is that most frames are built by blitting regions of prerendered images into the framebuffer. You send all the images to the video card once, and then everything is a video-memory to video-memory copy, which is incredibly fast.

In fact, replacing into_canvas().accelerated() with into_canvas().software() might increase performance here: the operations that @fernando is doing aren’t the ones acceleration is designed to speed up.

1 Like

The suggestion to use a [u8] feels the best to me. I have some code that does this—first, you’ll want to get a TextureCreator from the window’s canvas and make a Texture with it (I use ARGB8888 but that’s just because I’m copying from a source with that format):

let tex_creator = canvas.texture_creator();
let mut game_tex = tex_creator
    .create_texture(
        sdl2::pixels::PixelFormatEnum::ARGB8888,
        sdl2::render::TextureAccess::Streaming,
        w,
        h
    )
    .expect("Couldn't make game render texture");

Later, you can modify your u8 array as you like without any sdl calls, and then call game_tex.update(...) to copy it to the texture, and finally canvas.copy(...) to blit the texture to the canvas before presenting.

Edited to add more detail:

game_tex.update(
                    Rect::new(0, 0, w, h),
                    unsafe { &fb.align_to().1 },
                    4 * w,
                )
        .expect("Couldn't copy framebuffer to texture");
canvas.copy(&game_tex, None, None);
2 Likes

I definitely want to give up the drawing routine, I'd rather copy a rendered frame in one copy operation.

I will give your suggestion a try, I'm still figuring out my way around the language :slight_smile:

Thank you very much for your assistance!

EDIT: Added context

I tried that and that pretty much doubled the speed so thank you for the great trick, I wasn't expecting that!
Unfortunately, it is still at least 4 times too slow to render at 60fps :slight_smile:

Could you measure how the following approach performs? I would be interested.

use sdl2::{
    event::Event, keyboard::Keycode, pixels::PixelFormatEnum,
    render::Texture, render::TextureCreator
};
use std::{time::Duration, thread::sleep};
use std::cell::RefCell;

type Error = Box<dyn std::error::Error>;

#[allow(dead_code)]
struct Canvas {
    sdl_context: sdl2::Sdl,
    sdl_canvas: sdl2::render::Canvas<sdl2::video::Window>,
    creator: TextureCreator<sdl2::video::WindowContext>,
    texture: RefCell<Texture<'static>>,
    data: Vec<u32>, width: u32, height: u32
}
impl Canvas {
    fn new(width: u32, height: u32) -> Result<Self,Error> {
        // std::env::set_var("DBUS_FATAL_WARNINGS","0");
        let sdl_context = sdl2::init()?;
        let video_subsystem = sdl_context.video()?;
        let window = video_subsystem.window("Window",960,600)
            .position_centered().build()?;
        let sdl_canvas = window.into_canvas().build()?;
        let creator = sdl_canvas.texture_creator();
        let texture = creator.create_texture_target(
            PixelFormatEnum::RGBA8888,width,height)?;

        let texture = unsafe{
            std::mem::transmute::<_,Texture<'static>>(texture)
        };

        Ok(Canvas{
            width, height, data: vec![0; (width*height) as usize],
            sdl_canvas, sdl_context, creator,
            texture: RefCell::new(texture)
        })
    }
    fn flush(&mut self) {
        let mut texture = self.texture.borrow_mut();
        texture.update(None, self.data_raw(),
            (self.width*4) as usize).unwrap();
        self.sdl_canvas.copy(&texture,None,None).unwrap();
        self.sdl_canvas.present();
    }
    fn draw_pixel(&mut self, x: u32, y: u32, color: u32) {
        self.data[(y*self.width + x) as usize] = color;
    }
    fn data_raw(&self) -> &[u8] {
        unsafe{std::slice::from_raw_parts(
            self.data.as_ptr() as *const u8,
            self.data.len()*4)}
    }
    fn wait(&self) {
        let duration = Duration::from_millis(100); 
        let mut event_pump = self.sdl_context.event_pump().unwrap();
        'running: loop {
            for event in event_pump.poll_iter() {
                match event {
                    Event::Quit{..} |
                    Event::KeyDown{keycode: Some(Keycode::Escape), ..}
                    => break 'running,
                    _ => {}
                }
            }
            sleep(duration);
        }
    }
}

fn main() -> Result<(),Error> {
    let mut canvas = Canvas::new(960,600)?;
    for i in 0..10 {
        for j in 0..10 {
            canvas.draw_pixel(100+i, 100+j, 0x00ff00ff);
        }
    }
    canvas.flush();
    canvas.wait();
    Ok(())
}

@Finn Thanks a lot, it's much faster but it seems to panic with a "thread 'main' panicked at 'index out of bounds" when I try to make the canvas bigger.
I increased the subsystem window and canvas size but it fails nonetheless.

I will try to figure out why in the morning and come back with some measurements but I can see already that it's much faster.

Thank you!

Oops, x*self.width + y should be replaced with y*self.width + x. I edited my post.

2 Likes

Ok, so I did the measurements and it's significantly faster providing I create a release.

In debug mode, the frame time is around 60ms for a 1280x800 window.

Once compiled to a release, the frame time varies between 1 and 6ms, so it's great! it's finally above 60fps! I'm not sure the reason for the big variation in frame time, maybe some cache miss, I will need to investigate but first I need to understand your code entirely :slight_smile:

Thank you very much to all for helping me with this issue!

PS: Sorry if I couldn't reply earlier but I reached the daily limit as a new user

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.