New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Alloc-free way of using SubImage #2902
Comments
Instead of allocating the result on heap for the user, allow the caller side to decide whether it should be heap-allocated or stack-allocated. This new method allows us to have a zero-alloc SubImage routine for the use cases where we don't want to make an extra allocation. The performance difference is measurable (comparison is done with benchstat): name old time/op new time/op delta SubImage-12 203ns ± 2% 33ns ± 2% -83.91% (p=0.000 n=20+19) name old alloc/op new alloc/op delta SubImage-12 112B ± 0% 0B -100.00% (p=0.000 n=20+20) Using SubImage is `~203ns`, SubImageInto is `~33ns`. SubImage does 1 allocation, SubImageInto does none. SubImage is rewritten in a way to avoid the code duplication. It should work identically to the previous contract (it returns nil if `i` is disposed, etc.) Fixes hajimehoshi#2902
I would probably go with @tinne26's idea of transferring a subimage, mainly because that should maintain current functionality. I like the idea of adding a subimage field to @quasilyte Just wondering, is it possible or performant for you to simply use |
@SolarLune the intermediate image solution could work, but I haven't measured the performance difference. For now, I'm caching the subimage inside the sprite object and updating it whether the "frame" fields were updated (like the frame size being changed, etc.) |
Operating System
n/a, all systems.
What feature would you like to be added?
A way to render/draw a subimage of an image without extra allocations.
Some ways to do it:
img.TransferSubImage(&dst, rect)
(suggested by @tinne26)DrawImageOptions
field that can take a subimage bounds (zero value - no subimage is needed)DrawSubImage
function that takes the bounds in addition to the image and optionsThere could be other ways to solve it.
I don't really need a generic solution, but I want there to be a way to avoid that extra allocation. Then I can wrap the Image type with my own code and make this allocation go away using any means possible.
Right now I'm using an unsafe approach.
https://github.com/quasilyte/ge/blob/master/ebiten_image.go
https://github.com/quasilyte/ge/blob/7696bd01a11f683b4067c8fdf14c63ab29dee920/image_cache.go#L20
https://github.com/quasilyte/ge/blob/7696bd01a11f683b4067c8fdf14c63ab29dee920/sprite.go#L299-L309
Existing ways of avoiding that:
Why is this needed?
Let's assume that there is a user-defined Sprite type. It uses the ebitengine Image to do stuff.
Some of the use-cases of this Sprite may involve SubImage per Draw call: an underlying image represents a sprite-sheet with multiple frames (we only want to render single "frame"). This happens with animations, tilesets, user-defined atlases, etc.
Let's suppose that this Sprite calls an
Image.SubImage
for everyDraw
call (every sprite's Draw is called for every Game's Draw call). This will cause the game to potentially do hundreds of unintended heap allocations per every Draw call.This is especially painful on mobiles and wasm targets.
Given this benchmark:
We'll get these results:
This allocation comes from here:
And even if we would return
Image
instead of*Image
, it would still allocate as we return it as an interface value.Every allocation is of
sizeof(ebiten.Image)
.The text was updated successfully, but these errors were encountered: