Plotting with SDL

Hello Everyone!

I would like to use SDL2 to plot points for all the various types of functions/graphs big or small.
I was thinking of using SDL_RenderPoint(s) for this.

When we have large-scale maps it obviously will go beyond the window screens width/height.

Is it possible to have horizontal/vertical scrolling for points? Would I create a texture?
If not I assume scaling is possible with SDL?

Any ideas on how I should do this would be very helpful :slight_smile:

Yes, the approach I use is to set a render target with SDL_SetRenderTarget() which makes it very easy to scroll and/or scale when you eventually copy that texture to your canvas for display.

Could you show me an example of how I can achieve this? I am only using SDL_RenderDrawPoint in a for loop for all my coordinates that I am plotting.

How can I create a texture from this so that I may use SDL_SetRenderTarget() ?

You will need to create a texture with the SDL_TEXTUREACCESS_TARGET access parameter, then you can select that as the render target. Where you would normally do SDL_RenderPresent() replace it with something like this:

	target = SDL_GetRenderTarget(renderer);
	SDL_SetRenderTarget(renderer, NULL);
	SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
	SDL_RenderClear(renderer);
	SDL_RenderCopy(renderer, target, &srcrect, &dstrect);
	SDL_RenderPresent(renderer);
	SDL_SetRenderTarget(renderer, target);

You can control the scrolling and scaling by adjusting srcrect and/or dstrect.

So what I have so far results in a black screen as I think I am still confused on how exactly I should be doing this.

SDL_Texture* texture = SDL_CreateTexture(graphics.getRenderer(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 20, 20); 
SDL_RenderDrawPoint(graphics.getRenderer(), 50, 50);
SDL_Rect dstrect = { 200, 200, 100, 100 };

	SDL_SetRenderTarget(graphics.getRenderer(), NULL);
	SDL_SetRenderDrawColor(graphics.getRenderer(), 0, 0, 0, 255);
	SDL_RenderClear(graphics.getRenderer());
	SDL_RenderCopy(graphics.getRenderer(), texture, NULL, &dstrect);
	SDL_RenderPresent(graphics.getRenderer());
	SDL_SetRenderTarget(graphics.getRenderer(), texture);

I am trying (as a test) to draw a point using SDL_RenderDrawPoint to 50,50(x,y) on the screen.
I set texture’s last 2 parameters as 20,20 because I assume that means the pixels (my DrawPoint width/height) will be drawn that big.
And for source I set as NULL because it says to use NULL if you want the entire texture.
For destRect I am not too sure. Would I set destRect bigger than the screen size?

In the future I want to do something like:
for (int i = 0; i < yCoordinates.size(); i++) {
SDL_RenderDrawPoint(graphics.getRenderer(), xCoordinates, yCoordinates.at(i));
}
Which can result in many points being drawn and then be able to scroll to see the points that were drawn past the window size.

Thanks for all your help so far!

You seem to be drawing a point at coordinates (50,50) on a texture that is only 20x20 pixels in size! That won’t do anything.

There are different ways to achieve scrolling so the method I suggested, which suits my application, may not suit yours. It will depend on a number of factors, such as the range of coordinates over which you want to scroll. If this is several thousands of pixels, the method I suggested probably isn’t suitable.

Ah! I see my mistake. I assumed wrong about the meaning of “Pixel Width/Height” I thought it was referring to what I was drawing itself. Fixed that and now it appears!

Do you happen to know a method I can use for scrolling? The x/y range really just depends on what the user inputs. What I mostly care about is being able to see things such as a plot that invovles negative coordinates. For example: xMin = -10 and xMax = 10. How can I allow the user to scroll to the left to see the negative coordinates such as -6,10.

If I were to use your method how does RenderCopy work? What would be my Source and Destination? I assume source is NULL and the destination can be the window screen size. And I would have a SDL_SCANCODE for arrow keys and if pressed it would subtract/add to destination’s rect x/y ?

If you don’t know ahead of time how big of a range you need, or what the min/max coordinates will be, you won’t be able to create a render target texture since you won’t know how big to make it, so I’d skip the render target route.

Keep a scroll offset, and add that to all the points when drawing them. The offset will have to be the opposite of what direction the user is scrolling. That way you’re translating the point coordinates into screen space coordinates. So scrolling to the right or down will give you a negative offset, and scrolling to the left or up will give a positive value, and and then add it to the point’s real coordinates.

Yes, but the OP asked about scaling as well as scrolling which is why I suggested the target texture. Also, your method only works when you are redrawing the entire scene from scratch every frame, which in my application I am not.

You would typically set the destination rectangle to the entire screen (NULL) and the source rectangle would then cover the region of your target texture that you want to be seen. In order to scroll, you move the source rectangle to a different region. To scale (zoom) you would alter the size of the source rectangle: make it smaller to zoom in and larger to zoom out.

I tried your way and it doesnt seem to do anything when moving source rectangle at least with the way I set up my code. Moving destination rect allows me to actually see the point but it starts drawing random points in a different color. I assume moving dest rect updates the position of the screen so 50,50 is now somewhere else and that is why I am getting these new random points?

Let me be more clear on what I am trying to achieve here. The user inputs a keyword command for my calculator program such as plotGUI(sin,-6,6,-1.1,1.1,0.075,0.09). This runs the PlotGUI.cpp (Which I have only put the SDL relative stuff in this post. My Vect2D gets its data from a function call before what you see below):

PlotGUI.cpp
Graphics graphics;
Camera camera;
SDL_Event event;
Input input;
   Vect2D<std::pair<double, double> > points
        SDL_Texture* texture = SDL_CreateTexture(graphics.getRenderer(), 
        SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 640, 480);
        SDL_Rect destRect = {0,0,640,480};
	    SDL_Rect srcRect = { 0,0,640,480 };
         while (true) {
		input.beginNewFrame(); //reset released key/press key
		if (SDL_PollEvent(&event)) {
			if (event.type == SDL_KEYDOWN) {
				if (event.key.repeat == 0) {
					input.keyDownEvent(event); //if we are holding key : start keydown event
				}
			}
			else if (event.type == SDL_KEYUP) { // if key was released
				input.keyUpEvent(event);
			}
			else if (event.type == SDL_QUIT) {
				throw 0; //User exits window (Force)
			}
		}

		if (input.wasKeyPressed(SDL_SCANCODE_ESCAPE) == true) {
			throw 0; //User exits normally with ESC key
		}
		else if (input.isKeyHeld(SDL_SCANCODE_A) == true) {
			graphics.d_rect.x -= 10;
		}
		else if (input.isKeyHeld(SDL_SCANCODE_D) == true) {
			graphics.d_rect.x += 10;

		}

		else if (input.isKeyHeld(SDL_SCANCODE_W) == true) {
			graphics.d_rect.y -= 10;
   
		}

		else if (input.isKeyHeld(SDL_SCANCODE_S) == true) {
			graphics.d_rect.y += 10;
		}
        SDL_SetRenderTarget(graphics.getRenderer(), texture);
		SDL_SetRenderDrawColor(graphics.getRenderer(), 0x00, 0x00, 0x00, 0x00);
		graphics.clear();
		SDL_SetRenderDrawColor(graphics.getRenderer(), 0xFF, 0x00, 0x00, 0x00);
		for (int i = 0; i < points.size(); i++) {
			SDL_RenderDrawPoint(graphics.getRenderer(), points[i].first, points[i].second);
			//std::cout << "x, y = " << points[i].first << "," << points[i].second << std::endl;
		}
		//SDL_RenderDrawPoint(graphics.getRenderer(), 50, 50);
		SDL_SetRenderTarget(graphics.getRenderer(), NULL);
		graphics.blitSurface(texture, NULL, &destRect);
		//SDL_RenderCopy(graphics.getRenderer(), texture, NULL, NULL);
		SDL_RenderPresent(graphics.getRenderer());
        camera.Update(std::min(ELAPSED_TIME_MS, MAX_FRAME_TIME), graphics);

Where graphics.blitSurface() refers to (In Graphics.cpp)

Graphics.cpp
void Graphics::blitSurface(SDL_Texture* texture, SDL_Rect* sourceRectangle, SDL_Rect* destinationRectangle) {
SDL_Rect tmp = { destinationRectangle->x - Camera::GetRect().x, destinationRectangle->y - Camera::GetRect().y, destinationRectangle->w, destinationRectangle->h };
SDL_RenderCopy(this->_renderer, texture, sourceRectangle, &tmp);
}

And camera.Update() refers to (In camera.cpp)

Camera.cpp
SDL_Rect Camera::GetRect()
{
SDL_Rect GetRect = { m_rect.x,m_rect.y,640,480 };
return GetRect;
}

void Camera::Update(float elapsedTime, Graphics& graphics)
{
m_rect.x = graphics.d_rect.x;
m_rect.y = graphics.d_rect.y;
GetRect();
}

The calculations when using the plotGUI command are indeed correct and they do plot with a different command that plots in on the terminal window using symbols and not a graphical interface. So I know the math for the points is correct. It’s just that using SDL to draw them does not work out for me so far.

I would like for the user to be able to plot their function and if it has a large amount of points for example 150 coordinates. It would be great if they can scroll around the screen to see it all. I am open to scaling and fitting everything in my 640x480 window (I don’t mind making it bigger that’s just a test resolution).

So what exactly am I doing wrong here?

As for @sjr
I’ve never used something like offsets. Can you explain in-depth what I would need to do?

I haven’t studied your code, but to enable scrolling (without scaling) your target texture needs to be bigger than your window, so you can move the source rectangle without it ‘falling off the edge’. Imagine, for example, the texture being twice the width of the window; then you can scroll horizontally by a distance equal to the width of the window.

I mean, it’s literally just keeping the X and Y offset of the camera and then adding that to the point coordinates when drawing (but inverting the offset).

You wouldn’t need to bother with render textures or rects.

So, like:

struct Point {
    double x, y;
};
...
std::vector<Point> points;
// let's say the user has scrolled 50 pixels right, and 100 pixels down
Point cameraPosition = { 50.0, 100.0 };
for(auto &thisPoint : points) {
    // We invert the camera position when drawing, which moves the points
    // in the opposite direction, giving us scrolling
    SDL_RenderDrawPoint(graphics.getRenderer(),
        thisPoint.x + (-cameraPosition.x),
        thisPoint.y + (-cameraPosition.y));
}

As an aside, storing your point coordinates in an std::pair is kind of unusual. Storing them as structs in an std::vector is probably gonna be faster.

@rtrussell @sjr Awesome thank you both! I got it to work :slight_smile:

I ended up making this

        SDL_SetRenderTarget(graphics.getRenderer(), texture);
		SDL_SetRenderDrawColor(graphics.getRenderer(), 0x00, 0x00, 0x00, 0x00);
		graphics.clear();
		SDL_SetRenderDrawColor(graphics.getRenderer(), 0xFF, 0x00, 0x00, 0x00);
		for (int i = 0; i < points.size(); i++) {
			double realX = ((points[i].first - xMin) / (xMax - xMin)) * graphics.getScreenWidth();
			double realY = graphics.getScreenHeight() - ((points[i].second - yMin) / (yMax - yMin) * graphics.getScreenHeight());
			SDL_RenderDrawPoint(graphics.getRenderer(), realX + (-cameraPosition.x), realY + (-cameraPosition.y));
		}

		SDL_SetRenderTarget(graphics.getRenderer(), NULL);
		graphics.blitSurface(texture, NULL, &destRect);
		SDL_RenderPresent(graphics.getRenderer());
		camera.Update(std::min(ELAPSED_TIME_MS, MAX_FRAME_TIME), graphics);

I am not sure if there’s any downside to doing it like this but it works perfectly with multiple different functions of sin/cosine.

The best way to store the points, from a performance perspective, is in the format needed by SDL_RenderDrawPoints(), that is as an array of SDL_Point structures. That way you can draw all the points in one SDL call, rather than having to loop through them individually.

Unfortunately doing it that way complicates adding the cameraPosition offsets to the coordinates, because you no longer have the opportunity of doing so as you loop through each individual point. :frowning_face:

You could add the offsets in a loop as before - you would lose some of the performance benefit of using SDL_RenderDrawPoints() but I expect you would still win. But an advantage of the target texture method is that there’s no need to adjust the individual coordinates at all in order to scroll.

The only weird thing I’m seeing is that you’re using an alpha of 0. If you don’t have blending turned on then it isn’t a problem, but if you ever do turn it on you won’t see anything. So use 0xFF. You may also want to use it when clearing your texture, depending on how you want it drawn.

@rtrussell the reason I suggested std::vector is because the OP was already using C++11 STL containers to store the points, probably for easier management. Also, using a render target texture is useful if you know in advance how big it needs to be. At first it seemed like @RogueExile’s plotting program was unbounded.