Search code examples
optimizationrustrenderingx11xcb

How to draw points of different colours in XCB


I'm trying to draw points on the window, using the PolyPoint XCB request. Note that I'm using the crate "xcb" in Rust.

Here is my function :

fn set_pixels(&mut self, pixels: Vec<(usize, usize, u32)>) {
    self.connection.send_request(
        &x::PolyPoint {
            coordinate_mode: x::CoordMode::Origin,
            drawable: x::Drawable::Window(self.handle.unwrap()),
            gc: self.gc.unwrap(),
            points: pixels.into_iter().map(|(x, y, colour)| {
                x::Point {
                    x: x as i16,
                    y: y as i16,
                }
            })
            .collect::<Vec<x::Point>>().as_slice(),
        }
    );
}

At first, I'm not sure if this part is the easiest way to get a slice of x::Point from the vector :

pixels.into_iter().map(|(x, y, colour)| {
    x::Point {
        x: x as i16,
        y: y as i16,
    }
})
.collect::<Vec<x::Point>>().as_slice(),

Well, as we can see, we got a "colour" for each pixel, and I would like to use x::PolyPoint with a colour for each point I want to draw.

I know I can use ChangeGc to set a drawing colour :

self.connection.send_request(
    &x::ChangeGc {
        gc: self.gc.unwrap(),
        value_list: &[
            x::Gc::Foreground(/* hex colour */),
        ],
    }
);

But this would set the same colour for all the pixels.

How can I use "PolyPoint" to set pixels of different colours ? Without passing by a loop that would ChangeGC then just after use PolyPoint for one single pixel (this solution is too slow).

Earlier, I was doing a loop calling this function, to set pixels one by one. But this is too slow :

fn set_pixel(&mut self, x: usize, y: usize, hex_colour: u32) {
    self.connection.send_request(
        &x::ChangeGc {
            gc: self.gc.unwrap(),
            value_list: &[
                x::Gc::Foreground(hex_colour),
            ],
        }
    );

    self.connection.send_request(
        &x::PolyPoint {
            coordinate_mode: x::CoordMode::Origin,
            drawable: x::Drawable::Window(self.handle.unwrap()),
            gc: self.gc.unwrap(),
            points: &[
                x::Point {
                    x: x as i16,
                    y: y as i16,
                }
            ]
        }
    )
}

Solution

  • I'm now working with the double-buffering method. A simple way is to draw on a x::Pixmap object, and then create an update() function for the window's structure :

    /// Copies the `self.pixmap` area to the window.
    fn update(&mut self) {
        self.connection.send_and_check_request(
            &x::CopyArea {
                src_drawable: x::Drawable::Pixmap(self.pixmap.unwrap()),
                dst_drawable: x::Drawable::Window(self.window.unwrap()),
                gc: self.gc.unwrap(),
                src_x: 0,
                src_y: 0,
                dst_x: 0,
                dst_y: 0,
                width: self.width as u16,
                height: self.height as u16,
            }
        )
        .expect("double buffering: unable to copy the buffer to the window");
    }
    

    Here is my set_pixels method (renamed to draw_points :

    fn draw_points(&mut self, coordinates: &Vec<(isize, isize)>, colour: u32) {
        self.change_draw_colour(colour);
    
        // Creates an `x::Point` vector.
        let points = coordinates.into_iter().map(|coordinate: &(isize, isize)| {
            x::Point {
                x: coordinate.0 as i16,
                y: coordinate.1 as i16,
            }
        })
        .collect::<Vec<x::Point>>();
    
        self.connection.send_and_check_request(
            &x::PolyPoint {
                coordinate_mode: x::CoordMode::Origin,
                drawable: x::Drawable::Pixmap(self.pixmap.unwrap()),
                gc: self.gc.unwrap(),
                points: points.as_slice(),
            }
        )
        .expect("unable to draw points on the pixmap");
    }
    

    For external reasons that I won't go into, I'm not directly using a vector of x::Point, there is why I transform my coordinates value to a Vec<x::Point>.

    For optimisation, I'm also saving the previous colour to avoid changing colour to the same colour :

    fn change_draw_colour(&mut self, colour: u32) {
        if self.previous_colour == Some(colour) {
            return;
        }
    
        self.connection.send_and_check_request(
            &x::ChangeGc {
                gc: self.gc.unwrap(),
                value_list: &[
                    x::Gc::Foreground(colour),
                ],
            }
        )
        .expect("unable to change the graphics context colour");
    
        self.previous_colour = Some(colour);
    }