How is SFML so fast? - c++

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;
}

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.

Related

Display win/lose picture after gameState changes (SFML Minesweeper)

So I wrote minesweeper game and made GUI with SFML, just have a problem with displaying picture of win/lose when player finishes the game or loses it. Game states are set well and they trigger at right moments, the problem is that pictures I want to display I think are too big and in the window, I can see only their background (like top left corner of the picture). Is there a way to scale the picture to the window and display it fully in the window?
Constructor:
minesweeperBoard* board;
sf::Texture t;
MSSFMLView::MSSFMLView(minesweeperBoard& board) {
this->board = &board;
t.loadFromFile("src/images/tiles.jpg");
window.create(sf::VideoMode(32*this->board->getBoardWidth(), 32*this->board->getBoardHeight()), "Minesweeper");
}
Gamestate if trigger in a game loop:
if(board->state == GameState::FINISHED_WIN){
t.loadFromFile("src/images/win.jpg");
sf::Sprite s(t);
s.scale(32*this->board->getBoardWidth(),32*this->board->getBoardHeight());
window.draw(s);
Sleep(1000);
window.clear();
}else if(board->state == GameState::FINISHED_LOSS){
t.loadFromFile("src/images/lose.jpg");
sf::Sprite s(t);
s.scale(32*this->board->getBoardWidth(),32*this->board->getBoardHeight());
window.draw(s);
Sleep(1000);
window.clear();
}
Those are the pictures I use: https://imgur.com/a/KVCuyab
256x256 size
When you call s.scale(32this->board->getBoardWidth(),32this->board->getBoardHeight());
what you are doing is increasing the size of the image by an amount in the x and y direction, in this case 32 times the board size. This will make the image too large to fit onto the screen.
Note that the scale function changes the size of a sf::Sprite by multiplying it, not by setting it.
So if you want to change the size of an image to fit your board, don't use
s.scale(32*this->board->getBoardWidth(),32*this->board->getBoardHeight());
because that is multiplying the size of the image, making it huge. Instead, scale it by the desired size divided by the actual size:
float desiredW = 32*this->board->getBoardWidth();
float desiredH = 32*this->board->getBoardHeight();
float currentW = s.getGlobalBounds().width;
float currentH = s.getGlobalBounds().height;
s.scale(desiredW / currentW, desiredH / currentH);

Moving Sprite (SFML) in c++ using Loops

I am new to c++ and as well as SFML. I am trying to make my sprite object move down in position relative to its last position using a loop. I am looking for the animation of it sprite object falling when the program starts.
I thought implementing a the sleep function in my for loop would help solve the issue i was having where the program would just display the object at the last iteration of the loop. However my program just freezes and crashes.
Looking for some direction. Maybe the sleep function isn't the right thing to call here?
#include <SFML/Graphics.hpp>
#include <Windows.h>
#include <iostream>
using namespace std;
int main()
{
// Create the window here. Calling out the dimensions
sf::RenderWindow window(sf::VideoMode(800, 600), "Example Window");
// run the program as long as the window is open
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
//close window we requested
if (event.type == sf::Event::Closed)
{
window.close();
}
}
window.clear(sf::Color::Black);
sf::Texture texture;
if (!texture.loadFromFile("c:\\abstract.png"))
{
cout<<"Failed to load image...";
}
sf::Sprite sprite;
sprite.setTexture(texture);
sprite.setTextureRect(sf::IntRect(20,20,30,30));
for (float i = 0; i < 30.; i++)
{
sprite.move(sf::Vector2f(5.f, i));
window.draw(sprite);
Sleep(50);
}
window.display();
}
return 0;
}
What you are doing in your for is : Processing, drawing, processing, drawing... And finally displaying what you've drawn using window.display().
Meaning that what will be displayed on your window every frames, is the result of your "Processing, drawing" thing, in other word, 30 times your sprite at different positions.
What you want is to move your sprite a bit every frames. Thus, you have to finish your current while (window.isOpen()) iteration to move your sprite, draw it, and display it, and this over and over.
What you should do is declaring your sprite outside of your game loop (Which is while (window.isOpen())), and move it in this loop.
Step by step, your program should look like:
[Start]
Initialize your context
Create a sprite
Start looping
Clear the screen
Collect inputs
Move your sprite
Draw your sprite
Display your drawing on the window
End looping
[Exit]
The last thing you will need to handle is deltaTime (The timestep). Because if you move your sprite from (x,y) every frames, it means that the faster your computer is (Able to render a lot of frames quickly), the faster your sprite will move. In order to fix this problem, you'll have to move your sprite considering the time elapsed between the current frame and the previous frame (The slower is your PC, the more your sprite will move in one frame, the faster is your PC, the less your sprite will move in one frame). Timestep will cause your sprite to move (x,y) per second instead of (x,y) per frame, which is what you want in most graphic applications.

Why the texture appears only in the first quadrant

What's wrong with this code using SFML?
In the code below, I have this image (1000x1000) and I want to show it in a window (500x500) using sf::RenderTexture.
However, only part of the image appears in the first quadrant:
#include <SFML/Graphics.hpp>
using namespace sf;
int main()
{
RenderWindow window({500, 500}, "SFML Views", Style::Close);
View camera;
camera.setSize(Vector2f(window.getSize()));
Texture background;
background.loadFromFile("numeros.png");
Sprite numeros (background);
RenderTexture texture;
texture.create(window.getSize().x, window.getSize().y);
Sprite content;
content.setTexture(texture.getTexture());
texture.draw(numeros);
texture.display();
while (window.isOpen())
{
for (Event event; window.pollEvent(event);)
if (event.type == Event::Closed)
window.close();
window.clear();
window.setView(camera);
window.draw(content);
window.display();
}
return EXIT_SUCCESS;
}
As far as I can understand, the code should generate the original image (1000x1000) automatically adjusted to 500x500.
Could anyone tell you what is wrong?
You're facing, in fact, two distinct problems:
First one:
As far as I can understand, the code should generate the original
image (1000x1000) automatically adjusted to 500x500.
This is not really true. SFML handles the sprites with the real size of the texture. If your image is 1000x1000, but you want representing it as 500x500, you should assign the texture to a sprite, as you do:
Sprite numeros(background);
and then scale this sprite to fit in a 500x500 window, this is:
numeros.setScale(0.5, 0.5);
With this change you should view the whole image, but...
Second one:
You're messing with the view of the window. If we check SFML documentation, we can see that sf::View expects:
A sf::FloatRect: this is, a coordinate (x,y) - in this case the top-left corner - and a size(width, height)
or
Two sf::Vector2f: one corresponding to the coordinates of the center and the other corresponding to the size of the view.
Assuming you want to use the second one, you're missing the first parameter, the center coordinates, but this is not really necessary. If you simply don't apply the view, the image should be shown in the whole window.
So you simply need to remove:
window.setView(camera);
The code I've tried:
int main()
{
RenderWindow window({ 500, 500 }, "SFML Views", Style::Close);
View camera;
camera.setSize(Vector2f(window.getSize()));
Texture background;
background.loadFromFile("numeros.png");
Sprite numeros(background);
numeros.setScale(0.5, 0.5); // <-- Add this
RenderTexture texture;
texture.create(window.getSize().x, window.getSize().y);
Sprite content;
content.setTexture(texture.getTexture());
texture.draw(numeros);
texture.display();
while (window.isOpen())
{
for (Event event; window.pollEvent(event);)
if (event.type == Event::Closed)
window.close();
window.clear();
//window.setView(camera); <-- Remove this
window.draw(content);
window.display();
}
return EXIT_SUCCESS;
}
And my result:
Just to add another option to #alseether 's excellent response, I realized that the whole issue consisted of that bad View initialization.
This way you can also set the size of the view = to the size of the background image (1000,1000) and finally set the center of the view to the windows's upper left corner.
As the view is larger than the window size (500,500) it will automatically be adjusted to this new size.
In short, the section to be changed would be:
View camera;
camera.setSize(Vector2f(background.getSize().x, background.getSize().y));
camera.setCenter(Vector2f(window.getSize()));

VertexArray of circles

I am wondering if it is possible to create a VertexArray of circles in SFML. I have looked for answers but I didn't find anything that could help. Moreover, I don't understand the part on the SFML documentation where it is written that I can create my own entities, I think this is maybe what I want to do in fact.
EDIT : I want to do that because I have to draw a lot of circles.
Thanks for helping me
While #nvoigt answer is correct, I found it useful in my implementations to work with vectors (see http://en.cppreference.com/w/cpp/container/vector for more details, look up "c++ containers", there are several types of containers to optimize read/write times).
You probably do not need it for the above described use case, but you could need it in future implementations and consider this for a good coding practice.
#include <SFML/Graphics.hpp>
#include <vector>
int main()
{
// create the window
sf::RenderWindow window(sf::VideoMode(800, 600), "My window");
// run the program as long as the window is open
while (window.isOpen())
{
// check all the window's events that were triggered since the last iteration of the loop
sf::Event event;
while (window.pollEvent(event))
{
// "close requested" event: we close the window
if (event.type == sf::Event::Closed)
window.close();
}
// clear the window with black color
window.clear(sf::Color::Black);
// initialize myvector
std::vector<sf::CircleShape> myvector;
// add 10 circles
for (int i = 0; i < 10; i++)
{
sf::CircleShape shape(50);
// draw a circle every 100 pixels
shape.setPosition(i * 100, 25);
shape.setFillColor(sf::Color(100, 250, 50));
// copy shape to vector
myvector.push_back(shape);
}
// iterate through vector
for (std::vector<sf::CircleShape>::iterator it = myvector.begin() ; it != myvector.end(); ++it)
{
// draw all circles
window.draw(*it);
}
window.display();
}
return 0;
}
sf::CircleShape is already using a vertex array (thanks to being inherited from sf::Shape). There is nothing extra you need to do.
If you have a lot of circles, try using sf::CircleShape first and only optimize when you have a real use-case that you can measure your solution against.
In addition two previous answers I will try to explain why there is no default VertexArray of circles.
By ideology of computer graphics (and SFML in our case) vertex is a smallest drawing primitive with least necessary functionality. Classical example of vertices are point, line, triange, guad, and polygone. The first four are really simple for your videocard to store and to draw. Polygon can be any geometrical figure, but it will be heavier to process, that's why e.g in 3D grapichs polygons are triangles.
Circle is a bit more complicated. For example videocard doesn't know how much points she need to draw your circle smooth enough. So, as #nvoigt answered there exists a sf::CircleShape that is being built from more primitive verticies.

SFML Drawing Pixel Array

I found this (http://lodev.org/cgtutor/raycasting.html) tutorial on the Internet and was interested and wanted to make my own. I wanted to do it in SFML though, and I wanted to extend it, and make a 3D version, so there could be different levels the player can walk on. Thus, you would need 1 ray for every pixel, and thus each pixel would have to be drawn independently. I found this (http://www.sfml-dev.org/tutorials/2.1/graphics-vertex-array.php) tutorial, and it seemed easy enough to have the array be of individual vertices. To start, I figured the best thing to do would be to create a class that could read the pixels returned by the rays, and draw them to the screen. I used the VertexArray, but things were not working for some reason. I tried to isolate the problem, but I've had little success. I wrote a simple vertex array of just green pixels that should fill up part of the screen, and still there are problems. The pixels only show my code and the pic. of what I mean.
#include "SFML/Graphics.hpp"
int main() {
sf::RenderWindow window(sf::VideoMode(400, 240), "Test Window");
window.setFramerateLimit(30);
sf::VertexArray pointmap(sf::Points, 400 * 10);
for(register int a = 0;a < 400 * 10;a++) {
pointmap[a].position = sf::Vector2f(a % 400,a / 400);
pointmap[a].color = sf::Color::Green;
}
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed)
window.close();
}
window.clear();
window.draw(pointmap);
//</debug>
window.display();
}
return 0;
}
I meant for this to just fill in the top 10 rows with Green, but apparently that is not what I did... I think if I can figure out what is causing this not to work, I can probably fix the main problem. Also if you think there is a better way to do this instead, you could let me know :)
Thanks!
I think you misused the vertex array. Take a look at the sf::Quads primitive in the tutorial's table : you need to define 4 points (coordinates) to draw a quad, and a pixel is just a quad of side length 1.
So what you need is to create a vertex array of size 400*10*4, and set the same position to every following four vertices.
You can also use another method provided by SFML : draw directly a texture pixel by pixel and display it. It may not be the most efficient thing to do (you'll have to compare with vertices) but it has the advantage of being rather simple.
const unsigned int W = 400;
const unsigned int H = 10; // you can change this to full window size later
sf::UInt8* pixels = new sf::UInt8[W*H*4];
sf::Texture texture;
texture.create(W, H);
sf::Sprite sprite(texture); // needed to draw the texture on screen
// ...
for(register int i = 0; i < W*H*4; i += 4) {
pixels[i] = r; // obviously, assign the values you need here to form your color
pixels[i+1] = g;
pixels[i+2] = b;
pixels[i+3] = a;
}
texture.update(pixels);
// ...
window.draw(sprite);
The sf::Texture::update function accepts an array of sf::UInt8. They represent the color of each pixel of the texture. But as the pixels need to be 32bit RGBA, 4 following sf::UInt8 are the RGBA composants of the pixel.
Replace the line:
pointmap[a].position = sf::Vector2f(a % 400,a / 400);
With:
pointmap[a].position = sf::Vector2f(a % 400,(a/400) % 400);