Skip to content

Instantly share code, notes, and snippets.

@qatoqat
Last active March 5, 2025 12:28
Show Gist options
  • Select an option

  • Save qatoqat/9c429db6c39d133e7d3f32b0329c49d7 to your computer and use it in GitHub Desktop.

Select an option

Save qatoqat/9c429db6c39d133e7d3f32b0329c49d7 to your computer and use it in GitHub Desktop.
SDL3 RGB Circles Benchmark - Surface to Texture vs Streaming Texture
// Total textures created (Surface -> Texture): 300
// Time taken: 2.1722655s
// Memory Usage: 1533 MB
// Total textures created (Streaming Texture): 300
// Time taken: 798.8623ms
// Memory Usage: 1567 MB
use std::ffi::{c_int, CString};
use sdl3_sys::everything::*;
use std::ptr::{null, null_mut, NonNull};
use std::time::Instant;
#[cfg(target_os = "linux")]
fn print_memory_usage() {
use std::fs;
if let Ok(status) = fs::read_to_string("/proc/self/status") {
if let Some(line) = status.lines().find(|l| l.starts_with("VmRSS:")) {
println!("{}", line);
}
}
}
#[cfg(target_os = "windows")]
fn print_memory_usage() {
use std::mem::zeroed;
use winapi::um::processthreadsapi::GetCurrentProcess;
use winapi::um::psapi::{GetProcessMemoryInfo, PROCESS_MEMORY_COUNTERS};
unsafe {
let mut pmc: PROCESS_MEMORY_COUNTERS = zeroed();
if GetProcessMemoryInfo(
GetCurrentProcess(),
&mut pmc,
std::mem::size_of::<PROCESS_MEMORY_COUNTERS>() as u32,
) != 0
{
println!("Memory Usage: {} KB", pmc.WorkingSetSize / 1024);
}
}
}
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
fn print_memory_usage() {
println!("Memory usage tracking not implemented for this OS.");
}
fn generate_rgb_circle(width: usize, height: usize, rotation_degrees: f32) -> Vec<u8> {
let mut pixels = vec![0; width * height * 4];
let center_x = (width / 2) as f32;
let center_y = (height / 2) as f32;
let radius = center_x.min(center_y);
let rotation_radians = rotation_degrees.to_radians();
let cos_theta = rotation_radians.cos();
let sin_theta = rotation_radians.sin();
for y in 0..height {
for x in 0..width {
let dx = x as f32 - center_x;
let dy = y as f32 - center_y;
let distance = (dx * dx + dy * dy).sqrt();
if distance <= radius {
let rotated_x = cos_theta * dx - sin_theta * dy;
let rotated_y = sin_theta * dx + cos_theta * dy;
let fx = (rotated_x / radius + 1.0) * 0.5;
let fy = (rotated_y / radius + 1.0) * 0.5;
let r = ((1.0 - fx) * 255.0) as u8;
let g = (fx * 255.0) as u8;
let b = (fy * 255.0) as u8;
let index = (y * width + x) * 4;
pixels[index] = 255;
pixels[index + 1] = b;
pixels[index + 2] = g;
pixels[index + 3] = r;
}
}
}
pixels
}
fn draw_to_surface_and_texture(
renderer: *mut SDL_Renderer,
rgb_circles: &Vec<Vec<u8>>,
width: usize,
height: usize,
iterations: usize,
) {
unsafe {
let start = Instant::now();
let mut textures = vec![];
for i in 0..iterations {
let surface =
SDL_CreateSurface(width as c_int, height as c_int, SDL_PIXELFORMAT_RGBA8888);
if SDL_LockSurface(surface) {
let surface_ptr = (*surface).pixels as *mut u8;
let len = (*surface).pitch as usize * (*surface).h as usize;
std::ptr::copy_nonoverlapping(rgb_circles[i].as_ptr(), surface_ptr, len);
SDL_UnlockSurface(surface);
}
let texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_DestroySurface(surface);
if !texture.is_null() {
textures.push(NonNull::new(texture).unwrap());
}
}
println!(
"Total textures created (Surface -> Texture): {}",
textures.len()
);
let duration = start.elapsed();
println!("Time taken: {:?}", duration);
print_memory_usage();
// Now render all textures
render_textures(renderer, &textures, iterations);
for texture in textures {
SDL_DestroyTexture(texture.as_ptr());
}
}
}
fn draw_to_texture(
renderer: *mut SDL_Renderer,
rgb_circles: &Vec<Vec<u8>>,
width: usize,
height: usize,
iterations: usize,
) {
unsafe {
let start = Instant::now();
let mut textures = vec![];
for i in 0..iterations {
let texture = SDL_CreateTexture(
renderer,
SDL_PIXELFORMAT_RGBA8888,
SDL_TEXTUREACCESS_STREAMING,
width as c_int,
height as c_int,
);
SDL_UpdateTexture(
texture,
null(),
rgb_circles[i].as_ptr() as *const _,
(width * 4) as c_int,
);
if !texture.is_null() {
textures.push(NonNull::new(texture).unwrap());
}
}
println!(
"Total textures created (Streaming Texture): {}",
textures.len()
);
let duration = start.elapsed();
println!("Time taken: {:?}", duration);
print_memory_usage();
render_textures(renderer, &textures, iterations);
for texture in textures {
SDL_DestroyTexture(texture.as_ptr());
}
}
}
fn render_textures(
renderer: *mut SDL_Renderer,
textures: &Vec<NonNull<SDL_Texture>>,
iterations: usize,
) {
unsafe {
for texture in textures {
let mut event = SDL_Event::default();
if SDL_PollEvent(&mut event) {
match event.r#type {
_ => {}
}
}
SDL_RenderClear(renderer);
SDL_RenderTexture(renderer, texture.as_ptr(), null_mut(), null_mut());
SDL_RenderPresent(renderer);
SDL_Delay((5000 / iterations) as Uint32);
}
}
}
use crossbeam::channel;
use sdl3_sys::everything::*;
use std::sync::{
atomic::{AtomicUsize, Ordering}, Arc,
Mutex,
};
use std::thread;
/// Updates the window title with the given text
unsafe fn update_window_title(window: *mut SDL_Window, text: &str) {
let title = format!("{} - SDL3 Benchmark", text);
let title = CString::new(title).unwrap();
SDL_SetWindowTitle(window, title.as_ptr() as *const i8);
}
/// Generates multiple RGB circles concurrently, updating progress in window title
fn generate_rgb_circles_concurrently(
window: *mut SDL_Window,
width: usize,
height: usize,
iterations: usize,
) -> Vec<Vec<u8>> {
let (tx, rx) = channel::unbounded(); // Channel to send generated circles
let rgb_circles = Arc::new(Mutex::new(vec![vec![]; iterations])); // Pre-allocate storage
let completed = Arc::new(AtomicUsize::new(0)); // Tracks completed circles
// Spawn worker threads
let handles: Vec<_> = (0..iterations)
.map(|i| {
let tx = tx.clone();
let completed = Arc::clone(&completed);
thread::spawn(move || {
let circle =
generate_rgb_circle(width, height, (i as f32 / iterations as f32) * 360.0);
tx.send((i, circle)).unwrap();
completed.fetch_add(1, Ordering::Relaxed);
})
})
.collect();
// Collect results & update window title
for _ in 0..iterations {
let (i, circle) = rx.recv().unwrap();
rgb_circles.lock().unwrap()[i] = circle;
// Update window title with progress
let count = completed.load(Ordering::Relaxed);
unsafe {
update_window_title(
window,
&format!("Loading Circles: {}/{}", count, iterations),
)
};
}
// Wait for all threads to finish
for handle in handles {
handle.join().unwrap();
}
Arc::try_unwrap(rgb_circles).unwrap().into_inner().unwrap()
}
fn main() {
let (width, height) = (800, 800);
let iterations = 300;
let fps = iterations / 5;
unsafe {
SDL_Init(SDL_INIT_VIDEO);
let window = SDL_CreateWindow(
b"SDL3 Benchmark\0".as_ptr() as *const i8,
width as c_int,
height as c_int,
SDL_WINDOW_RESIZABLE,
);
let now = Instant::now();
let rgb_circles = generate_rgb_circles_concurrently(window, width, height, iterations);
println!("Loaded circles in {:?}", now.elapsed());
let renderer = SDL_CreateRenderer(window, null());
update_window_title(
window,
&format!("(Streaming Texture) - {fps} fps in 5 secs"),
);
draw_to_texture(renderer, &rgb_circles, width, height, iterations);
update_window_title(
window,
&format!("(Surface -> Texture) - {fps} fps in 5 secs"),
);
draw_to_surface_and_texture(renderer, &rgb_circles, width, height, iterations);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment