updated ebiten version from 2.7.9 to 2.9.9

This commit is contained in:
2026-06-15 19:06:55 +02:00
parent 21edbc41c4
commit db1b625069
405 changed files with 31913 additions and 12595 deletions
+6 -6
View File
@@ -1,14 +1,14 @@
module github.com/roodletoof/whiteboard module github.com/roodletoof/whiteboard
go 1.23.1 go 1.24.0
require github.com/hajimehoshi/ebiten/v2 v2.7.9 require github.com/hajimehoshi/ebiten/v2 v2.9.9
require ( require (
github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 // indirect github.com/ebitengine/gomobile v0.0.0-20250923094054-ea854a63cce1 // indirect
github.com/ebitengine/hideconsole v1.0.0 // indirect github.com/ebitengine/hideconsole v1.0.0 // indirect
github.com/ebitengine/purego v0.7.0 // indirect github.com/ebitengine/purego v0.9.0 // indirect
github.com/jezek/xgb v1.1.1 // indirect github.com/jezek/xgb v1.1.1 // indirect
golang.org/x/sync v0.7.0 // indirect golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.20.0 // indirect golang.org/x/sys v0.36.0 // indirect
) )
+12 -12
View File
@@ -1,16 +1,16 @@
github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 h1:48bCqKTuD7Z0UovDfvpCn7wZ0GUZ+yosIteNDthn3FU= github.com/ebitengine/gomobile v0.0.0-20250923094054-ea854a63cce1 h1:+kz5iTT3L7uU+VhlMfTb8hHcxLO3TlaELlX8wa4XjA0=
github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895/go.mod h1:XZdLv05c5hOZm3fM2NlJ92FyEZjnslcMcNRrhxs8+8M= github.com/ebitengine/gomobile v0.0.0-20250923094054-ea854a63cce1/go.mod h1:lKJoeixeJwnFmYsBny4vvCJGVFc3aYDalhuDsfZzWHI=
github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE= github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE=
github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A= github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A=
github.com/ebitengine/purego v0.7.0 h1:HPZpl61edMGCEW6XK2nsR6+7AnJ3unUxpTZBkkIXnMc= github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k=
github.com/ebitengine/purego v0.7.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/hajimehoshi/ebiten/v2 v2.7.9 h1:DYH/usAa9dMHcGkBIIEApJsVqDekrJBxYHmsBuly8Iw= github.com/hajimehoshi/ebiten/v2 v2.9.9 h1:JdDag6Ndj12iD4lxQGG8kbsrh7ssj4Sbzth6r929H/M=
github.com/hajimehoshi/ebiten/v2 v2.7.9/go.mod h1:Ulbq5xDmdx47P24EJ+Mb31Zps7vQq+guieG9mghQUaA= github.com/hajimehoshi/ebiten/v2 v2.9.9/go.mod h1:DAt4tnkYYpCvu3x9i1X/nK/vOruNXIlYq/tBXxnhrXM=
github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4= github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= golang.org/x/image v0.31.0 h1:mLChjE2MV6g1S7oqbXC0/UcKijjm5fnJLUYKIYrLESA=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/image v0.31.0/go.mod h1:R9ec5Lcp96v9FTF+ajwaH3uGxPH4fKfHHAVbUILxghA=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+2 -2
View File
@@ -1,4 +1,4 @@
Copyright (c) 2009 The Go Authors. All rights reserved. Copyright 2009 The Go Authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
@@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
copyright notice, this list of conditions and the following disclaimer copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the in the documentation and/or other materials provided with the
distribution. distribution.
* Neither the name of Google Inc. nor the names of its * Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from contributors may be used to endorse or promote products derived from
this software without specific prior written permission. this software without specific prior written permission.
+1 -1
View File
@@ -85,4 +85,4 @@ response to lifecycle events. Such packages should call:
in an init function inside that package. in an init function inside that package.
*/ */
package app // import "github.com/ebitengine/gomobile/app" package app
+1 -1
View File
@@ -17,7 +17,7 @@
// means it has lost the focus. // means it has lost the focus.
// //
// See the github.com/ebitengine/gomobile/app package for details on the event model. // See the github.com/ebitengine/gomobile/app package for details on the event model.
package lifecycle // import "github.com/ebitengine/gomobile/event/lifecycle" package lifecycle
import ( import (
"fmt" "fmt"
+1 -1
View File
@@ -5,7 +5,7 @@
// Package paint defines an event for the app being ready to paint. // Package paint defines an event for the app being ready to paint.
// //
// See the github.com/ebitengine/gomobile/app package for details on the event model. // See the github.com/ebitengine/gomobile/app package for details on the event model.
package paint // import "github.com/ebitengine/gomobile/event/paint" package paint
// Event indicates that the app is ready to paint the next frame of the GUI. // Event indicates that the app is ready to paint the next frame of the GUI.
// //
+1 -1
View File
@@ -6,7 +6,7 @@
// orientation of the app's window. // orientation of the app's window.
// //
// See the github.com/ebitengine/gomobile/app package for details on the event model. // See the github.com/ebitengine/gomobile/app package for details on the event model.
package size // import "github.com/ebitengine/gomobile/event/size" package size
import ( import (
"image" "image"
+1 -1
View File
@@ -5,7 +5,7 @@
// Package touch defines an event for touch input. // Package touch defines an event for touch input.
// //
// See the github.com/ebitengine/gomobile/app package for details on the event model. // See the github.com/ebitengine/gomobile/app package for details on the event model.
package touch // import "github.com/ebitengine/gomobile/event/touch" package touch
// The best source on android input events is the NDK: include/android/input.h // The best source on android input events is the NDK: include/android/input.h
// //
+1 -1
View File
@@ -19,7 +19,7 @@ the screen. Positions on the plane are measured in typographic points,
Any interface that draws to the screen using types from the geom package Any interface that draws to the screen using types from the geom package
scales the number of pixels to maintain a Pt as 1/72 of an inch. scales the number of pixels to maintain a Pt as 1/72 of an inch.
*/ */
package geom // import "github.com/ebitengine/gomobile/geom" package geom
/* /*
Notes on the various underlying coordinate systems. Notes on the various underlying coordinate systems.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2014 The Go Authors. All rights reserved. // Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
+8 -9
View File
@@ -10,7 +10,6 @@ import (
"debug/pe" "debug/pe"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"os" "os"
@@ -18,7 +17,7 @@ import (
"runtime" "runtime"
) )
var debug = log.New(ioutil.Discard, "gl: ", log.LstdFlags) var debug = log.New(io.Discard, "gl: ", log.LstdFlags)
func downloadDLLs() (path string, err error) { func downloadDLLs() (path string, err error) {
url := "https://dl.google.com/go/mobile/angle-bd3f8780b-" + runtime.GOARCH + ".tgz" url := "https://dl.google.com/go/mobile/angle-bd3f8780b-" + runtime.GOARCH + ".tgz"
@@ -54,11 +53,11 @@ func downloadDLLs() (path string, err error) {
} }
switch header.Name { switch header.Name {
case "angle-" + runtime.GOARCH + "/libglesv2.dll": case "angle-" + runtime.GOARCH + "/libglesv2.dll":
bytesGLESv2, err = ioutil.ReadAll(tr) bytesGLESv2, err = io.ReadAll(tr)
case "angle-" + runtime.GOARCH + "/libegl.dll": case "angle-" + runtime.GOARCH + "/libegl.dll":
bytesEGL, err = ioutil.ReadAll(tr) bytesEGL, err = io.ReadAll(tr)
case "angle-" + runtime.GOARCH + "/d3dcompiler_47.dll": case "angle-" + runtime.GOARCH + "/d3dcompiler_47.dll":
bytesD3DCompiler, err = ioutil.ReadAll(tr) bytesD3DCompiler, err = io.ReadAll(tr)
default: // skip default: // skip
} }
if err != nil { if err != nil {
@@ -70,13 +69,13 @@ func downloadDLLs() (path string, err error) {
} }
writeDLLs := func(path string) error { writeDLLs := func(path string) error {
if err := ioutil.WriteFile(filepath.Join(path, "libglesv2.dll"), bytesGLESv2, 0755); err != nil { if err := os.WriteFile(filepath.Join(path, "libglesv2.dll"), bytesGLESv2, 0755); err != nil {
return fmt.Errorf("gl: cannot install ANGLE: %v", err) return fmt.Errorf("gl: cannot install ANGLE: %v", err)
} }
if err := ioutil.WriteFile(filepath.Join(path, "libegl.dll"), bytesEGL, 0755); err != nil { if err := os.WriteFile(filepath.Join(path, "libegl.dll"), bytesEGL, 0755); err != nil {
return fmt.Errorf("gl: cannot install ANGLE: %v", err) return fmt.Errorf("gl: cannot install ANGLE: %v", err)
} }
if err := ioutil.WriteFile(filepath.Join(path, "d3dcompiler_47.dll"), bytesD3DCompiler, 0755); err != nil { if err := os.WriteFile(filepath.Join(path, "d3dcompiler_47.dll"), bytesD3DCompiler, 0755); err != nil {
return fmt.Errorf("gl: cannot install ANGLE: %v", err) return fmt.Errorf("gl: cannot install ANGLE: %v", err)
} }
return nil return nil
@@ -152,7 +151,7 @@ func chromePath() string {
} }
for _, installdir := range installdirs { for _, installdir := range installdirs {
versiondirs, err := ioutil.ReadDir(installdir) versiondirs, err := os.ReadDir(installdir)
if err != nil { if err != nil {
continue continue
} }
+2 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2014 The Go Authors. All rights reserved. // Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@@ -42,7 +42,7 @@ error messages. For example,
The gldebug tracing has very high overhead, so make sure to remove The gldebug tracing has very high overhead, so make sure to remove
the build tag before deploying any binaries. the build tag before deploying any binaries.
*/ */
package gl // import "github.com/ebitengine/gomobile/gl" package gl
/* /*
Implementation details. Implementation details.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2015 The Go Authors. All rights reserved. // Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2014 The Go Authors. All rights reserved. // Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2014 The Go Authors. All rights reserved. // Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
+5 -5
View File
@@ -1,4 +1,4 @@
// Copyright 2014 The Go Authors. All rights reserved. // Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@@ -81,7 +81,7 @@ type Context interface {
// http://www.khronos.org/opengles/sdk/docs/man3/html/glBlendFunc.xhtml // http://www.khronos.org/opengles/sdk/docs/man3/html/glBlendFunc.xhtml
BlendFunc(sfactor, dfactor Enum) BlendFunc(sfactor, dfactor Enum)
// BlendFunc sets the pixel RGB and alpha blending factors separately. // BlendFuncSeparate sets the pixel RGB and alpha blending factors separately.
// //
// http://www.khronos.org/opengles/sdk/docs/man3/html/glBlendFuncSeparate.xhtml // http://www.khronos.org/opengles/sdk/docs/man3/html/glBlendFuncSeparate.xhtml
BlendFuncSeparate(sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha Enum) BlendFuncSeparate(sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha Enum)
@@ -192,7 +192,7 @@ type Context interface {
// http://www.khronos.org/opengles/sdk/docs/man3/html/glGenTextures.xhtml // http://www.khronos.org/opengles/sdk/docs/man3/html/glGenTextures.xhtml
CreateTexture() Texture CreateTexture() Texture
// CreateTVertexArray creates a vertex array. // CreateVertexArray creates a vertex array.
// //
// http://www.khronos.org/opengles/sdk/docs/man3/html/glGenVertexArrays.xhtml // http://www.khronos.org/opengles/sdk/docs/man3/html/glGenVertexArrays.xhtml
CreateVertexArray() VertexArray CreateVertexArray() VertexArray
@@ -599,7 +599,7 @@ type Context interface {
// http://www.khronos.org/opengles/sdk/docs/man3/html/glStencilFunc.xhtml // http://www.khronos.org/opengles/sdk/docs/man3/html/glStencilFunc.xhtml
StencilFunc(fn Enum, ref int, mask uint32) StencilFunc(fn Enum, ref int, mask uint32)
// StencilFunc sets the front or back stencil test reference value. // StencilFuncSeparate sets the front or back stencil test reference value.
// //
// http://www.khronos.org/opengles/sdk/docs/man3/html/glStencilFuncSeparate.xhtml // http://www.khronos.org/opengles/sdk/docs/man3/html/glStencilFuncSeparate.xhtml
StencilFuncSeparate(face, fn Enum, ref int, mask uint32) StencilFuncSeparate(face, fn Enum, ref int, mask uint32)
@@ -737,7 +737,7 @@ type Context interface {
// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml // http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml
Uniform4i(dst Uniform, v0, v1, v2, v3 int32) Uniform4i(dst Uniform, v0, v1, v2, v3 int32)
// Uniform4i writes an ivec4 uniform array of len(src)/4 elements. // Uniform4iv writes an ivec4 uniform array of len(src)/4 elements.
// //
// http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml // http://www.khronos.org/opengles/sdk/docs/man3/html/glUniform.xhtml
Uniform4iv(dst Uniform, src []int32) Uniform4iv(dst Uniform, src []int32)
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2014 The Go Authors. All rights reserved. // Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2014 The Go Authors. All rights reserved. // Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
+1 -1
View File
@@ -160,7 +160,7 @@ func (ctx *context) cString(str string) (uintptr, func()) {
return uintptr(ptr), func() { C.free(ptr) } return uintptr(ptr), func() { C.free(ptr) }
} }
// cString creates a pointer to a C string off the Go heap. // cStringPtr creates a pointer to a C string off the Go heap.
// ret is a **char. // ret is a **char.
func (ctx *context) cStringPtr(str string) (uintptr, func()) { func (ctx *context) cStringPtr(str string) (uintptr, func()) {
s, free := ctx.cString(str) s, free := ctx.cString(str)
+24 -7
View File
@@ -26,16 +26,31 @@ except for float arguments and return values.
## Supported Platforms ## Supported Platforms
- **FreeBSD**: amd64, arm64 ### Tier 1
- **Linux**: amd64, arm64
- **macOS / iOS**: amd64, arm64
- **Windows**: 386*, amd64, arm*, arm64
`*` These architectures only support SyscallN and NewCallback Tier 1 platforms are the primary targets officially supported by PureGo. When a new version of PureGo is released, any critical bugs found on Tier 1 platforms are treated as release blockers. The release will be postponed until such issues are resolved.
- **Android**: amd64, arm64
- **iOS**: amd64, arm64
- **Linux**: amd64, arm64
- **macOS**: amd64, arm64
- **Windows**: amd64, arm64
### Tier 2
Tier 2 platforms are supported by PureGo on a best-effort basis. Critical bugs on Tier 2 platforms do not block new PureGo releases. However, fixes contributed by external contributors are very welcome and encouraged.
- **Android**: 386, arm
- **FreeBSD**: amd64, arm64
- **Linux**: 386, arm, loong64
- **Windows**: 386*, arm*
`*` These architectures only support `SyscallN` and `NewCallback`
## Example ## Example
This example only works on macOS and Linux. For a complete example look at [libc](https://github.com/ebitengine/purego/tree/main/examples/libc) which supports Windows and FreeBSD. The example below only showcases purego use for macOS and Linux. The other platforms require special handling which can
be seen in the complete example at [examples/libc](https://github.com/ebitengine/purego/tree/main/examples/libc) which supports FreeBSD and Windows.
```go ```go
package main package main
@@ -74,7 +89,7 @@ Then to run: `CGO_ENABLED=0 go run main.go`
## Questions ## Questions
If you have questions about how to incorporate purego in your project or want to discuss If you have questions about how to incorporate purego in your project or want to discuss
how it works join the [Discord](https://discord.com/channels/842049801528016967/1123106378731487345)! how it works join the [Discord](https://discord.gg/HzGZVD6BkY)!
### External Code ### External Code
@@ -83,6 +98,7 @@ License that can be found [in the Go Source](https://github.com/golang/go/blob/m
This is a list of the copied files: This is a list of the copied files:
* `abi_*.h` from package `runtime/cgo` * `abi_*.h` from package `runtime/cgo`
* `wincallback.go` from package `runtime`
* `zcallback_darwin_*.s` from package `runtime` * `zcallback_darwin_*.s` from package `runtime`
* `internal/fakecgo/abi_*.h` from package `runtime/cgo` * `internal/fakecgo/abi_*.h` from package `runtime/cgo`
* `internal/fakecgo/asm_GOARCH.s` from package `runtime/cgo` * `internal/fakecgo/asm_GOARCH.s` from package `runtime/cgo`
@@ -91,6 +107,7 @@ This is a list of the copied files:
* `internal/fakecgo/iscgo.go` from package `runtime/cgo` * `internal/fakecgo/iscgo.go` from package `runtime/cgo`
* `internal/fakecgo/setenv.go` from package `runtime/cgo` * `internal/fakecgo/setenv.go` from package `runtime/cgo`
* `internal/fakecgo/freebsd.go` from package `runtime/cgo` * `internal/fakecgo/freebsd.go` from package `runtime/cgo`
* `internal/fakecgo/netbsd.go` from package `runtime/cgo`
The files `abi_*.h` and `internal/fakecgo/abi_*.h` are the same because Bazel does not support cross-package use of The files `abi_*.h` and `internal/fakecgo/abi_*.h` are the same because Bazel does not support cross-package use of
`#include` so we need each one once per package. (cf. [issue](https://github.com/bazelbuild/rules_go/issues/3636)) `#include` so we need each one once per package. (cf. [issue](https://github.com/bazelbuild/rules_go/issues/3636))
+60
View File
@@ -0,0 +1,60 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Macros for transitioning from the host ABI to Go ABI0.
//
// These macros save and restore the callee-saved registers
// from the stack, but they don't adjust stack pointer, so
// the user should prepare stack space in advance.
// SAVE_R22_TO_R31(offset) saves R22 ~ R31 to the stack space
// of ((offset)+0*8)(R3) ~ ((offset)+9*8)(R3).
//
// SAVE_F24_TO_F31(offset) saves F24 ~ F31 to the stack space
// of ((offset)+0*8)(R3) ~ ((offset)+7*8)(R3).
//
// Note: g is R22
#define SAVE_R22_TO_R31(offset) \
MOVV g, ((offset)+(0*8))(R3) \
MOVV R23, ((offset)+(1*8))(R3) \
MOVV R24, ((offset)+(2*8))(R3) \
MOVV R25, ((offset)+(3*8))(R3) \
MOVV R26, ((offset)+(4*8))(R3) \
MOVV R27, ((offset)+(5*8))(R3) \
MOVV R28, ((offset)+(6*8))(R3) \
MOVV R29, ((offset)+(7*8))(R3) \
MOVV R30, ((offset)+(8*8))(R3) \
MOVV R31, ((offset)+(9*8))(R3)
#define SAVE_F24_TO_F31(offset) \
MOVD F24, ((offset)+(0*8))(R3) \
MOVD F25, ((offset)+(1*8))(R3) \
MOVD F26, ((offset)+(2*8))(R3) \
MOVD F27, ((offset)+(3*8))(R3) \
MOVD F28, ((offset)+(4*8))(R3) \
MOVD F29, ((offset)+(5*8))(R3) \
MOVD F30, ((offset)+(6*8))(R3) \
MOVD F31, ((offset)+(7*8))(R3)
#define RESTORE_R22_TO_R31(offset) \
MOVV ((offset)+(0*8))(R3), g \
MOVV ((offset)+(1*8))(R3), R23 \
MOVV ((offset)+(2*8))(R3), R24 \
MOVV ((offset)+(3*8))(R3), R25 \
MOVV ((offset)+(4*8))(R3), R26 \
MOVV ((offset)+(5*8))(R3), R27 \
MOVV ((offset)+(6*8))(R3), R28 \
MOVV ((offset)+(7*8))(R3), R29 \
MOVV ((offset)+(8*8))(R3), R30 \
MOVV ((offset)+(9*8))(R3), R31
#define RESTORE_F24_TO_F31(offset) \
MOVD ((offset)+(0*8))(R3), F24 \
MOVD ((offset)+(1*8))(R3), F25 \
MOVD ((offset)+(2*8))(R3), F26 \
MOVD ((offset)+(3*8))(R3), F27 \
MOVD ((offset)+(4*8))(R3), F28 \
MOVD ((offset)+(5*8))(R3), F29 \
MOVD ((offset)+(6*8))(R3), F30 \
MOVD ((offset)+(7*8))(R3), F31
+1 -1
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build cgo && (darwin || freebsd || linux) //go:build cgo && (darwin || freebsd || linux || netbsd)
package purego package purego
+3 -1
View File
@@ -1,11 +1,13 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors // SPDX-FileCopyrightText: 2023 The Ebitengine Authors
//go:build darwin || freebsd || linux //go:build darwin || freebsd || linux || netbsd
package purego package purego
// Dlerror represents an error value returned from Dlopen, Dlsym, or Dlclose. // Dlerror represents an error value returned from Dlopen, Dlsym, or Dlclose.
//
// This type is not available on Windows as there is no counterpart to it on Windows.
type Dlerror struct { type Dlerror struct {
s string s string
} }
+15 -10
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux //go:build (darwin || freebsd || linux || netbsd) && !android && !faketime
package purego package purego
@@ -33,6 +33,10 @@ func init() {
// A second call to Dlopen with the same path will return the same handle, but the internal // A second call to Dlopen with the same path will return the same handle, but the internal
// reference count for the handle will be incremented. Therefore, all // reference count for the handle will be incremented. Therefore, all
// Dlopen calls should be balanced with a Dlclose call. // Dlopen calls should be balanced with a Dlclose call.
//
// This function is not available on Windows.
// Use [golang.org/x/sys/windows.LoadLibrary], [golang.org/x/sys/windows.LoadLibraryEx],
// [golang.org/x/sys/windows.NewLazyDLL], or [golang.org/x/sys/windows.NewLazySystemDLL] for Windows instead.
func Dlopen(path string, mode int) (uintptr, error) { func Dlopen(path string, mode int) (uintptr, error) {
u := fnDlopen(path, mode) u := fnDlopen(path, mode)
if u == 0 { if u == 0 {
@@ -45,6 +49,9 @@ func Dlopen(path string, mode int) (uintptr, error) {
// It returns the address where that symbol is loaded into memory. If the symbol is not found, // It returns the address where that symbol is loaded into memory. If the symbol is not found,
// in the specified library or any of the libraries that were automatically loaded by Dlopen // in the specified library or any of the libraries that were automatically loaded by Dlopen
// when that library was loaded, Dlsym returns zero. // when that library was loaded, Dlsym returns zero.
//
// This function is not available on Windows.
// Use [golang.org/x/sys/windows.GetProcAddress] for Windows instead.
func Dlsym(handle uintptr, name string) (uintptr, error) { func Dlsym(handle uintptr, name string) (uintptr, error) {
u := fnDlsym(handle, name) u := fnDlsym(handle, name)
if u == 0 { if u == 0 {
@@ -56,6 +63,9 @@ func Dlsym(handle uintptr, name string) (uintptr, error) {
// Dlclose decrements the reference count on the dynamic library handle. // Dlclose decrements the reference count on the dynamic library handle.
// If the reference count drops to zero and no other loaded libraries // If the reference count drops to zero and no other loaded libraries
// use symbols in it, then the dynamic library is unloaded. // use symbols in it, then the dynamic library is unloaded.
//
// This function is not available on Windows.
// Use [golang.org/x/sys/windows.FreeLibrary] for Windows instead.
func Dlclose(handle uintptr) error { func Dlclose(handle uintptr) error {
if fnDlclose(handle) { if fnDlclose(handle) {
return Dlerror{fnDlerror()} return Dlerror{fnDlerror()}
@@ -63,11 +73,6 @@ func Dlclose(handle uintptr) error {
return nil return nil
} }
//go:linkname openLibrary openLibrary
func openLibrary(name string) (uintptr, error) {
return Dlopen(name, RTLD_NOW|RTLD_GLOBAL)
}
func loadSymbol(handle uintptr, name string) (uintptr, error) { func loadSymbol(handle uintptr, name string) (uintptr, error) {
return Dlsym(handle, name) return Dlsym(handle, name)
} }
@@ -78,17 +83,17 @@ func loadSymbol(handle uintptr, name string) (uintptr, error) {
// appear to work if you link directly to the C function on darwin arm64. // appear to work if you link directly to the C function on darwin arm64.
//go:linkname dlopen dlopen //go:linkname dlopen dlopen
var dlopen uintptr var dlopen uint8
var dlopenABI0 = uintptr(unsafe.Pointer(&dlopen)) var dlopenABI0 = uintptr(unsafe.Pointer(&dlopen))
//go:linkname dlsym dlsym //go:linkname dlsym dlsym
var dlsym uintptr var dlsym uint8
var dlsymABI0 = uintptr(unsafe.Pointer(&dlsym)) var dlsymABI0 = uintptr(unsafe.Pointer(&dlsym))
//go:linkname dlclose dlclose //go:linkname dlclose dlclose
var dlclose uintptr var dlclose uint8
var dlcloseABI0 = uintptr(unsafe.Pointer(&dlclose)) var dlcloseABI0 = uintptr(unsafe.Pointer(&dlclose))
//go:linkname dlerror dlerror //go:linkname dlerror dlerror
var dlerror uintptr var dlerror uint8
var dlerrorABI0 = uintptr(unsafe.Pointer(&dlerror)) var dlerrorABI0 = uintptr(unsafe.Pointer(&dlerror))
+34
View File
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
package purego
import "github.com/ebitengine/purego/internal/cgo"
// Source for constants: https://android.googlesource.com/platform/bionic/+/refs/heads/main/libc/include/dlfcn.h
const (
is64bit = 1 << (^uintptr(0) >> 63) / 2
is32bit = 1 - is64bit
RTLD_DEFAULT = is32bit * 0xffffffff
RTLD_LAZY = 0x00000001
RTLD_NOW = is64bit * 0x00000002
RTLD_LOCAL = 0x00000000
RTLD_GLOBAL = is64bit*0x00100 | is32bit*0x00000002
)
func Dlopen(path string, mode int) (uintptr, error) {
return cgo.Dlopen(path, mode)
}
func Dlsym(handle uintptr, name string) (uintptr, error) {
return cgo.Dlsym(handle, name)
}
func Dlclose(handle uintptr) error {
return cgo.Dlclose(handle)
}
func loadSymbol(handle uintptr, name string) (uintptr, error) {
return Dlsym(handle, name)
}
+5 -10
View File
@@ -6,19 +6,14 @@ package purego
// Source for constants: https://opensource.apple.com/source/dyld/dyld-360.14/include/dlfcn.h.auto.html // Source for constants: https://opensource.apple.com/source/dyld/dyld-360.14/include/dlfcn.h.auto.html
const ( const (
RTLD_DEFAULT = ^uintptr(0) - 1 // Pseudo-handle for dlsym so search for any loaded symbol RTLD_DEFAULT = 1<<64 - 2 // Pseudo-handle for dlsym so search for any loaded symbol
RTLD_LAZY = 0x1 // Relocations are performed at an implementation-dependent time. RTLD_LAZY = 0x1 // Relocations are performed at an implementation-dependent time.
RTLD_NOW = 0x2 // Relocations are performed when the object is loaded. RTLD_NOW = 0x2 // Relocations are performed when the object is loaded.
RTLD_LOCAL = 0x4 // All symbols are not made available for relocation processing by other modules. RTLD_LOCAL = 0x4 // All symbols are not made available for relocation processing by other modules.
RTLD_GLOBAL = 0x8 // All symbols are available for relocation processing of other modules. RTLD_GLOBAL = 0x8 // All symbols are available for relocation processing of other modules.
) )
//go:cgo_import_dynamic purego_dlopen dlopen "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_dlopen dlopen "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic purego_dlsym dlsym "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_dlsym dlsym "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic purego_dlerror dlerror "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_dlerror dlerror "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic purego_dlclose dlclose "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_dlclose dlclose "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic purego_dlopen dlopen "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic purego_dlsym dlsym "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic purego_dlerror dlerror "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic purego_dlclose dlclose "/usr/lib/libSystem.B.dylib"
+6 -5
View File
@@ -5,9 +5,10 @@ package purego
// Constants as defined in https://github.com/freebsd/freebsd-src/blob/main/include/dlfcn.h // Constants as defined in https://github.com/freebsd/freebsd-src/blob/main/include/dlfcn.h
const ( const (
RTLD_DEFAULT = ^uintptr(0) - 2 // Pseudo-handle for dlsym so search for any loaded symbol intSize = 32 << (^uint(0) >> 63) // 32 or 64
RTLD_LAZY = 0x00001 // Relocations are performed at an implementation-dependent time. RTLD_DEFAULT = 1<<intSize - 2 // Pseudo-handle for dlsym so search for any loaded symbol
RTLD_NOW = 0x00002 // Relocations are performed when the object is loaded. RTLD_LAZY = 0x00000001 // Relocations are performed at an implementation-dependent time.
RTLD_LOCAL = 0x00000 // All symbols are not made available for relocation processing by other modules. RTLD_NOW = 0x00000002 // Relocations are performed when the object is loaded.
RTLD_GLOBAL = 0x00100 // All symbols are available for relocation processing of other modules. RTLD_LOCAL = 0x00000000 // All symbols are not made available for relocation processing by other modules.
RTLD_GLOBAL = 0x00000100 // All symbols are available for relocation processing of other modules.
) )
+2
View File
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build !android
package purego package purego
// Source for constants: https://codebrowser.dev/glibc/glibc/bits/dlfcn.h.html // Source for constants: https://codebrowser.dev/glibc/glibc/bits/dlfcn.h.html
+15
View File
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2025 The Ebitengine Authors
package purego
// Source for constants: https://github.com/NetBSD/src/blob/trunk/include/dlfcn.h
const (
intSize = 32 << (^uint(0) >> 63) // 32 or 64
RTLD_DEFAULT = 1<<intSize - 2 // Pseudo-handle for dlsym so search for any loaded symbol
RTLD_LAZY = 0x00000001 // Relocations are performed at an implementation-dependent time.
RTLD_NOW = 0x00000002 // Relocations are performed when the object is loaded.
RTLD_LOCAL = 0x00000000 // All symbols are not made available for relocation processing by other modules.
RTLD_GLOBAL = 0x00000100 // All symbols are available for relocation processing of other modules.
)
+1 -1
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build !cgo //go:build !cgo && !faketime
package purego package purego
+9
View File
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2025 The Ebitengine Authors
package purego
//go:cgo_import_dynamic purego_dlopen dlopen "libc.so"
//go:cgo_import_dynamic purego_dlsym dlsym "libc.so"
//go:cgo_import_dynamic purego_dlerror dlerror "libc.so"
//go:cgo_import_dynamic purego_dlclose dlclose "libc.so"
+24
View File
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
//go:build faketime
package purego
import "errors"
func Dlopen(path string, mode int) (uintptr, error) {
return 0, errors.New("Dlopen is not supported in the playground")
}
func Dlsym(handle uintptr, name string) (uintptr, error) {
return 0, errors.New("Dlsym is not supported in the playground")
}
func Dlclose(handle uintptr) error {
return errors.New("Dlclose is not supported in the playground")
}
func loadSymbol(handle uintptr, name string) (uintptr, error) {
return Dlsym(handle, name)
}
+1 -1
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || !cgo && (freebsd || linux) //go:build darwin || !cgo && (freebsd || linux || netbsd) && !faketime
#include "textflag.h" #include "textflag.h"
+160 -80
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux || windows //go:build darwin || freebsd || linux || netbsd || windows
package purego package purego
@@ -10,14 +10,20 @@ import (
"math" "math"
"reflect" "reflect"
"runtime" "runtime"
"strconv"
"sync"
"unsafe" "unsafe"
"github.com/ebitengine/purego/internal/strings" "github.com/ebitengine/purego/internal/strings"
) )
var thePool = sync.Pool{New: func() any {
return new(syscall15Args)
}}
// RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name). // RegisterLibFunc is a wrapper around RegisterFunc that uses the C function returned from Dlsym(handle, name).
// It panics if it can't find the name symbol. // It panics if it can't find the name symbol.
func RegisterLibFunc(fptr interface{}, handle uintptr, name string) { func RegisterLibFunc(fptr any, handle uintptr, name string) {
sym, err := loadSymbol(handle, name) sym, err := loadSymbol(handle, name)
if err != nil { if err != nil {
panic(err) panic(err)
@@ -60,7 +66,7 @@ func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
// //
// There is a special case when the last argument of fptr is a variadic interface (or []interface} // There is a special case when the last argument of fptr is a variadic interface (or []interface}
// it will be expanded into a call to the C function as if it had the arguments in that slice. // it will be expanded into a call to the C function as if it had the arguments in that slice.
// This means that using arg ...interface{} is like a cast to the function with the arguments inside arg. // This means that using arg ...any is like a cast to the function with the arguments inside arg.
// This is not the same as C variadic. // This is not the same as C variadic.
// //
// # Memory // # Memory
@@ -105,7 +111,7 @@ func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
// defer free(mustFree) // defer free(mustFree)
// //
// [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C // [Cgo rules]: https://pkg.go.dev/cmd/cgo#hdr-Go_references_to_C
func RegisterFunc(fptr interface{}, cfn uintptr) { func RegisterFunc(fptr any, cfn uintptr) {
fn := reflect.ValueOf(fptr).Elem() fn := reflect.ValueOf(fptr).Elem()
ty := fn.Type() ty := fn.Type()
if ty.Kind() != reflect.Func { if ty.Kind() != reflect.Func {
@@ -117,6 +123,10 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
if cfn == 0 { if cfn == 0 {
panic("purego: cfn is nil") panic("purego: cfn is nil")
} }
if ty.NumOut() == 1 && (ty.Out(0).Kind() == reflect.Float32 || ty.Out(0).Kind() == reflect.Float64) &&
runtime.GOARCH != "arm64" && runtime.GOARCH != "amd64" && runtime.GOARCH != "loong64" {
panic("purego: float returns are not supported")
}
{ {
// this code checks how many registers and stack this function will use // this code checks how many registers and stack this function will use
// to avoid crashing with too many arguments // to avoid crashing with too many arguments
@@ -126,19 +136,33 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
for i := 0; i < ty.NumIn(); i++ { for i := 0; i < ty.NumIn(); i++ {
arg := ty.In(i) arg := ty.In(i)
switch arg.Kind() { switch arg.Kind() {
case reflect.Func:
// This only does preliminary testing to ensure the CDecl argument
// is the first argument. Full testing is done when the callback is actually
// created in NewCallback.
for j := 0; j < arg.NumIn(); j++ {
in := arg.In(j)
if !in.AssignableTo(reflect.TypeOf(CDecl{})) {
continue
}
if j != 0 {
panic("purego: CDecl must be the first argument")
}
}
case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, case reflect.String, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer, reflect.Slice, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer,
reflect.Func, reflect.Bool: reflect.Slice, reflect.Bool:
if ints < numOfIntegerRegisters() { if ints < numOfIntegerRegisters() {
ints++ ints++
} else { } else {
stack++ stack++
} }
case reflect.Float32, reflect.Float64: case reflect.Float32, reflect.Float64:
const is32bit = unsafe.Sizeof(uintptr(0)) == 4
if is32bit { if is32bit {
panic("purego: floats only supported on 64bit platforms") panic("purego: floats only supported on 64bit platforms")
} }
if floats < numOfFloats { if floats < numOfFloatRegisters {
floats++ floats++
} else { } else {
stack++ stack++
@@ -182,21 +206,8 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
} }
} }
v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) { v := reflect.MakeFunc(ty, func(args []reflect.Value) (results []reflect.Value) {
if len(args) > 0 {
if variadic, ok := args[len(args)-1].Interface().([]interface{}); ok {
// subtract one from args bc the last argument in args is []interface{}
// which we are currently expanding
tmp := make([]reflect.Value, len(args)-1+len(variadic))
n := copy(tmp, args[:len(args)-1])
for i, v := range variadic {
tmp[n+i] = reflect.ValueOf(v)
}
args = tmp
}
}
var sysargs [maxArgs]uintptr var sysargs [maxArgs]uintptr
stack := sysargs[numOfIntegerRegisters():] var floats [numOfFloatRegisters]uintptr
var floats [numOfFloats]uintptr
var numInts int var numInts int
var numFloats int var numFloats int
var numStack int var numStack int
@@ -204,7 +215,7 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" { if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
// Windows arm64 uses the same calling convention as macOS and Linux // Windows arm64 uses the same calling convention as macOS and Linux
addStack = func(x uintptr) { addStack = func(x uintptr) {
stack[numStack] = x sysargs[numOfIntegerRegisters()+numStack] = x
numStack++ numStack++
} }
addInt = func(x uintptr) { addInt = func(x uintptr) {
@@ -237,69 +248,91 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
addFloat = addStack addFloat = addStack
} }
var keepAlive []interface{} var keepAlive []any
defer func() { defer func() {
runtime.KeepAlive(keepAlive) runtime.KeepAlive(keepAlive)
runtime.KeepAlive(args) runtime.KeepAlive(args)
}() }()
var syscall syscall15Args
var arm64_r8 uintptr
if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct { if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
outType := ty.Out(0) outType := ty.Out(0)
if runtime.GOARCH == "amd64" && outType.Size() > maxRegAllocStructSize { if (runtime.GOARCH == "amd64" || runtime.GOARCH == "loong64") && outType.Size() > maxRegAllocStructSize {
val := reflect.New(outType) val := reflect.New(outType)
keepAlive = append(keepAlive, val) keepAlive = append(keepAlive, val)
addInt(val.Pointer()) addInt(val.Pointer())
} else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize { } else if runtime.GOARCH == "arm64" && outType.Size() > maxRegAllocStructSize {
if !isAllSameFloat(outType) || outType.NumField() > 4 { isAllFloats, numFields := isAllSameFloat(outType)
if !isAllFloats || numFields > 4 {
val := reflect.New(outType) val := reflect.New(outType)
keepAlive = append(keepAlive, val) keepAlive = append(keepAlive, val)
syscall.arm64_r8 = val.Pointer() arm64_r8 = val.Pointer()
} }
} }
} }
for _, v := range args { for i, v := range args {
switch v.Kind() { if variadic, ok := args[i].Interface().([]any); ok {
case reflect.String: if i != len(args)-1 {
ptr := strings.CString(v.String()) panic("purego: can only expand last parameter")
keepAlive = append(keepAlive, ptr)
addInt(uintptr(unsafe.Pointer(ptr)))
case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
addInt(uintptr(v.Uint()))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
addInt(uintptr(v.Int()))
case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
// There is no need to keepAlive this pointer separately because it is kept alive in the args variable
addInt(v.Pointer())
case reflect.Func:
addInt(NewCallback(v.Interface()))
case reflect.Bool:
if v.Bool() {
addInt(1)
} else {
addInt(0)
} }
case reflect.Float32: for _, x := range variadic {
addFloat(uintptr(math.Float32bits(float32(v.Float())))) keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
case reflect.Float64: }
addFloat(uintptr(math.Float64bits(v.Float()))) continue
case reflect.Struct:
keepAlive = addStruct(v, &numInts, &numFloats, &numStack, addInt, addFloat, addStack, keepAlive)
default:
panic("purego: unsupported kind: " + v.Kind().String())
} }
if runtime.GOARCH == "arm64" && runtime.GOOS == "darwin" &&
(numInts >= numOfIntegerRegisters() || numFloats >= numOfFloatRegisters) && v.Kind() != reflect.Struct { // hit the stack
fields := make([]reflect.StructField, len(args[i:]))
for j, val := range args[i:] {
if val.Kind() == reflect.String {
ptr := strings.CString(v.String())
keepAlive = append(keepAlive, ptr)
val = reflect.ValueOf(ptr)
args[i+j] = val
}
fields[j] = reflect.StructField{
Name: "X" + strconv.Itoa(j),
Type: val.Type(),
}
}
structType := reflect.StructOf(fields)
structInstance := reflect.New(structType).Elem()
for j, val := range args[i:] {
structInstance.Field(j).Set(val)
}
placeRegisters(structInstance, addFloat, addInt)
break
}
keepAlive = addValue(v, keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
} }
if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
// Use the normal arm64 calling convention even on Windows syscall := thePool.Get().(*syscall15Args)
syscall = syscall15Args{ defer thePool.Put(syscall)
if runtime.GOARCH == "loong64" {
*syscall = syscall15Args{
cfn, cfn,
sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5], sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5],
sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
sysargs[12], sysargs[13], sysargs[14], sysargs[12], sysargs[13], sysargs[14],
floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7], floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
syscall.arm64_r8, 0,
} }
runtime_cgocall(syscall15XABI0, unsafe.Pointer(&syscall)) runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))
} else if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
// Use the normal arm64 calling convention even on Windows
*syscall = syscall15Args{
cfn,
sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], sysargs[5],
sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
sysargs[12], sysargs[13], sysargs[14],
floats[0], floats[1], floats[2], floats[3], floats[4], floats[5], floats[6], floats[7],
arm64_r8,
}
runtime_cgocall(syscall15XABI0, unsafe.Pointer(syscall))
} else { } else {
*syscall = syscall15Args{}
// This is a fallback for Windows amd64, 386, and arm. Note this may not support floats // This is a fallback for Windows amd64, 386, and arm. Note this may not support floats
syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4], syscall.a1, syscall.a2, _ = syscall_syscall15X(cfn, sysargs[0], sysargs[1], sysargs[2], sysargs[3], sysargs[4],
sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11], sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
@@ -322,8 +355,7 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
// We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer // We take the address and then dereference it to trick go vet from creating a possible miss-use of unsafe.Pointer
v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))) v.SetPointer(*(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1)))
case reflect.Ptr: case reflect.Ptr:
// It is safe to have the address of syscall.r1 not escape because it is immediately dereferenced with .Elem() v = reflect.NewAt(outType, unsafe.Pointer(&syscall.a1)).Elem()
v = reflect.NewAt(outType, runtime_noescape(unsafe.Pointer(&syscall.a1))).Elem()
case reflect.Func: case reflect.Func:
// wrap this C function in a nicely typed Go function // wrap this C function in a nicely typed Go function
v = reflect.New(outType) v = reflect.New(outType)
@@ -339,32 +371,85 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
// On 32bit platforms syscall.r2 is the upper part of a 64bit return. // On 32bit platforms syscall.r2 is the upper part of a 64bit return.
v.SetFloat(math.Float64frombits(uint64(syscall.f1))) v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
case reflect.Struct: case reflect.Struct:
v = getStruct(outType, syscall) v = getStruct(outType, *syscall)
default: default:
panic("purego: unsupported return kind: " + outType.Kind().String()) panic("purego: unsupported return kind: " + outType.Kind().String())
} }
return []reflect.Value{v} if len(args) > 0 {
// reuse args slice instead of allocating one when possible
args[0] = v
return args[:1]
} else {
return []reflect.Value{v}
}
}) })
fn.Set(v) fn.Set(v)
} }
func addValue(v reflect.Value, keepAlive []any, addInt func(x uintptr), addFloat func(x uintptr), addStack func(x uintptr), numInts *int, numFloats *int, numStack *int) []any {
switch v.Kind() {
case reflect.String:
ptr := strings.CString(v.String())
keepAlive = append(keepAlive, ptr)
addInt(uintptr(unsafe.Pointer(ptr)))
case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
addInt(uintptr(v.Uint()))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
addInt(uintptr(v.Int()))
case reflect.Ptr, reflect.UnsafePointer, reflect.Slice:
// There is no need to keepAlive this pointer separately because it is kept alive in the args variable
addInt(v.Pointer())
case reflect.Func:
addInt(NewCallback(v.Interface()))
case reflect.Bool:
if v.Bool() {
addInt(1)
} else {
addInt(0)
}
case reflect.Float32:
addFloat(uintptr(math.Float32bits(float32(v.Float()))))
case reflect.Float64:
addFloat(uintptr(math.Float64bits(v.Float())))
case reflect.Struct:
keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive)
default:
panic("purego: unsupported kind: " + v.Kind().String())
}
return keepAlive
}
// maxRegAllocStructSize is the biggest a struct can be while still fitting in registers. // maxRegAllocStructSize is the biggest a struct can be while still fitting in registers.
// if it is bigger than this than enough space must be allocated on the heap and then passed into // if it is bigger than this than enough space must be allocated on the heap and then passed into
// the function as the first parameter on amd64 or in R8 on arm64. // the function as the first parameter on amd64 or in R8 on arm64.
//
// If you change this make sure to update it in objc_runtime_darwin.go
const maxRegAllocStructSize = 16 const maxRegAllocStructSize = 16
func isAllSameFloat(ty reflect.Type) bool { func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) {
first := ty.Field(0).Type.Kind() allFloats = true
root := ty.Field(0).Type
for root.Kind() == reflect.Struct {
root = root.Field(0).Type
}
first := root.Kind()
if first != reflect.Float32 && first != reflect.Float64 { if first != reflect.Float32 && first != reflect.Float64 {
return false allFloats = false
} }
for i := 0; i < ty.NumField(); i++ { for i := 0; i < ty.NumField(); i++ {
f := ty.Field(i) f := ty.Field(i).Type
if f.Type.Kind() != first { if f.Kind() == reflect.Struct {
return false var structNumFields int
allFloats, structNumFields = isAllSameFloat(f)
numFields += structNumFields
continue
}
numFields++
if f.Kind() != first {
allFloats = false
} }
} }
return true return allFloats, numFields
} }
func checkStructFieldsSupported(ty reflect.Type) { func checkStructFieldsSupported(ty reflect.Type) {
@@ -386,24 +471,19 @@ func checkStructFieldsSupported(ty reflect.Type) {
} }
} }
const is32bit = unsafe.Sizeof(uintptr(0)) == 4
func roundUpTo8(val uintptr) uintptr { func roundUpTo8(val uintptr) uintptr {
return (val + 7) &^ 7 return (val + 7) &^ 7
} }
func numOfIntegerRegisters() int { func numOfIntegerRegisters() int {
switch runtime.GOARCH { switch runtime.GOARCH {
case "arm64": case "arm64", "loong64":
return 8 return 8
case "amd64": case "amd64":
return 6 return 6
// TODO: figure out why 386 tests are not working
/*case "386":
return 0
case "arm":
return 4*/
default: default:
panic("purego: unknown GOARCH (" + runtime.GOARCH + ")") // since this platform isn't supported and can therefore only access
// integer registers it is fine to return the maxArgs
return maxArgs
} }
} }
+6
View File
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2025 The Ebitengine Authors
package purego
//go:generate go run wincallback.go
+1 -5
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux || windows //go:build darwin || freebsd || linux || netbsd || windows
package purego package purego
@@ -11,7 +11,3 @@ import (
//go:linkname runtime_cgocall runtime.cgocall //go:linkname runtime_cgocall runtime.cgocall
func runtime_cgocall(fn uintptr, arg unsafe.Pointer) int32 // from runtime/sys_libc.go func runtime_cgocall(fn uintptr, arg unsafe.Pointer) int32 // from runtime/sys_libc.go
//go:linkname runtime_noescape runtime.noescape
//go:noescape
func runtime_noescape(p unsafe.Pointer) unsafe.Pointer // from runtime/stubs.go
+36 -2
View File
@@ -1,17 +1,51 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2024 The Ebitengine Authors // SPDX-FileCopyrightText: 2024 The Ebitengine Authors
//go:build freebsd || linux //go:build freebsd || linux || netbsd
package cgo package cgo
/* /*
#cgo LDFLAGS: -ldl #cgo !netbsd LDFLAGS: -ldl
#include <dlfcn.h> #include <dlfcn.h>
#include <stdlib.h>
*/ */
import "C" import "C"
import (
"errors"
"unsafe"
)
func Dlopen(filename string, flag int) (uintptr, error) {
cfilename := C.CString(filename)
defer C.free(unsafe.Pointer(cfilename))
handle := C.dlopen(cfilename, C.int(flag))
if handle == nil {
return 0, errors.New(C.GoString(C.dlerror()))
}
return uintptr(handle), nil
}
func Dlsym(handle uintptr, symbol string) (uintptr, error) {
csymbol := C.CString(symbol)
defer C.free(unsafe.Pointer(csymbol))
symbolAddr := C.dlsym(*(*unsafe.Pointer)(unsafe.Pointer(&handle)), csymbol)
if symbolAddr == nil {
return 0, errors.New(C.GoString(C.dlerror()))
}
return uintptr(symbolAddr), nil
}
func Dlclose(handle uintptr) error {
result := C.dlclose(*(*unsafe.Pointer)(unsafe.Pointer(&handle)))
if result != 0 {
return errors.New(C.GoString(C.dlerror()))
}
return nil
}
// all that is needed is to assign each dl function because then its // all that is needed is to assign each dl function because then its
// symbol will then be made available to the linker and linked to inside dlfcn.go // symbol will then be made available to the linker and linked to inside dlfcn.go
var ( var (
+6 -6
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build freebsd || (linux && !(arm64 || amd64)) //go:build freebsd || (linux && !(arm64 || amd64 || loong64)) || netbsd
package cgo package cgo
@@ -9,7 +9,7 @@ package cgo
// because Cgo and assembly files can't be in the same package. // because Cgo and assembly files can't be in the same package.
/* /*
#cgo LDFLAGS: -ldl #cgo !netbsd LDFLAGS: -ldl
#include <stdint.h> #include <stdint.h>
#include <dlfcn.h> #include <dlfcn.h>
@@ -20,7 +20,7 @@ typedef struct syscall15Args {
uintptr_t fn; uintptr_t fn;
uintptr_t a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15; uintptr_t a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15;
uintptr_t f1, f2, f3, f4, f5, f6, f7, f8; uintptr_t f1, f2, f3, f4, f5, f6, f7, f8;
uintptr_t r1, r2, err; uintptr_t err;
} syscall15Args; } syscall15Args;
void syscall15(struct syscall15Args *args) { void syscall15(struct syscall15Args *args) {
@@ -31,7 +31,7 @@ void syscall15(struct syscall15Args *args) {
*(void**)(&func_name) = (void*)(args->fn); *(void**)(&func_name) = (void*)(args->fn);
uintptr_t r1 = func_name(args->a1,args->a2,args->a3,args->a4,args->a5,args->a6,args->a7,args->a8,args->a9, uintptr_t r1 = func_name(args->a1,args->a2,args->a3,args->a4,args->a5,args->a6,args->a7,args->a8,args->a9,
args->a10,args->a11,args->a12,args->a13,args->a14,args->a15); args->a10,args->a11,args->a12,args->a13,args->a14,args->a15);
args->r1 = r1; args->a1 = r1;
args->err = errno; args->err = errno;
} }
@@ -48,8 +48,8 @@ func Syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14,
C.uintptr_t(fn), C.uintptr_t(a1), C.uintptr_t(a2), C.uintptr_t(a3), C.uintptr_t(fn), C.uintptr_t(a1), C.uintptr_t(a2), C.uintptr_t(a3),
C.uintptr_t(a4), C.uintptr_t(a5), C.uintptr_t(a6), C.uintptr_t(a4), C.uintptr_t(a5), C.uintptr_t(a6),
C.uintptr_t(a7), C.uintptr_t(a8), C.uintptr_t(a9), C.uintptr_t(a10), C.uintptr_t(a11), C.uintptr_t(a12), C.uintptr_t(a7), C.uintptr_t(a8), C.uintptr_t(a9), C.uintptr_t(a10), C.uintptr_t(a11), C.uintptr_t(a12),
C.uintptr_t(a13), C.uintptr_t(a14), C.uintptr_t(a15), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, C.uintptr_t(a13), C.uintptr_t(a14), C.uintptr_t(a15), 0, 0, 0, 0, 0, 0, 0, 0, 0,
} }
C.syscall15(&args) C.syscall15(&args)
return uintptr(args.r1), uintptr(args.r2), uintptr(args.err) return uintptr(args.a1), 0, uintptr(args.err)
} }
+60
View File
@@ -0,0 +1,60 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Macros for transitioning from the host ABI to Go ABI0.
//
// These macros save and restore the callee-saved registers
// from the stack, but they don't adjust stack pointer, so
// the user should prepare stack space in advance.
// SAVE_R22_TO_R31(offset) saves R22 ~ R31 to the stack space
// of ((offset)+0*8)(R3) ~ ((offset)+9*8)(R3).
//
// SAVE_F24_TO_F31(offset) saves F24 ~ F31 to the stack space
// of ((offset)+0*8)(R3) ~ ((offset)+7*8)(R3).
//
// Note: g is R22
#define SAVE_R22_TO_R31(offset) \
MOVV g, ((offset)+(0*8))(R3) \
MOVV R23, ((offset)+(1*8))(R3) \
MOVV R24, ((offset)+(2*8))(R3) \
MOVV R25, ((offset)+(3*8))(R3) \
MOVV R26, ((offset)+(4*8))(R3) \
MOVV R27, ((offset)+(5*8))(R3) \
MOVV R28, ((offset)+(6*8))(R3) \
MOVV R29, ((offset)+(7*8))(R3) \
MOVV R30, ((offset)+(8*8))(R3) \
MOVV R31, ((offset)+(9*8))(R3)
#define SAVE_F24_TO_F31(offset) \
MOVD F24, ((offset)+(0*8))(R3) \
MOVD F25, ((offset)+(1*8))(R3) \
MOVD F26, ((offset)+(2*8))(R3) \
MOVD F27, ((offset)+(3*8))(R3) \
MOVD F28, ((offset)+(4*8))(R3) \
MOVD F29, ((offset)+(5*8))(R3) \
MOVD F30, ((offset)+(6*8))(R3) \
MOVD F31, ((offset)+(7*8))(R3)
#define RESTORE_R22_TO_R31(offset) \
MOVV ((offset)+(0*8))(R3), g \
MOVV ((offset)+(1*8))(R3), R23 \
MOVV ((offset)+(2*8))(R3), R24 \
MOVV ((offset)+(3*8))(R3), R25 \
MOVV ((offset)+(4*8))(R3), R26 \
MOVV ((offset)+(5*8))(R3), R27 \
MOVV ((offset)+(6*8))(R3), R28 \
MOVV ((offset)+(7*8))(R3), R29 \
MOVV ((offset)+(8*8))(R3), R30 \
MOVV ((offset)+(9*8))(R3), R31
#define RESTORE_F24_TO_F31(offset) \
MOVD ((offset)+(0*8))(R3), F24 \
MOVD ((offset)+(1*8))(R3), F25 \
MOVD ((offset)+(2*8))(R3), F26 \
MOVD ((offset)+(3*8))(R3), F27 \
MOVD ((offset)+(4*8))(R3), F28 \
MOVD ((offset)+(5*8))(R3), F29 \
MOVD ((offset)+(6*8))(R3), F30 \
MOVD ((offset)+(7*8))(R3), F31
+40
View File
@@ -0,0 +1,40 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "textflag.h"
#include "abi_loong64.h"
// Called by C code generated by cmd/cgo.
// func crosscall2(fn, a unsafe.Pointer, n int32, ctxt uintptr)
// Saves C callee-saved registers and calls cgocallback with three arguments.
// fn is the PC of a func(a unsafe.Pointer) function.
TEXT crosscall2(SB),NOSPLIT|NOFRAME,$0
/*
* We still need to save all callee save register as before, and then
* push 3 args for fn (R4, R5, R7), skipping R6.
* Also note that at procedure entry in gc world, 8(R29) will be the
* first arg.
*/
ADDV $(-23*8), R3
MOVV R4, (1*8)(R3) // fn unsafe.Pointer
MOVV R5, (2*8)(R3) // a unsafe.Pointer
MOVV R7, (3*8)(R3) // ctxt uintptr
SAVE_R22_TO_R31((4*8))
SAVE_F24_TO_F31((14*8))
MOVV R1, (22*8)(R3)
// Initialize Go ABI environment
JAL runtime·load_g(SB)
JAL runtime·cgocallback(SB)
RESTORE_R22_TO_R31((4*8))
RESTORE_F24_TO_F31((14*8))
MOVV (22*8)(R3), R1
ADDV $(23*8), R3
RET
+1 -1
View File
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build darwin || freebsd || linux //go:build !cgo && (darwin || freebsd || linux || netbsd)
package fakecgo package fakecgo
+1 -2
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd //go:build !cgo && (darwin || freebsd || linux || netbsd)
// Package fakecgo implements the Cgo runtime (runtime/cgo) entirely in Go. // Package fakecgo implements the Cgo runtime (runtime/cgo) entirely in Go.
// This allows code that calls into C to function properly when CGO_ENABLED=0. // This allows code that calls into C to function properly when CGO_ENABLED=0.
@@ -30,4 +30,3 @@
package fakecgo package fakecgo
//go:generate go run gen.go //go:generate go run gen.go
//go:generate gofmt -s -w symbols.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !cgo
package fakecgo package fakecgo
import "unsafe" import "unsafe"
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !cgo
package fakecgo package fakecgo
import "unsafe" import "unsafe"
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !cgo
package fakecgo package fakecgo
import "unsafe" import "unsafe"
+3 -1
View File
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !cgo
package fakecgo package fakecgo
import "unsafe" import "unsafe"
@@ -14,7 +16,7 @@ func _cgo_sys_thread_start(ts *ThreadStart) {
var size size_t var size size_t
var err int var err int
//fprintf(stderr, "runtime/cgo: _cgo_sys_thread_start: fn=%p, g=%p\n", ts->fn, ts->g); // debug // fprintf(stderr, "runtime/cgo: _cgo_sys_thread_start: fn=%p, g=%p\n", ts->fn, ts->g); // debug
sigfillset(&ign) sigfillset(&ign)
pthread_sigmask(SIG_SETMASK, &ign, &oset) pthread_sigmask(SIG_SETMASK, &ign, &oset)
+10 -4
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux //go:build !cgo && (darwin || freebsd || linux || netbsd)
package fakecgo package fakecgo
@@ -19,6 +19,7 @@ var (
) )
//go:nosplit //go:nosplit
//go:norace
func x_cgo_notify_runtime_init_done() { func x_cgo_notify_runtime_init_done() {
pthread_mutex_lock(&runtime_init_mu) pthread_mutex_lock(&runtime_init_mu)
runtime_init_done = 1 runtime_init_done = 1
@@ -28,6 +29,8 @@ func x_cgo_notify_runtime_init_done() {
// Store the g into a thread-specific value associated with the pthread key pthread_g. // Store the g into a thread-specific value associated with the pthread key pthread_g.
// And pthread_key_destructor will dropm when the thread is exiting. // And pthread_key_destructor will dropm when the thread is exiting.
//
//go:norace
func x_cgo_bindm(g unsafe.Pointer) { func x_cgo_bindm(g unsafe.Pointer) {
// We assume this will always succeed, otherwise, there might be extra M leaking, // We assume this will always succeed, otherwise, there might be extra M leaking,
// when a C thread exits after a cgo call. // when a C thread exits after a cgo call.
@@ -50,9 +53,11 @@ func _cgo_try_pthread_create(thread *pthread_t, attr *pthread_attr_t, pfn unsafe
var err int var err int
for tries = 0; tries < 20; tries++ { for tries = 0; tries < 20; tries++ {
err = int(pthread_create(thread, attr, pfn, unsafe.Pointer(arg))) // inlined this call because it ran out of stack when inlining was disabled
err = int(call5(pthread_createABI0, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(pfn), uintptr(unsafe.Pointer(arg)), 0))
if err == 0 { if err == 0 {
pthread_detach(*thread) // inlined this call because it ran out of stack when inlining was disabled
call5(pthread_detachABI0, uintptr(*thread), 0, 0, 0, 0)
return 0 return 0
} }
if err != int(syscall.EAGAIN) { if err != int(syscall.EAGAIN) {
@@ -60,7 +65,8 @@ func _cgo_try_pthread_create(thread *pthread_t, attr *pthread_attr_t, pfn unsafe
} }
ts.Sec = 0 ts.Sec = 0
ts.Nsec = (tries + 1) * 1000 * 1000 // Milliseconds. ts.Nsec = (tries + 1) * 1000 * 1000 // Milliseconds.
nanosleep(&ts, nil) // inlined this call because it ran out of stack when inlining was disabled
call5(nanosleepABI0, uintptr(unsafe.Pointer(&ts)), 0, 0, 0, 0)
} }
return int(syscall.EAGAIN) return int(syscall.EAGAIN)
} }
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !cgo
package fakecgo package fakecgo
import "unsafe" import "unsafe"
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !cgo
package fakecgo package fakecgo
import "unsafe" import "unsafe"
@@ -0,0 +1,92 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !cgo
package fakecgo
import "unsafe"
//go:nosplit
func _cgo_sys_thread_start(ts *ThreadStart) {
var attr pthread_attr_t
var ign, oset sigset_t
var p pthread_t
var size size_t
var err int
sigfillset(&ign)
pthread_sigmask(SIG_SETMASK, &ign, &oset)
pthread_attr_init(&attr)
pthread_attr_getstacksize(&attr, &size)
// Leave stacklo=0 and set stackhi=size; mstart will do the rest.
ts.g.stackhi = uintptr(size)
err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts)
pthread_sigmask(SIG_SETMASK, &oset, nil)
if err != 0 {
print("fakecgo: pthread_create failed: ")
println(err)
abort()
}
}
// threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function
//
//go:linkname x_threadentry_trampoline threadentry_trampoline
var x_threadentry_trampoline byte
var threadentry_trampolineABI0 = &x_threadentry_trampoline
//go:nosplit
func threadentry(v unsafe.Pointer) unsafe.Pointer {
ts := *(*ThreadStart)(v)
free(v)
setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g)))
// faking funcs in go is a bit a... involved - but the following works :)
fn := uintptr(unsafe.Pointer(&ts.fn))
(*(*func())(unsafe.Pointer(&fn)))()
return nil
}
// here we will store a pointer to the provided setg func
var setg_func uintptr
//go:nosplit
func x_cgo_init(g *G, setg uintptr) {
var size size_t
var attr *pthread_attr_t
/* The memory sanitizer distributed with versions of clang
before 3.8 has a bug: if you call mmap before malloc, mmap
may return an address that is later overwritten by the msan
library. Avoid this problem by forcing a call to malloc
here, before we ever call malloc.
This is only required for the memory sanitizer, so it's
unfortunate that we always run it. It should be possible
to remove this when we no longer care about versions of
clang before 3.8. The test for this is
misc/cgo/testsanitizers.
GCC works hard to eliminate a seemingly unnecessary call to
malloc, so we actually use the memory we allocate. */
setg_func = setg
attr = (*pthread_attr_t)(malloc(unsafe.Sizeof(*attr)))
if attr == nil {
println("fakecgo: malloc failed")
abort()
}
pthread_attr_init(attr)
pthread_attr_getstacksize(attr, &size)
g.stacklo = uintptr(unsafe.Pointer(&size)) - uintptr(size) + 4096
pthread_attr_destroy(attr)
free(unsafe.Pointer(attr))
}
+106
View File
@@ -0,0 +1,106 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !cgo && (amd64 || arm64)
package fakecgo
import "unsafe"
//go:nosplit
func _cgo_sys_thread_start(ts *ThreadStart) {
var attr pthread_attr_t
var ign, oset sigset_t
var p pthread_t
var size size_t
var err int
// fprintf(stderr, "runtime/cgo: _cgo_sys_thread_start: fn=%p, g=%p\n", ts->fn, ts->g); // debug
sigfillset(&ign)
pthread_sigmask(SIG_SETMASK, &ign, &oset)
pthread_attr_init(&attr)
pthread_attr_getstacksize(&attr, &size)
// Leave stacklo=0 and set stackhi=size; mstart will do the rest.
ts.g.stackhi = uintptr(size)
err = _cgo_try_pthread_create(&p, &attr, unsafe.Pointer(threadentry_trampolineABI0), ts)
pthread_sigmask(SIG_SETMASK, &oset, nil)
if err != 0 {
print("fakecgo: pthread_create failed: ")
println(err)
abort()
}
}
// threadentry_trampolineABI0 maps the C ABI to Go ABI then calls the Go function
//
//go:linkname x_threadentry_trampoline threadentry_trampoline
var x_threadentry_trampoline byte
var threadentry_trampolineABI0 = &x_threadentry_trampoline
//go:nosplit
func threadentry(v unsafe.Pointer) unsafe.Pointer {
var ss stack_t
ts := *(*ThreadStart)(v)
free(v)
// On NetBSD, a new thread inherits the signal stack of the
// creating thread. That confuses minit, so we remove that
// signal stack here before calling the regular mstart. It's
// a bit baroque to remove a signal stack here only to add one
// in minit, but it's a simple change that keeps NetBSD
// working like other OS's. At this point all signals are
// blocked, so there is no race.
ss.ss_flags = SS_DISABLE
sigaltstack(&ss, nil)
setg_trampoline(setg_func, uintptr(unsafe.Pointer(ts.g)))
// faking funcs in go is a bit a... involved - but the following works :)
fn := uintptr(unsafe.Pointer(&ts.fn))
(*(*func())(unsafe.Pointer(&fn)))()
return nil
}
// here we will store a pointer to the provided setg func
var setg_func uintptr
//go:nosplit
func x_cgo_init(g *G, setg uintptr) {
var size size_t
var attr *pthread_attr_t
/* The memory sanitizer distributed with versions of clang
before 3.8 has a bug: if you call mmap before malloc, mmap
may return an address that is later overwritten by the msan
library. Avoid this problem by forcing a call to malloc
here, before we ever call malloc.
This is only required for the memory sanitizer, so it's
unfortunate that we always run it. It should be possible
to remove this when we no longer care about versions of
clang before 3.8. The test for this is
misc/cgo/testsanitizers.
GCC works hard to eliminate a seemingly unnecessary call to
malloc, so we actually use the memory we allocate. */
setg_func = setg
attr = (*pthread_attr_t)(malloc(unsafe.Sizeof(*attr)))
if attr == nil {
println("fakecgo: malloc failed")
abort()
}
pthread_attr_init(attr)
pthread_attr_getstacksize(attr, &size)
// runtime/cgo uses __builtin_frame_address(0) instead of `uintptr(unsafe.Pointer(&size))`
// but this should be OK since we are taking the address of the first variable in this function.
g.stacklo = uintptr(unsafe.Pointer(&size)) - uintptr(size) + 4096
pthread_attr_destroy(attr)
free(unsafe.Pointer(attr))
}
+1 -1
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux //go:build !cgo && (darwin || freebsd || linux || netbsd)
package fakecgo package fakecgo
+7 -3
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux //go:build !cgo && (darwin || freebsd || linux || netbsd)
package fakecgo package fakecgo
@@ -27,7 +27,11 @@ func x_cgo_thread_start(arg *ThreadStart) {
println("fakecgo: out of memory in thread_start") println("fakecgo: out of memory in thread_start")
abort() abort()
} }
// *ts = *arg would cause a writebarrier so use memmove instead // *ts = *arg would cause a writebarrier so copy using slices
memmove(unsafe.Pointer(ts), unsafe.Pointer(arg), unsafe.Sizeof(*ts)) s1 := unsafe.Slice((*uintptr)(unsafe.Pointer(ts)), unsafe.Sizeof(*ts)/8)
s2 := unsafe.Slice((*uintptr)(unsafe.Pointer(arg)), unsafe.Sizeof(*arg)/8)
for i := range s2 {
s1[i] = s2[i]
}
_cgo_sys_thread_start(ts) // OS-dependent half _cgo_sys_thread_start(ts) // OS-dependent half
} }
+1 -1
View File
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build darwin || freebsd || linux //go:build !cgo && (darwin || freebsd || linux || netbsd)
// The runtime package contains an uninitialized definition // The runtime package contains an uninitialized definition
// for runtime·iscgo. Override it to tell the runtime we're here. // for runtime·iscgo. Override it to tell the runtime we're here.
+6 -2
View File
@@ -1,12 +1,16 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux //go:build !cgo && (darwin || freebsd || linux || netbsd)
package fakecgo package fakecgo
type ( type (
size_t uintptr size_t uintptr
// Sources:
// Darwin (32 bytes) - https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/_types.h#L74
// FreeBSD (32 bytes) - https://github.com/DoctorWkt/xv6-freebsd/blob/d2a294c2a984baed27676068b15ed9a29b06ab6f/include/signal.h#L98C9-L98C21
// Linux (128 bytes) - https://github.com/torvalds/linux/blob/ab75170520d4964f3acf8bb1f91d34cbc650688e/arch/x86/include/asm/signal.h#L25
sigset_t [128]byte sigset_t [128]byte
pthread_attr_t [64]byte pthread_attr_t [64]byte
pthread_t int pthread_t int
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build !cgo
package fakecgo package fakecgo
type ( type (
@@ -18,3 +20,7 @@ var (
PTHREAD_COND_INITIALIZER = pthread_cond_t{sig: 0x3CB0B1BB} PTHREAD_COND_INITIALIZER = pthread_cond_t{sig: 0x3CB0B1BB}
PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t{sig: 0x32AAABA7} PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t{sig: 0x32AAABA7}
) )
type stack_t struct {
/* not implemented */
}
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build !cgo
package fakecgo package fakecgo
type ( type (
@@ -12,3 +14,7 @@ var (
PTHREAD_COND_INITIALIZER = pthread_cond_t(0) PTHREAD_COND_INITIALIZER = pthread_cond_t(0)
PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t(0) PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t(0)
) )
type stack_t struct {
/* not implemented */
}
+6
View File
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build !cgo
package fakecgo package fakecgo
type ( type (
@@ -12,3 +14,7 @@ var (
PTHREAD_COND_INITIALIZER = pthread_cond_t{} PTHREAD_COND_INITIALIZER = pthread_cond_t{}
PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t{} PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t{}
) )
type stack_t struct {
/* not implemented */
}
+26
View File
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2025 The Ebitengine Authors
//go:build !cgo
package fakecgo
type (
pthread_cond_t uintptr
pthread_mutex_t uintptr
)
var (
PTHREAD_COND_INITIALIZER = pthread_cond_t(0)
PTHREAD_MUTEX_INITIALIZER = pthread_mutex_t(0)
)
// Source: https://github.com/NetBSD/src/blob/613e27c65223fd2283b6ed679da1197e12f50e27/sys/compat/linux/arch/m68k/linux_signal.h#L133
type stack_t struct {
ss_sp uintptr
ss_flags int32
ss_size uintptr
}
// Source: https://github.com/NetBSD/src/blob/613e27c65223fd2283b6ed679da1197e12f50e27/sys/sys/signal.h#L261
const SS_DISABLE = 0x004
+23
View File
@@ -0,0 +1,23 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build netbsd
package fakecgo
import _ "unsafe" // for go:linkname
// Supply environ and __progname, because we don't
// link against the standard NetBSD crt0.o and the
// libc dynamic library needs them.
//go:linkname _environ environ
//go:linkname _progname __progname
//go:linkname ___ps_strings __ps_strings
var (
_environ uintptr
_progname uintptr
___ps_strings uintptr
)
+1 -1
View File
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build darwin || freebsd || linux //go:build !cgo && (darwin || freebsd || linux || netbsd)
package fakecgo package fakecgo
+71 -24
View File
@@ -3,7 +3,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux //go:build !cgo && (darwin || freebsd || linux || netbsd)
package fakecgo package fakecgo
@@ -15,170 +15,217 @@ import (
// setg_trampoline calls setg with the G provided // setg_trampoline calls setg with the G provided
func setg_trampoline(setg uintptr, G uintptr) func setg_trampoline(setg uintptr, G uintptr)
//go:linkname memmove runtime.memmove
func memmove(to, from unsafe.Pointer, n uintptr)
// call5 takes fn the C function and 5 arguments and calls the function with those arguments // call5 takes fn the C function and 5 arguments and calls the function with those arguments
func call5(fn, a1, a2, a3, a4, a5 uintptr) uintptr func call5(fn, a1, a2, a3, a4, a5 uintptr) uintptr
//go:nosplit
//go:norace
func malloc(size uintptr) unsafe.Pointer { func malloc(size uintptr) unsafe.Pointer {
ret := call5(mallocABI0, uintptr(size), 0, 0, 0, 0) ret := call5(mallocABI0, uintptr(size), 0, 0, 0, 0)
// this indirection is to avoid go vet complaining about possible misuse of unsafe.Pointer // this indirection is to avoid go vet complaining about possible misuse of unsafe.Pointer
return *(*unsafe.Pointer)(unsafe.Pointer(&ret)) return *(*unsafe.Pointer)(unsafe.Pointer(&ret))
} }
//go:nosplit
//go:norace
func free(ptr unsafe.Pointer) { func free(ptr unsafe.Pointer) {
call5(freeABI0, uintptr(ptr), 0, 0, 0, 0) call5(freeABI0, uintptr(ptr), 0, 0, 0, 0)
} }
//go:nosplit
//go:norace
func setenv(name *byte, value *byte, overwrite int32) int32 { func setenv(name *byte, value *byte, overwrite int32) int32 {
return int32(call5(setenvABI0, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(value)), uintptr(overwrite), 0, 0)) return int32(call5(setenvABI0, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(value)), uintptr(overwrite), 0, 0))
} }
//go:nosplit
//go:norace
func unsetenv(name *byte) int32 { func unsetenv(name *byte) int32 {
return int32(call5(unsetenvABI0, uintptr(unsafe.Pointer(name)), 0, 0, 0, 0)) return int32(call5(unsetenvABI0, uintptr(unsafe.Pointer(name)), 0, 0, 0, 0))
} }
//go:nosplit
//go:norace
func sigfillset(set *sigset_t) int32 { func sigfillset(set *sigset_t) int32 {
return int32(call5(sigfillsetABI0, uintptr(unsafe.Pointer(set)), 0, 0, 0, 0)) return int32(call5(sigfillsetABI0, uintptr(unsafe.Pointer(set)), 0, 0, 0, 0))
} }
//go:nosplit
//go:norace
func nanosleep(ts *syscall.Timespec, rem *syscall.Timespec) int32 { func nanosleep(ts *syscall.Timespec, rem *syscall.Timespec) int32 {
return int32(call5(nanosleepABI0, uintptr(unsafe.Pointer(ts)), uintptr(unsafe.Pointer(rem)), 0, 0, 0)) return int32(call5(nanosleepABI0, uintptr(unsafe.Pointer(ts)), uintptr(unsafe.Pointer(rem)), 0, 0, 0))
} }
//go:nosplit
//go:norace
func abort() { func abort() {
call5(abortABI0, 0, 0, 0, 0, 0) call5(abortABI0, 0, 0, 0, 0, 0)
} }
//go:nosplit
//go:norace
func sigaltstack(ss *stack_t, old_ss *stack_t) int32 {
return int32(call5(sigaltstackABI0, uintptr(unsafe.Pointer(ss)), uintptr(unsafe.Pointer(old_ss)), 0, 0, 0))
}
//go:nosplit
//go:norace
func pthread_attr_init(attr *pthread_attr_t) int32 { func pthread_attr_init(attr *pthread_attr_t) int32 {
return int32(call5(pthread_attr_initABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0)) return int32(call5(pthread_attr_initABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0))
} }
//go:nosplit
//go:norace
func pthread_create(thread *pthread_t, attr *pthread_attr_t, start unsafe.Pointer, arg unsafe.Pointer) int32 { func pthread_create(thread *pthread_t, attr *pthread_attr_t, start unsafe.Pointer, arg unsafe.Pointer) int32 {
return int32(call5(pthread_createABI0, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(start), uintptr(arg), 0)) return int32(call5(pthread_createABI0, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(start), uintptr(arg), 0))
} }
//go:nosplit
//go:norace
func pthread_detach(thread pthread_t) int32 { func pthread_detach(thread pthread_t) int32 {
return int32(call5(pthread_detachABI0, uintptr(thread), 0, 0, 0, 0)) return int32(call5(pthread_detachABI0, uintptr(thread), 0, 0, 0, 0))
} }
//go:nosplit
//go:norace
func pthread_sigmask(how sighow, ign *sigset_t, oset *sigset_t) int32 { func pthread_sigmask(how sighow, ign *sigset_t, oset *sigset_t) int32 {
return int32(call5(pthread_sigmaskABI0, uintptr(how), uintptr(unsafe.Pointer(ign)), uintptr(unsafe.Pointer(oset)), 0, 0)) return int32(call5(pthread_sigmaskABI0, uintptr(how), uintptr(unsafe.Pointer(ign)), uintptr(unsafe.Pointer(oset)), 0, 0))
} }
//go:nosplit
//go:norace
func pthread_self() pthread_t { func pthread_self() pthread_t {
return pthread_t(call5(pthread_selfABI0, 0, 0, 0, 0, 0)) return pthread_t(call5(pthread_selfABI0, 0, 0, 0, 0, 0))
} }
//go:nosplit
//go:norace
func pthread_get_stacksize_np(thread pthread_t) size_t { func pthread_get_stacksize_np(thread pthread_t) size_t {
return size_t(call5(pthread_get_stacksize_npABI0, uintptr(thread), 0, 0, 0, 0)) return size_t(call5(pthread_get_stacksize_npABI0, uintptr(thread), 0, 0, 0, 0))
} }
//go:nosplit
//go:norace
func pthread_attr_getstacksize(attr *pthread_attr_t, stacksize *size_t) int32 { func pthread_attr_getstacksize(attr *pthread_attr_t, stacksize *size_t) int32 {
return int32(call5(pthread_attr_getstacksizeABI0, uintptr(unsafe.Pointer(attr)), uintptr(unsafe.Pointer(stacksize)), 0, 0, 0)) return int32(call5(pthread_attr_getstacksizeABI0, uintptr(unsafe.Pointer(attr)), uintptr(unsafe.Pointer(stacksize)), 0, 0, 0))
} }
//go:nosplit
//go:norace
func pthread_attr_setstacksize(attr *pthread_attr_t, size size_t) int32 { func pthread_attr_setstacksize(attr *pthread_attr_t, size size_t) int32 {
return int32(call5(pthread_attr_setstacksizeABI0, uintptr(unsafe.Pointer(attr)), uintptr(size), 0, 0, 0)) return int32(call5(pthread_attr_setstacksizeABI0, uintptr(unsafe.Pointer(attr)), uintptr(size), 0, 0, 0))
} }
//go:nosplit
//go:norace
func pthread_attr_destroy(attr *pthread_attr_t) int32 { func pthread_attr_destroy(attr *pthread_attr_t) int32 {
return int32(call5(pthread_attr_destroyABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0)) return int32(call5(pthread_attr_destroyABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0))
} }
//go:nosplit
//go:norace
func pthread_mutex_lock(mutex *pthread_mutex_t) int32 { func pthread_mutex_lock(mutex *pthread_mutex_t) int32 {
return int32(call5(pthread_mutex_lockABI0, uintptr(unsafe.Pointer(mutex)), 0, 0, 0, 0)) return int32(call5(pthread_mutex_lockABI0, uintptr(unsafe.Pointer(mutex)), 0, 0, 0, 0))
} }
//go:nosplit
//go:norace
func pthread_mutex_unlock(mutex *pthread_mutex_t) int32 { func pthread_mutex_unlock(mutex *pthread_mutex_t) int32 {
return int32(call5(pthread_mutex_unlockABI0, uintptr(unsafe.Pointer(mutex)), 0, 0, 0, 0)) return int32(call5(pthread_mutex_unlockABI0, uintptr(unsafe.Pointer(mutex)), 0, 0, 0, 0))
} }
//go:nosplit
//go:norace
func pthread_cond_broadcast(cond *pthread_cond_t) int32 { func pthread_cond_broadcast(cond *pthread_cond_t) int32 {
return int32(call5(pthread_cond_broadcastABI0, uintptr(unsafe.Pointer(cond)), 0, 0, 0, 0)) return int32(call5(pthread_cond_broadcastABI0, uintptr(unsafe.Pointer(cond)), 0, 0, 0, 0))
} }
//go:nosplit
//go:norace
func pthread_setspecific(key pthread_key_t, value unsafe.Pointer) int32 { func pthread_setspecific(key pthread_key_t, value unsafe.Pointer) int32 {
return int32(call5(pthread_setspecificABI0, uintptr(key), uintptr(value), 0, 0, 0)) return int32(call5(pthread_setspecificABI0, uintptr(key), uintptr(value), 0, 0, 0))
} }
//go:linkname _malloc _malloc //go:linkname _malloc _malloc
var _malloc uintptr var _malloc uint8
var mallocABI0 = uintptr(unsafe.Pointer(&_malloc)) var mallocABI0 = uintptr(unsafe.Pointer(&_malloc))
//go:linkname _free _free //go:linkname _free _free
var _free uintptr var _free uint8
var freeABI0 = uintptr(unsafe.Pointer(&_free)) var freeABI0 = uintptr(unsafe.Pointer(&_free))
//go:linkname _setenv _setenv //go:linkname _setenv _setenv
var _setenv uintptr var _setenv uint8
var setenvABI0 = uintptr(unsafe.Pointer(&_setenv)) var setenvABI0 = uintptr(unsafe.Pointer(&_setenv))
//go:linkname _unsetenv _unsetenv //go:linkname _unsetenv _unsetenv
var _unsetenv uintptr var _unsetenv uint8
var unsetenvABI0 = uintptr(unsafe.Pointer(&_unsetenv)) var unsetenvABI0 = uintptr(unsafe.Pointer(&_unsetenv))
//go:linkname _sigfillset _sigfillset //go:linkname _sigfillset _sigfillset
var _sigfillset uintptr var _sigfillset uint8
var sigfillsetABI0 = uintptr(unsafe.Pointer(&_sigfillset)) var sigfillsetABI0 = uintptr(unsafe.Pointer(&_sigfillset))
//go:linkname _nanosleep _nanosleep //go:linkname _nanosleep _nanosleep
var _nanosleep uintptr var _nanosleep uint8
var nanosleepABI0 = uintptr(unsafe.Pointer(&_nanosleep)) var nanosleepABI0 = uintptr(unsafe.Pointer(&_nanosleep))
//go:linkname _abort _abort //go:linkname _abort _abort
var _abort uintptr var _abort uint8
var abortABI0 = uintptr(unsafe.Pointer(&_abort)) var abortABI0 = uintptr(unsafe.Pointer(&_abort))
//go:linkname _sigaltstack _sigaltstack
var _sigaltstack uint8
var sigaltstackABI0 = uintptr(unsafe.Pointer(&_sigaltstack))
//go:linkname _pthread_attr_init _pthread_attr_init //go:linkname _pthread_attr_init _pthread_attr_init
var _pthread_attr_init uintptr var _pthread_attr_init uint8
var pthread_attr_initABI0 = uintptr(unsafe.Pointer(&_pthread_attr_init)) var pthread_attr_initABI0 = uintptr(unsafe.Pointer(&_pthread_attr_init))
//go:linkname _pthread_create _pthread_create //go:linkname _pthread_create _pthread_create
var _pthread_create uintptr var _pthread_create uint8
var pthread_createABI0 = uintptr(unsafe.Pointer(&_pthread_create)) var pthread_createABI0 = uintptr(unsafe.Pointer(&_pthread_create))
//go:linkname _pthread_detach _pthread_detach //go:linkname _pthread_detach _pthread_detach
var _pthread_detach uintptr var _pthread_detach uint8
var pthread_detachABI0 = uintptr(unsafe.Pointer(&_pthread_detach)) var pthread_detachABI0 = uintptr(unsafe.Pointer(&_pthread_detach))
//go:linkname _pthread_sigmask _pthread_sigmask //go:linkname _pthread_sigmask _pthread_sigmask
var _pthread_sigmask uintptr var _pthread_sigmask uint8
var pthread_sigmaskABI0 = uintptr(unsafe.Pointer(&_pthread_sigmask)) var pthread_sigmaskABI0 = uintptr(unsafe.Pointer(&_pthread_sigmask))
//go:linkname _pthread_self _pthread_self //go:linkname _pthread_self _pthread_self
var _pthread_self uintptr var _pthread_self uint8
var pthread_selfABI0 = uintptr(unsafe.Pointer(&_pthread_self)) var pthread_selfABI0 = uintptr(unsafe.Pointer(&_pthread_self))
//go:linkname _pthread_get_stacksize_np _pthread_get_stacksize_np //go:linkname _pthread_get_stacksize_np _pthread_get_stacksize_np
var _pthread_get_stacksize_np uintptr var _pthread_get_stacksize_np uint8
var pthread_get_stacksize_npABI0 = uintptr(unsafe.Pointer(&_pthread_get_stacksize_np)) var pthread_get_stacksize_npABI0 = uintptr(unsafe.Pointer(&_pthread_get_stacksize_np))
//go:linkname _pthread_attr_getstacksize _pthread_attr_getstacksize //go:linkname _pthread_attr_getstacksize _pthread_attr_getstacksize
var _pthread_attr_getstacksize uintptr var _pthread_attr_getstacksize uint8
var pthread_attr_getstacksizeABI0 = uintptr(unsafe.Pointer(&_pthread_attr_getstacksize)) var pthread_attr_getstacksizeABI0 = uintptr(unsafe.Pointer(&_pthread_attr_getstacksize))
//go:linkname _pthread_attr_setstacksize _pthread_attr_setstacksize //go:linkname _pthread_attr_setstacksize _pthread_attr_setstacksize
var _pthread_attr_setstacksize uintptr var _pthread_attr_setstacksize uint8
var pthread_attr_setstacksizeABI0 = uintptr(unsafe.Pointer(&_pthread_attr_setstacksize)) var pthread_attr_setstacksizeABI0 = uintptr(unsafe.Pointer(&_pthread_attr_setstacksize))
//go:linkname _pthread_attr_destroy _pthread_attr_destroy //go:linkname _pthread_attr_destroy _pthread_attr_destroy
var _pthread_attr_destroy uintptr var _pthread_attr_destroy uint8
var pthread_attr_destroyABI0 = uintptr(unsafe.Pointer(&_pthread_attr_destroy)) var pthread_attr_destroyABI0 = uintptr(unsafe.Pointer(&_pthread_attr_destroy))
//go:linkname _pthread_mutex_lock _pthread_mutex_lock //go:linkname _pthread_mutex_lock _pthread_mutex_lock
var _pthread_mutex_lock uintptr var _pthread_mutex_lock uint8
var pthread_mutex_lockABI0 = uintptr(unsafe.Pointer(&_pthread_mutex_lock)) var pthread_mutex_lockABI0 = uintptr(unsafe.Pointer(&_pthread_mutex_lock))
//go:linkname _pthread_mutex_unlock _pthread_mutex_unlock //go:linkname _pthread_mutex_unlock _pthread_mutex_unlock
var _pthread_mutex_unlock uintptr var _pthread_mutex_unlock uint8
var pthread_mutex_unlockABI0 = uintptr(unsafe.Pointer(&_pthread_mutex_unlock)) var pthread_mutex_unlockABI0 = uintptr(unsafe.Pointer(&_pthread_mutex_unlock))
//go:linkname _pthread_cond_broadcast _pthread_cond_broadcast //go:linkname _pthread_cond_broadcast _pthread_cond_broadcast
var _pthread_cond_broadcast uintptr var _pthread_cond_broadcast uint8
var pthread_cond_broadcastABI0 = uintptr(unsafe.Pointer(&_pthread_cond_broadcast)) var pthread_cond_broadcastABI0 = uintptr(unsafe.Pointer(&_pthread_cond_broadcast))
//go:linkname _pthread_setspecific _pthread_setspecific //go:linkname _pthread_setspecific _pthread_setspecific
var _pthread_setspecific uintptr var _pthread_setspecific uint8
var pthread_setspecificABI0 = uintptr(unsafe.Pointer(&_pthread_setspecific)) var pthread_setspecificABI0 = uintptr(unsafe.Pointer(&_pthread_setspecific))
@@ -3,6 +3,8 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build !cgo
package fakecgo package fakecgo
//go:cgo_import_dynamic purego_malloc malloc "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_malloc malloc "/usr/lib/libSystem.B.dylib"
@@ -12,6 +14,7 @@ package fakecgo
//go:cgo_import_dynamic purego_sigfillset sigfillset "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_sigfillset sigfillset "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic purego_nanosleep nanosleep "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_nanosleep nanosleep "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic purego_abort abort "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_abort abort "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic purego_sigaltstack sigaltstack "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic purego_pthread_create pthread_create "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_pthread_create pthread_create "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic purego_pthread_detach pthread_detach "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic purego_pthread_detach pthread_detach "/usr/lib/libSystem.B.dylib"
@@ -3,6 +3,8 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build !cgo
package fakecgo package fakecgo
//go:cgo_import_dynamic purego_malloc malloc "libc.so.7" //go:cgo_import_dynamic purego_malloc malloc "libc.so.7"
@@ -12,6 +14,7 @@ package fakecgo
//go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so.7" //go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so.7"
//go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so.7" //go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so.7"
//go:cgo_import_dynamic purego_abort abort "libc.so.7" //go:cgo_import_dynamic purego_abort abort "libc.so.7"
//go:cgo_import_dynamic purego_sigaltstack sigaltstack "libc.so.7"
//go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "libpthread.so" //go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "libpthread.so"
//go:cgo_import_dynamic purego_pthread_create pthread_create "libpthread.so" //go:cgo_import_dynamic purego_pthread_create pthread_create "libpthread.so"
//go:cgo_import_dynamic purego_pthread_detach pthread_detach "libpthread.so" //go:cgo_import_dynamic purego_pthread_detach pthread_detach "libpthread.so"
@@ -3,6 +3,8 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build !cgo
package fakecgo package fakecgo
//go:cgo_import_dynamic purego_malloc malloc "libc.so.6" //go:cgo_import_dynamic purego_malloc malloc "libc.so.6"
@@ -12,6 +14,7 @@ package fakecgo
//go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so.6" //go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so.6"
//go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so.6" //go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so.6"
//go:cgo_import_dynamic purego_abort abort "libc.so.6" //go:cgo_import_dynamic purego_abort abort "libc.so.6"
//go:cgo_import_dynamic purego_sigaltstack sigaltstack "libc.so.6"
//go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "libpthread.so.0" //go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "libpthread.so.0"
//go:cgo_import_dynamic purego_pthread_create pthread_create "libpthread.so.0" //go:cgo_import_dynamic purego_pthread_create pthread_create "libpthread.so.0"
//go:cgo_import_dynamic purego_pthread_detach pthread_detach "libpthread.so.0" //go:cgo_import_dynamic purego_pthread_detach pthread_detach "libpthread.so.0"
+30
View File
@@ -0,0 +1,30 @@
// Code generated by 'go generate' with gen.go. DO NOT EDIT.
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build !cgo
package fakecgo
//go:cgo_import_dynamic purego_malloc malloc "libc.so"
//go:cgo_import_dynamic purego_free free "libc.so"
//go:cgo_import_dynamic purego_setenv setenv "libc.so"
//go:cgo_import_dynamic purego_unsetenv unsetenv "libc.so"
//go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so"
//go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so"
//go:cgo_import_dynamic purego_abort abort "libc.so"
//go:cgo_import_dynamic purego_sigaltstack sigaltstack "libc.so"
//go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "libpthread.so"
//go:cgo_import_dynamic purego_pthread_create pthread_create "libpthread.so"
//go:cgo_import_dynamic purego_pthread_detach pthread_detach "libpthread.so"
//go:cgo_import_dynamic purego_pthread_sigmask pthread_sigmask "libpthread.so"
//go:cgo_import_dynamic purego_pthread_self pthread_self "libpthread.so"
//go:cgo_import_dynamic purego_pthread_get_stacksize_np pthread_get_stacksize_np "libpthread.so"
//go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "libpthread.so"
//go:cgo_import_dynamic purego_pthread_attr_setstacksize pthread_attr_setstacksize "libpthread.so"
//go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "libpthread.so"
//go:cgo_import_dynamic purego_pthread_mutex_lock pthread_mutex_lock "libpthread.so"
//go:cgo_import_dynamic purego_pthread_mutex_unlock pthread_mutex_unlock "libpthread.so"
//go:cgo_import_dynamic purego_pthread_cond_broadcast pthread_cond_broadcast "libpthread.so"
//go:cgo_import_dynamic purego_pthread_setspecific pthread_setspecific "libpthread.so"
+1 -1
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || linux || freebsd //go:build !cgo && (darwin || linux || freebsd)
/* /*
trampoline for emulating required C functions for cgo in go (see cgo.go) trampoline for emulating required C functions for cgo in go (see cgo.go)
+1 -1
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux //go:build !cgo && (darwin || freebsd || linux)
#include "textflag.h" #include "textflag.h"
#include "go_asm.h" #include "go_asm.h"
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2025 The Ebitengine Authors
//go:build !cgo && linux
#include "textflag.h"
#include "go_asm.h"
// these trampolines map the gcc ABI to Go ABI and then calls into the Go equivalent functions.
TEXT x_cgo_init_trampoline(SB), NOSPLIT, $16
MOVV R4, 8(R3)
MOVV R5, 16(R3)
MOVV ·x_cgo_init_call(SB), R6
MOVV (R6), R7
CALL (R7)
RET
TEXT x_cgo_thread_start_trampoline(SB), NOSPLIT, $8
MOVV R4, 8(R3)
MOVV ·x_cgo_thread_start_call(SB), R5
MOVV (R5), R6
CALL (R6)
RET
TEXT x_cgo_setenv_trampoline(SB), NOSPLIT, $8
MOVV R4, 8(R3)
MOVV ·x_cgo_setenv_call(SB), R5
MOVV (R5), R6
CALL (R6)
RET
TEXT x_cgo_unsetenv_trampoline(SB), NOSPLIT, $8
MOVV R4, 8(R3)
MOVV ·x_cgo_unsetenv_call(SB), R5
MOVV (R5), R6
CALL (R6)
RET
TEXT x_cgo_notify_runtime_init_done_trampoline(SB), NOSPLIT, $0
CALL ·x_cgo_notify_runtime_init_done(SB)
RET
TEXT x_cgo_bindm_trampoline(SB), NOSPLIT, $0
CALL ·x_cgo_bindm(SB)
RET
// func setg_trampoline(setg uintptr, g uintptr)
TEXT ·setg_trampoline(SB), NOSPLIT, $0
MOVV G+8(FP), R4
MOVV setg+0(FP), R5
CALL (R5)
RET
TEXT threadentry_trampoline(SB), NOSPLIT, $16
MOVV R4, 8(R3)
MOVV ·threadentry_call(SB), R5
MOVV (R5), R6
CALL (R6)
RET
TEXT ·call5(SB), NOSPLIT, $0-0
MOVV fn+0(FP), R9
MOVV a1+8(FP), R4
MOVV a2+16(FP), R5
MOVV a3+24(FP), R6
MOVV a4+32(FP), R7
MOVV a5+40(FP), R8
CALL (R9)
MOVV R4, ret+48(FP)
RET
+5 -1
View File
@@ -3,7 +3,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux //go:build !cgo && (darwin || freebsd || linux || netbsd)
#include "textflag.h" #include "textflag.h"
@@ -37,6 +37,10 @@ TEXT _abort(SB), NOSPLIT|NOFRAME, $0-0
JMP purego_abort(SB) JMP purego_abort(SB)
RET RET
TEXT _sigaltstack(SB), NOSPLIT|NOFRAME, $0-0
JMP purego_sigaltstack(SB)
RET
TEXT _pthread_attr_init(SB), NOSPLIT|NOFRAME, $0-0 TEXT _pthread_attr_init(SB), NOSPLIT|NOFRAME, $0-0
JMP purego_pthread_attr_init(SB) JMP purego_pthread_attr_init(SB)
RET RET
+1 -1
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build !cgo && (darwin || freebsd || linux) //go:build !cgo && (darwin || freebsd || linux || netbsd)
package purego package purego
+267
View File
@@ -0,0 +1,267 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2025 The Ebitengine Authors
package objc
import (
"fmt"
"reflect"
"sync"
"unsafe"
"github.com/ebitengine/purego"
)
const (
// The end-goal of these defaults is to get an Objective-C memory-managed block object
// that won't try to free() a Go pointer, but will call our custom blockFunctionCache.Delete()
// when the reference count drops to zero, so the associated function is also unreferenced.
// blockBaseClass is the name of the class that block objects will be initialized with.
blockBaseClass = "__NSMallocBlock__"
// blockFlags is the set of flags that block objects will be initialized with.
blockFlags = blockHasCopyDispose | blockHasSignature
// blockHasCopyDispose is a flag that tells the Objective-C runtime the block exports Copy and/or Dispose helpers.
blockHasCopyDispose = 1 << 25
// blockHasSignature is a flag that tells the Objective-C runtime the block exports a function signature.
blockHasSignature = 1 << 30
)
// blockDescriptor is the Go representation of an Objective-C block descriptor.
// It is a component to be referenced by blockDescriptor.
//
// The layout of this struct matches Block_literal_1 described in https://clang.llvm.org/docs/Block-ABI-Apple.html#high-level
type blockDescriptor struct {
_ uintptr
size uintptr
_ uintptr
dispose uintptr
signature *uint8
}
// blockLayout is the Go representation of the structure abstracted by a block pointer.
// From the Objective-C point of view, a pointer to this struct is equivalent to an ID that
// references a block.
//
// The layout of this struct matches __block_literal_1 described in https://clang.llvm.org/docs/Block-ABI-Apple.html#high-level
type blockLayout struct {
isa Class
flags uint32
_ uint32
invoke uintptr
descriptor *blockDescriptor
}
// blockFunctionCache is a thread safe cache of block layouts.
//
// The function closures themselves are kept alive by caching them internally until the Objective-C runtime indicates that
// they can be released (presumably when the reference count reaches zero). This approach is used instead of appending the function
// object to the block allocation, where it is out of the visible domain of Go's GC.
type blockFunctionCache struct {
mutex sync.RWMutex
functions map[Block]reflect.Value
}
// Load retrieves a function (in the form of a reflect.Value, so Call can be invoked) associated with the key Block.
func (b *blockFunctionCache) Load(key Block) reflect.Value {
b.mutex.RLock()
defer b.mutex.RUnlock()
return b.functions[key]
}
// Store associates a function (in the form of a reflect.Value) with the key Block.
func (b *blockFunctionCache) Store(key Block, value reflect.Value) Block {
b.mutex.Lock()
defer b.mutex.Unlock()
b.functions[key] = value
return key
}
// Delete removed the function associated with the key Block.
func (b *blockFunctionCache) Delete(key Block) {
b.mutex.Lock()
defer b.mutex.Unlock()
delete(b.functions, key)
}
// newBlockFunctionCache initializes a new blockFunctionCache
func newBlockFunctionCache() *blockFunctionCache {
return &blockFunctionCache{functions: map[Block]reflect.Value{}}
}
// blockCache is a thread safe cache of block layouts.
//
// It takes advantage of the block being the first argument of a block call being the block closure,
// only invoking [github.com/ebitengine/purego.NewCallback] when it encounters a new function type (rather than on for every block creation).
// This should mitigate block creations putting pressure on the callback limit.
type blockCache struct {
sync.Mutex
descriptorTemplate blockDescriptor
layoutTemplate blockLayout
layouts map[reflect.Type]blockLayout
Functions *blockFunctionCache
}
// encode returns a blocks type as if it was given to @encode(typ)
func (*blockCache) encode(typ reflect.Type) *uint8 {
// this algorithm was copied from encodeFunc,
// but altered to panic on error, and to only accept a block-type signature.
if typ == nil || typ.Kind() != reflect.Func {
panic("objc: not a function")
}
var encoding string
switch typ.NumOut() {
case 0:
encoding = encVoid
default:
returnType, err := encodeType(typ.Out(0), false)
if err != nil {
panic(fmt.Sprintf("objc: %v", err))
}
encoding = returnType
}
if typ.NumIn() == 0 || typ.In(0) != reflect.TypeOf(Block(0)) {
panic(fmt.Sprintf("objc: A Block implementation must take a Block as its first argument; got %v", typ.String()))
}
encoding += encId
for i := 1; i < typ.NumIn(); i++ {
argType, err := encodeType(typ.In(i), false)
if err != nil {
panic(fmt.Sprintf("objc: %v", err))
}
encoding = fmt.Sprint(encoding, argType)
}
// return the encoding as a C-style string.
return &append([]uint8(encoding), 0)[0]
}
// getLayout retrieves a blockLayout VALUE constructed with the supplied function type.
// It will panic if the type is not a valid block function.
func (b *blockCache) getLayout(typ reflect.Type) blockLayout {
b.Lock()
defer b.Unlock()
// return the cached layout, if it exists.
if layout, ok := b.layouts[typ]; ok {
return layout
}
// otherwise: create a layout, and populate it with the default templates
layout := b.layoutTemplate
layout.descriptor = &blockDescriptor{}
*layout.descriptor = b.descriptorTemplate
// getting the signature now will panic on invalid types before we invest in creating a callback.
layout.descriptor.signature = b.encode(typ)
// create a global callback.
// this single callback can dispatch to any function with the same signature,
// since the user-provided functions are associated with the actual block allocations.
layout.invoke = purego.NewCallback(
reflect.MakeFunc(
typ,
func(args []reflect.Value) (results []reflect.Value) {
return b.Functions.Load(args[0].Interface().(Block)).Call(args)
},
).Interface(),
)
// store it and return it
b.layouts[typ] = layout
return layout
}
// newBlockCache initializes a block cache.
// It should not be called until AFTER libobjc is fully initialized.
func newBlockCache() *blockCache {
cache := &blockCache{
descriptorTemplate: blockDescriptor{
size: unsafe.Sizeof(blockLayout{}),
},
layoutTemplate: blockLayout{
isa: GetClass(blockBaseClass),
flags: blockFlags,
},
layouts: map[reflect.Type]blockLayout{},
Functions: newBlockFunctionCache(),
}
cache.descriptorTemplate.dispose = purego.NewCallback(cache.Functions.Delete)
return cache
}
// theBlocksCache is the global block cache
var theBlocksCache *blockCache
// Block is an opaque pointer to an Objective-C object containing a function with its associated closure.
type Block ID
// Copy creates a copy of a block on the Objective-C heap (or increments the reference count if already on the heap).
// Use [Block.Release] to free the copy when it is no longer in use.
func (b Block) Copy() Block {
return _Block_copy(b)
}
// Invoke calls the implementation of a block.
func (b Block) Invoke(args ...any) {
fn := theBlocksCache.Functions.Load(b)
reflectedArgs := make([]reflect.Value, len(args)+1)
reflectedArgs[0] = reflect.ValueOf(b)
for i := range args {
reflectedArgs[i+1] = reflect.ValueOf(args[i])
}
fn.Call(reflectedArgs)
}
// Release decrements the Block's reference count, and if it is the last reference, frees it.
func (b Block) Release() {
_Block_release(b)
}
// NewBlock takes a Go function that takes a Block as its first argument.
// It returns an Block that can be called by Objective-C code.
// The function panics if an error occurs.
// Use [Block.Release] to free this block when it is no longer in use.
func NewBlock(fn any) Block {
// get or create a block layout for the callback.
layout := theBlocksCache.getLayout(reflect.TypeOf(fn))
// we created the layout in Go memory, so we'll copy it to a newly-created Objective-C object.
block := Block(unsafe.Pointer(&layout)).Copy()
// associate the fn with the block we created before returning it.
return theBlocksCache.Functions.Store(block, reflect.ValueOf(fn))
}
// InvokeBlock is a convenience method for calling the implementation of a block.
// The block implementation must return 1 value.
func InvokeBlock[T any](block Block, args ...any) (result T, err error) {
block = block.Copy()
defer block.Release()
fn := theBlocksCache.Functions.Load(block)
if fn.Type().NumIn() != len(args)+1 {
return result, fmt.Errorf("objc: block callback expects %d arguments, got %d", fn.Type().NumIn()-1, len(args))
}
reflectedArgs := make([]reflect.Value, len(args)+1)
reflectedArgs[0] = reflect.ValueOf(block)
for i := range args {
reflectedArgs[i+1] = reflect.ValueOf(args[i])
}
callResult := fn.Call(reflectedArgs)
var ok bool
result, ok = callResult[0].Interface().(T)
if !ok {
return result, fmt.Errorf("objc: the returned value type %s was not %T", callResult[0].Type().String(), result)
}
return result, nil
}
+193 -60
View File
@@ -11,41 +11,59 @@ import (
"math" "math"
"reflect" "reflect"
"regexp" "regexp"
"runtime"
"unicode" "unicode"
"unsafe" "unsafe"
"github.com/ebitengine/purego" "github.com/ebitengine/purego"
"github.com/ebitengine/purego/internal/strings"
) )
// TODO: support try/catch? // TODO: support try/catch?
// https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime // https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime
var ( var (
objc_msgSend_fn uintptr objc_msgSend_fn uintptr
objc_msgSend func(obj ID, cmd SEL, args ...interface{}) ID objc_msgSend_stret_fn uintptr
objc_msgSendSuper2_fn uintptr objc_msgSend func(obj ID, cmd SEL, args ...any) ID
objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...interface{}) ID objc_msgSendSuper2_fn uintptr
objc_getClass func(name string) Class objc_msgSendSuper2_stret_fn uintptr
objc_getProtocol func(name string) *Protocol objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...any) ID
objc_allocateClassPair func(super Class, name string, extraBytes uintptr) Class objc_getClass func(name string) Class
objc_registerClassPair func(class Class) objc_getProtocol func(name string) *Protocol
sel_registerName func(name string) SEL objc_allocateProtocol func(name string) *Protocol
class_getSuperclass func(class Class) Class objc_registerProtocol func(protocol *Protocol)
class_getInstanceVariable func(class Class, name string) Ivar objc_allocateClassPair func(super Class, name string, extraBytes uintptr) Class
class_getInstanceSize func(class Class) uintptr objc_registerClassPair func(class Class)
class_addMethod func(class Class, name SEL, imp IMP, types string) bool sel_registerName func(name string) SEL
class_addIvar func(class Class, name string, size uintptr, alignment uint8, types string) bool class_getSuperclass func(class Class) Class
class_addProtocol func(class Class, protocol *Protocol) bool class_getInstanceVariable func(class Class, name string) Ivar
ivar_getOffset func(ivar Ivar) uintptr class_getInstanceSize func(class Class) uintptr
ivar_getName func(ivar Ivar) string class_addMethod func(class Class, name SEL, imp IMP, types string) bool
object_getClass func(obj ID) Class class_addIvar func(class Class, name string, size uintptr, alignment uint8, types string) bool
object_getIvar func(obj ID, ivar Ivar) ID class_addProtocol func(class Class, protocol *Protocol) bool
object_setIvar func(obj ID, ivar Ivar, value ID) ivar_getOffset func(ivar Ivar) uintptr
protocol_getName func(protocol *Protocol) string ivar_getName func(ivar Ivar) string
protocol_isEqual func(p *Protocol, p2 *Protocol) bool object_getClass func(obj ID) Class
object_getIvar func(obj ID, ivar Ivar) ID
object_setIvar func(obj ID, ivar Ivar, value ID)
protocol_getName func(protocol *Protocol) string
protocol_isEqual func(p *Protocol, p2 *Protocol) bool
protocol_addMethodDescription func(p *Protocol, name SEL, types string, isRequiredMethod bool, isInstanceMethod bool)
protocol_copyMethodDescriptionList func(p *Protocol, isRequiredMethod bool, isInstanceMethod bool, outCount *uint32) *MethodDescription
protocol_copyProtocolList func(p *Protocol, outCount *uint32) **Protocol
protocol_copyPropertyList2 func(p *Protocol, outCount *uint32, isRequiredProperty, isInstanceProperty bool) *Property
protocol_addProtocol func(p *Protocol, p2 *Protocol)
protocol_addProperty func(p *Protocol, name string, attributes []PropertyAttribute, attributeCount uint32, isRequiredProperty bool, isInstanceProperty bool)
property_getName func(p Property) string
property_getAttributes func(p Property) string
free func(ptr unsafe.Pointer)
_Block_copy func(Block) Block
_Block_release func(Block)
) )
func init() { func init() {
objc, err := purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL) objc, err := purego.Dlopen("/usr/lib/libobjc.A.dylib", purego.RTLD_GLOBAL|purego.RTLD_NOW)
if err != nil { if err != nil {
panic(fmt.Errorf("objc: %w", err)) panic(fmt.Errorf("objc: %w", err))
} }
@@ -53,6 +71,16 @@ func init() {
if err != nil { if err != nil {
panic(fmt.Errorf("objc: %w", err)) panic(fmt.Errorf("objc: %w", err))
} }
if runtime.GOARCH == "amd64" {
objc_msgSend_stret_fn, err = purego.Dlsym(objc, "objc_msgSend_stret")
if err != nil {
panic(fmt.Errorf("objc: %w", err))
}
objc_msgSendSuper2_stret_fn, err = purego.Dlsym(objc, "objc_msgSendSuper2_stret")
if err != nil {
panic(fmt.Errorf("objc: %w", err))
}
}
purego.RegisterFunc(&objc_msgSend, objc_msgSend_fn) purego.RegisterFunc(&objc_msgSend, objc_msgSend_fn)
objc_msgSendSuper2_fn, err = purego.Dlsym(objc, "objc_msgSendSuper2") objc_msgSendSuper2_fn, err = purego.Dlsym(objc, "objc_msgSendSuper2")
if err != nil { if err != nil {
@@ -62,6 +90,8 @@ func init() {
purego.RegisterLibFunc(&object_getClass, objc, "object_getClass") purego.RegisterLibFunc(&object_getClass, objc, "object_getClass")
purego.RegisterLibFunc(&objc_getClass, objc, "objc_getClass") purego.RegisterLibFunc(&objc_getClass, objc, "objc_getClass")
purego.RegisterLibFunc(&objc_getProtocol, objc, "objc_getProtocol") purego.RegisterLibFunc(&objc_getProtocol, objc, "objc_getProtocol")
purego.RegisterLibFunc(&objc_allocateProtocol, objc, "objc_allocateProtocol")
purego.RegisterLibFunc(&objc_registerProtocol, objc, "objc_registerProtocol")
purego.RegisterLibFunc(&objc_allocateClassPair, objc, "objc_allocateClassPair") purego.RegisterLibFunc(&objc_allocateClassPair, objc, "objc_allocateClassPair")
purego.RegisterLibFunc(&objc_registerClassPair, objc, "objc_registerClassPair") purego.RegisterLibFunc(&objc_registerClassPair, objc, "objc_registerClassPair")
purego.RegisterLibFunc(&sel_registerName, objc, "sel_registerName") purego.RegisterLibFunc(&sel_registerName, objc, "sel_registerName")
@@ -75,8 +105,21 @@ func init() {
purego.RegisterLibFunc(&ivar_getName, objc, "ivar_getName") purego.RegisterLibFunc(&ivar_getName, objc, "ivar_getName")
purego.RegisterLibFunc(&protocol_getName, objc, "protocol_getName") purego.RegisterLibFunc(&protocol_getName, objc, "protocol_getName")
purego.RegisterLibFunc(&protocol_isEqual, objc, "protocol_isEqual") purego.RegisterLibFunc(&protocol_isEqual, objc, "protocol_isEqual")
purego.RegisterLibFunc(&protocol_addMethodDescription, objc, "protocol_addMethodDescription")
purego.RegisterLibFunc(&protocol_copyMethodDescriptionList, objc, "protocol_copyMethodDescriptionList")
purego.RegisterLibFunc(&protocol_copyProtocolList, objc, "protocol_copyProtocolList")
purego.RegisterLibFunc(&protocol_addProtocol, objc, "protocol_addProtocol")
purego.RegisterLibFunc(&protocol_addProperty, objc, "protocol_addProperty")
purego.RegisterLibFunc(&protocol_copyPropertyList2, objc, "protocol_copyPropertyList2")
purego.RegisterLibFunc(&property_getName, objc, "property_getName")
purego.RegisterLibFunc(&property_getAttributes, objc, "property_getAttributes")
purego.RegisterLibFunc(&object_getIvar, objc, "object_getIvar") purego.RegisterLibFunc(&object_getIvar, objc, "object_getIvar")
purego.RegisterLibFunc(&object_setIvar, objc, "object_setIvar") purego.RegisterLibFunc(&object_setIvar, objc, "object_setIvar")
purego.RegisterLibFunc(&free, purego.RTLD_DEFAULT, "free")
purego.RegisterLibFunc(&_Block_copy, objc, "_Block_copy")
purego.RegisterLibFunc(&_Block_release, objc, "_Block_release")
theBlocksCache = newBlockCache()
} }
// ID is an opaque pointer to some Objective-C object // ID is an opaque pointer to some Objective-C object
@@ -90,7 +133,7 @@ func (id ID) Class() Class {
// Send is a convenience method for sending messages to objects. This function takes a SEL // Send is a convenience method for sending messages to objects. This function takes a SEL
// instead of a string since RegisterName grabs the global Objective-C lock. It is best to cache the result // instead of a string since RegisterName grabs the global Objective-C lock. It is best to cache the result
// of RegisterName. // of RegisterName.
func (id ID) Send(sel SEL, args ...interface{}) ID { func (id ID) Send(sel SEL, args ...any) ID {
return objc_msgSend(id, sel, args...) return objc_msgSend(id, sel, args...)
} }
@@ -104,12 +147,22 @@ func (id ID) SetIvar(ivar Ivar, value ID) {
object_setIvar(id, ivar, value) object_setIvar(id, ivar, value)
} }
// keep in sync with func.go
const maxRegAllocStructSize = 16
// Send is a convenience method for sending messages to objects that can return any type. // Send is a convenience method for sending messages to objects that can return any type.
// This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock. // This function takes a SEL instead of a string since RegisterName grabs the global Objective-C lock.
// It is best to cache the result of RegisterName. // It is best to cache the result of RegisterName.
func Send[T any](id ID, sel SEL, args ...any) T { func Send[T any](id ID, sel SEL, args ...any) T {
var fn func(id ID, sel SEL, args ...any) T var fn func(id ID, sel SEL, args ...any) T
purego.RegisterFunc(&fn, objc_msgSend_fn) var zero T
if runtime.GOARCH == "amd64" &&
reflect.ValueOf(zero).Kind() == reflect.Struct &&
reflect.ValueOf(zero).Type().Size() > maxRegAllocStructSize {
purego.RegisterFunc(&fn, objc_msgSend_stret_fn)
} else {
purego.RegisterFunc(&fn, objc_msgSend_fn)
}
return fn(id, sel, args...) return fn(id, sel, args...)
} }
@@ -124,7 +177,7 @@ type objc_super struct {
// SendSuper is a convenience method for sending message to object's super. This function takes a SEL // SendSuper is a convenience method for sending message to object's super. This function takes a SEL
// instead of a string since RegisterName grabs the global Objective-C lock. It is best to cache the result // instead of a string since RegisterName grabs the global Objective-C lock. It is best to cache the result
// of RegisterName. // of RegisterName.
func (id ID) SendSuper(sel SEL, args ...interface{}) ID { func (id ID) SendSuper(sel SEL, args ...any) ID {
super := &objc_super{ super := &objc_super{
receiver: id, receiver: id,
superClass: id.Class(), superClass: id.Class(),
@@ -141,7 +194,14 @@ func SendSuper[T any](id ID, sel SEL, args ...any) T {
superClass: id.Class(), superClass: id.Class(),
} }
var fn func(objcSuper *objc_super, sel SEL, args ...any) T var fn func(objcSuper *objc_super, sel SEL, args ...any) T
purego.RegisterFunc(&fn, objc_msgSendSuper2_fn) var zero T
if runtime.GOARCH == "amd64" &&
reflect.ValueOf(zero).Kind() == reflect.Struct &&
reflect.ValueOf(zero).Type().Size() > maxRegAllocStructSize {
purego.RegisterFunc(&fn, objc_msgSendSuper2_stret_fn)
} else {
purego.RegisterFunc(&fn, objc_msgSendSuper2_fn)
}
return fn(super, sel, args...) return fn(super, sel, args...)
} }
@@ -163,13 +223,6 @@ func GetClass(name string) Class {
return objc_getClass(name) return objc_getClass(name)
} }
// AllocateClassPair creates a new class and metaclass. Then returns the new class, or Nil if the class could not be created
//
// Deprecated: use RegisterClass instead
func AllocateClassPair(super Class, name string, extraBytes uintptr) Class {
return objc_allocateClassPair(super, name, extraBytes)
}
// MethodDef represents the Go function and the selector that ObjC uses to access that function. // MethodDef represents the Go function and the selector that ObjC uses to access that function.
type MethodDef struct { type MethodDef struct {
Cmd SEL Cmd SEL
@@ -360,7 +413,7 @@ func encodeType(typ reflect.Type, insidePtr bool) (string, error) {
switch typ { switch typ {
case reflect.TypeOf(Class(0)): case reflect.TypeOf(Class(0)):
return encClass, nil return encClass, nil
case reflect.TypeOf(ID(0)): case reflect.TypeOf(ID(0)), reflect.TypeOf(Block(0)):
return encId, nil return encId, nil
case reflect.TypeOf(SEL(0)): case reflect.TypeOf(SEL(0)):
return encSelector, nil return encSelector, nil
@@ -414,7 +467,7 @@ func encodeType(typ reflect.Type, insidePtr bool) (string, error) {
} }
encoding += tmp encoding += tmp
} }
encoding = encStructEnd encoding += encStructEnd
return encoding, nil return encoding, nil
case reflect.UnsafePointer: case reflect.UnsafePointer:
return encUnsafePtr, nil return encUnsafePtr, nil
@@ -426,7 +479,7 @@ func encodeType(typ reflect.Type, insidePtr bool) (string, error) {
} }
// encodeFunc returns a functions type as if it was given to @encode(fn) // encodeFunc returns a functions type as if it was given to @encode(fn)
func encodeFunc(fn interface{}) (string, error) { func encodeFunc(fn any) (string, error) {
typ := reflect.TypeOf(fn) typ := reflect.TypeOf(fn)
if typ.Kind() != reflect.Func { if typ.Kind() != reflect.Func {
return "", errors.New("not a func") return "", errors.New("not a func")
@@ -476,20 +529,6 @@ func (c Class) AddMethod(name SEL, imp IMP, types string) bool {
return class_addMethod(c, name, imp, types) return class_addMethod(c, name, imp, types)
} }
// AddIvar adds a new instance variable to a class.
// It may only be called after AllocateClassPair and before Register.
// Adding an instance variable to an existing class is not supported.
// The class must not be a metaclass. Adding an instance variable to a metaclass is not supported.
// It takes the instance of the type of the Ivar and a string representing the type.
//
// Deprecated: use RegisterClass instead
func (c Class) AddIvar(name string, ty interface{}, types string) bool {
typeOf := reflect.TypeOf(ty)
size := typeOf.Size()
alignment := uint8(math.Log2(float64(typeOf.Align())))
return class_addIvar(c, name, size, alignment, types)
}
// AddProtocol adds a protocol to a class. // AddProtocol adds a protocol to a class.
// Returns true if the protocol was added successfully, otherwise false (for example, // Returns true if the protocol was added successfully, otherwise false (for example,
// the class already conforms to that protocol). // the class already conforms to that protocol).
@@ -507,14 +546,6 @@ func (c Class) InstanceVariable(name string) Ivar {
return class_getInstanceVariable(c, name) return class_getInstanceVariable(c, name)
} }
// Register registers a class that was allocated using AllocateClassPair.
// It can now be used to make objects by sending it either alloc and init or new.
//
// Deprecated: use RegisterClass instead
func (c Class) Register() {
objc_registerClassPair(c)
}
// Ivar an opaque type that represents an instance variable. // Ivar an opaque type that represents an instance variable.
type Ivar uintptr type Ivar uintptr
@@ -530,6 +561,39 @@ func (i Ivar) Name() string {
return ivar_getName(i) return ivar_getName(i)
} }
// MethodDescription holds the name and type definition of a method.
type MethodDescription struct {
name, types uintptr
}
// Name returns the name of this method.
func (m MethodDescription) Name() string {
return strings.GoString(m.name)
}
// Types returns the OBJC runtime encoded type description.
func (m MethodDescription) Types() string {
return strings.GoString(m.types)
}
// PropertyAttribute contains the null-terminated Name and Value pair of a Properties internal description.
type PropertyAttribute struct {
Name, Value *byte
}
// Property is an opaque type for Objective-C property metadata.
type Property uintptr
// Name returns the name of this property.
func (p Property) Name() string {
return property_getName(p)
}
// Attributes returns a comma separated list of PropertyAttribute
func (p Property) Attributes() string {
return property_getAttributes(p)
}
// Protocol is a type that declares methods that can be implemented by any class. // Protocol is a type that declares methods that can be implemented by any class.
type Protocol [0]func() type Protocol [0]func()
@@ -538,11 +602,69 @@ func GetProtocol(name string) *Protocol {
return objc_getProtocol(name) return objc_getProtocol(name)
} }
// AllocateProtocol creates a new protocol in the OBJC runtime or nil if the protocol already exists.
func AllocateProtocol(name string) *Protocol {
return objc_allocateProtocol(name)
}
// Register registers the protocol created using AllocateProtocol with the runtime. This must be done
// before it is used anywhere and can only be called once.
func (p *Protocol) Register() {
objc_registerProtocol(p)
}
// CopyMethodDescriptionList returns a list of methods that this protocol has given it isRequiredMethod and isInstanceMethod.
func (p *Protocol) CopyMethodDescriptionList(isRequiredMethod, isInstanceMethod bool) []MethodDescription {
count := uint32(0)
desc := protocol_copyMethodDescriptionList(p, isRequiredMethod, isInstanceMethod, &count)
methods := clone(unsafe.Slice(desc, count))
free(unsafe.Pointer(desc))
return methods
}
// CopyProtocolList returns a list of the protocols that this protocol inherits from.
func (p *Protocol) CopyProtocolList() []*Protocol {
count := uint32(0)
desc := protocol_copyProtocolList(p, &count)
protocols := clone(unsafe.Slice(desc, count))
free(unsafe.Pointer(desc))
return protocols
}
// CopyPropertyList returns a list of properties that this protocol has given it isRequiredProperty and isInstanceProperty.
func (p *Protocol) CopyPropertyList(isRequiredProperty, isInstanceProperty bool) []Property {
count := uint32(0)
desc := protocol_copyPropertyList2(p, &count, isRequiredProperty, isInstanceProperty)
protocols := clone(unsafe.Slice(desc, count))
free(unsafe.Pointer(desc))
return protocols
}
// Name returns the name of this protocol.
func (p *Protocol) Name() string {
return protocol_getName(p)
}
// Equals return true if the two protocols are the same. // Equals return true if the two protocols are the same.
func (p *Protocol) Equals(p2 *Protocol) bool { func (p *Protocol) Equals(p2 *Protocol) bool {
return protocol_isEqual(p, p2) return protocol_isEqual(p, p2)
} }
// AddMethodDescription adds a method to a protocol. This can only be called between AllocateProtocol and Protocol.Register.
func (p *Protocol) AddMethodDescription(name SEL, types string, isRequiredMethod, isInstanceMethod bool) {
protocol_addMethodDescription(p, name, types, isRequiredMethod, isInstanceMethod)
}
// AddProtocol marks the protocol as inheriting from another. This can only be called between AllocateProtocol and Protocol.Register.
func (p *Protocol) AddProtocol(protocol *Protocol) {
protocol_addProtocol(p, protocol)
}
// AddProperty adds a property to the protocol. This can only be called between AllocateProtocol and Protocol.Register.
func (p *Protocol) AddProperty(name string, attributes []PropertyAttribute, isRequiredProperty, isInstanceProperty bool) {
protocol_addProperty(p, name, attributes, uint32(len(attributes)), isRequiredProperty, isInstanceProperty)
}
// IMP is a function pointer that can be called by Objective-C code. // IMP is a function pointer that can be called by Objective-C code.
type IMP uintptr type IMP uintptr
@@ -550,7 +672,7 @@ type IMP uintptr
// It returns an IMP function pointer that can be called by Objective-C code. // It returns an IMP function pointer that can be called by Objective-C code.
// The function panics if an error occurs. // The function panics if an error occurs.
// The function pointer is never deallocated. // The function pointer is never deallocated.
func NewIMP(fn interface{}) IMP { func NewIMP(fn any) IMP {
ty := reflect.TypeOf(fn) ty := reflect.TypeOf(fn)
if ty.Kind() != reflect.Func { if ty.Kind() != reflect.Func {
panic("objc: not a function") panic("objc: not a function")
@@ -567,3 +689,14 @@ func NewIMP(fn interface{}) IMP {
} }
return IMP(purego.NewCallback(fn)) return IMP(purego.NewCallback(fn))
} }
// TODO: remove and use slices.Clone when minimum version for purego is 1.21
func clone[S ~[]E, E any](s S) S {
// Preserve nilness in case it matters.
if s == nil {
return nil
}
// Avoid s[:0:0] as it leads to unwanted liveness when cloning a
// zero-length slice of a large array; see https://go.dev/issue/68488.
return append(S{}, s...)
}
+10 -18
View File
@@ -85,7 +85,7 @@ const (
_MEMORY = 0b1111 _MEMORY = 0b1111
) )
func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} { func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {
if v.Type().Size() == 0 { if v.Type().Size() == 0 {
return keepAlive return keepAlive
} }
@@ -111,7 +111,7 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl
return keepAlive return keepAlive
} }
func postMerger(t reflect.Type) bool { func postMerger(t reflect.Type) (passInMemory bool) {
// (c) If the size of the aggregate exceeds two eightbytes and the first eight- byte isnt SSE or any other // (c) If the size of the aggregate exceeds two eightbytes and the first eight- byte isnt SSE or any other
// eightbyte isnt SSEUP, the whole argument is passed in memory. // eightbyte isnt SSEUP, the whole argument is passed in memory.
if t.Kind() != reflect.Struct { if t.Kind() != reflect.Struct {
@@ -120,19 +120,7 @@ func postMerger(t reflect.Type) bool {
if t.Size() <= 2*8 { if t.Size() <= 2*8 {
return false return false
} }
first := getFirst(t).Kind() return true // Go does not have an SSE/SSEUP type so this is always true
if first != reflect.Float32 && first != reflect.Float64 {
return false
}
return true
}
func getFirst(t reflect.Type) reflect.Type {
first := t.Field(0).Type
if first.Kind() == reflect.Struct {
return getFirst(first)
}
return first
} }
func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) (ok bool) { func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) (ok bool) {
@@ -196,7 +184,7 @@ func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintp
val |= uint64(f.Int()&0xFFFF_FFFF) << shift val |= uint64(f.Int()&0xFFFF_FFFF) << shift
shift += 32 shift += 32
class |= _INTEGER class |= _INTEGER
case reflect.Int64: case reflect.Int64, reflect.Int:
val = uint64(f.Int()) val = uint64(f.Int())
shift = 64 shift = 64
class = _INTEGER class = _INTEGER
@@ -212,7 +200,7 @@ func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintp
val |= f.Uint() << shift val |= f.Uint() << shift
shift += 32 shift += 32
class |= _INTEGER class |= _INTEGER
case reflect.Uint64: case reflect.Uint64, reflect.Uint, reflect.Uintptr:
val = f.Uint() val = f.Uint()
shift = 64 shift = 64
class = _INTEGER class = _INTEGER
@@ -257,7 +245,7 @@ func placeStack(v reflect.Value, addStack func(uintptr)) {
addStack(f.Pointer()) addStack(f.Pointer())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
addStack(uintptr(f.Int())) addStack(uintptr(f.Int()))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
addStack(uintptr(f.Uint())) addStack(uintptr(f.Uint()))
case reflect.Float32: case reflect.Float32:
addStack(uintptr(math.Float32bits(float32(f.Float())))) addStack(uintptr(math.Float32bits(float32(f.Float()))))
@@ -270,3 +258,7 @@ func placeStack(v reflect.Value, addStack func(uintptr)) {
} }
} }
} }
func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
panic("purego: not needed on amd64")
}
+22 -12
View File
@@ -16,17 +16,17 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
return reflect.New(outType).Elem() return reflect.New(outType).Elem()
case outSize <= 8: case outSize <= 8:
r1 := syscall.a1 r1 := syscall.a1
if isAllSameFloat(outType) { if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
r1 = syscall.f1 r1 = syscall.f1
if outType.NumField() == 2 { if numFields == 2 {
r1 = syscall.f2<<32 | syscall.f1 r1 = syscall.f2<<32 | syscall.f1
} }
} }
return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem() return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()
case outSize <= 16: case outSize <= 16:
r1, r2 := syscall.a1, syscall.a2 r1, r2 := syscall.a1, syscall.a2
if isAllSameFloat(outType) { if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
switch outType.NumField() { switch numFields {
case 4: case 4:
r1 = syscall.f2<<32 | syscall.f1 r1 = syscall.f2<<32 | syscall.f1
r2 = syscall.f4<<32 | syscall.f3 r2 = syscall.f4<<32 | syscall.f3
@@ -42,8 +42,8 @@ func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
} }
return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem() return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
default: default:
if isAllSameFloat(outType) && outType.NumField() <= 4 { if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats && numFields <= 4 {
switch outType.NumField() { switch numFields {
case 4: case 4:
return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c, d uintptr }{syscall.f1, syscall.f2, syscall.f3, syscall.f4})).Elem() return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c, d uintptr }{syscall.f1, syscall.f2, syscall.f3, syscall.f4})).Elem()
case 3: case 3:
@@ -65,7 +65,7 @@ const (
_INT = 0b11 _INT = 0b11
) )
func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} { func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {
if v.Type().Size() == 0 { if v.Type().Size() == 0 {
return keepAlive return keepAlive
} }
@@ -73,8 +73,8 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl
if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 { if hva, hfa, size := isHVA(v.Type()), isHFA(v.Type()), v.Type().Size(); hva || hfa || size <= 16 {
// if this doesn't fit entirely in registers then // if this doesn't fit entirely in registers then
// each element goes onto the stack // each element goes onto the stack
if hfa && *numFloats+v.NumField() > numOfFloats { if hfa && *numFloats+v.NumField() > numOfFloatRegisters {
*numFloats = numOfFloats *numFloats = numOfFloatRegisters
} else if hva && *numInts+v.NumField() > numOfIntegerRegisters() { } else if hva && *numInts+v.NumField() > numOfIntegerRegisters() {
*numInts = numOfIntegerRegisters() *numInts = numOfIntegerRegisters()
} }
@@ -107,6 +107,8 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
} else { } else {
f = v.Index(k) f = v.Index(k)
} }
align := byte(f.Type().Align()*8 - 1)
shift = (shift + align) &^ align
if shift >= 64 { if shift >= 64 {
shift = 0 shift = 0
flushed = true flushed = true
@@ -137,10 +139,11 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
val |= f.Uint() << shift val |= f.Uint() << shift
shift += 32 shift += 32
class |= _INT class |= _INT
case reflect.Uint64: case reflect.Uint64, reflect.Uint, reflect.Uintptr:
addInt(uintptr(f.Uint())) addInt(uintptr(f.Uint()))
shift = 0 shift = 0
flushed = true flushed = true
class = _NO_CLASS
case reflect.Int8: case reflect.Int8:
val |= uint64(f.Int()&0xFF) << shift val |= uint64(f.Int()&0xFF) << shift
shift += 8 shift += 8
@@ -153,10 +156,11 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
val |= uint64(f.Int()&0xFFFF_FFFF) << shift val |= uint64(f.Int()&0xFFFF_FFFF) << shift
shift += 32 shift += 32
class |= _INT class |= _INT
case reflect.Int64: case reflect.Int64, reflect.Int:
addInt(uintptr(f.Int())) addInt(uintptr(f.Int()))
shift = 0 shift = 0
flushed = true flushed = true
class = _NO_CLASS
case reflect.Float32: case reflect.Float32:
if class == _FLOAT { if class == _FLOAT {
addFloat(uintptr(val)) addFloat(uintptr(val))
@@ -170,6 +174,12 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
addFloat(uintptr(math.Float64bits(float64(f.Float())))) addFloat(uintptr(math.Float64bits(float64(f.Float()))))
shift = 0 shift = 0
flushed = true flushed = true
class = _NO_CLASS
case reflect.Ptr:
addInt(f.Pointer())
shift = 0
flushed = true
class = _NO_CLASS
case reflect.Array: case reflect.Array:
place(f) place(f)
default: default:
@@ -187,7 +197,7 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
} }
} }
func placeStack(v reflect.Value, keepAlive []interface{}, addInt func(uintptr)) []interface{} { func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any {
// Struct is too big to be placed in registers. // Struct is too big to be placed in registers.
// Copy to heap and place the pointer in register // Copy to heap and place the pointer in register
ptrStruct := reflect.New(v.Type()) ptrStruct := reflect.New(v.Type())
+190
View File
@@ -0,0 +1,190 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2025 The Ebitengine Authors
package purego
import (
"math"
"reflect"
"unsafe"
)
func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
outSize := outType.Size()
switch {
case outSize == 0:
return reflect.New(outType).Elem()
case outSize <= 8:
r1 := syscall.a1
if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
r1 = syscall.f1
if numFields == 2 {
r1 = syscall.f2<<32 | syscall.f1
}
}
return reflect.NewAt(outType, unsafe.Pointer(&struct{ a uintptr }{r1})).Elem()
case outSize <= 16:
r1, r2 := syscall.a1, syscall.a2
if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
switch numFields {
case 4:
r1 = syscall.f2<<32 | syscall.f1
r2 = syscall.f4<<32 | syscall.f3
case 3:
r1 = syscall.f2<<32 | syscall.f1
r2 = syscall.f3
case 2:
r1 = syscall.f1
r2 = syscall.f2
default:
panic("unreachable")
}
}
return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b uintptr }{r1, r2})).Elem()
default:
// create struct from the Go pointer created above
// weird pointer dereference to circumvent go vet
return reflect.NewAt(outType, *(*unsafe.Pointer)(unsafe.Pointer(&syscall.a1))).Elem()
}
}
const (
_NO_CLASS = 0b00
_FLOAT = 0b01
_INT = 0b11
)
func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {
if v.Type().Size() == 0 {
return keepAlive
}
if size := v.Type().Size(); size <= 16 {
placeRegisters(v, addFloat, addInt)
} else {
keepAlive = placeStack(v, keepAlive, addInt)
}
return keepAlive // the struct was allocated so don't panic
}
func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
var val uint64
var shift byte
var flushed bool
class := _NO_CLASS
var place func(v reflect.Value)
place = func(v reflect.Value) {
var numFields int
if v.Kind() == reflect.Struct {
numFields = v.Type().NumField()
} else {
numFields = v.Type().Len()
}
for k := 0; k < numFields; k++ {
flushed = false
var f reflect.Value
if v.Kind() == reflect.Struct {
f = v.Field(k)
} else {
f = v.Index(k)
}
align := byte(f.Type().Align()*8 - 1)
shift = (shift + align) &^ align
if shift >= 64 {
shift = 0
flushed = true
if class == _FLOAT {
addFloat(uintptr(val))
} else {
addInt(uintptr(val))
}
}
switch f.Type().Kind() {
case reflect.Struct:
place(f)
case reflect.Bool:
if f.Bool() {
val |= 1
}
shift += 8
class |= _INT
case reflect.Uint8:
val |= f.Uint() << shift
shift += 8
class |= _INT
case reflect.Uint16:
val |= f.Uint() << shift
shift += 16
class |= _INT
case reflect.Uint32:
val |= f.Uint() << shift
shift += 32
class |= _INT
case reflect.Uint64, reflect.Uint, reflect.Uintptr:
addInt(uintptr(f.Uint()))
shift = 0
flushed = true
class = _NO_CLASS
case reflect.Int8:
val |= uint64(f.Int()&0xFF) << shift
shift += 8
class |= _INT
case reflect.Int16:
val |= uint64(f.Int()&0xFFFF) << shift
shift += 16
class |= _INT
case reflect.Int32:
val |= uint64(f.Int()&0xFFFF_FFFF) << shift
shift += 32
class |= _INT
case reflect.Int64, reflect.Int:
addInt(uintptr(f.Int()))
shift = 0
flushed = true
class = _NO_CLASS
case reflect.Float32:
if class == _FLOAT {
addFloat(uintptr(val))
val = 0
shift = 0
}
val |= uint64(math.Float32bits(float32(f.Float()))) << shift
shift += 32
class |= _FLOAT
case reflect.Float64:
addFloat(uintptr(math.Float64bits(float64(f.Float()))))
shift = 0
flushed = true
class = _NO_CLASS
case reflect.Ptr:
addInt(f.Pointer())
shift = 0
flushed = true
class = _NO_CLASS
case reflect.Array:
place(f)
default:
panic("purego: unsupported kind " + f.Kind().String())
}
}
}
place(v)
if !flushed {
if class == _FLOAT {
addFloat(uintptr(val))
} else {
addInt(uintptr(val))
}
}
}
func placeStack(v reflect.Value, keepAlive []any, addInt func(uintptr)) []any {
// Struct is too big to be placed in registers.
// Copy to heap and place the pointer in register
ptrStruct := reflect.New(v.Type())
ptrStruct.Elem().Set(v)
ptr := ptrStruct.Elem().Addr().UnsafePointer()
keepAlive = append(keepAlive, ptr)
addInt(uintptr(ptr))
return keepAlive
}
+6 -2
View File
@@ -1,16 +1,20 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2024 The Ebitengine Authors // SPDX-FileCopyrightText: 2024 The Ebitengine Authors
//go:build !amd64 && !arm64 //go:build !amd64 && !arm64 && !loong64
package purego package purego
import "reflect" import "reflect"
func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []interface{}) []interface{} { func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFloat, addStack func(uintptr), keepAlive []any) []any {
panic("purego: struct arguments are not supported") panic("purego: struct arguments are not supported")
} }
func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) { func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
panic("purego: struct returns are not supported") panic("purego: struct returns are not supported")
} }
func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr)) {
panic("purego: not needed on other platforms")
}
+4 -4
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux //go:build darwin || freebsd || linux || netbsd
#include "textflag.h" #include "textflag.h"
#include "abi_amd64.h" #include "abi_amd64.h"
@@ -122,6 +122,9 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
PUSHQ R10 // push the stack pointer below registers PUSHQ R10 // push the stack pointer below registers
// Switch from the host ABI to the Go ABI.
PUSH_REGS_HOST_TO_ABI0()
// determine index into runtime·cbs table // determine index into runtime·cbs table
MOVQ $callbackasm(SB), DX MOVQ $callbackasm(SB), DX
SUBQ DX, AX SUBQ DX, AX
@@ -130,9 +133,6 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
DIVL CX DIVL CX
SUBQ $1, AX // subtract 1 because return PC is to the next slot SUBQ $1, AX // subtract 1 because return PC is to the next slot
// Switch from the host ABI to the Go ABI.
PUSH_REGS_HOST_TO_ABI0()
// Create a struct callbackArgs on our stack to be passed as // Create a struct callbackArgs on our stack to be passed as
// the "frame" to cgocallback and on to callbackWrap. // the "frame" to cgocallback and on to callbackWrap.
// $24 to make enough room for the arguments to runtime.cgocallback // $24 to make enough room for the arguments to runtime.cgocallback
+1 -1
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux || windows //go:build darwin || freebsd || linux || netbsd || windows
#include "textflag.h" #include "textflag.h"
#include "go_asm.h" #include "go_asm.h"
+96
View File
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2025 The Ebitengine Authors
//go:build linux
#include "textflag.h"
#include "go_asm.h"
#include "funcdata.h"
#define STACK_SIZE 64
#define PTR_ADDRESS (STACK_SIZE - 8)
// syscall15X calls a function in libc on behalf of the syscall package.
// syscall15X takes a pointer to a struct like:
// struct {
// fn uintptr
// a1 uintptr
// a2 uintptr
// a3 uintptr
// a4 uintptr
// a5 uintptr
// a6 uintptr
// a7 uintptr
// a8 uintptr
// a9 uintptr
// a10 uintptr
// a11 uintptr
// a12 uintptr
// a13 uintptr
// a14 uintptr
// a15 uintptr
// r1 uintptr
// r2 uintptr
// err uintptr
// }
// syscall15X must be called on the g0 stack with the
// C calling convention (use libcCall).
GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8
DATA ·syscall15XABI0(SB)/8, $syscall15X(SB)
TEXT syscall15X(SB), NOSPLIT, $0
// push structure pointer
SUBV $STACK_SIZE, R3
MOVV R4, PTR_ADDRESS(R3)
MOVV R4, R13
MOVD syscall15Args_f1(R13), F0 // f1
MOVD syscall15Args_f2(R13), F1 // f2
MOVD syscall15Args_f3(R13), F2 // f3
MOVD syscall15Args_f4(R13), F3 // f4
MOVD syscall15Args_f5(R13), F4 // f5
MOVD syscall15Args_f6(R13), F5 // f6
MOVD syscall15Args_f7(R13), F6 // f7
MOVD syscall15Args_f8(R13), F7 // f8
MOVV syscall15Args_a1(R13), R4 // a1
MOVV syscall15Args_a2(R13), R5 // a2
MOVV syscall15Args_a3(R13), R6 // a3
MOVV syscall15Args_a4(R13), R7 // a4
MOVV syscall15Args_a5(R13), R8 // a5
MOVV syscall15Args_a6(R13), R9 // a6
MOVV syscall15Args_a7(R13), R10 // a7
MOVV syscall15Args_a8(R13), R11 // a8
// push a9-a15 onto stack
MOVV syscall15Args_a9(R13), R12
MOVV R12, 0(R3)
MOVV syscall15Args_a10(R13), R12
MOVV R12, 8(R3)
MOVV syscall15Args_a11(R13), R12
MOVV R12, 16(R3)
MOVV syscall15Args_a12(R13), R12
MOVV R12, 24(R3)
MOVV syscall15Args_a13(R13), R12
MOVV R12, 32(R3)
MOVV syscall15Args_a14(R13), R12
MOVV R12, 40(R3)
MOVV syscall15Args_a15(R13), R12
MOVV R12, 48(R3)
MOVV syscall15Args_fn(R13), R12
JAL (R12)
// pop structure pointer
MOVV PTR_ADDRESS(R3), R13
ADDV $STACK_SIZE, R3
// save R4, R5
MOVV R4, syscall15Args_a1(R13)
MOVV R5, syscall15Args_a2(R13)
// save f0-f3
MOVD F0, syscall15Args_f1(R13)
MOVD F1, syscall15Args_f2(R13)
MOVD F2, syscall15Args_f3(R13)
MOVD F3, syscall15Args_f4(R13)
RET
+1 -1
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors // SPDX-FileCopyrightText: 2023 The Ebitengine Authors
//go:build darwin || freebsd || linux //go:build darwin || freebsd || linux || netbsd
#include "textflag.h" #include "textflag.h"
#include "go_asm.h" #include "go_asm.h"
+75
View File
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2025 The Ebitengine Authors
//go:build linux
#include "textflag.h"
#include "go_asm.h"
#include "funcdata.h"
#include "abi_loong64.h"
TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
NO_LOCAL_POINTERS
SUBV $(16*8), R3, R14
MOVD F0, 0(R14)
MOVD F1, 8(R14)
MOVD F2, 16(R14)
MOVD F3, 24(R14)
MOVD F4, 32(R14)
MOVD F5, 40(R14)
MOVD F6, 48(R14)
MOVD F7, 56(R14)
MOVV R4, 64(R14)
MOVV R5, 72(R14)
MOVV R6, 80(R14)
MOVV R7, 88(R14)
MOVV R8, 96(R14)
MOVV R9, 104(R14)
MOVV R10, 112(R14)
MOVV R11, 120(R14)
// Adjust SP by frame size.
SUBV $(22*8), R3
// It is important to save R30 because the go assembler
// uses it for move instructions for a variable.
// This line:
// MOVV ·callbackWrap_call(SB), R4
// Creates the instructions:
// PCALAU12I off1(PC), R30
// MOVV off2(R30), R4
// R30 is a callee saved register so we are responsible
// for ensuring its value doesn't change. So save it and
// restore it at the end of this function.
// R1 is the link register. crosscall2 doesn't save it
// so it's saved here.
MOVV R1, 0(R3)
MOVV R30, 8(R3)
// Create a struct callbackArgs on our stack.
MOVV $(callbackArgs__size)(R3), R13
MOVV R12, callbackArgs_index(R13) // callback index
MOVV R14, callbackArgs_args(R13) // address of args vector
MOVV $0, callbackArgs_result(R13) // result
// Move parameters into registers
// Get the ABIInternal function pointer
// without <ABIInternal> by using a closure.
MOVV ·callbackWrap_call(SB), R4
MOVV (R4), R4 // fn unsafe.Pointer
MOVV R13, R5 // frame (&callbackArgs{...})
MOVV $0, R7 // ctxt uintptr
JAL crosscall2(SB)
// Get callback result.
MOVV $(callbackArgs__size)(R3), R13
MOVV callbackArgs_result(R13), R4
// Restore LR and R30
MOVV 0(R3), R1
MOVV 8(R3), R30
ADDV $(22*8), R3
RET
+19 -3
View File
@@ -1,19 +1,35 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux || windows //go:build darwin || freebsd || linux || netbsd || windows
package purego package purego
// CDecl marks a function as being called using the __cdecl calling convention as defined in
// the [MSDocs] when passed to NewCallback. It must be the first argument to the function.
// This is only useful on 386 Windows, but it is safe to use on other platforms.
//
// [MSDocs]: https://learn.microsoft.com/en-us/cpp/cpp/cdecl?view=msvc-170
type CDecl struct{}
const ( const (
maxArgs = 15 maxArgs = 15
numOfFloats = 8 // arm64 and amd64 both have 8 float registers numOfFloatRegisters = 8 // arm64 and amd64 both have 8 float registers
) )
type syscall15Args struct {
fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr
f1, f2, f3, f4, f5, f6, f7, f8 uintptr
arm64_r8 uintptr
}
// SyscallN takes fn, a C function pointer and a list of arguments as uintptr. // SyscallN takes fn, a C function pointer and a list of arguments as uintptr.
// There is an internal maximum number of arguments that SyscallN can take. It panics // There is an internal maximum number of arguments that SyscallN can take. It panics
// when the maximum is exceeded. It returns the result and the libc error code if there is one. // when the maximum is exceeded. It returns the result and the libc error code if there is one.
// //
// In order to call this function properly make sure to follow all the rules specified in [unsafe.Pointer]
// especially point 4.
//
// NOTE: SyscallN does not properly call functions that have both integer and float parameters. // NOTE: SyscallN does not properly call functions that have both integer and float parameters.
// See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607 // See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607
// for an explanation of why that is. // for an explanation of why that is.
+3 -13
View File
@@ -1,31 +1,21 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build cgo && !(amd64 || arm64) //go:build cgo && !(amd64 || arm64 || loong64)
package purego package purego
import ( import (
_ "unsafe" // for go:linkname
"github.com/ebitengine/purego/internal/cgo" "github.com/ebitengine/purego/internal/cgo"
) )
var syscall15XABI0 = uintptr(cgo.Syscall15XABI0) var syscall15XABI0 = uintptr(cgo.Syscall15XABI0)
// this is only here to make the assembly files happy :)
type syscall15Args struct {
fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr
f1, f2, f3, f4, f5, f6, f7, f8 uintptr
r1, r2, err uintptr
arm64_r8 uintptr
}
//go:nosplit //go:nosplit
func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) {
return cgo.Syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) return cgo.Syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15)
} }
func NewCallback(_ interface{}) uintptr { func NewCallback(_ any) uintptr {
panic("purego: NewCallback on Linux is only supported on amd64/arm64") panic("purego: NewCallback on Linux is only supported on amd64/arm64/loong64")
} }
+34 -17
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || (linux && (amd64 || arm64)) //go:build darwin || freebsd || (linux && (amd64 || arm64 || loong64)) || netbsd
package purego package purego
@@ -14,20 +14,17 @@ import (
var syscall15XABI0 uintptr var syscall15XABI0 uintptr
type syscall15Args struct {
fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr
f1, f2, f3, f4, f5, f6, f7, f8 uintptr
arm64_r8 uintptr
}
//go:nosplit
func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) {
args := syscall15Args{ args := thePool.Get().(*syscall15Args)
defer thePool.Put(args)
*args = syscall15Args{
fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15,
a1, a2, a3, a4, a5, a6, a7, a8, a1, a2, a3, a4, a5, a6, a7, a8,
0, 0,
} }
runtime_cgocall(syscall15XABI0, unsafe.Pointer(&args))
runtime_cgocall(syscall15XABI0, unsafe.Pointer(args))
return args.a1, args.a2, 0 return args.a1, args.a2, 0
} }
@@ -37,7 +34,17 @@ func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a
// of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory allocated // of uintptr. Only a limited number of callbacks may be created in a single Go process, and any memory allocated
// for these callbacks is never released. At least 2000 callbacks can always be created. Although this function // for these callbacks is never released. At least 2000 callbacks can always be created. Although this function
// provides similar functionality to windows.NewCallback it is distinct. // provides similar functionality to windows.NewCallback it is distinct.
func NewCallback(fn interface{}) uintptr { func NewCallback(fn any) uintptr {
ty := reflect.TypeOf(fn)
for i := 0; i < ty.NumIn(); i++ {
in := ty.In(i)
if !in.AssignableTo(reflect.TypeOf(CDecl{})) {
continue
}
if i != 0 {
panic("purego: CDecl must be the first argument")
}
}
return compileCallback(fn) return compileCallback(fn)
} }
@@ -67,7 +74,7 @@ type callbackArgs struct {
result uintptr result uintptr
} }
func compileCallback(fn interface{}) uintptr { func compileCallback(fn any) uintptr {
val := reflect.ValueOf(fn) val := reflect.ValueOf(fn)
if val.Kind() != reflect.Func { if val.Kind() != reflect.Func {
panic("purego: the type must be a function but was not") panic("purego: the type must be a function but was not")
@@ -79,7 +86,12 @@ func compileCallback(fn interface{}) uintptr {
for i := 0; i < ty.NumIn(); i++ { for i := 0; i < ty.NumIn(); i++ {
in := ty.In(i) in := ty.In(i)
switch in.Kind() { switch in.Kind() {
case reflect.Struct, reflect.Interface, reflect.Func, reflect.Slice, case reflect.Struct:
if i == 0 && in.AssignableTo(reflect.TypeOf(CDecl{})) {
continue
}
fallthrough
case reflect.Interface, reflect.Func, reflect.Slice,
reflect.Chan, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Complex64, reflect.Complex128,
reflect.String, reflect.Map, reflect.Invalid: reflect.String, reflect.Map, reflect.Invalid:
panic("purego: unsupported argument type: " + in.Kind().String()) panic("purego: unsupported argument type: " + in.Kind().String())
@@ -137,25 +149,30 @@ func callbackWrap(a *callbackArgs) {
var intsN int // intsN represents the number of integer arguments processed var intsN int // intsN represents the number of integer arguments processed
// stack points to the index into frame of the current stack element. // stack points to the index into frame of the current stack element.
// The stack begins after the float and integer registers. // The stack begins after the float and integer registers.
stack := numOfIntegerRegisters() + numOfFloats stack := numOfIntegerRegisters() + numOfFloatRegisters
for i := range args { for i := range args {
var pos int var pos int
switch fnType.In(i).Kind() { switch fnType.In(i).Kind() {
case reflect.Float32, reflect.Float64: case reflect.Float32, reflect.Float64:
if floatsN >= numOfFloats { if floatsN >= numOfFloatRegisters {
pos = stack pos = stack
stack++ stack++
} else { } else {
pos = floatsN pos = floatsN
} }
floatsN++ floatsN++
case reflect.Struct:
// This is the CDecl field
args[i] = reflect.Zero(fnType.In(i))
continue
default: default:
if intsN >= numOfIntegerRegisters() { if intsN >= numOfIntegerRegisters() {
pos = stack pos = stack
stack++ stack++
} else { } else {
// the integers begin after the floats in frame // the integers begin after the floats in frame
pos = intsN + numOfFloats pos = intsN + numOfFloatRegisters
} }
intsN++ intsN++
} }
@@ -200,7 +217,7 @@ func callbackasmAddr(i int) uintptr {
panic("purego: unsupported architecture") panic("purego: unsupported architecture")
case "386", "amd64": case "386", "amd64":
entrySize = 5 entrySize = 5
case "arm", "arm64": case "arm", "arm64", "loong64":
// On ARM and ARM64, each entry is a MOV instruction // On ARM and ARM64, each entry is a MOV instruction
// followed by a branch instruction // followed by a branch instruction
entrySize = 8 entrySize = 8
+18 -17
View File
@@ -4,20 +4,12 @@
package purego package purego
import ( import (
"reflect"
"syscall" "syscall"
_ "unsafe" // only for go:linkname
"golang.org/x/sys/windows"
) )
var syscall15XABI0 uintptr var syscall15XABI0 uintptr
type syscall15Args struct {
fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr
f1, f2, f3, f4, f5, f6, f7, f8 uintptr
arm64_r8 uintptr
}
func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) { func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 uintptr) (r1, r2, err uintptr) {
r1, r2, errno := syscall.Syscall15(fn, 15, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) r1, r2, errno := syscall.Syscall15(fn, 15, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15)
return r1, r2, uintptr(errno) return r1, r2, uintptr(errno)
@@ -30,16 +22,25 @@ func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a
// allocated for these callbacks is never released. Between NewCallback and NewCallbackCDecl, at least 1024 // allocated for these callbacks is never released. Between NewCallback and NewCallbackCDecl, at least 1024
// callbacks can always be created. Although this function is similiar to the darwin version it may act // callbacks can always be created. Although this function is similiar to the darwin version it may act
// differently. // differently.
func NewCallback(fn interface{}) uintptr { func NewCallback(fn any) uintptr {
isCDecl := false
ty := reflect.TypeOf(fn)
for i := 0; i < ty.NumIn(); i++ {
in := ty.In(i)
if !in.AssignableTo(reflect.TypeOf(CDecl{})) {
continue
}
if i != 0 {
panic("purego: CDecl must be the first argument")
}
isCDecl = true
}
if isCDecl {
return syscall.NewCallbackCDecl(fn)
}
return syscall.NewCallback(fn) return syscall.NewCallback(fn)
} }
//go:linkname openLibrary openLibrary
func openLibrary(name string) (uintptr, error) {
handle, err := windows.LoadLibrary(name)
return uintptr(handle), err
}
func loadSymbol(handle uintptr, name string) (uintptr, error) { func loadSymbol(handle uintptr, name string) (uintptr, error) {
return windows.GetProcAddress(windows.Handle(handle), name) return syscall.GetProcAddress(syscall.Handle(handle), name)
} }
+2002 -2002
View File
File diff suppressed because it is too large Load Diff
+4002 -4002
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
CommentPragmas: '^go:build'
+5
View File
@@ -7,3 +7,8 @@
.vscode .vscode
go.work go.work
go.work.sum go.work.sum
*.fxc
!dummy.fxc
*.metallib
!dummy.metallib
+131 -2
View File
@@ -24,7 +24,7 @@ Ebitengine (formerly known as Ebiten) is an open source game engine for the Go p
* [iOS](https://ebitengine.org/en/documents/mobile.html) * [iOS](https://ebitengine.org/en/documents/mobile.html)
* [WebAssembly](https://ebitengine.org/en/documents/webassembly.html) * [WebAssembly](https://ebitengine.org/en/documents/webassembly.html)
* Nintendo Switch * Nintendo Switch
* Xbox * Xbox (Xbox support is limited and not available to everyone. Negotiations are currently underway to make it accessible to all.)
For installation on desktops, see [the installation instruction](https://ebitengine.org/en/documents/install.html). For installation on desktops, see [the installation instruction](https://ebitengine.org/en/documents/install.html).
@@ -60,4 +60,133 @@ For installation on desktops, see [the installation instruction](https://ebiteng
Ebitengine is licensed under Apache license version 2.0. See [LICENSE](LICENSE) file. Ebitengine is licensed under Apache license version 2.0. See [LICENSE](LICENSE) file.
[The Ebitengine logo](https://ebitengine.org/images/logo.png) by Hajime Hoshi is licensed under [the Creative Commons Attribution-NoDerivatives 4.0](https://creativecommons.org/licenses/by-nd/4.0/). [The Ebitengine logo](https://ebitengine.org/images/logo.png) by Hajime Hoshi is licensed under [the Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/).
### GLFW
https://github.com/glfw/glfw
```
Copyright (c) 2002-2006 Marcus Geelnard
Copyright (c) 2006-2019 Camilla Löwy
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would
be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not
be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
```
### Go
https://cs.opensource.google/go/go
```
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
```
### go-gl/gl
https://github.com/go-gl/gl
```
The MIT License (MIT)
Copyright (c) 2014 Eric Woroshow
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
### go-gl/glfw
https://github.com/go-gl/glfw
```
Copyright (c) 2012 The glfw3-go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
```
+68 -104
View File
@@ -179,6 +179,33 @@ func (b BlendFactor) internalBlendFactor(source bool) graphicsdriver.BlendFactor
} }
} }
func internalBlendFactorToBlendFactor(blendFactor graphicsdriver.BlendFactor) BlendFactor {
switch blendFactor {
case graphicsdriver.BlendFactorZero:
return BlendFactorZero
case graphicsdriver.BlendFactorOne:
return BlendFactorOne
case graphicsdriver.BlendFactorSourceColor:
return BlendFactorSourceColor
case graphicsdriver.BlendFactorOneMinusSourceColor:
return BlendFactorOneMinusSourceColor
case graphicsdriver.BlendFactorSourceAlpha:
return BlendFactorSourceAlpha
case graphicsdriver.BlendFactorOneMinusSourceAlpha:
return BlendFactorOneMinusSourceAlpha
case graphicsdriver.BlendFactorDestinationColor:
return BlendFactorDestinationColor
case graphicsdriver.BlendFactorOneMinusDestinationColor:
return BlendFactorOneMinusDestinationColor
case graphicsdriver.BlendFactorDestinationAlpha:
return BlendFactorDestinationAlpha
case graphicsdriver.BlendFactorOneMinusDestinationAlpha:
return BlendFactorOneMinusDestinationAlpha
default:
panic(fmt.Sprintf("ebiten: invalid blend factor: %d", blendFactor))
}
}
// BlendOperation is an operation for source and destination color values. // BlendOperation is an operation for source and destination color values.
type BlendOperation byte type BlendOperation byte
@@ -233,6 +260,34 @@ func (b BlendOperation) internalBlendOperation() graphicsdriver.BlendOperation {
} }
} }
func internalBlendOperationToBlendOperation(blendOperation graphicsdriver.BlendOperation) BlendOperation {
switch blendOperation {
case graphicsdriver.BlendOperationAdd:
return BlendOperationAdd
case graphicsdriver.BlendOperationSubtract:
return BlendOperationSubtract
case graphicsdriver.BlendOperationReverseSubtract:
return BlendOperationReverseSubtract
case graphicsdriver.BlendOperationMin:
return BlendOperationMin
case graphicsdriver.BlendOperationMax:
return BlendOperationMax
default:
panic(fmt.Sprintf("ebiten: invalid blend operation: %d", blendOperation))
}
}
func internalBlendToBlend(blend graphicsdriver.Blend) Blend {
return Blend{
BlendFactorSourceRGB: internalBlendFactorToBlendFactor(blend.BlendFactorSourceRGB),
BlendFactorSourceAlpha: internalBlendFactorToBlendFactor(blend.BlendFactorSourceAlpha),
BlendFactorDestinationRGB: internalBlendFactorToBlendFactor(blend.BlendFactorDestinationRGB),
BlendFactorDestinationAlpha: internalBlendFactorToBlendFactor(blend.BlendFactorDestinationAlpha),
BlendOperationRGB: internalBlendOperationToBlendOperation(blend.BlendOperationRGB),
BlendOperationAlpha: internalBlendOperationToBlendOperation(blend.BlendOperationAlpha),
}
}
// This name convention follows CSS compositing: https://drafts.fxtf.org/compositing-2/. // This name convention follows CSS compositing: https://drafts.fxtf.org/compositing-2/.
// //
// In the comments, // In the comments,
@@ -242,169 +297,78 @@ var (
// //
// c_out = c_src + c_dst × (1 - α_src) // c_out = c_src + c_dst × (1 - α_src)
// α_out = α_src + α_dst × (1 - α_src) // α_out = α_src + α_dst × (1 - α_src)
BlendSourceOver = Blend{ BlendSourceOver = internalBlendToBlend(graphicsdriver.BlendSourceOver)
BlendFactorSourceRGB: BlendFactorOne,
BlendFactorSourceAlpha: BlendFactorOne,
BlendFactorDestinationRGB: BlendFactorOneMinusSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorOneMinusSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendClear is a preset Blend for Porter Duff's 'clear'. // BlendClear is a preset Blend for Porter Duff's 'clear'.
// //
// c_out = 0 // c_out = 0
// α_out = 0 // α_out = 0
BlendClear = Blend{ BlendClear = internalBlendToBlend(graphicsdriver.BlendClear)
BlendFactorSourceRGB: BlendFactorZero,
BlendFactorSourceAlpha: BlendFactorZero,
BlendFactorDestinationRGB: BlendFactorZero,
BlendFactorDestinationAlpha: BlendFactorZero,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendCopy is a preset Blend for Porter Duff's 'copy'. // BlendCopy is a preset Blend for Porter Duff's 'copy'.
// //
// c_out = c_src // c_out = c_src
// α_out = α_src // α_out = α_src
BlendCopy = Blend{ BlendCopy = internalBlendToBlend(graphicsdriver.BlendCopy)
BlendFactorSourceRGB: BlendFactorOne,
BlendFactorSourceAlpha: BlendFactorOne,
BlendFactorDestinationRGB: BlendFactorZero,
BlendFactorDestinationAlpha: BlendFactorZero,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendDestination is a preset Blend for Porter Duff's 'destination'. // BlendDestination is a preset Blend for Porter Duff's 'destination'.
// //
// c_out = c_dst // c_out = c_dst
// α_out = α_dst // α_out = α_dst
BlendDestination = Blend{ BlendDestination = internalBlendToBlend(graphicsdriver.BlendDestination)
BlendFactorSourceRGB: BlendFactorZero,
BlendFactorSourceAlpha: BlendFactorZero,
BlendFactorDestinationRGB: BlendFactorOne,
BlendFactorDestinationAlpha: BlendFactorOne,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendDestinationOver is a preset Blend for Porter Duff's 'destination-over'. // BlendDestinationOver is a preset Blend for Porter Duff's 'destination-over'.
// //
// c_out = c_src × (1 - α_dst) + c_dst // c_out = c_src × (1 - α_dst) + c_dst
// α_out = α_src × (1 - α_dst) + α_dst // α_out = α_src × (1 - α_dst) + α_dst
BlendDestinationOver = Blend{ BlendDestinationOver = internalBlendToBlend(graphicsdriver.BlendDestinationOver)
BlendFactorSourceRGB: BlendFactorOneMinusDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorOneMinusDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorOne,
BlendFactorDestinationAlpha: BlendFactorOne,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendSourceIn is a preset Blend for Porter Duff's 'source-in'. // BlendSourceIn is a preset Blend for Porter Duff's 'source-in'.
// //
// c_out = c_src × α_dst // c_out = c_src × α_dst
// α_out = α_src × α_dst // α_out = α_src × α_dst
BlendSourceIn = Blend{ BlendSourceIn = internalBlendToBlend(graphicsdriver.BlendSourceIn)
BlendFactorSourceRGB: BlendFactorDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorZero,
BlendFactorDestinationAlpha: BlendFactorZero,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendDestinationIn is a preset Blend for Porter Duff's 'destination-in'. // BlendDestinationIn is a preset Blend for Porter Duff's 'destination-in'.
// //
// c_out = c_dst × α_src // c_out = c_dst × α_src
// α_out = α_dst × α_src // α_out = α_dst × α_src
BlendDestinationIn = Blend{ BlendDestinationIn = internalBlendToBlend(graphicsdriver.BlendDestinationIn)
BlendFactorSourceRGB: BlendFactorZero,
BlendFactorSourceAlpha: BlendFactorZero,
BlendFactorDestinationRGB: BlendFactorSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendSourceOut is a preset Blend for Porter Duff's 'source-out'. // BlendSourceOut is a preset Blend for Porter Duff's 'source-out'.
// //
// c_out = c_src × (1 - α_dst) // c_out = c_src × (1 - α_dst)
// α_out = α_src × (1 - α_dst) // α_out = α_src × (1 - α_dst)
BlendSourceOut = Blend{ BlendSourceOut = internalBlendToBlend(graphicsdriver.BlendSourceOut)
BlendFactorSourceRGB: BlendFactorOneMinusDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorOneMinusDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorZero,
BlendFactorDestinationAlpha: BlendFactorZero,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendDestinationOut is a preset Blend for Porter Duff's 'destination-out'. // BlendDestinationOut is a preset Blend for Porter Duff's 'destination-out'.
// //
// c_out = c_dst × (1 - α_src) // c_out = c_dst × (1 - α_src)
// α_out = α_dst × (1 - α_src) // α_out = α_dst × (1 - α_src)
BlendDestinationOut = Blend{ BlendDestinationOut = internalBlendToBlend(graphicsdriver.BlendDestinationOut)
BlendFactorSourceRGB: BlendFactorZero,
BlendFactorSourceAlpha: BlendFactorZero,
BlendFactorDestinationRGB: BlendFactorOneMinusSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorOneMinusSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendSourceAtop is a preset Blend for Porter Duff's 'source-atop'. // BlendSourceAtop is a preset Blend for Porter Duff's 'source-atop'.
// //
// c_out = c_src × α_dst + c_dst × (1 - α_src) // c_out = c_src × α_dst + c_dst × (1 - α_src)
// α_out = α_src × α_dst + α_dst × (1 - α_src) // α_out = α_src × α_dst + α_dst × (1 - α_src)
BlendSourceAtop = Blend{ BlendSourceAtop = internalBlendToBlend(graphicsdriver.BlendSourceAtop)
BlendFactorSourceRGB: BlendFactorDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorOneMinusSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorOneMinusSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendDestinationAtop is a preset Blend for Porter Duff's 'destination-atop'. // BlendDestinationAtop is a preset Blend for Porter Duff's 'destination-atop'.
// //
// c_out = c_src × (1 - α_dst) + c_dst × α_src // c_out = c_src × (1 - α_dst) + c_dst × α_src
// α_out = α_src × (1 - α_dst) + α_dst × α_src // α_out = α_src × (1 - α_dst) + α_dst × α_src
BlendDestinationAtop = Blend{ BlendDestinationAtop = internalBlendToBlend(graphicsdriver.BlendDestinationAtop)
BlendFactorSourceRGB: BlendFactorOneMinusDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorOneMinusDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendXor is a preset Blend for Porter Duff's 'xor'. // BlendXor is a preset Blend for Porter Duff's 'xor'.
// //
// c_out = c_src × (1 - α_dst) + c_dst × (1 - α_src) // c_out = c_src × (1 - α_dst) + c_dst × (1 - α_src)
// α_out = α_src × (1 - α_dst) + α_dst × (1 - α_src) // α_out = α_src × (1 - α_dst) + α_dst × (1 - α_src)
BlendXor = Blend{ BlendXor = internalBlendToBlend(graphicsdriver.BlendXor)
BlendFactorSourceRGB: BlendFactorOneMinusDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorOneMinusDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorOneMinusSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorOneMinusSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendLighter is a preset Blend for Porter Duff's 'lighter'. // BlendLighter is a preset Blend for Porter Duff's 'lighter'.
// This is sum of source and destination (a.k.a. 'plus' or 'additive') // This is sum of source and destination (a.k.a. 'plus' or 'additive')
// //
// c_out = c_src + c_dst // c_out = c_src + c_dst
// α_out = α_src + α_dst // α_out = α_src + α_dst
BlendLighter = Blend{ BlendLighter = internalBlendToBlend(graphicsdriver.BlendLighter)
BlendFactorSourceRGB: BlendFactorOne,
BlendFactorSourceAlpha: BlendFactorOne,
BlendFactorDestinationRGB: BlendFactorOne,
BlendFactorDestinationAlpha: BlendFactorOne,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
) )
+1 -1
View File
@@ -20,7 +20,7 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/affine" "github.com/hajimehoshi/ebiten/v2/internal/affine"
) )
// ColorMDim is a dimension of a ColorM. // ColorMDim is the dimension of a ColorM.
// //
// Deprecated: as of v2.5. Use the colorm package instead. // Deprecated: as of v2.5. Use the colorm package instead.
const ColorMDim = affine.ColorMDim const ColorMDim = affine.ColorMDim
+23 -5
View File
@@ -19,17 +19,17 @@ import (
) )
// CursorModeType represents a render and coordinate mode of a mouse cursor. // CursorModeType represents a render and coordinate mode of a mouse cursor.
type CursorModeType = ui.CursorMode type CursorModeType int
// CursorModeTypes // CursorModeTypes
const ( const (
CursorModeVisible CursorModeType = ui.CursorModeVisible CursorModeVisible CursorModeType = CursorModeType(ui.CursorModeVisible)
CursorModeHidden CursorModeType = ui.CursorModeHidden CursorModeHidden CursorModeType = CursorModeType(ui.CursorModeHidden)
CursorModeCaptured CursorModeType = ui.CursorModeCaptured CursorModeCaptured CursorModeType = CursorModeType(ui.CursorModeCaptured)
) )
// CursorShapeType represents a shape of a mouse cursor. // CursorShapeType represents a shape of a mouse cursor.
type CursorShapeType = ui.CursorShape type CursorShapeType int
// CursorShapeTypes // CursorShapeTypes
const ( const (
@@ -44,3 +44,21 @@ const (
CursorShapeMove CursorShapeType = CursorShapeType(ui.CursorShapeMove) CursorShapeMove CursorShapeType = CursorShapeType(ui.CursorShapeMove)
CursorShapeNotAllowed CursorShapeType = CursorShapeType(ui.CursorShapeNotAllowed) CursorShapeNotAllowed CursorShapeType = CursorShapeType(ui.CursorShapeNotAllowed)
) )
// CursorShape returns the current cursor shape.
//
// CursorShape returns CursorShapeDefault on mobiles.
//
// CursorShape is concurrent-safe.
func CursorShape() CursorShapeType {
return CursorShapeType(ui.Get().CursorShape())
}
// SetCursorShape sets the cursor shape.
//
// If the platform doesn't implement the given shape, the default cursor shape is used.
//
// SetCursorShape is concurrent-safe.
func SetCursorShape(shape CursorShapeType) {
ui.Get().SetCursorShape(ui.CursorShape(shape))
}
-5
View File
@@ -93,11 +93,6 @@
// The option "featurelevel" is valid only for DirectX 12. // The option "featurelevel" is valid only for DirectX 12.
// The possible values are "11_0", "11_1", "12_0", "12_1", and "12_2". The default value is "11_0". // The possible values are "11_0", "11_1", "12_0", "12_1", and "12_2". The default value is "11_0".
// //
// `EBITENGINE_OPENGL` environment variable specifies various parameters for OpenGL.
// You can specify multiple values separated by a comma. The default value is empty (i.e. no parameters).
//
// "es": Use OpenGL ES. Without this, OpenGL and OpenGL ES are automatically chosen.
//
// # Build tags // # Build tags
// //
// `ebitenginedebug` outputs a log of graphics commands. This is useful to know what happens in Ebitengine. In general, the // `ebitenginedebug` outputs a log of graphics commands. This is useful to know what happens in Ebitengine. In general, the
+26 -57
View File
@@ -15,59 +15,27 @@
package ebiten package ebiten
import ( import (
"fmt"
"image" "image"
"math" "math"
"sync/atomic" "sync/atomic"
"github.com/hajimehoshi/ebiten/v2/internal/atlas" "github.com/hajimehoshi/ebiten/v2/internal/atlas"
"github.com/hajimehoshi/ebiten/v2/internal/inputstate"
"github.com/hajimehoshi/ebiten/v2/internal/ui" "github.com/hajimehoshi/ebiten/v2/internal/ui"
) )
const screenShaderSrc = `//kage:unit pixels var screenFilterEnabled atomic.Bool
package main func init() {
screenFilterEnabled.Store(true)
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
// Blend source colors in a square region, which size is 1/scale.
scale := imageDstSize()/imageSrc0Size()
pos := srcPos
p0 := pos - 1/2.0/scale
p1 := pos + 1/2.0/scale
// Texels must be in the source rect, so it is not necessary to check.
c0 := imageSrc0UnsafeAt(p0)
c1 := imageSrc0UnsafeAt(vec2(p1.x, p0.y))
c2 := imageSrc0UnsafeAt(vec2(p0.x, p1.y))
c3 := imageSrc0UnsafeAt(p1)
// p is the p1 value in one pixel assuming that the pixel's upper-left is (0, 0) and the lower-right is (1, 1).
rate := clamp(fract(p1)*scale, 0, 1)
return mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y)
}
`
var screenFilterEnabled = int32(1)
func isScreenFilterEnabled() bool {
return atomic.LoadInt32(&screenFilterEnabled) != 0
}
func setScreenFilterEnabled(enabled bool) {
v := int32(0)
if enabled {
v = 1
}
atomic.StoreInt32(&screenFilterEnabled, v)
} }
type gameForUI struct { type gameForUI struct {
game Game game Game
offscreen *Image offscreen *Image
screen *Image screen *Image
screenShader *Shader imageDumper imageDumper
imageDumper imageDumper transparent bool
transparent bool
} }
func newGameForUI(game Game, transparent bool) *gameForUI { func newGameForUI(game Game, transparent bool) *gameForUI {
@@ -75,13 +43,6 @@ func newGameForUI(game Game, transparent bool) *gameForUI {
game: game, game: game,
transparent: transparent, transparent: transparent,
} }
s, err := NewShader([]byte(screenShaderSrc))
if err != nil {
panic(fmt.Sprintf("ebiten: compiling the screen shader failed: %v", err))
}
g.screenShader = s
return g return g
} }
@@ -129,13 +90,12 @@ func (g *gameForUI) Layout(outsideWidth, outsideHeight float64) (float64, float6
outsideHeight = 1 outsideHeight = 1
} }
// TODO: Add a new Layout function taking float values (#2285).
sw, sh := g.game.Layout(int(outsideWidth), int(outsideHeight)) sw, sh := g.game.Layout(int(outsideWidth), int(outsideHeight))
return float64(sw), float64(sh) return float64(sw), float64(sh)
} }
func (g *gameForUI) UpdateInputState(fn func(*ui.InputState)) { func (g *gameForUI) UpdateInputState(fn func(*ui.InputState)) {
theInputState.update(fn) inputstate.Get().Update(fn)
} }
func (g *gameForUI) Update() error { func (g *gameForUI) Update() error {
@@ -166,21 +126,30 @@ func (g *gameForUI) DrawFinalScreen(scale, offsetX, offsetY float64) {
return return
} }
DefaultDrawFinalScreen(g.screen, g.offscreen, geoM)
}
// DefaultDrawFinalScreen is the default implementation of [FinalScreenDrawer.DrawFinalScreen],
// used when a [Game] doesn't implement [FinalScreenDrawer].
//
// You can use DefaultDrawFinalScreen when you need the default implementation of [FinalScreenDrawer.DrawFinalScreen]
// in your implementation of [FinalScreenDrawer], for example.
func DefaultDrawFinalScreen(screen FinalScreen, offscreen *Image, geoM GeoM) {
scale := geoM.Element(0, 0)
switch { switch {
case !isScreenFilterEnabled(), math.Floor(scale) == scale: case !screenFilterEnabled.Load(), math.Floor(scale) == scale:
op := &DrawImageOptions{} op := &DrawImageOptions{}
op.GeoM = geoM op.GeoM = geoM
g.screen.DrawImage(g.offscreen, op) screen.DrawImage(offscreen, op)
case scale < 1: case scale < 1:
op := &DrawImageOptions{} op := &DrawImageOptions{}
op.GeoM = geoM op.GeoM = geoM
op.Filter = FilterLinear op.Filter = FilterLinear
g.screen.DrawImage(g.offscreen, op) screen.DrawImage(offscreen, op)
default: default:
op := &DrawRectShaderOptions{} op := &DrawImageOptions{}
op.Images[0] = g.offscreen
op.GeoM = geoM op.GeoM = geoM
w, h := g.offscreen.Bounds().Dx(), g.offscreen.Bounds().Dy() op.Filter = FilterPixelated
g.screen.DrawRectShader(w, h, g.screenShader, op) screen.DrawImage(offscreen, op)
} }
} }
+1 -1
View File
@@ -121,7 +121,7 @@ func (g *GeoM) Translate(tx, ty float64) {
g.ty += ty g.ty += ty
} }
// Rotate rotates the matrix by theta. // Rotate rotates the matrix clockwise by theta.
// The unit is radian. // The unit is radian.
func (g *GeoM) Rotate(theta float64) { func (g *GeoM) Rotate(theta float64) {
if theta == 0 { if theta == 0 {
+26
View File
@@ -15,6 +15,7 @@
package ebiten package ebiten
import ( import (
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
"github.com/hajimehoshi/ebiten/v2/internal/builtinshader" "github.com/hajimehoshi/ebiten/v2/internal/builtinshader"
"github.com/hajimehoshi/ebiten/v2/internal/ui" "github.com/hajimehoshi/ebiten/v2/internal/ui"
) )
@@ -28,6 +29,10 @@ const (
// FilterLinear represents linear filter // FilterLinear represents linear filter
FilterLinear Filter = Filter(builtinshader.FilterLinear) FilterLinear Filter = Filter(builtinshader.FilterLinear)
// FilterPixelated represents a pixelated filter.
// FilterPixelated is similar to FilterNearest, but it preserves the pixelated appearance even when scaled to non-integer sizes.
FilterPixelated Filter = Filter(builtinshader.FilterPixelated)
) )
// GraphicsLibrary represents graphics libraries supported by the engine. // GraphicsLibrary represents graphics libraries supported by the engine.
@@ -66,9 +71,30 @@ var _ [GraphicsLibraryAuto]int = [0]int{}
type DebugInfo struct { type DebugInfo struct {
// GraphicsLibrary represents the graphics library currently in use. // GraphicsLibrary represents the graphics library currently in use.
GraphicsLibrary GraphicsLibrary GraphicsLibrary GraphicsLibrary
// TotalGPUImageMemoryUsageInBytes is the total image memory usage for GPU in bytes.
// TotalGPUImageMemoryUsageInBytes is approximately the total memory usage for GPU.
TotalGPUImageMemoryUsageInBytes int64
} }
// ReadDebugInfo writes debug info (e.g. current graphics library) into a provided struct. // ReadDebugInfo writes debug info (e.g. current graphics library) into a provided struct.
//
// ReadDebugInfo is concurrent-safe.
func ReadDebugInfo(d *DebugInfo) { func ReadDebugInfo(d *DebugInfo) {
d.GraphicsLibrary = GraphicsLibrary(ui.Get().GraphicsLibrary()) d.GraphicsLibrary = GraphicsLibrary(ui.Get().GraphicsLibrary())
d.TotalGPUImageMemoryUsageInBytes = atlas.TotalGPUImageMemoryUsageInBytes()
} }
// ColorSpace represents the color space of the screen.
type ColorSpace int
const (
// ColorSpaceDefault represents the default color space.
ColorSpaceDefault ColorSpace = iota
// ColorSpaceSRGB represents the sRGB color space (https://en.wikipedia.org/wiki/SRGB).
ColorSpaceSRGB
// ColorSpaceDisplayP3 represents the Display P3 color space (https://en.wikipedia.org/wiki/DCI-P3).
ColorSpaceDisplayP3
)
+513 -75
View File
@@ -18,6 +18,11 @@ import (
"fmt" "fmt"
"image" "image"
"image/color" "image/color"
"math"
"slices"
"sync"
"sync/atomic"
"unsafe"
"github.com/hajimehoshi/ebiten/v2/internal/affine" "github.com/hajimehoshi/ebiten/v2/internal/affine"
"github.com/hajimehoshi/ebiten/v2/internal/atlas" "github.com/hajimehoshi/ebiten/v2/internal/atlas"
@@ -25,6 +30,7 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand" "github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/restorable"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir" "github.com/hajimehoshi/ebiten/v2/internal/shaderir"
"github.com/hajimehoshi/ebiten/v2/internal/ui" "github.com/hajimehoshi/ebiten/v2/internal/ui"
) )
@@ -43,19 +49,56 @@ type Image struct {
// tmpVertices must not be reused until ui.Image.Draw* is called. // tmpVertices must not be reused until ui.Image.Draw* is called.
tmpVertices []float32 tmpVertices []float32
// tmpIndices must not be reused until ui.Image.Draw* is called.
tmpIndices []uint32
// tmpUniforms must not be reused until ui.Image.Draw* is called. // tmpUniforms must not be reused until ui.Image.Draw* is called.
tmpUniforms []uint32 tmpUniforms []uint32
// subImageCache is a cache for sub-images.
// subImageCache is valid only when the image is not a sub-image.
subImageCache map[image.Rectangle]*Image
// subImageGCLastTick is the last tick when old sub images are removed from the cache.
subImageGCLastTick int64
// subImageCacheM is a mutex for subImageCache.
// subImageCache can be accessed from the image and its sub-images at the same time,
// so the map must be protected by a mutex.
subImageCacheM sync.Mutex
// atime is the last access time.
// atime needs to be an atomic value since a sub-image atime can be accessed from its original image.
atime atomic.Int64
// usageCallbacks are callbacks that are invoked when the image is used.
// usageCallbacks is valid only when the image is not a sub-image.
usageCallbacks map[int64]usageCallback
// inUsageCallbacks reports whether the image is in usageCallbacks.
inUsageCallbacks atomic.Bool
// usageCallbacksM is a mutex for usageCallbacks.
usageCallbacksM sync.Mutex
// Do not add a 'buffering' member that are resolved lazily. // Do not add a 'buffering' member that are resolved lazily.
// This tends to forget resolving the buffer easily (#2362). // This tends to forget resolving the buffer easily (#2362).
} }
type usageCallback struct {
fn func(image *Image)
}
func (i *Image) copyCheck() { func (i *Image) copyCheck() {
if i.addr != i { if i.addr != i {
panic("ebiten: illegal use of non-zero Image copied by value") panic("ebiten: illegal use of non-zero Image copied by value")
} }
} }
func (i *Image) updateAccessTime() {
i.atime.Store(Tick())
}
// Size returns the size of the image. // Size returns the size of the image.
// //
// Deprecated: as of v2.5. Use Bounds().Dx() and Bounds().Dy() or Bounds().Size() instead. // Deprecated: as of v2.5. Use Bounds().Dx() and Bounds().Dy() or Bounds().Size() instead.
@@ -88,6 +131,10 @@ func (i *Image) Fill(clr color.Color) {
return return
} }
i.invokeUsageCallbacks()
i.updateAccessTime()
var crf, cgf, cbf, caf float32 var crf, cgf, cbf, caf float32
cr, cg, cb, ca := clr.RGBA() cr, cg, cb, ca := clr.RGBA()
crf = float32(cr) / 0xffff crf = float32(cr) / 0xffff
@@ -97,11 +144,11 @@ func (i *Image) Fill(clr color.Color) {
i.image.Fill(crf, cgf, cbf, caf, i.adjustedBounds()) i.image.Fill(crf, cgf, cbf, caf, i.adjustedBounds())
} }
func canSkipMipmap(geom GeoM, filter builtinshader.Filter) bool { func canSkipMipmap(det float32, filter builtinshader.Filter) bool {
if filter != builtinshader.FilterLinear { if filter != builtinshader.FilterLinear {
return true return true
} }
return geom.det2x2() >= 0.999 return math.Abs(float64(det)) >= 0.999
} }
// DrawImageOptions represents options for DrawImage. // DrawImageOptions represents options for DrawImage.
@@ -139,6 +186,16 @@ type DrawImageOptions struct {
// Filter is a type of texture filter. // Filter is a type of texture filter.
// The default (zero) value is FilterNearest. // The default (zero) value is FilterNearest.
Filter Filter Filter Filter
// DisableMipmaps disables mipmaps.
// When Filter is FilterLinear and GeoM shrinks the image, mipmaps are used by default.
// Mipmap is useful to render a shrunk image with high quality.
// However, mipmaps can be expensive, especially on mobiles.
// When DisableMipmaps is true, mipmap is not used.
// When Filter is not FilterLinear, DisableMipmaps is ignored.
//
// The default (zero) value is false.
DisableMipmaps bool
} }
// adjustPosition converts the position in the *ebiten.Image coordinate to the *ui.Image coordinate. // adjustPosition converts the position in the *ebiten.Image coordinate to the *ui.Image coordinate.
@@ -220,6 +277,12 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
return return
} }
i.invokeUsageCallbacks()
img.invokeUsageCallbacks()
i.updateAccessTime()
img.updateAccessTime()
if options == nil { if options == nil {
options = &DrawImageOptions{} options = &DrawImageOptions{}
} }
@@ -237,6 +300,10 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
geoM.Translate(float64(offsetX), float64(offsetY)) geoM.Translate(float64(offsetX), float64(offsetY))
} }
a, b, c, d, tx, ty := geoM.elements32() a, b, c, d, tx, ty := geoM.elements32()
det := a*d - b*c
if det == 0 {
return
}
bounds := img.Bounds() bounds := img.Bounds()
sx0, sy0 := img.adjustPosition(bounds.Min.X, bounds.Min.Y) sx0, sy0 := img.adjustPosition(bounds.Min.X, bounds.Min.Y)
@@ -244,10 +311,10 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
colorm, cr, cg, cb, ca := colorMToScale(options.ColorM.affineColorM()) colorm, cr, cg, cb, ca := colorMToScale(options.ColorM.affineColorM())
cr, cg, cb, ca = options.ColorScale.apply(cr, cg, cb, ca) cr, cg, cb, ca = options.ColorScale.apply(cr, cg, cb, ca)
vs := i.ensureTmpVertices(4 * graphics.VertexFloatCount) vs := i.ensureTmpVertices(4 * graphics.VertexFloatCount)
graphics.QuadVertices(vs, float32(sx0), float32(sy0), float32(sx1), float32(sy1), a, b, c, d, tx, ty, cr, cg, cb, ca) graphics.QuadVerticesFromSrcAndMatrix(vs, float32(sx0), float32(sy0), float32(sx1), float32(sy1), a, b, c, d, tx, ty, cr, cg, cb, ca)
is := graphics.QuadIndices() is := graphics.QuadIndices()
srcs := [graphics.ShaderImageCount]*ui.Image{img.image} srcs := [graphics.ShaderSrcImageCount]*ui.Image{img.image}
useColorM := !colorm.IsIdentity() useColorM := !colorm.IsIdentity()
shader := builtinShader(filter, builtinshader.AddressUnsafe, useColorM) shader := builtinShader(filter, builtinshader.AddressUnsafe, useColorM)
@@ -262,7 +329,36 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
}) })
} }
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillAll, canSkipMipmap(geoM, filter), false) dr := i.adjustedBounds()
hint := restorable.HintNone
if overwritesDstRegion(options.Blend, dr, geoM, sx0, sy0, sx1, sy1) {
hint = restorable.HintOverwriteDstRegion
}
skipMipmap := options.DisableMipmaps
if !skipMipmap {
skipMipmap = canSkipMipmap(det, filter)
}
i.image.DrawTriangles(srcs, vs, is, blend, dr, [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRuleFillAll, skipMipmap, false, hint)
}
// overwritesDstRegion reports whether the given parameters overwrite the destination region completely.
func overwritesDstRegion(blend Blend, dstRegion image.Rectangle, geoM GeoM, sx0, sy0, sx1, sy1 int) bool {
// TODO: More precisely, BlendFactorDestinationRGB, BlendFactorDestinationAlpha, and operations should be checked.
if blend != BlendCopy && blend != BlendClear {
return false
}
// Check the result vertices is not a rotated rectangle.
if geoM.b != 0 || geoM.c != 0 {
return false
}
// Check the result vertices completely covers dstRegion.
x0, y0 := geoM.Apply(float64(sx0), float64(sy0))
x1, y1 := geoM.Apply(float64(sx1), float64(sy1))
if float64(dstRegion.Min.X) < x0 || float64(dstRegion.Min.Y) < y0 || float64(dstRegion.Max.X) > x1 || float64(dstRegion.Max.Y) > y1 {
return false
}
return true
} }
// Vertex represents a vertex passed to DrawTriangles. // Vertex represents a vertex passed to DrawTriangles.
@@ -274,6 +370,10 @@ type Vertex struct {
// SrcX and SrcY represents a point on a source image. // SrcX and SrcY represents a point on a source image.
// Be careful that SrcX/SrcY coordinates are on the image's bounds. // Be careful that SrcX/SrcY coordinates are on the image's bounds.
// This means that an upper-left point of a sub-image might not be (0, 0). // This means that an upper-left point of a sub-image might not be (0, 0).
//
// Before passing vertices to a Kage shader, SrcX/SrcY are converted to texture coordinates of the first image,
// which is DrawRectShaderOptions.Image[0] or DrawTrianglesShaderOptions.Images[0].
// If the image is nil, SrcX/SrcY are not converted and used as-is.
SrcX float32 SrcX float32
SrcY float32 SrcY float32
@@ -291,8 +391,20 @@ type Vertex struct {
ColorG float32 ColorG float32
ColorB float32 ColorB float32
ColorA float32 ColorA float32
// Custom0/Custom1/Custom2/Custom3 represents general-purpose values passed to the shader.
// In order to use them, Fragment must have an additional vec4 argument.
//
// These values are valid only when DrawTrianglesShader is used.
// In other cases, these values are ignored.
Custom0 float32
Custom1 float32
Custom2 float32
Custom3 float32
} }
var _ [0]byte = [unsafe.Sizeof(Vertex{}) - unsafe.Sizeof(float32(0))*graphics.VertexFloatCount]byte{}
// Address represents a sampler address mode. // Address represents a sampler address mode.
type Address int type Address int
@@ -308,19 +420,46 @@ const (
) )
// FillRule is the rule whether an overlapped region is rendered with DrawTriangles(Shader). // FillRule is the rule whether an overlapped region is rendered with DrawTriangles(Shader).
//
// Deprecated: as of v2.9.
type FillRule int type FillRule int
const (
// FillRuleFillAll indicates all the triangles are rendered regardless of overlaps.
//
// Deprecated: as of v2.9.
FillRuleFillAll FillRule = FillRule(graphicsdriver.FillRuleFillAll)
// FillRuleNonZero means that triangles are rendered based on the non-zero rule.
// If and only if the number of overlaps is not 0, the region is rendered.
//
// Deprecated: as of v2.9.
FillRuleNonZero FillRule = FillRule(graphicsdriver.FillRuleNonZero)
// FillRuleEvenOdd means that triangles are rendered based on the even-odd rule.
// If and only if the number of overlaps is odd, the region is rendered.
//
// Deprecated: as of v2.9.
FillRuleEvenOdd FillRule = FillRule(graphicsdriver.FillRuleEvenOdd)
)
const ( const (
// FillAll indicates all the triangles are rendered regardless of overlaps. // FillAll indicates all the triangles are rendered regardless of overlaps.
FillAll FillRule = FillRule(graphicsdriver.FillAll) //
// Deprecated: as of v2.8. Use FillRuleFillAll instead.
FillAll = FillRuleFillAll
// NonZero means that triangles are rendered based on the non-zero rule. // NonZero means that triangles are rendered based on the non-zero rule.
// If and only if the number of overlaps is not 0, the region is rendered. // If and only if the number of overlaps is not 0, the region is rendered.
NonZero FillRule = FillRule(graphicsdriver.NonZero) //
// Deprecated: as of v2.8. Use FillRuleNonZero instead.
NonZero = FillRuleNonZero
// EvenOdd means that triangles are rendered based on the even-odd rule. // EvenOdd means that triangles are rendered based on the even-odd rule.
// If and only if the number of overlaps is odd, the region is rendered. // If and only if the number of overlaps is odd, the region is rendered.
EvenOdd FillRule = FillRule(graphicsdriver.EvenOdd) //
// Deprecated: as of v2.8. Use FillRuleEvenOdd instead.
EvenOdd = FillRuleEvenOdd
) )
// ColorScaleMode is the mode of color scales in vertices. // ColorScaleMode is the mode of color scales in vertices.
@@ -371,11 +510,13 @@ type DrawTrianglesOptions struct {
// FillRule indicates the rule how an overlapped region is rendered. // FillRule indicates the rule how an overlapped region is rendered.
// //
// The rules NonZero and EvenOdd are useful when you want to render a complex polygon. // The rules FillRuleNonZero and FillRuleEvenOdd are useful when you want to render a complex polygon.
// A complex polygon is a non-convex polygon like a concave polygon, a polygon with holes, or a self-intersecting polygon. // A complex polygon is a non-convex polygon like a concave polygon, a polygon with holes, or a self-intersecting polygon.
// See examples/vector for actual usages. // See examples/vector for actual usages.
// //
// The default (zero) value is FillAll. // The default (zero) value is FillRuleFillAll.
//
// Deprecated: as of v2.9. Use [github.com/hajimehoshi/ebiten/v2/vector.FillPath] instead.
FillRule FillRule FillRule FillRule
// AntiAlias indicates whether the rendering uses anti-alias or not. // AntiAlias indicates whether the rendering uses anti-alias or not.
@@ -384,8 +525,20 @@ type DrawTrianglesOptions struct {
// AntiAlias increases internal draw calls and might affect performance. // AntiAlias increases internal draw calls and might affect performance.
// Use the build tag `ebitenginedebug` to check the number of draw calls if you care. // Use the build tag `ebitenginedebug` to check the number of draw calls if you care.
// //
// The default (zero) value is false. // The default (zero) value is false.//
//
// Deprecated: as of v2.9. Use [github.com/hajimehoshi/ebiten/v2/vector.FillPath] instead.
AntiAlias bool AntiAlias bool
// DisableMipmaps disables mipmaps.
// When Filter is FilterLinear and GeoM shrinks the image, mipmaps are used by default.
// Mipmap is useful to render a shrunk image with high quality.
// However, mipmaps can be expensive, especially on mobiles.
// When DisableMipmaps is true, mipmap is not used.
// When Filter is not FilterLinear, DisableMipmaps is ignored.
//
// The default (zero) value is false.
DisableMipmaps bool
} }
// MaxIndicesCount is the maximum number of indices for DrawTriangles and DrawTrianglesShader. // MaxIndicesCount is the maximum number of indices for DrawTriangles and DrawTrianglesShader.
@@ -428,6 +581,36 @@ const MaxVertexCount = graphicscommand.MaxVertexCount
// //
// When the image i is disposed, DrawTriangles does nothing. // When the image i is disposed, DrawTriangles does nothing.
func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, options *DrawTrianglesOptions) { func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, options *DrawTrianglesOptions) {
is := i.ensureTmpIndices(len(indices))
for i := range is {
is[i] = uint32(indices[i])
}
i.DrawTriangles32(vertices, is, img, options)
}
// DrawTriangles32 draws triangles with the specified vertices and their indices.
// DrawTriangles32 is the version of DrawTriangles with uint32 indices.
//
// img is used as a source image. img cannot be nil.
// If you want to draw triangles with a solid color, use a small white image
// and adjust the color elements in the vertices. For an actual implementation,
// see the example 'vector'.
//
// Vertex contains color values, which are interpreted as straight-alpha colors by default.
// This depends on the option's ColorScaleMode.
//
// If len(vertices) is more than MaxVertexCount, the exceeding part is ignored.
//
// If len(indices) is not multiple of 3, DrawTriangles32 panics.
//
// If a value in indices is out of range of vertices, or not less than MaxVertexCount, DrawTriangles32 panics.
//
// The rule in which DrawTriangles32 works effectively is same as DrawImage's.
//
// When the given image is disposed, DrawTriangles32 panics.
//
// When the image i is disposed, DrawTriangles32 does nothing.
func (i *Image) DrawTriangles32(vertices []Vertex, indices []uint32, img *Image, options *DrawTrianglesOptions) {
i.copyCheck() i.copyCheck()
if img != nil && img.isDisposed() { if img != nil && img.isDisposed() {
@@ -437,6 +620,16 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
return return
} }
if len(indices) == 0 {
return
}
i.invokeUsageCallbacks()
img.invokeUsageCallbacks()
img.updateAccessTime()
i.updateAccessTime()
if len(vertices) > graphicscommand.MaxVertexCount { if len(vertices) > graphicscommand.MaxVertexCount {
// The last part cannot be specified by indices. Just omit them. // The last part cannot be specified by indices. Just omit them.
vertices = vertices[:graphicscommand.MaxVertexCount] vertices = vertices[:graphicscommand.MaxVertexCount]
@@ -469,38 +662,40 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
vs := i.ensureTmpVertices(len(vertices) * graphics.VertexFloatCount) vs := i.ensureTmpVertices(len(vertices) * graphics.VertexFloatCount)
dst := i dst := i
if options.ColorScaleMode == ColorScaleModeStraightAlpha { if options.ColorScaleMode == ColorScaleModeStraightAlpha {
for i, v := range vertices { // Avoid using `for i, v := range vertices` as adding `v` creates a copy from `vertices` unnecessarily on each loop (#3103).
dx, dy := dst.adjustPositionF32(v.DstX, v.DstY) for i := range vertices {
vs[i*graphics.VertexFloatCount] = dx // Create a temporary slice to reduce boundary checks.
vs[i*graphics.VertexFloatCount+1] = dy vs := vs[i*graphics.VertexFloatCount : i*graphics.VertexFloatCount+8]
sx, sy := img.adjustPositionF32(v.SrcX, v.SrcY) dx, dy := dst.adjustPositionF32(vertices[i].DstX, vertices[i].DstY)
vs[i*graphics.VertexFloatCount+2] = sx vs[0] = dx
vs[i*graphics.VertexFloatCount+3] = sy vs[1] = dy
vs[i*graphics.VertexFloatCount+4] = v.ColorR * v.ColorA * cr sx, sy := img.adjustPositionF32(vertices[i].SrcX, vertices[i].SrcY)
vs[i*graphics.VertexFloatCount+5] = v.ColorG * v.ColorA * cg vs[2] = sx
vs[i*graphics.VertexFloatCount+6] = v.ColorB * v.ColorA * cb vs[3] = sy
vs[i*graphics.VertexFloatCount+7] = v.ColorA * ca vs[4] = vertices[i].ColorR * vertices[i].ColorA * cr
vs[5] = vertices[i].ColorG * vertices[i].ColorA * cg
vs[6] = vertices[i].ColorB * vertices[i].ColorA * cb
vs[7] = vertices[i].ColorA * ca
} }
} else { } else {
for i, v := range vertices { // See comment above (#3103).
dx, dy := dst.adjustPositionF32(v.DstX, v.DstY) for i := range vertices {
vs[i*graphics.VertexFloatCount] = dx // Create a temporary slice to reduce boundary checks.
vs[i*graphics.VertexFloatCount+1] = dy vs := vs[i*graphics.VertexFloatCount : i*graphics.VertexFloatCount+8]
sx, sy := img.adjustPositionF32(v.SrcX, v.SrcY) dx, dy := dst.adjustPositionF32(vertices[i].DstX, vertices[i].DstY)
vs[i*graphics.VertexFloatCount+2] = sx vs[0] = dx
vs[i*graphics.VertexFloatCount+3] = sy vs[1] = dy
vs[i*graphics.VertexFloatCount+4] = v.ColorR * cr sx, sy := img.adjustPositionF32(vertices[i].SrcX, vertices[i].SrcY)
vs[i*graphics.VertexFloatCount+5] = v.ColorG * cg vs[2] = sx
vs[i*graphics.VertexFloatCount+6] = v.ColorB * cb vs[3] = sy
vs[i*graphics.VertexFloatCount+7] = v.ColorA * ca vs[4] = vertices[i].ColorR * cr
vs[5] = vertices[i].ColorG * cg
vs[6] = vertices[i].ColorB * cb
vs[7] = vertices[i].ColorA * ca
} }
} }
is := make([]uint32, len(indices))
for i := range is {
is[i] = uint32(indices[i])
}
srcs := [graphics.ShaderImageCount]*ui.Image{img.image} srcs := [graphics.ShaderSrcImageCount]*ui.Image{img.image}
useColorM := !colorm.IsIdentity() useColorM := !colorm.IsIdentity()
shader := builtinShader(filter, address, useColorM) shader := builtinShader(filter, address, useColorM)
@@ -515,7 +710,11 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
}) })
} }
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), filter != builtinshader.FilterLinear, options.AntiAlias) skipMipmap := options.DisableMipmaps
if !skipMipmap {
skipMipmap = filter != builtinshader.FilterLinear
}
i.image.DrawTriangles(srcs, vs, indices, blend, i.adjustedBounds(), [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), skipMipmap, options.AntiAlias, restorable.HintNone)
} }
// DrawTrianglesShaderOptions represents options for DrawTrianglesShader. // DrawTrianglesShaderOptions represents options for DrawTrianglesShader.
@@ -533,7 +732,7 @@ type DrawTrianglesShaderOptions struct {
// Uniforms is a set of uniform variables for the shader. // Uniforms is a set of uniform variables for the shader.
// The keys are the names of the uniform variables. // The keys are the names of the uniform variables.
// The values must be a numeric type, or a slice or an array of a numeric type. // The values must be a numeric/boolean type, or a slice or an array of a numeric/boolean type.
// If the uniform variable type is an array, a vector or a matrix, // If the uniform variable type is an array, a vector or a matrix,
// you have to specify linearly flattened values as a slice or an array. // you have to specify linearly flattened values as a slice or an array.
// For example, if the uniform variable type is [4]vec4, the length will be 16. // For example, if the uniform variable type is [4]vec4, the length will be 16.
@@ -542,16 +741,19 @@ type DrawTrianglesShaderOptions struct {
Uniforms map[string]any Uniforms map[string]any
// Images is a set of the source images. // Images is a set of the source images.
// All the images' sizes must be the same. // In the texel mode, all the image sizes must be the same.
// The pixel mode allows images of different sizes.
Images [4]*Image Images [4]*Image
// FillRule indicates the rule how an overlapped region is rendered. // FillRule indicates the rule how an overlapped region is rendered.
// //
// The rules NonZero and EvenOdd are useful when you want to render a complex polygon. // The rules FillRuleNonZero and FillRuleEvenOdd are useful when you want to render a complex polygon.
// A complex polygon is a non-convex polygon like a concave polygon, a polygon with holes, or a self-intersecting polygon. // A complex polygon is a non-convex polygon like a concave polygon, a polygon with holes, or a self-intersecting polygon.
// See examples/vector for actual usages. // See examples/vector for actual usages.
// //
// The default (zero) value is FillAll. // The default (zero) value is FillRuleFillAll.
//
// Deprecated: as of v2.9. Use [github.com/hajimehoshi/ebiten/v2/vector.FillPath] instead.
FillRule FillRule FillRule FillRule
// AntiAlias indicates whether the rendering uses anti-alias or not. // AntiAlias indicates whether the rendering uses anti-alias or not.
@@ -561,11 +763,13 @@ type DrawTrianglesShaderOptions struct {
// Use the build tag `ebitenginedebug` to check the number of draw calls if you care. // Use the build tag `ebitenginedebug` to check the number of draw calls if you care.
// //
// The default (zero) value is false. // The default (zero) value is false.
//
// Deprecated: as of v2.9. Use [github.com/hajimehoshi/ebiten/v2/vector.FillPath] instead.
AntiAlias bool AntiAlias bool
} }
// Check the number of images. // Check the number of images.
var _ [len(DrawTrianglesShaderOptions{}.Images) - graphics.ShaderImageCount]struct{} = [0]struct{}{} var _ [len(DrawTrianglesShaderOptions{}.Images) - graphics.ShaderSrcImageCount]struct{} = [0]struct{}{}
// DrawTrianglesShader draws triangles with the specified vertices and their indices with the specified shader. // DrawTrianglesShader draws triangles with the specified vertices and their indices with the specified shader.
// //
@@ -592,6 +796,39 @@ var _ [len(DrawTrianglesShaderOptions{}.Images) - graphics.ShaderImageCount]stru
// //
// When the image i is disposed, DrawTrianglesShader does nothing. // When the image i is disposed, DrawTrianglesShader does nothing.
func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader *Shader, options *DrawTrianglesShaderOptions) { func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader *Shader, options *DrawTrianglesShaderOptions) {
is := i.ensureTmpIndices(len(indices))
for i := range is {
is[i] = uint32(indices[i])
}
i.DrawTrianglesShader32(vertices, is, shader, options)
}
// DrawTrianglesShader32 draws triangles with the specified vertices and their indices with the specified shader.
// DrawTrianglesShader32 is the version of DrawTrianglesShader with uint32 indices.
//
// Vertex contains color values, which can be interpreted for any purpose by the shader.
//
// For the details about the shader, see https://ebitengine.org/en/documents/shader.html.
//
// If the shader unit is texels, one of the specified image is non-nil and its size is different from (width, height),
// DrawTrianglesShader32 panics.
// If one of the specified image is non-nil and is disposed, DrawTrianglesShader32 panics.
//
// If len(vertices) is more than MaxVertexCount, the exceeding part is ignored.
//
// If len(indices) is not multiple of 3, DrawTrianglesShader32 panics.
//
// If a value in indices is out of range of vertices, or not less than MaxVertexCount, DrawTrianglesShader32 panics.
//
// When a specified image is non-nil and is disposed, DrawTrianglesShader32 panics.
//
// If a specified uniform variable's length or type doesn't match with an expected one, DrawTrianglesShader32 panics.
//
// Even if a result is an invalid color as a premultiplied-alpha color, i.e. an alpha value exceeds other color values,
// the value is kept and is not clamped.
//
// When the image i is disposed, DrawTrianglesShader32 does nothing.
func (i *Image) DrawTrianglesShader32(vertices []Vertex, indices []uint32, shader *Shader, options *DrawTrianglesShaderOptions) {
i.copyCheck() i.copyCheck()
if i.isDisposed() { if i.isDisposed() {
@@ -602,6 +839,30 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
panic("ebiten: the given shader to DrawTrianglesShader must not be disposed") panic("ebiten: the given shader to DrawTrianglesShader must not be disposed")
} }
if len(indices) == 0 {
return
}
i.invokeUsageCallbacks()
if options != nil {
for _, img := range options.Images {
if img == nil {
continue
}
img.invokeUsageCallbacks()
}
}
if options != nil {
for _, img := range options.Images {
if img == nil {
continue
}
img.updateAccessTime()
}
}
i.updateAccessTime()
if len(vertices) > graphicscommand.MaxVertexCount { if len(vertices) > graphicscommand.MaxVertexCount {
// The last part cannot be specified by indices. Just omit them. // The last part cannot be specified by indices. Just omit them.
vertices = vertices[:graphicscommand.MaxVertexCount] vertices = vertices[:graphicscommand.MaxVertexCount]
@@ -629,28 +890,30 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
vs := i.ensureTmpVertices(len(vertices) * graphics.VertexFloatCount) vs := i.ensureTmpVertices(len(vertices) * graphics.VertexFloatCount)
dst := i dst := i
src := options.Images[0] src := options.Images[0]
for i, v := range vertices { // Avoid using `for i, v := range vertices` as adding `v` creates a copy from `vertices` unnecessarily on each loop (#3103).
dx, dy := dst.adjustPositionF32(v.DstX, v.DstY) for i := range vertices {
vs[i*graphics.VertexFloatCount] = dx // Create a temporary slice to reduce boundary checks.
vs[i*graphics.VertexFloatCount+1] = dy vs := vs[i*graphics.VertexFloatCount : i*graphics.VertexFloatCount+12]
sx, sy := v.SrcX, v.SrcY dx, dy := dst.adjustPositionF32(vertices[i].DstX, vertices[i].DstY)
vs[0] = dx
vs[1] = dy
sx, sy := vertices[i].SrcX, vertices[i].SrcY
if src != nil { if src != nil {
sx, sy = src.adjustPositionF32(sx, sy) sx, sy = src.adjustPositionF32(sx, sy)
} }
vs[i*graphics.VertexFloatCount+2] = sx vs[2] = sx
vs[i*graphics.VertexFloatCount+3] = sy vs[3] = sy
vs[i*graphics.VertexFloatCount+4] = v.ColorR vs[4] = vertices[i].ColorR
vs[i*graphics.VertexFloatCount+5] = v.ColorG vs[5] = vertices[i].ColorG
vs[i*graphics.VertexFloatCount+6] = v.ColorB vs[6] = vertices[i].ColorB
vs[i*graphics.VertexFloatCount+7] = v.ColorA vs[7] = vertices[i].ColorA
vs[8] = vertices[i].Custom0
vs[9] = vertices[i].Custom1
vs[10] = vertices[i].Custom2
vs[11] = vertices[i].Custom3
} }
is := make([]uint32, len(indices)) var imgs [graphics.ShaderSrcImageCount]*ui.Image
for i := range is {
is[i] = uint32(indices[i])
}
var imgs [graphics.ShaderImageCount]*ui.Image
var imgSize image.Point var imgSize image.Point
for i, img := range options.Images { for i, img := range options.Images {
if img == nil { if img == nil {
@@ -672,7 +935,7 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
imgs[i] = img.image imgs[i] = img.image
} }
var srcRegions [graphics.ShaderImageCount]image.Rectangle var srcRegions [graphics.ShaderSrcImageCount]image.Rectangle
for i, img := range options.Images { for i, img := range options.Images {
if img == nil { if img == nil {
continue continue
@@ -683,7 +946,7 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
i.tmpUniforms = i.tmpUniforms[:0] i.tmpUniforms = i.tmpUniforms[:0]
i.tmpUniforms = shader.appendUniforms(i.tmpUniforms, options.Uniforms) i.tmpUniforms = shader.appendUniforms(i.tmpUniforms, options.Uniforms)
i.image.DrawTriangles(imgs, vs, is, blend, i.adjustedBounds(), srcRegions, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), true, options.AntiAlias) i.image.DrawTriangles(imgs, vs, indices, blend, i.adjustedBounds(), srcRegions, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), true, options.AntiAlias, restorable.HintNone)
} }
// DrawRectShaderOptions represents options for DrawRectShader. // DrawRectShaderOptions represents options for DrawRectShader.
@@ -710,7 +973,7 @@ type DrawRectShaderOptions struct {
// Uniforms is a set of uniform variables for the shader. // Uniforms is a set of uniform variables for the shader.
// The keys are the names of the uniform variables. // The keys are the names of the uniform variables.
// The values must be a numeric type, or a slice or an array of a numeric type. // The values must be a numeric/boolean type, or a slice or an array of a numeric/boolean type.
// If the uniform variable type is an array, a vector or a matrix, // If the uniform variable type is an array, a vector or a matrix,
// you have to specify linearly flattened values as a slice or an array. // you have to specify linearly flattened values as a slice or an array.
// For example, if the uniform variable type is [4]vec4, the length will be 16. // For example, if the uniform variable type is [4]vec4, the length will be 16.
@@ -724,7 +987,7 @@ type DrawRectShaderOptions struct {
} }
// Check the number of images. // Check the number of images.
var _ [len(DrawRectShaderOptions{}.Images)]struct{} = [graphics.ShaderImageCount]struct{}{} var _ [len(DrawRectShaderOptions{}.Images)]struct{} = [graphics.ShaderSrcImageCount]struct{}{}
// DrawRectShader draws a rectangle with the specified width and height with the specified shader. // DrawRectShader draws a rectangle with the specified width and height with the specified shader.
// //
@@ -760,6 +1023,26 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR
panic("ebiten: the given shader to DrawRectShader must not be disposed") panic("ebiten: the given shader to DrawRectShader must not be disposed")
} }
if options != nil {
for _, img := range options.Images {
if img == nil {
continue
}
img.invokeUsageCallbacks()
}
}
i.invokeUsageCallbacks()
if options != nil {
for _, img := range options.Images {
if img == nil {
continue
}
img.updateAccessTime()
}
}
i.updateAccessTime()
if options == nil { if options == nil {
options = &DrawRectShaderOptions{} options = &DrawRectShaderOptions{}
} }
@@ -771,7 +1054,7 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR
blend = options.CompositeMode.blend().internalBlend() blend = options.CompositeMode.blend().internalBlend()
} }
var imgs [graphics.ShaderImageCount]*ui.Image var imgs [graphics.ShaderSrcImageCount]*ui.Image
for i, img := range options.Images { for i, img := range options.Images {
if img == nil { if img == nil {
continue continue
@@ -785,7 +1068,7 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR
imgs[i] = img.image imgs[i] = img.image
} }
var srcRegions [graphics.ShaderImageCount]image.Rectangle var srcRegions [graphics.ShaderSrcImageCount]image.Rectangle
for i, img := range options.Images { for i, img := range options.Images {
if img == nil { if img == nil {
if shader.unit == shaderir.Pixels && i == 0 { if shader.unit == shaderir.Pixels && i == 0 {
@@ -803,11 +1086,14 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR
geoM.Translate(float64(offsetX), float64(offsetY)) geoM.Translate(float64(offsetX), float64(offsetY))
} }
a, b, c, d, tx, ty := geoM.elements32() a, b, c, d, tx, ty := geoM.elements32()
if det := a*d - b*c; det == 0 {
return
}
cr, cg, cb, ca := options.ColorScale.elements() cr, cg, cb, ca := options.ColorScale.elements()
vs := i.ensureTmpVertices(4 * graphics.VertexFloatCount) vs := i.ensureTmpVertices(4 * graphics.VertexFloatCount)
// Do not use srcRegions[0].Dx() and srcRegions[0].Dy() as these might be empty. // Do not use srcRegions[0].Dx() and srcRegions[0].Dy() as these might be empty.
graphics.QuadVertices(vs, graphics.QuadVerticesFromSrcAndMatrix(vs,
float32(srcRegions[0].Min.X), float32(srcRegions[0].Min.Y), float32(srcRegions[0].Min.X), float32(srcRegions[0].Min.Y),
float32(srcRegions[0].Min.X+width), float32(srcRegions[0].Min.Y+height), float32(srcRegions[0].Min.X+width), float32(srcRegions[0].Min.Y+height),
a, b, c, d, tx, ty, cr, cg, cb, ca) a, b, c, d, tx, ty, cr, cg, cb, ca)
@@ -816,7 +1102,14 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR
i.tmpUniforms = i.tmpUniforms[:0] i.tmpUniforms = i.tmpUniforms[:0]
i.tmpUniforms = shader.appendUniforms(i.tmpUniforms, options.Uniforms) i.tmpUniforms = shader.appendUniforms(i.tmpUniforms, options.Uniforms)
i.image.DrawTriangles(imgs, vs, is, blend, i.adjustedBounds(), srcRegions, shader.shader, i.tmpUniforms, graphicsdriver.FillAll, true, false) dr := i.adjustedBounds()
hint := restorable.HintNone
// Do not use srcRegions[0].Dx() and srcRegions[0].Dy() as these might be empty.
if overwritesDstRegion(options.Blend, dr, geoM, srcRegions[0].Min.X, srcRegions[0].Min.Y, srcRegions[0].Min.X+width, srcRegions[0].Min.Y+height) {
hint = restorable.HintOverwriteDstRegion
}
i.image.DrawTriangles(imgs, vs, is, blend, dr, srcRegions, shader.shader, i.tmpUniforms, graphicsdriver.FillRuleFillAll, true, false, hint)
} }
// SubImage returns an image representing the portion of the image p visible through r. // SubImage returns an image representing the portion of the image p visible through r.
@@ -838,24 +1131,53 @@ func (i *Image) SubImage(r image.Rectangle) image.Image {
return nil return nil
} }
if i.isSubImage() {
return i.original.SubImage(r.Intersect(i.Bounds()))
}
r = r.Intersect(i.Bounds()) r = r.Intersect(i.Bounds())
// Need to check Empty explicitly. See the standard image package implementations. // Need to check Empty explicitly. See the standard image package implementations.
if r.Empty() { if r.Empty() {
r = image.ZR r = image.Rectangle{}
} }
var orig = i i.subImageCacheM.Lock()
if i.isSubImage() { defer i.subImageCacheM.Unlock()
orig = i.original
// The image might already be disposed in another goroutine.
// Recheck this.
if i.isDisposed() {
return nil
}
if img, ok := i.subImageCache[r]; ok {
img.updateAccessTime()
return img
}
if tick := Tick(); i.subImageGCLastTick < tick {
i.subImageGCLastTick = tick
for _, img := range i.subImageCache {
if img.atime.Load()+60 < tick {
delete(i.subImageCache, img.bounds)
}
}
} }
img := &Image{ img := &Image{
image: i.image, image: i.image,
bounds: r, bounds: r,
original: orig, original: i,
} }
img.addr = img img.addr = img
if i.subImageCache == nil {
i.subImageCache = map[image.Rectangle]*Image{}
}
i.subImageCache[r] = img
img.updateAccessTime()
return img return img
} }
@@ -906,6 +1228,8 @@ func (i *Image) ReadPixels(pixels []byte) {
return return
} }
i.invokeUsageCallbacks()
i.image.ReadPixels(pixels, i.adjustedBounds()) i.image.ReadPixels(pixels, i.adjustedBounds())
} }
@@ -950,6 +1274,8 @@ func (i *Image) at(x, y int) (r, g, b, a byte) {
return 0, 0, 0, 0 return 0, 0, 0, 0
} }
i.invokeUsageCallbacks()
x, y = i.adjustPosition(x, y) x, y = i.adjustPosition(x, y)
var pix [4]byte var pix [4]byte
i.image.ReadPixels(pix[:], image.Rect(x, y, x+1, y+1)) i.image.ReadPixels(pix[:], image.Rect(x, y, x+1, y+1))
@@ -960,15 +1286,24 @@ func (i *Image) at(x, y int) (r, g, b, a byte) {
// //
// Set implements the standard draw.Image's Set. // Set implements the standard draw.Image's Set.
// //
// If (x, y) is outside the image bounds, Set does nothing.
//
// Even if a result is an invalid color as a premultiplied-alpha color, i.e. an alpha value exceeds other color values, // Even if a result is an invalid color as a premultiplied-alpha color, i.e. an alpha value exceeds other color values,
// the value is kept and is not clamped. // the value is kept and is not clamped.
// //
// If the image is disposed, Set does nothing. // If the image is disposed, Set does nothing.
//
// For performance, it is recommended to use WritePixels instead of Set whenever possible.
func (i *Image) Set(x, y int, clr color.Color) { func (i *Image) Set(x, y int, clr color.Color) {
i.copyCheck() i.copyCheck()
if i.isDisposed() { if i.isDisposed() {
return return
} }
i.invokeUsageCallbacks()
i.updateAccessTime()
if !image.Pt(x, y).In(i.Bounds()) { if !image.Pt(x, y).In(i.Bounds()) {
return return
} }
@@ -1003,6 +1338,10 @@ func (i *Image) Dispose() {
} }
i.image.Deallocate() i.image.Deallocate()
i.image = nil i.image = nil
i.subImageCacheM.Lock()
i.subImageCache = nil
i.subImageCacheM.Unlock()
i.usageCallbacks = nil
} }
// Deallocate clears the image and deallocates the internal state of the image. // Deallocate clears the image and deallocates the internal state of the image.
@@ -1026,6 +1365,7 @@ func (i *Image) Deallocate() {
return return
} }
i.image.Deallocate() i.image.Deallocate()
i.usageCallbacks = nil
} }
// WritePixels replaces the pixels of the image. // WritePixels replaces the pixels of the image.
@@ -1048,6 +1388,8 @@ func (i *Image) WritePixels(pixels []byte) {
return return
} }
i.invokeUsageCallbacks()
// Do not need to copy pixels here. // Do not need to copy pixels here.
// * In internal/mipmap, pixels are copied when necessary. // * In internal/mipmap, pixels are copied when necessary.
// * In internal/atlas, pixels are copied to make its paddings. // * In internal/atlas, pixels are copied to make its paddings.
@@ -1110,7 +1452,7 @@ func NewImageWithOptions(bounds image.Rectangle, options *NewImageOptions) *Imag
func newImage(bounds image.Rectangle, imageType atlas.ImageType) *Image { func newImage(bounds image.Rectangle, imageType atlas.ImageType) *Image {
if isRunGameEnded() { if isRunGameEnded() {
panic(fmt.Sprintf("ebiten: NewImage cannot be called after RunGame finishes")) panic("ebiten: NewImage cannot be called after RunGame finishes")
} }
width, height := bounds.Dx(), bounds.Dy() width, height := bounds.Dx(), bounds.Dy()
@@ -1246,6 +1588,102 @@ func (i *Image) ensureTmpVertices(n int) []float32 {
return i.tmpVertices[:n] return i.tmpVertices[:n]
} }
func (i *Image) ensureTmpIndices(n int) []uint32 {
if cap(i.tmpIndices) < n {
i.tmpIndices = make([]uint32, n)
}
return i.tmpIndices[:n]
}
// private implements FinalScreen. // private implements FinalScreen.
func (*Image) private() { func (*Image) private() {
} }
// Do not use usage callbacks except for Ebitengine packages.
// There is no guarantee for compatibility of this function.
var currentCallbackToken atomic.Int64
//go:linkname originalImage
func originalImage(img *Image) *Image {
if img.isSubImage() {
return img.original
}
return img
}
//go:linkname addUsageCallback
func addUsageCallback(img *Image, callback func(image *Image)) int64 {
return img.addUsageCallback(callback)
}
func (i *Image) addUsageCallback(callback func(image *Image)) int64 {
if i.isSubImage() {
return i.original.addUsageCallback(callback)
}
token := currentCallbackToken.Add(1)
i.usageCallbacksM.Lock()
defer i.usageCallbacksM.Unlock()
if i.usageCallbacks == nil {
i.usageCallbacks = map[int64]usageCallback{}
}
i.usageCallbacks[token] = usageCallback{
fn: callback,
}
return token
}
//go:linkname removeUsageCallback
func removeUsageCallback(img *Image, token int64) {
img.removeUsageCallback(token)
}
func (i *Image) removeUsageCallback(token int64) {
if i.isSubImage() {
i.original.removeUsageCallback(token)
return
}
i.usageCallbacksM.Lock()
defer i.usageCallbacksM.Unlock()
delete(i.usageCallbacks, token)
}
var theTmpUsageCallbackSlicePool = sync.Pool{
New: func() any {
slice := make([]usageCallback, 0, 16)
return &slice
},
}
func (i *Image) invokeUsageCallbacks() {
if i.isSubImage() {
i.original.invokeUsageCallbacks()
return
}
// Do not allow recursive calls.
if !i.inUsageCallbacks.CompareAndSwap(false, true) {
return
}
defer i.inUsageCallbacks.Store(false)
tmpUsageCallbackSlice := theTmpUsageCallbackSlicePool.Get().(*[]usageCallback)
func() {
i.usageCallbacksM.Lock()
defer i.usageCallbacksM.Unlock()
for _, cb := range i.usageCallbacks {
*tmpUsageCallbackSlice = append(*tmpUsageCallbackSlice, cb)
}
}()
for _, cb := range *tmpUsageCallbackSlice {
cb.fn(i)
}
*tmpUsageCallbackSlice = slices.Delete(*tmpUsageCallbackSlice, 0, len(*tmpUsageCallbackSlice))
theTmpUsageCallbackSlicePool.Put(tmpUsageCallbackSlice)
}

Some files were not shown because too many files have changed in this diff Show More