4
votes

I need to draw some graphics in c++, pixel by pixel on a window. In order to do this I create a SFML window, sprite and texture. I draw my desired graphics to a uint8_t array and then update the texture and sprite with it. This process takes about 2500 us. Drawing two triangles which fill the entire window takes only 10 us. How is this massive difference possible? I've tried multithreading the pixel-by-pixel drawing, but the difference of two orders of magnitude remains. I've also tried drawing the pixels using a point-map, with no improvement. I understand that SFML uses some GPU-acceleration in the background, but simply looping and assigning the values to the pixel array already takes hundreds of microseconds.

Does anyone know of a more effective way to assign the values of pixels in a window?

Here is an example of the code I'm using to compare the speed of triangle and pixel-by-pixel drawing:

#include <SFML/Graphics.hpp>
#include <chrono>
using namespace std::chrono;
#include <iostream>
#include<cmath>

uint8_t* pixels;

int main(int, char const**)
{
    const unsigned int width=1200;
    const unsigned int height=1200;
    sf::RenderWindow window(sf::VideoMode(width, height), "MA: Rasterization Test");
    pixels = new uint8_t[width*height*4];
    sf::Texture pixels_texture;
    pixels_texture.create(width, height);
    sf::Sprite pixels_sprite(pixels_texture);
    sf::Clock clock;
    sf::VertexArray triangle(sf::Triangles, 3);

    triangle[0].position = sf::Vector2f(0, height);
    triangle[1].position = sf::Vector2f(width, height);
    triangle[2].position = sf::Vector2f(width/2, height-std::sqrt(std::pow(width,2)-std::pow(width/2,2)));
    triangle[0].color = sf::Color::Red;
    triangle[1].color = sf::Color::Blue;
    triangle[2].color = sf::Color::Green;
    
    
    while (window.isOpen()){
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed) {
                window.close();
            }
            if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape) {
                window.close();
            }
        }

        window.clear(sf::Color(255,255,255,255));

        // Pixel-by-pixel
        int us = duration_cast< microseconds >(system_clock::now().time_since_epoch()).count();
        for(int i=0;i!=width*height*4;++i){
            pixels[i]=255;
        }
        pixels_texture.update(pixels);
        window.draw(pixels_sprite);
        int duration=duration_cast< microseconds >(system_clock::now().time_since_epoch()).count()-us;
        std::cout<<"Background: "<<duration<<" us\n";

        // Triangle
        us = duration_cast< microseconds >(system_clock::now().time_since_epoch()).count();
        window.draw(triangle);
         duration=duration_cast< microseconds >(system_clock::now().time_since_epoch()).count()-us;
        std::cout<<"Triangle: "<<duration<<" us\n";

        window.display();
    }
    return EXIT_SUCCESS;
}
1
In the latter case you are measuring time required to enqueue rendering command to GPU, not drawing time. Note that real rendering time should still be pretty fast because any half decent GPU should be able to easily beat CPU on such task. - user7860670
The drawing and coloring are done on the graphics card. SFML will be sending the card (graphics driver) just the instructions. - Richard Critten
Thanks for the answers. I didn't consider that the GPU rendering time isn't being measured, just the enqueue time. Do you know of a method for me to measure the time it takes for the gpu to draw the objects? - Tomas
Note also that you basically spend almost all the time updating individual pixel values. Everything becomes faster by a factor of 3 for me if I replace the pixels[i]=255 loop with a memset-- updating the texture in place would be even more efficient if possible.. But YMMV. - radioflash
@radioflash I believe memset only runs faster if all the pixels are set to the same value. I however need to calculate individual colors for each pixel. - Tomas

1 Answers

5
votes

Graphics drawing in modern devices using Graphic cards, and the speed of drawing depends on how many triangles in the data you sent to the Graphic memory. That's why just drawing two triangles is fast.

As you mentioned about multithreading, if you using OpenGL (I don't remember what SFML use, but should be the same), what you thinking you are drawing is basically send commands and data to graphic cards, so multithreading here is not very useful, the graphic card has it's own thread to do this.

If you are curious about how graphic card works, this tutorial is the book you should read.

P.S. As you edit you question, I guess the duration 2500us vs 10us is because you for loop create a texture(even if the texture is a pure white background)(and the for loop, you probably need to start counting after the for loop), and send texture to graphic card need time, while draw triangle only send several points. Still, I suggest to read the tutorial, create a texture pixel by pixel potentially prove the miss understanding of how GPU works.