Rust-SDL2 / rust-sdl2

SDL2 bindings for Rust
MIT License
2.75k stars 470 forks source link

Why does Rust-SDL2 use `with_texture_canvas` instead of `set_render_target`? #1438

Open waqs102 opened 2 days ago

waqs102 commented 2 days ago

Canvas has two methods for working with drawing on textures: with_texture_canvas and with_multiple_texture_canvas. The first one is used for single drawing on single texture (meaning single closure). The second one is used for single drawing on multiple textures (meaning single closure is called multiple times).

But there is no way to use multiple function calls for one or more textures, avoiding useless checks and returning the target to the original one each time the closure ends (or without using some kind of buffer with rectangles to perform all partial drawings within a single closure, for example).

This is especially true for external event processing, where each event in poll_iter needs to call its own separate drawing function before the whole texture will be presented. There is no problem if you draw on Window or Surface using a Canvas draw API, but this isn't true for drawing on Texture.

So my question is: why? As I understood it could look something like this:

canvas.set_render_target(&mut texture);
// here do any things you want and drawing on texture
canvas.reset_render_target();
// here do any things you want and drawing on canvas
canvas.present();

instead of this:

// here do any things you want
canvas.with_texture_canvas(&mut texture, |canvas| {
    // here drawing on texture
});
// here do any things you want and drawing on canvas
canvas.present();
Cobrand commented 2 days ago

Basically lifetimes. Try changing the lib to work like you put in your post, you will very quickly face lifetime problems. Now we could add a unsafe way of doing what you want, but safe, I don't it's possible.

waqs102 commented 2 days ago

Basically lifetimes. Try changing the lib to work like you put in your post, you will very quickly face lifetime problems. Now we could add a unsafe way of doing what you want, but safe, I don't it's possible.

Thanks for the reply. I didn't quite understand the problem with lifetimes, but an unsafe option would suit me perfectly. Is it possible at the moment? Unfortunately, I'm new to programming – in which direction should I look? Should I somehow use sdl2_sys::SDL_SetRenderTarget and raw pointers?

Cobrand commented 2 days ago

With Canvas.raw() you can obtain the raw pointer of the Renderer. https://docs.rs/sdl2/latest/sdl2/render/struct.Canvas.html#method.raw .

Before anything, get the current target (Surface, Window, etc): sys::SDL_GetRenderTarget you will need it later.

From that: sys::SDL_SetRenderTarget(raw_renderer_ptr, raw_texture_ptr).

Then do your calls, and at the end sys::SDL_SetRenderTarget(raw_renderer_ptr, original_target_ptr)

I do have to say I don't really understand your use case. Everything that you said should be possible to do with with_multiple_texture_canvas. Perhaps your methology is un-rust-y? If I understand right, you want to do direct calls to the render API from the poll_iter events. Why not push "Change"s to a vector, and then group them and process them with with_multiple_texture_canvas later?

waqs102 commented 2 days ago

@Cobrand Thank you so much for the tips!

Yes, in my case the user's actions directly affect which parts of the screen will be updated (there are many of these parts and they are small, but they are not updated too often). So I decided not to iterate all the states of the parts on each frame. Therefore, I draw changes on the main texture first (acting with it as a kind of a buffer, since SDL does not guarantee the backbuffer of the previous frame will remain valid), and then I just draw that texture on the screen.

Yes, at the moment I really use draw_buffer: Vec<(Rect, Rect)> to perform everything inside single closure (and if it is empty then nothing happens). The first Rect indicates src, the second one dst to call canvas.copy then. However, this action looks unnecessary and could be 100% avoided using the option I have given. That's why I asked this question. As I understood from your answers – the problem lies in the unsafe approach of the C language in contrast to Rust. And I can't understand your answer about lifetimes yet (but I will try).

Also, should I close the issue, since its solution does not correspond to the concepts of safety of the crate?