I’m working on an NES emulator in Rust using Iced for the UI. In my previous implementation, I used SDL2 and streamed the framebuffer into an SDL2 texture, which was then rendered at 60 FPS.
Now I want to migrate to Iced’s widgets for rendering. The emulator has a game loop that processes cycles and produces an RGBA framebuffer of size 256×240. I need to:
My initial thought was to use iced::widget::canvas::Canvas to draw the framebuffer pixel by pixel, but this doesn't seem like an efficient approach. Is there a better way to update a texture or image in Iced at 60 FPS without manually drawing every pixel in the Canvas draw method?
I'm not familiar with using iced
myself, but as a general rule, this is the ranking of the efficiency of means of displaying an arbitrary animated image:
With a small image on today’s desktop computers — 256 × 240 counts as small — you can afford method 3, but you shouldn’t if you don't have to. iced
does not appear to have a way to perform method 1, but it does have method 2: use an Image
widget, and give it a Handle::Rgba
containing your image pixels.
Here is a program giving a proof of the concept; it is just edited from one of iced
’s own examples, and I am not generally familiar with iced
so this may contain unwise choices:
[dependencies]
iced = { version = "0.13.1", features = ["tokio", "image-without-codecs"] }
use std::time::Duration;
use iced::widget::center;
use iced::widget::image::FilterMethod;
use iced::Element;
pub fn main() -> iced::Result {
iced::application("Pixels", Example::update, Example::view)
// This subscription is just to make the animation work
.subscription(|_| iced::time::every(Duration::from_millis(16)).map(|_| Message::Step))
.run()
}
#[derive(Default)]
struct Example {
frame: u64,
}
#[derive(Debug, Clone, Copy)]
enum Message {
Step,
}
impl Example {
fn update(&mut self, message: Message) {
match message {
Message::Step => self.frame += 1,
}
}
fn view(&self) -> Element<Message> {
// Replace this simple demo effect with your emulator’s frame buffer
let mut image = vec![0u8; 256 * 240 * 4];
for x in 0..256 {
for y in 0..240 {
let pixel_index = (x + y * 256) * 4;
let val = ((x ^ y) * 1) as u8;
image[pixel_index + 0] = val.wrapping_sub((self.frame * 100 / 80) as u8);
image[pixel_index + 1] = val.wrapping_sub((self.frame * 100 / 60) as u8);
image[pixel_index + 2] = val.wrapping_sub((self.frame * 100 / 40) as u8);
image[pixel_index + 3] = 255;
}
}
let content = iced::widget::image(iced::widget::image::Handle::from_rgba(256, 240, image))
.width(512)
.height(480)
.filter_method(FilterMethod::Nearest);
center(content).into()
}
}
The window should look like this, but animated: