Save Texture is broken? (solved: I'm a bozo)

I just noticed a change since I last updated SDL3.
We used to be able to render Textures out to a surface by setting the render target to that texture, then call surface = SDL_RenderReadPixels(renderer, NULL). Finally that surface could be saved using IMG_SavePNG(surface, filepath). But not anymore.

SDL_RenderReadPixels() now only reads directly from the window screen/renderer.
Anything offscreen of the window gets cropped, anything behind or in front of the texture gets included.
Can we perhaps ask for a SDL_TextureToSurface() function to replace this?
Or perhaps IMG_SaveTextureToPNG(renderer, texture, filepath);

Basically, I just started developing a small board designer in which the user creates a box, hits a shortcut that first saves that box to “uniqueID.png” then launches Krita (or whatever png editor you wish) with that file path. After the user saves in Krita, they hit another shortcut that reloads the image in the board, no restart required.
Some editing of the texture may occur in the board editor itself, so I need to save the contents of just the texture each time the edit shortcut is used.
But now, if my object is partially off-screen it gets cropped which is bad enough, there is a pixel rounding error when converting the Float-based image to SDL_Rect based window positioning, and it also makes parallax backgrounds impossible to edit in this situation.

My apologies if I missed something, I’ll dig into some research now. I kind of panicked and ran here right away… But does anyone have a work-around, or some other method to save just a texture’s contents?

SDL_RenderReadPixels() has always essentially just been for taking screenshots.

If you’re editing the texture in your app, don’t you already have a copy of it in CPU memory?

I don’t think so, not changes made using the render target API (lines, rectangles, rotation, etc).
I also have a “brush” mode that sets the Target texture, then drags another texture over it as the user paints.

I would probably do that stuff on the CPU side then.

Keep a copy in a Surface, do any edits with the surface, then update the texture from that.

Unfortunately SDL_Renderer doesn’t have a way to copy texture contents back to the CPU. And if it did, you’d still have to contend with that it won’t copy anything you’re drawing on top of the texture (probably worked around by using a separate render target).

edit: You could use a render target for this. Set the render target, draw the texture + any lines, rects, etc, then use SDL_RenderReadPixels() while the target is still set. Once the read is complete, switch back to the main render target.

(make the render target texture the size of your tile/box/whatever)

In reply to the edit: That is the issue, the new version of SDL_RenderReadPixels() seems to ignore that a render target has been set. It seems that now it only reads directly from the screen of the window. It has been majorly nerfed in this form.

I am triple checking my code now, I’m hoping that I’m wrong. Can anyone confirm this new behavior?

Snippet from my base Drawable class:

	std::string savePNG()
	{
		SDL_SetRenderTarget(renderer, image);
//		SDL_Rect hPos = {(int) pos.x, (int) pos.y, (int) pos.w, (int) pos.h};
//		SDL_Surface * dest = SDL_RenderReadPixels(renderer, &hPos);
		SDL_Surface * dest = SDL_RenderReadPixels(renderer, NULL);
		IMG_SavePNG(dest, imagePath.c_str());
		SDL_SetRenderTarget(renderer, NULL);
		SDL_DestroySurface(dest);
		return imagePath;
	}

I’m considering just rolling back my version of SDL3 to be able to work on my project.

Once you have the Surface from SDL_ReadPixels(), you could always just crop it by blitting a section of it (SDL_BlitSurface()) to an appropriately-sized Surface, and then save that

1 Like

I decided to give up all image editing in the board editor, I’ll let the launched image editor handle all of that. The board editor will just be in charge of how the image acts after it is [re]-loaded. (I can still apply rotation, squash and stretch, and many other post effects, not image editing).

I will still create the initial png file from a surface if the image texture is NULL so that I can skip the initial step of telling the editor what size of an asset I need since was the initial goal.

Thank you Sjr for your time and help.

Well gosh, this is embarrassing.
It looks like I fell for a trap that I should have been able to guess about.
When I use IMG_LoadTexture() to reload the image, it is handing me a static texture, not one with a SDL_TEXTUREACCESS_TARGET. All the strange behavior was caused by this.
The errors seemed to line up with the wording of the documentation, so I didn’t dig deeper there.

I already started to tear things down, but I’m certain it will work now if I load the modified image into a temporary texture, then render it to the initial target texture.

Sorry for panicking over this.

2 Likes