-
Notifications
You must be signed in to change notification settings - Fork 2
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
Wishlist - What would you like to have prioritized? #4
Comments
When the new version of SDL_ttf v3 comes out, I would need bindings for those (font rendering is very important for my application). The same for SDL_image. |
I like that you use the phrasing carefully implemented. I think this is my main wish and priority for the purego-sdl3 repository. To allow it to take time before freezing the API. I'd love for this repo to become the way to interact with SDL from Go. And I'd love for it to be done through idiomatic and consistent approaches. To give an example, functions which return errors could be updated to return the Go |
@mewmew Returning errors instead of false in case of a failure sounds good. We can really think about doing that! Today I implement another function, where returning an error is very useful too: // GetMouseNameForID returns the name of the selected mouse, or error on failure.
//
// This function returns "" if the mouse doesn't have a name.
func GetMouseNameForID(instanceId MouseID) (string, error) {
ret := sdlGetMouseNameForID(instanceId)
if ret == nil {
return "", errors.New(GetError())
}
return convert.ToString(ret), nil
} Without the error type, the user wouldn't know, if the mouse has no name or a failure occurred. |
Wanted to mention that SDL_image got a 3.2 release yesterday: https://github.com/libsdl-org/SDL_image/releases SDL_net and SDL_mixer still have not released v3. Finally, none of the above are in Fedora's repos, and Debian/Ubuntu don't have libsdl3 itself, let alone these others, lol. |
Here's a list of stuff that I use and will need for Profectus, my Gemini Protocol browser application:
Aside from the SDL_ttf and SDL_image stuff, this should be pretty much everything I use for SDL. |
It seems that the $ pacman -Ql sdl3 | grep "\.so"
sdl3 /usr/lib/libSDL3.so
sdl3 /usr/lib/libSDL3.so.0
sdl3 /usr/lib/libSDL3.so.0.2.4 The
|
Will support for graphics (drawing primitives) be added? Also, could you please provide some insight into performance, especially compared to cgo. Does purego introduce any overhead? I want to create something similar to awesomewm, i.e., draw my own panel and dashboards. Since it will always be running and displayed, I'm concerned about performance. |
@Jipok Yes, I am planning to add all relevant functions, types and #defines.
|
@clseibold ttf is implemented now and ready for usage. |
@JupiterRider Thanks! I think most of the core things I need are implemented, so I will try it soon. I appreciate the work you've put into this! |
There are not enough details, and I myself do not understand it to analyze the code. |
@clseibold SDL_image is implemented now too! |
@Jipok I re-created raylib's bunnnymark example in purego-sdl3 und C SDL: Go code: package main
import (
_ "embed"
"fmt"
"image/color"
"math/rand"
sdl "github.com/jupiterrider/purego-sdl3/sdl"
)
const maxBunnies = 50_000
const width, height = 1280, 720
func main() {
defer sdl.Quit()
if !sdl.Init(sdl.InitVideo) {
panic(sdl.GetError())
}
var window *sdl.Window
var renderer *sdl.Renderer
if !sdl.CreateWindowAndRenderer("Bunnymark", width, height, sdl.WindowResizable, &window, &renderer) {
panic(sdl.GetError())
}
defer sdl.DestroyRenderer(renderer)
defer sdl.DestroyWindow(window)
surface := sdl.LoadBMP("wabbit_alpha.bmp")
if surface == nil {
panic(sdl.GetError())
}
texture := sdl.CreateTextureFromSurface(renderer, surface)
if texture == nil {
panic(sdl.GetError())
}
defer sdl.DestroyTexture(texture)
sdl.DestroySurface(surface)
var bunniesCount int = 0
var bunnies [maxBunnies]Bunny
last := sdl.GetTicksNS()
Outer:
for {
var event sdl.Event
for sdl.PollEvent(&event) {
switch event.Type() {
case sdl.EventQuit:
break Outer
case sdl.EventKeyDown:
if event.Key().Scancode == sdl.ScancodeEscape {
break Outer
}
case sdl.EventMouseButtonDown:
if btn := event.Button(); btn.Button == uint8(sdl.ButtonLeft) {
for i := 0; i < 100; i++ {
if bunniesCount < maxBunnies {
bunnies[bunniesCount].PositionX = btn.X
bunnies[bunniesCount].PositionY = btn.Y
bunnies[bunniesCount].SpeedX = float32(rand.Intn(501) - 250)
bunnies[bunniesCount].SpeedY = float32(rand.Intn(501) - 250)
bunnies[bunniesCount].Color.R = byte(rand.Intn(240+1-50) + 50)
bunnies[bunniesCount].Color.G = byte(rand.Intn(240+1-80) + 80)
bunnies[bunniesCount].Color.B = byte(rand.Intn(240+1-100) + 100)
bunniesCount++
}
}
}
}
}
sdl.SetRenderDrawColor(renderer, 255, 255, 255, 255)
sdl.RenderClear(renderer)
now := sdl.GetTicksNS()
ticks := now - last
last = now
delta := float64(ticks) / 1_000_000_000.0
for i := 0; i < bunniesCount; i++ {
bunnies[i].PositionX += bunnies[i].SpeedX * float32(delta)
bunnies[i].PositionY += bunnies[i].SpeedY * float32(delta)
if ((bunnies[i].PositionX + float32(texture.W)/2) > width) || ((bunnies[i].PositionX + float32(texture.W)/2) < 0) {
bunnies[i].SpeedX *= -1
}
if ((bunnies[i].PositionY + float32(texture.H)/2) > width) || ((bunnies[i].PositionY + float32(texture.H)/2) < 0) {
bunnies[i].SpeedY *= -1
}
var dstrect sdl.FRect
dstrect.X = bunnies[i].PositionX
dstrect.Y = bunnies[i].PositionY
dstrect.W = float32(texture.W)
dstrect.H = float32(texture.H)
sdl.SetTextureColorMod(texture, bunnies[i].Color.R, bunnies[i].Color.G, bunnies[i].Color.B)
sdl.RenderTexture(renderer, texture, nil, &dstrect)
}
sdl.SetRenderDrawColor(renderer, 0, 0, 0, 255)
sdl.RenderFillRect(renderer, &sdl.FRect{W: 120, H: 20})
sdl.SetRenderDrawColor(renderer, 0, 255, 0, 255)
sdl.RenderDebugText(renderer, 5, 5, fmt.Sprintf("bunnies %d", bunniesCount))
sdl.RenderPresent(renderer)
}
}
type Bunny struct {
PositionX float32
PositionY float32
SpeedX float32
SpeedY float32
Color color.RGBA
} C code: #include <SDL3/SDL.h>
#include <stdio.h>
#define MAX_BUNNIES 50000
#define WIDTH 1280
#define HEIGHT 720
typedef struct
{
float PositionX;
float PositionY;
float SpeedX;
float SpeedY;
SDL_Color Color;
} Bunny;
int main(int argc, char *argv[])
{
SDL_Init(SDL_INIT_VIDEO);
SDL_Window *window;
SDL_Renderer *renderer;
SDL_CreateWindowAndRenderer("Bunnymark", WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer);
SDL_Surface *surface = SDL_LoadBMP("wabbit_alpha.bmp");
SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_DestroySurface(surface);
int bunniesCount = 0;
Bunny *bunnies = (Bunny *)SDL_calloc(MAX_BUNNIES, sizeof(Bunny));
bool running = true;
Uint64 last = SDL_GetTicksNS();
while (running)
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_EVENT_QUIT:
running = 0;
break;
case SDL_EVENT_KEY_DOWN:
if (event.key.scancode == SDL_SCANCODE_ESCAPE)
{
running = 0;
}
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
if (event.button.button == SDL_BUTTON_LEFT)
{
for (int i = 0; i < 100; i++)
{
if (bunniesCount < MAX_BUNNIES)
{
bunnies[bunniesCount].PositionX = event.button.x;
bunnies[bunniesCount].PositionY = event.button.y;
bunnies[bunniesCount].SpeedX = (float)(SDL_rand(501) - 250);
bunnies[bunniesCount].SpeedY = (float)(SDL_rand(501) - 250);
bunnies[bunniesCount].Color.r = (Uint8)(SDL_rand(240 + 1 - 50) + 50);
bunnies[bunniesCount].Color.g = (Uint8)(SDL_rand(240 + 1 - 80) + 80);
bunnies[bunniesCount].Color.b = (Uint8)(SDL_rand(240 + 1 - 100) + 100);
bunniesCount++;
}
}
}
break;
}
}
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderClear(renderer);
Uint64 now = SDL_GetTicksNS();
Uint64 ticks = now - last;
last = now;
double delta = (double)(ticks) / 1000000000.0;
for (int i = 0; i < bunniesCount; i++)
{
bunnies[i].PositionX += bunnies[i].SpeedX * (float)(delta);
bunnies[i].PositionY += bunnies[i].SpeedY * (float)(delta);
if (((bunnies[i].PositionX + (float)(texture->w) / 2) > WIDTH) ||
((bunnies[i].PositionX + (float)(texture->w) / 2) < 0))
{
bunnies[i].SpeedX *= -1;
}
if (((bunnies[i].PositionY + (float)(texture->h) / 2) > HEIGHT) ||
((bunnies[i].PositionY + (float)(texture->h) / 2) < 0))
{
bunnies[i].SpeedY *= -1;
}
SDL_FRect dstrect = {0};
dstrect.x = bunnies[i].PositionX;
dstrect.y = bunnies[i].PositionY;
dstrect.w = (float)(texture->w);
dstrect.h = (float)(texture->h);
SDL_SetTextureColorMod(texture, bunnies[i].Color.r, bunnies[i].Color.g, bunnies[i].Color.b);
SDL_RenderTexture(renderer, texture, NULL, &dstrect);
}
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_FRect rect = {0};
rect.w = 120;
rect.h = 20;
SDL_RenderFillRect(renderer, &rect);
SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
char buffer[100];
sprintf(buffer, "bunnies %d", bunniesCount);
SDL_RenderDebugText(renderer, 5, 5, buffer);
SDL_RenderPresent(renderer);
}
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
} This is the Result: Go: C: |
But why such a difference in gpu? |
Oof, I didn't realize calling into C code from Go was so slow! Jumping up over 40 ms per frame is quite a lot. |
I'm afraid that this is a purego issue, because cgo is much faster: package main
import (
"fmt"
"image/color"
"math/rand"
"runtime"
"unsafe"
)
// #cgo LDFLAGS: -lSDL3
// #include <SDL3/SDL.h>
import "C"
func init() {
runtime.LockOSThread()
}
const maxBunnies = 50_000
const width, height = 1280, 720
func main() {
defer C.SDL_Quit()
if !C.SDL_Init(C.SDL_INIT_VIDEO) {
panic(C.GoString(C.SDL_GetError()))
}
var window *C.SDL_Window
var renderer *C.SDL_Renderer
title := C.CString("Bunnymark")
if !C.SDL_CreateWindowAndRenderer(title, width, height, C.SDL_WINDOW_RESIZABLE, &window, &renderer) {
panic(C.GoString(C.SDL_GetError()))
}
C.SDL_free(unsafe.Pointer(title))
defer C.SDL_DestroyRenderer(renderer)
defer C.SDL_DestroyWindow(window)
file := C.CString("wabbit_alpha.bmp")
surface := C.SDL_LoadBMP(file)
if surface == nil {
panic(C.GoString(C.SDL_GetError()))
}
C.SDL_free(unsafe.Pointer(file))
texture := C.SDL_CreateTextureFromSurface(renderer, surface)
if texture == nil {
panic(C.GoString(C.SDL_GetError()))
}
defer C.SDL_DestroyTexture(texture)
C.SDL_DestroySurface(surface)
var bunniesCount int = 0
var bunnies [maxBunnies]Bunny
last := C.SDL_GetTicksNS()
Outer:
for {
var event C.SDL_Event
for C.SDL_PollEvent(&event) {
eventType := *(*uint32)(unsafe.Pointer(&event))
switch eventType {
case C.SDL_EVENT_QUIT:
break Outer
case C.SDL_EVENT_KEY_DOWN:
key := *(*C.SDL_KeyboardEvent)(unsafe.Pointer(&event))
if key.scancode == C.SDL_SCANCODE_ESCAPE {
break Outer
}
case C.SDL_EVENT_MOUSE_BUTTON_DOWN:
btn := *(*C.SDL_MouseButtonEvent)(unsafe.Pointer(&event))
if btn.button == C.SDL_BUTTON_LEFT {
for i := 0; i < 100; i++ {
if bunniesCount < maxBunnies {
bunnies[bunniesCount].PositionX = float32(btn.x)
bunnies[bunniesCount].PositionY = float32(btn.y)
bunnies[bunniesCount].SpeedX = float32(rand.Intn(501) - 250)
bunnies[bunniesCount].SpeedY = float32(rand.Intn(501) - 250)
bunnies[bunniesCount].Color.R = byte(rand.Intn(240+1-50) + 50)
bunnies[bunniesCount].Color.G = byte(rand.Intn(240+1-80) + 80)
bunnies[bunniesCount].Color.B = byte(rand.Intn(240+1-100) + 100)
bunniesCount++
}
}
}
}
}
C.SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255)
C.SDL_RenderClear(renderer)
now := C.SDL_GetTicksNS()
ticks := now - last
last = now
delta := float64(ticks) / 1_000_000_000.0
for i := 0; i < bunniesCount; i++ {
bunnies[i].PositionX += bunnies[i].SpeedX * float32(delta)
bunnies[i].PositionY += bunnies[i].SpeedY * float32(delta)
if ((bunnies[i].PositionX + float32(texture.w)/2) > width) || ((bunnies[i].PositionX + float32(texture.w)/2) < 0) {
bunnies[i].SpeedX *= -1
}
if ((bunnies[i].PositionY + float32(texture.h)/2) > height) || ((bunnies[i].PositionY + float32(texture.h)/2) < 0) {
bunnies[i].SpeedY *= -1
}
var dstrect C.SDL_FRect
dstrect.x = C.float(bunnies[i].PositionX)
dstrect.y = C.float(bunnies[i].PositionY)
dstrect.w = C.float(texture.w)
dstrect.h = C.float(texture.h)
C.SDL_SetTextureColorMod(texture, C.Uint8(bunnies[i].Color.R), C.Uint8(bunnies[i].Color.G), C.Uint8(bunnies[i].Color.B))
C.SDL_RenderTexture(renderer, texture, nil, &dstrect)
}
C.SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255)
C.SDL_RenderFillRect(renderer, &C.SDL_FRect{w: 120, h: 20})
C.SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255)
text := C.CString(fmt.Sprintf("bunnies %d", bunniesCount))
C.SDL_RenderDebugText(renderer, 5, 5, text)
C.SDL_free(unsafe.Pointer(text))
C.SDL_RenderPresent(renderer)
}
}
type Bunny struct {
PositionX float32
PositionY float32
SpeedX float32
SpeedY float32
Color color.RGBA
} |
Well, this is what I assumed and feared. |
@TotallyGamerJet can you clarify this issue? Is there any hope for performance improvement? |
The best suggestion I can give for performance is to avoid RegisterLib as much as possible. If you can replace uses with Purego.SyscallN you should see similar performance characteristics to native Cgo. You'll have to use RegisterFunc for functions that take or return structs or have both floats and int arguments. All other cases should be able to use SyscallN. Also, SyscallN could probably be optimized slightly more since it makes an allocation every time but it basically does just what a normal Cgo call does. Hope this helps. |
^ edit: I re-generated the file after TotallyGamerJet's observation! |
@Zyko0 After looking at the generated code I noticed that it is invalid as it breaks what is guaranteed by the unsafe package. See point 4 in https://pkg.go.dev/unsafe#Pointer
Perhaps I should add this part to purego's documentation. |
@TotallyGamerJet That's interesting, thanks! I didn't know about that, I'll need to refactor a few things haha edit: I made the changes, but now I'm also thinking about some function arguments that I'm taking as uintptr already, which is probably incorrect too, the conversion happening too early (in the parent context), breaking the guarantee. |
Thanks everyone! I will start to move performance critical functions (like drawing, rendering) to And special thanks to @Jipok . If you hadn't asked for a benchmark, I wouldn't have noticed. |
It was unclear how to properly use this function. Add some clarification. JupiterRider/purego-sdl3#4 (comment)
SDL3 has a large API and it will take some time, until everything is carefully implemented in this Golang binding.
Please let me know, if there is a function or type you would like to prioritize.
The text was updated successfully, but these errors were encountered: