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
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 (
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/purego v0.7.0 // indirect
github.com/ebitengine/purego v0.9.0 // indirect
github.com/jezek/xgb v1.1.1 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/sync v0.17.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-20240518074828-e86332849895/go.mod h1:XZdLv05c5hOZm3fM2NlJ92FyEZjnslcMcNRrhxs8+8M=
github.com/ebitengine/gomobile v0.0.0-20250923094054-ea854a63cce1 h1:+kz5iTT3L7uU+VhlMfTb8hHcxLO3TlaELlX8wa4XjA0=
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/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A=
github.com/ebitengine/purego v0.7.0 h1:HPZpl61edMGCEW6XK2nsR6+7AnJ3unUxpTZBkkIXnMc=
github.com/ebitengine/purego v0.7.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
github.com/hajimehoshi/ebiten/v2 v2.7.9 h1:DYH/usAa9dMHcGkBIIEApJsVqDekrJBxYHmsBuly8Iw=
github.com/hajimehoshi/ebiten/v2 v2.7.9/go.mod h1:Ulbq5xDmdx47P24EJ+Mb31Zps7vQq+guieG9mghQUaA=
github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k=
github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/hajimehoshi/ebiten/v2 v2.9.9 h1:JdDag6Ndj12iD4lxQGG8kbsrh7ssj4Sbzth6r929H/M=
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/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/image v0.31.0 h1:mLChjE2MV6g1S7oqbXC0/UcKijjm5fnJLUYKIYrLESA=
golang.org/x/image v0.31.0/go.mod h1:R9ec5Lcp96v9FTF+ajwaH3uGxPH4fKfHHAVbUILxghA=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
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
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
in the documentation and/or other materials provided with the
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
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.
*/
package app // import "github.com/ebitengine/gomobile/app"
package app
+1 -1
View File
@@ -17,7 +17,7 @@
// means it has lost the focus.
//
// 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 (
"fmt"
+1 -1
View File
@@ -5,7 +5,7 @@
// 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.
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.
//
+1 -1
View File
@@ -6,7 +6,7 @@
// orientation of the app's window.
//
// 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 (
"image"
+1 -1
View File
@@ -5,7 +5,7 @@
// Package touch defines an event for touch input.
//
// 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
//
+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
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.
+8 -9
View File
@@ -10,7 +10,6 @@ import (
"debug/pe"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
@@ -18,7 +17,7 @@ import (
"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) {
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 {
case "angle-" + runtime.GOARCH + "/libglesv2.dll":
bytesGLESv2, err = ioutil.ReadAll(tr)
bytesGLESv2, err = io.ReadAll(tr)
case "angle-" + runtime.GOARCH + "/libegl.dll":
bytesEGL, err = ioutil.ReadAll(tr)
bytesEGL, err = io.ReadAll(tr)
case "angle-" + runtime.GOARCH + "/d3dcompiler_47.dll":
bytesD3DCompiler, err = ioutil.ReadAll(tr)
bytesD3DCompiler, err = io.ReadAll(tr)
default: // skip
}
if err != nil {
@@ -70,13 +69,13 @@ func downloadDLLs() (path string, err 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)
}
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)
}
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 nil
@@ -152,7 +151,7 @@ func chromePath() string {
}
for _, installdir := range installdirs {
versiondirs, err := ioutil.ReadDir(installdir)
versiondirs, err := os.ReadDir(installdir)
if err != nil {
continue
}
+1 -1
View File
@@ -42,7 +42,7 @@ error messages. For example,
The gldebug tracing has very high overhead, so make sure to remove
the build tag before deploying any binaries.
*/
package gl // import "github.com/ebitengine/gomobile/gl"
package gl
/*
Implementation details.
+4 -4
View File
@@ -81,7 +81,7 @@ type Context interface {
// http://www.khronos.org/opengles/sdk/docs/man3/html/glBlendFunc.xhtml
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
BlendFuncSeparate(sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha Enum)
@@ -192,7 +192,7 @@ type Context interface {
// http://www.khronos.org/opengles/sdk/docs/man3/html/glGenTextures.xhtml
CreateTexture() Texture
// CreateTVertexArray creates a vertex array.
// CreateVertexArray creates a vertex array.
//
// http://www.khronos.org/opengles/sdk/docs/man3/html/glGenVertexArrays.xhtml
CreateVertexArray() VertexArray
@@ -599,7 +599,7 @@ type Context interface {
// http://www.khronos.org/opengles/sdk/docs/man3/html/glStencilFunc.xhtml
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
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
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
Uniform4iv(dst Uniform, src []int32)
+1 -1
View File
@@ -160,7 +160,7 @@ func (ctx *context) cString(str string) (uintptr, func()) {
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.
func (ctx *context) cStringPtr(str string) (uintptr, func()) {
s, free := ctx.cString(str)
+24 -7
View File
@@ -26,16 +26,31 @@ except for float arguments and return values.
## Supported Platforms
- **FreeBSD**: amd64, arm64
- **Linux**: amd64, arm64
- **macOS / iOS**: amd64, arm64
- **Windows**: 386*, amd64, arm*, arm64
### Tier 1
`*` 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
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
package main
@@ -74,7 +89,7 @@ Then to run: `CGO_ENABLED=0 go run main.go`
## Questions
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
@@ -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:
* `abi_*.h` from package `runtime/cgo`
* `wincallback.go` from package `runtime`
* `zcallback_darwin_*.s` from package `runtime`
* `internal/fakecgo/abi_*.h` 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/setenv.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
`#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-FileCopyrightText: 2022 The Ebitengine Authors
//go:build cgo && (darwin || freebsd || linux)
//go:build cgo && (darwin || freebsd || linux || netbsd)
package purego
+3 -1
View File
@@ -1,11 +1,13 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
//go:build darwin || freebsd || linux
//go:build darwin || freebsd || linux || netbsd
package purego
// 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 {
s string
}
+15 -10
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux
//go:build (darwin || freebsd || linux || netbsd) && !android && !faketime
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
// reference count for the handle will be incremented. Therefore, all
// 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) {
u := fnDlopen(path, mode)
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,
// in the specified library or any of the libraries that were automatically loaded by Dlopen
// 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) {
u := fnDlsym(handle, name)
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.
// If the reference count drops to zero and no other loaded libraries
// 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 {
if fnDlclose(handle) {
return Dlerror{fnDlerror()}
@@ -63,11 +73,6 @@ func Dlclose(handle uintptr) error {
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) {
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.
//go:linkname dlopen dlopen
var dlopen uintptr
var dlopen uint8
var dlopenABI0 = uintptr(unsafe.Pointer(&dlopen))
//go:linkname dlsym dlsym
var dlsym uintptr
var dlsym uint8
var dlsymABI0 = uintptr(unsafe.Pointer(&dlsym))
//go:linkname dlclose dlclose
var dlclose uintptr
var dlclose uint8
var dlcloseABI0 = uintptr(unsafe.Pointer(&dlclose))
//go:linkname dlerror dlerror
var dlerror uintptr
var dlerror uint8
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)
}
+1 -6
View File
@@ -6,7 +6,7 @@ package purego
// Source for constants: https://opensource.apple.com/source/dyld/dyld-360.14/include/dlfcn.h.auto.html
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_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.
@@ -17,8 +17,3 @@ const (
//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"
//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
const (
RTLD_DEFAULT = ^uintptr(0) - 2 // Pseudo-handle for dlsym so search for any loaded symbol
RTLD_LAZY = 0x00001 // Relocations are performed at an implementation-dependent time.
RTLD_NOW = 0x00002 // Relocations are performed when the object is loaded.
RTLD_LOCAL = 0x00000 // All symbols are not made available for relocation processing by other modules.
RTLD_GLOBAL = 0x00100 // All symbols are available for relocation processing of other modules.
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.
)
+2
View File
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build !android
package purego
// 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-FileCopyrightText: 2022 The Ebitengine Authors
//go:build !cgo
//go:build !cgo && !faketime
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-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || !cgo && (freebsd || linux)
//go:build darwin || !cgo && (freebsd || linux || netbsd) && !faketime
#include "textflag.h"
+184 -104
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux || windows
//go:build darwin || freebsd || linux || netbsd || windows
package purego
@@ -10,14 +10,20 @@ import (
"math"
"reflect"
"runtime"
"strconv"
"sync"
"unsafe"
"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).
// 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)
if err != nil {
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}
// 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.
//
// # Memory
@@ -105,7 +111,7 @@ func RegisterLibFunc(fptr interface{}, handle uintptr, name string) {
// defer free(mustFree)
//
// [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()
ty := fn.Type()
if ty.Kind() != reflect.Func {
@@ -117,6 +123,10 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
if cfn == 0 {
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
// to avoid crashing with too many arguments
@@ -126,19 +136,33 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
for i := 0; i < ty.NumIn(); i++ {
arg := ty.In(i)
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,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer, reflect.Slice,
reflect.Func, reflect.Bool:
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Ptr, reflect.UnsafePointer,
reflect.Slice, reflect.Bool:
if ints < numOfIntegerRegisters() {
ints++
} else {
stack++
}
case reflect.Float32, reflect.Float64:
const is32bit = unsafe.Sizeof(uintptr(0)) == 4
if is32bit {
panic("purego: floats only supported on 64bit platforms")
}
if floats < numOfFloats {
if floats < numOfFloatRegisters {
floats++
} else {
stack++
@@ -182,21 +206,8 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
}
}
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
stack := sysargs[numOfIntegerRegisters():]
var floats [numOfFloats]uintptr
var floats [numOfFloatRegisters]uintptr
var numInts int
var numFloats int
var numStack int
@@ -204,7 +215,7 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
if runtime.GOARCH == "arm64" || runtime.GOOS != "windows" {
// Windows arm64 uses the same calling convention as macOS and Linux
addStack = func(x uintptr) {
stack[numStack] = x
sysargs[numOfIntegerRegisters()+numStack] = x
numStack++
}
addInt = func(x uintptr) {
@@ -237,27 +248,145 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
addFloat = addStack
}
var keepAlive []interface{}
var keepAlive []any
defer func() {
runtime.KeepAlive(keepAlive)
runtime.KeepAlive(args)
}()
var syscall syscall15Args
var arm64_r8 uintptr
if ty.NumOut() == 1 && ty.Out(0).Kind() == reflect.Struct {
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)
keepAlive = append(keepAlive, val)
addInt(val.Pointer())
} 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)
keepAlive = append(keepAlive, val)
syscall.arm64_r8 = val.Pointer()
arm64_r8 = val.Pointer()
}
}
}
for _, v := range args {
for i, v := range args {
if variadic, ok := args[i].Interface().([]any); ok {
if i != len(args)-1 {
panic("purego: can only expand last parameter")
}
for _, x := range variadic {
keepAlive = addValue(reflect.ValueOf(x), keepAlive, addInt, addFloat, addStack, &numInts, &numFloats, &numStack)
}
continue
}
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)
}
syscall := thePool.Get().(*syscall15Args)
defer thePool.Put(syscall)
if runtime.GOARCH == "loong64" {
*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],
0,
}
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 {
*syscall = syscall15Args{}
// 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],
sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
sysargs[12], sysargs[13], sysargs[14])
syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support
}
if ty.NumOut() == 0 {
return nil
}
outType := ty.Out(0)
v := reflect.New(outType).Elem()
switch outType.Kind() {
case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
v.SetUint(uint64(syscall.a1))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
v.SetInt(int64(syscall.a1))
case reflect.Bool:
v.SetBool(byte(syscall.a1) != 0)
case reflect.UnsafePointer:
// 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)))
case reflect.Ptr:
v = reflect.NewAt(outType, unsafe.Pointer(&syscall.a1)).Elem()
case reflect.Func:
// wrap this C function in a nicely typed Go function
v = reflect.New(outType)
RegisterFunc(v.Interface(), syscall.a1)
case reflect.String:
v.SetString(strings.GoString(syscall.a1))
case reflect.Float32:
// NOTE: syscall.r2 is only the floating return value on 64bit platforms.
// On 32bit platforms syscall.r2 is the upper part of a 64bit return.
v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
case reflect.Float64:
// NOTE: syscall.r2 is only the floating return value on 64bit platforms.
// On 32bit platforms syscall.r2 is the upper part of a 64bit return.
v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
case reflect.Struct:
v = getStruct(outType, *syscall)
default:
panic("purego: unsupported return kind: " + outType.Kind().String())
}
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)
}
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())
@@ -283,88 +412,44 @@ func RegisterFunc(fptr interface{}, cfn uintptr) {
case reflect.Float64:
addFloat(uintptr(math.Float64bits(v.Float())))
case reflect.Struct:
keepAlive = addStruct(v, &numInts, &numFloats, &numStack, addInt, addFloat, addStack, keepAlive)
keepAlive = addStruct(v, numInts, numFloats, numStack, addInt, addFloat, addStack, keepAlive)
default:
panic("purego: unsupported kind: " + v.Kind().String())
}
}
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],
syscall.arm64_r8,
}
runtime_cgocall(syscall15XABI0, unsafe.Pointer(&syscall))
} else {
// 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],
sysargs[5], sysargs[6], sysargs[7], sysargs[8], sysargs[9], sysargs[10], sysargs[11],
sysargs[12], sysargs[13], sysargs[14])
syscall.f1 = syscall.a2 // on amd64 a2 stores the float return. On 32bit platforms floats aren't support
}
if ty.NumOut() == 0 {
return nil
}
outType := ty.Out(0)
v := reflect.New(outType).Elem()
switch outType.Kind() {
case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
v.SetUint(uint64(syscall.a1))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
v.SetInt(int64(syscall.a1))
case reflect.Bool:
v.SetBool(byte(syscall.a1) != 0)
case reflect.UnsafePointer:
// 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)))
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, runtime_noescape(unsafe.Pointer(&syscall.a1))).Elem()
case reflect.Func:
// wrap this C function in a nicely typed Go function
v = reflect.New(outType)
RegisterFunc(v.Interface(), syscall.a1)
case reflect.String:
v.SetString(strings.GoString(syscall.a1))
case reflect.Float32:
// NOTE: syscall.r2 is only the floating return value on 64bit platforms.
// On 32bit platforms syscall.r2 is the upper part of a 64bit return.
v.SetFloat(float64(math.Float32frombits(uint32(syscall.f1))))
case reflect.Float64:
// NOTE: syscall.r2 is only the floating return value on 64bit platforms.
// On 32bit platforms syscall.r2 is the upper part of a 64bit return.
v.SetFloat(math.Float64frombits(uint64(syscall.f1)))
case reflect.Struct:
v = getStruct(outType, syscall)
default:
panic("purego: unsupported return kind: " + outType.Kind().String())
}
return []reflect.Value{v}
})
fn.Set(v)
return keepAlive
}
// 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
// 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
func isAllSameFloat(ty reflect.Type) bool {
first := ty.Field(0).Type.Kind()
func isAllSameFloat(ty reflect.Type) (allFloats bool, numFields int) {
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 {
return false
allFloats = false
}
for i := 0; i < ty.NumField(); i++ {
f := ty.Field(i)
if f.Type.Kind() != first {
return false
f := ty.Field(i).Type
if f.Kind() == reflect.Struct {
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) {
@@ -386,24 +471,19 @@ func checkStructFieldsSupported(ty reflect.Type) {
}
}
const is32bit = unsafe.Sizeof(uintptr(0)) == 4
func roundUpTo8(val uintptr) uintptr {
return (val + 7) &^ 7
}
func numOfIntegerRegisters() int {
switch runtime.GOARCH {
case "arm64":
case "arm64", "loong64":
return 8
case "amd64":
return 6
// TODO: figure out why 386 tests are not working
/*case "386":
return 0
case "arm":
return 4*/
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-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux || windows
//go:build darwin || freebsd || linux || netbsd || windows
package purego
@@ -11,7 +11,3 @@ import (
//go:linkname runtime_cgocall runtime.cgocall
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-FileCopyrightText: 2024 The Ebitengine Authors
//go:build freebsd || linux
//go:build freebsd || linux || netbsd
package cgo
/*
#cgo LDFLAGS: -ldl
#cgo !netbsd LDFLAGS: -ldl
#include <dlfcn.h>
#include <stdlib.h>
*/
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
// symbol will then be made available to the linker and linked to inside dlfcn.go
var (
+6 -6
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build freebsd || (linux && !(arm64 || amd64))
//go:build freebsd || (linux && !(arm64 || amd64 || loong64)) || netbsd
package cgo
@@ -9,7 +9,7 @@ package cgo
// because Cgo and assembly files can't be in the same package.
/*
#cgo LDFLAGS: -ldl
#cgo !netbsd LDFLAGS: -ldl
#include <stdint.h>
#include <dlfcn.h>
@@ -20,7 +20,7 @@ typedef struct syscall15Args {
uintptr_t fn;
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 r1, r2, err;
uintptr_t err;
} syscall15Args;
void syscall15(struct syscall15Args *args) {
@@ -31,7 +31,7 @@ void syscall15(struct syscall15Args *args) {
*(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,
args->a10,args->a11,args->a12,args->a13,args->a14,args->a15);
args->r1 = r1;
args->a1 = r1;
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(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(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)
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
// license that can be found in the LICENSE file.
//go:build darwin || freebsd || linux
//go:build !cgo && (darwin || freebsd || linux || netbsd)
package fakecgo
+1 -2
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
// 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.
// This allows code that calls into C to function properly when CGO_ENABLED=0.
@@ -30,4 +30,3 @@
package fakecgo
//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
// license that can be found in the LICENSE file.
//go:build !cgo
package fakecgo
import "unsafe"
@@ -2,6 +2,8 @@
// 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"
@@ -2,6 +2,8 @@
// 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"
@@ -2,6 +2,8 @@
// 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"
+10 -4
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux
//go:build !cgo && (darwin || freebsd || linux || netbsd)
package fakecgo
@@ -19,6 +19,7 @@ var (
)
//go:nosplit
//go:norace
func x_cgo_notify_runtime_init_done() {
pthread_mutex_lock(&runtime_init_mu)
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.
// And pthread_key_destructor will dropm when the thread is exiting.
//
//go:norace
func x_cgo_bindm(g unsafe.Pointer) {
// We assume this will always succeed, otherwise, there might be extra M leaking,
// 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
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 {
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
}
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.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)
}
@@ -2,6 +2,8 @@
// 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"
@@ -2,6 +2,8 @@
// 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"
@@ -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-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux
//go:build !cgo && (darwin || freebsd || linux || netbsd)
package fakecgo
+7 -3
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux
//go:build !cgo && (darwin || freebsd || linux || netbsd)
package fakecgo
@@ -27,7 +27,11 @@ func x_cgo_thread_start(arg *ThreadStart) {
println("fakecgo: out of memory in thread_start")
abort()
}
// *ts = *arg would cause a writebarrier so use memmove instead
memmove(unsafe.Pointer(ts), unsafe.Pointer(arg), unsafe.Sizeof(*ts))
// *ts = *arg would cause a writebarrier so copy using slices
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
}
+1 -1
View File
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// 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
// for runtime·iscgo. Override it to tell the runtime we're here.
+5 -1
View File
@@ -1,12 +1,16 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux
//go:build !cgo && (darwin || freebsd || linux || netbsd)
package fakecgo
type (
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
pthread_attr_t [64]byte
pthread_t int
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build !cgo
package fakecgo
type (
@@ -18,3 +20,7 @@ var (
PTHREAD_COND_INITIALIZER = pthread_cond_t{sig: 0x3CB0B1BB}
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-FileCopyrightText: 2022 The Ebitengine Authors
//go:build !cgo
package fakecgo
type (
@@ -12,3 +14,7 @@ var (
PTHREAD_COND_INITIALIZER = pthread_cond_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-FileCopyrightText: 2022 The Ebitengine Authors
//go:build !cgo
package fakecgo
type (
@@ -12,3 +14,7 @@ var (
PTHREAD_COND_INITIALIZER = pthread_cond_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
// license that can be found in the LICENSE file.
//go:build darwin || freebsd || linux
//go:build !cgo && (darwin || freebsd || linux || netbsd)
package fakecgo
+71 -24
View File
@@ -3,7 +3,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux
//go:build !cgo && (darwin || freebsd || linux || netbsd)
package fakecgo
@@ -15,170 +15,217 @@ import (
// setg_trampoline calls setg with the G provided
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
func call5(fn, a1, a2, a3, a4, a5 uintptr) uintptr
//go:nosplit
//go:norace
func malloc(size uintptr) unsafe.Pointer {
ret := call5(mallocABI0, uintptr(size), 0, 0, 0, 0)
// this indirection is to avoid go vet complaining about possible misuse of unsafe.Pointer
return *(*unsafe.Pointer)(unsafe.Pointer(&ret))
}
//go:nosplit
//go:norace
func free(ptr unsafe.Pointer) {
call5(freeABI0, uintptr(ptr), 0, 0, 0, 0)
}
//go:nosplit
//go:norace
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))
}
//go:nosplit
//go:norace
func unsetenv(name *byte) int32 {
return int32(call5(unsetenvABI0, uintptr(unsafe.Pointer(name)), 0, 0, 0, 0))
}
//go:nosplit
//go:norace
func sigfillset(set *sigset_t) int32 {
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 {
return int32(call5(nanosleepABI0, uintptr(unsafe.Pointer(ts)), uintptr(unsafe.Pointer(rem)), 0, 0, 0))
}
//go:nosplit
//go:norace
func abort() {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
return int32(call5(pthread_setspecificABI0, uintptr(key), uintptr(value), 0, 0, 0))
}
//go:linkname _malloc _malloc
var _malloc uintptr
var _malloc uint8
var mallocABI0 = uintptr(unsafe.Pointer(&_malloc))
//go:linkname _free _free
var _free uintptr
var _free uint8
var freeABI0 = uintptr(unsafe.Pointer(&_free))
//go:linkname _setenv _setenv
var _setenv uintptr
var _setenv uint8
var setenvABI0 = uintptr(unsafe.Pointer(&_setenv))
//go:linkname _unsetenv _unsetenv
var _unsetenv uintptr
var _unsetenv uint8
var unsetenvABI0 = uintptr(unsafe.Pointer(&_unsetenv))
//go:linkname _sigfillset _sigfillset
var _sigfillset uintptr
var _sigfillset uint8
var sigfillsetABI0 = uintptr(unsafe.Pointer(&_sigfillset))
//go:linkname _nanosleep _nanosleep
var _nanosleep uintptr
var _nanosleep uint8
var nanosleepABI0 = uintptr(unsafe.Pointer(&_nanosleep))
//go:linkname _abort _abort
var _abort uintptr
var _abort uint8
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
var _pthread_attr_init uintptr
var _pthread_attr_init uint8
var pthread_attr_initABI0 = uintptr(unsafe.Pointer(&_pthread_attr_init))
//go:linkname _pthread_create _pthread_create
var _pthread_create uintptr
var _pthread_create uint8
var pthread_createABI0 = uintptr(unsafe.Pointer(&_pthread_create))
//go:linkname _pthread_detach _pthread_detach
var _pthread_detach uintptr
var _pthread_detach uint8
var pthread_detachABI0 = uintptr(unsafe.Pointer(&_pthread_detach))
//go:linkname _pthread_sigmask _pthread_sigmask
var _pthread_sigmask uintptr
var _pthread_sigmask uint8
var pthread_sigmaskABI0 = uintptr(unsafe.Pointer(&_pthread_sigmask))
//go:linkname _pthread_self _pthread_self
var _pthread_self uintptr
var _pthread_self uint8
var pthread_selfABI0 = uintptr(unsafe.Pointer(&_pthread_self))
//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))
//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))
//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))
//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))
//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))
//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))
//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))
//go:linkname _pthread_setspecific _pthread_setspecific
var _pthread_setspecific uintptr
var _pthread_setspecific uint8
var pthread_setspecificABI0 = uintptr(unsafe.Pointer(&_pthread_setspecific))
@@ -3,6 +3,8 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build !cgo
package fakecgo
//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_nanosleep nanosleep "/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_create pthread_create "/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-FileCopyrightText: 2022 The Ebitengine Authors
//go:build !cgo
package fakecgo
//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_nanosleep nanosleep "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_create pthread_create "libpthread.so"
//go:cgo_import_dynamic purego_pthread_detach pthread_detach "libpthread.so"
@@ -3,6 +3,8 @@
// 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.6"
@@ -12,6 +14,7 @@ package fakecgo
//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_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_create pthread_create "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-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)
+1 -1
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux
//go:build !cgo && (darwin || freebsd || linux)
#include "textflag.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-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux
//go:build !cgo && (darwin || freebsd || linux || netbsd)
#include "textflag.h"
@@ -37,6 +37,10 @@ TEXT _abort(SB), NOSPLIT|NOFRAME, $0-0
JMP purego_abort(SB)
RET
TEXT _sigaltstack(SB), NOSPLIT|NOFRAME, $0-0
JMP purego_sigaltstack(SB)
RET
TEXT _pthread_attr_init(SB), NOSPLIT|NOFRAME, $0-0
JMP purego_pthread_attr_init(SB)
RET
+1 -1
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build !cgo && (darwin || freebsd || linux)
//go:build !cgo && (darwin || freebsd || linux || netbsd)
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
}
+171 -38
View File
@@ -11,21 +11,27 @@ import (
"math"
"reflect"
"regexp"
"runtime"
"unicode"
"unsafe"
"github.com/ebitengine/purego"
"github.com/ebitengine/purego/internal/strings"
)
// TODO: support try/catch?
// https://stackoverflow.com/questions/7062599/example-of-how-objective-cs-try-catch-implementation-is-executed-at-runtime
var (
objc_msgSend_fn uintptr
objc_msgSend func(obj ID, cmd SEL, args ...interface{}) ID
objc_msgSend_stret_fn uintptr
objc_msgSend func(obj ID, cmd SEL, args ...any) ID
objc_msgSendSuper2_fn uintptr
objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...interface{}) ID
objc_msgSendSuper2_stret_fn uintptr
objc_msgSendSuper2 func(super *objc_super, cmd SEL, args ...any) ID
objc_getClass func(name string) Class
objc_getProtocol func(name string) *Protocol
objc_allocateProtocol func(name string) *Protocol
objc_registerProtocol func(protocol *Protocol)
objc_allocateClassPair func(super Class, name string, extraBytes uintptr) Class
objc_registerClassPair func(class Class)
sel_registerName func(name string) SEL
@@ -42,10 +48,22 @@ var (
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() {
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 {
panic(fmt.Errorf("objc: %w", err))
}
@@ -53,6 +71,16 @@ func init() {
if err != nil {
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)
objc_msgSendSuper2_fn, err = purego.Dlsym(objc, "objc_msgSendSuper2")
if err != nil {
@@ -62,6 +90,8 @@ func init() {
purego.RegisterLibFunc(&object_getClass, objc, "object_getClass")
purego.RegisterLibFunc(&objc_getClass, objc, "objc_getClass")
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_registerClassPair, objc, "objc_registerClassPair")
purego.RegisterLibFunc(&sel_registerName, objc, "sel_registerName")
@@ -75,8 +105,21 @@ func init() {
purego.RegisterLibFunc(&ivar_getName, objc, "ivar_getName")
purego.RegisterLibFunc(&protocol_getName, objc, "protocol_getName")
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_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
@@ -90,7 +133,7 @@ func (id ID) Class() Class {
// 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
// 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...)
}
@@ -104,12 +147,22 @@ func (id ID) SetIvar(ivar Ivar, value ID) {
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.
// 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.
func Send[T any](id ID, sel SEL, args ...any) T {
var fn func(id ID, sel SEL, args ...any) T
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...)
}
@@ -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
// instead of a string since RegisterName grabs the global Objective-C lock. It is best to cache the result
// of RegisterName.
func (id ID) SendSuper(sel SEL, args ...interface{}) ID {
func (id ID) SendSuper(sel SEL, args ...any) ID {
super := &objc_super{
receiver: id,
superClass: id.Class(),
@@ -141,7 +194,14 @@ func SendSuper[T any](id ID, sel SEL, args ...any) T {
superClass: id.Class(),
}
var fn func(objcSuper *objc_super, sel SEL, args ...any) T
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...)
}
@@ -163,13 +223,6 @@ func GetClass(name string) Class {
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.
type MethodDef struct {
Cmd SEL
@@ -360,7 +413,7 @@ func encodeType(typ reflect.Type, insidePtr bool) (string, error) {
switch typ {
case reflect.TypeOf(Class(0)):
return encClass, nil
case reflect.TypeOf(ID(0)):
case reflect.TypeOf(ID(0)), reflect.TypeOf(Block(0)):
return encId, nil
case reflect.TypeOf(SEL(0)):
return encSelector, nil
@@ -414,7 +467,7 @@ func encodeType(typ reflect.Type, insidePtr bool) (string, error) {
}
encoding += tmp
}
encoding = encStructEnd
encoding += encStructEnd
return encoding, nil
case reflect.UnsafePointer:
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)
func encodeFunc(fn interface{}) (string, error) {
func encodeFunc(fn any) (string, error) {
typ := reflect.TypeOf(fn)
if typ.Kind() != reflect.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)
}
// 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.
// Returns true if the protocol was added successfully, otherwise false (for example,
// the class already conforms to that protocol).
@@ -507,14 +546,6 @@ func (c Class) InstanceVariable(name string) Ivar {
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.
type Ivar uintptr
@@ -530,6 +561,39 @@ func (i Ivar) Name() string {
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.
type Protocol [0]func()
@@ -538,11 +602,69 @@ func GetProtocol(name string) *Protocol {
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.
func (p *Protocol) Equals(p2 *Protocol) bool {
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.
type IMP uintptr
@@ -550,7 +672,7 @@ type IMP uintptr
// It returns an IMP function pointer that can be called by Objective-C code.
// The function panics if an error occurs.
// The function pointer is never deallocated.
func NewIMP(fn interface{}) IMP {
func NewIMP(fn any) IMP {
ty := reflect.TypeOf(fn)
if ty.Kind() != reflect.Func {
panic("objc: not a function")
@@ -567,3 +689,14 @@ func NewIMP(fn interface{}) IMP {
}
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
)
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 {
return keepAlive
}
@@ -111,7 +111,7 @@ func addStruct(v reflect.Value, numInts, numFloats, numStack *int, addInt, addFl
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
// eightbyte isnt SSEUP, the whole argument is passed in memory.
if t.Kind() != reflect.Struct {
@@ -120,19 +120,7 @@ func postMerger(t reflect.Type) bool {
if t.Size() <= 2*8 {
return false
}
first := getFirst(t).Kind()
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
return true // Go does not have an SSE/SSEUP type so this is always true
}
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
shift += 32
class |= _INTEGER
case reflect.Int64:
case reflect.Int64, reflect.Int:
val = uint64(f.Int())
shift = 64
class = _INTEGER
@@ -212,7 +200,7 @@ func tryPlaceRegister(v reflect.Value, addFloat func(uintptr), addInt func(uintp
val |= f.Uint() << shift
shift += 32
class |= _INTEGER
case reflect.Uint64:
case reflect.Uint64, reflect.Uint, reflect.Uintptr:
val = f.Uint()
shift = 64
class = _INTEGER
@@ -257,7 +245,7 @@ func placeStack(v reflect.Value, addStack func(uintptr)) {
addStack(f.Pointer())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
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()))
case reflect.Float32:
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()
case outSize <= 8:
r1 := syscall.a1
if isAllSameFloat(outType) {
if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
r1 = syscall.f1
if outType.NumField() == 2 {
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 isAllSameFloat(outType) {
switch outType.NumField() {
if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats {
switch numFields {
case 4:
r1 = syscall.f2<<32 | syscall.f1
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()
default:
if isAllSameFloat(outType) && outType.NumField() <= 4 {
switch outType.NumField() {
if isAllFloats, numFields := isAllSameFloat(outType); isAllFloats && numFields <= 4 {
switch numFields {
case 4:
return reflect.NewAt(outType, unsafe.Pointer(&struct{ a, b, c, d uintptr }{syscall.f1, syscall.f2, syscall.f3, syscall.f4})).Elem()
case 3:
@@ -65,7 +65,7 @@ const (
_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 {
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 this doesn't fit entirely in registers then
// each element goes onto the stack
if hfa && *numFloats+v.NumField() > numOfFloats {
*numFloats = numOfFloats
if hfa && *numFloats+v.NumField() > numOfFloatRegisters {
*numFloats = numOfFloatRegisters
} else if hva && *numInts+v.NumField() > numOfIntegerRegisters() {
*numInts = numOfIntegerRegisters()
}
@@ -107,6 +107,8 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
} else {
f = v.Index(k)
}
align := byte(f.Type().Align()*8 - 1)
shift = (shift + align) &^ align
if shift >= 64 {
shift = 0
flushed = true
@@ -137,10 +139,11 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
val |= f.Uint() << shift
shift += 32
class |= _INT
case reflect.Uint64:
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
@@ -153,10 +156,11 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
val |= uint64(f.Int()&0xFFFF_FFFF) << shift
shift += 32
class |= _INT
case reflect.Int64:
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))
@@ -170,6 +174,12 @@ func placeRegisters(v reflect.Value, addFloat func(uintptr), addInt func(uintptr
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:
@@ -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.
// Copy to heap and place the pointer in register
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-FileCopyrightText: 2024 The Ebitengine Authors
//go:build !amd64 && !arm64
//go:build !amd64 && !arm64 && !loong64
package purego
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")
}
func getStruct(outType reflect.Type, syscall syscall15Args) (v reflect.Value) {
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-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux
//go:build darwin || freebsd || linux || netbsd
#include "textflag.h"
#include "abi_amd64.h"
@@ -122,6 +122,9 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
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
MOVQ $callbackasm(SB), DX
SUBQ DX, AX
@@ -130,9 +133,6 @@ TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
DIVL CX
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
// the "frame" to cgocallback and on to callbackWrap.
// $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-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux || windows
//go:build darwin || freebsd || linux || netbsd || windows
#include "textflag.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-FileCopyrightText: 2023 The Ebitengine Authors
//go:build darwin || freebsd || linux
//go:build darwin || freebsd || linux || netbsd
#include "textflag.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
+18 -2
View File
@@ -1,19 +1,35 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux || windows
//go:build darwin || freebsd || linux || netbsd || windows
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 (
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.
// 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.
//
// 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.
// See discussion comment https://github.com/ebiten/purego/pull/1#issuecomment-1128057607
// for an explanation of why that is.
+3 -13
View File
@@ -1,31 +1,21 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build cgo && !(amd64 || arm64)
//go:build cgo && !(amd64 || arm64 || loong64)
package purego
import (
_ "unsafe" // for go:linkname
"github.com/ebitengine/purego/internal/cgo"
)
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
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)
}
func NewCallback(_ interface{}) uintptr {
panic("purego: NewCallback on Linux is only supported on amd64/arm64")
func NewCallback(_ any) uintptr {
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-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || (linux && (amd64 || arm64))
//go:build darwin || freebsd || (linux && (amd64 || arm64 || loong64)) || netbsd
package purego
@@ -14,20 +14,17 @@ import (
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) {
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,
a1, a2, a3, a4, a5, a6, a7, a8,
0,
}
runtime_cgocall(syscall15XABI0, unsafe.Pointer(&args))
runtime_cgocall(syscall15XABI0, unsafe.Pointer(args))
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
// 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.
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)
}
@@ -67,7 +74,7 @@ type callbackArgs struct {
result uintptr
}
func compileCallback(fn interface{}) uintptr {
func compileCallback(fn any) uintptr {
val := reflect.ValueOf(fn)
if val.Kind() != reflect.Func {
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++ {
in := ty.In(i)
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.String, reflect.Map, reflect.Invalid:
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
// stack points to the index into frame of the current stack element.
// The stack begins after the float and integer registers.
stack := numOfIntegerRegisters() + numOfFloats
stack := numOfIntegerRegisters() + numOfFloatRegisters
for i := range args {
var pos int
switch fnType.In(i).Kind() {
case reflect.Float32, reflect.Float64:
if floatsN >= numOfFloats {
if floatsN >= numOfFloatRegisters {
pos = stack
stack++
} else {
pos = floatsN
}
floatsN++
case reflect.Struct:
// This is the CDecl field
args[i] = reflect.Zero(fnType.In(i))
continue
default:
if intsN >= numOfIntegerRegisters() {
pos = stack
stack++
} else {
// the integers begin after the floats in frame
pos = intsN + numOfFloats
pos = intsN + numOfFloatRegisters
}
intsN++
}
@@ -200,7 +217,7 @@ func callbackasmAddr(i int) uintptr {
panic("purego: unsupported architecture")
case "386", "amd64":
entrySize = 5
case "arm", "arm64":
case "arm", "arm64", "loong64":
// On ARM and ARM64, each entry is a MOV instruction
// followed by a branch instruction
entrySize = 8
+18 -17
View File
@@ -4,20 +4,12 @@
package purego
import (
"reflect"
"syscall"
_ "unsafe" // only for go:linkname
"golang.org/x/sys/windows"
)
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) {
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)
@@ -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
// callbacks can always be created. Although this function is similiar to the darwin version it may act
// 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)
}
//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) {
return windows.GetProcAddress(windows.Handle(handle), name)
return syscall.GetProcAddress(syscall.Handle(handle), name)
}
+1 -1
View File
@@ -1,6 +1,6 @@
// Code generated by wincallback.go using 'go generate'. DO NOT EDIT.
//go:build darwin || freebsd || linux
//go:build darwin || freebsd || linux || netbsd
// runtime·callbackasm is called by external code to
// execute Go implemented callback function. It is not
+1 -1
View File
@@ -1,6 +1,6 @@
// Code generated by wincallback.go using 'go generate'. DO NOT EDIT.
//go:build darwin || freebsd || linux
//go:build darwin || freebsd || linux || netbsd
// External code calls into callbackasm at an offset corresponding
// to the callback index. Callbackasm is a table of MOV and B instructions.
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
go.work
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)
* [WebAssembly](https://ebitengine.org/en/documents/webassembly.html)
* 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).
@@ -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.
[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.
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/.
//
// In the comments,
@@ -242,169 +297,78 @@ var (
//
// c_out = c_src + c_dst × (1 - α_src)
// α_out = α_src + α_dst × (1 - α_src)
BlendSourceOver = Blend{
BlendFactorSourceRGB: BlendFactorOne,
BlendFactorSourceAlpha: BlendFactorOne,
BlendFactorDestinationRGB: BlendFactorOneMinusSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorOneMinusSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendSourceOver = internalBlendToBlend(graphicsdriver.BlendSourceOver)
// BlendClear is a preset Blend for Porter Duff's 'clear'.
//
// c_out = 0
// α_out = 0
BlendClear = Blend{
BlendFactorSourceRGB: BlendFactorZero,
BlendFactorSourceAlpha: BlendFactorZero,
BlendFactorDestinationRGB: BlendFactorZero,
BlendFactorDestinationAlpha: BlendFactorZero,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendClear = internalBlendToBlend(graphicsdriver.BlendClear)
// BlendCopy is a preset Blend for Porter Duff's 'copy'.
//
// c_out = c_src
// α_out = α_src
BlendCopy = Blend{
BlendFactorSourceRGB: BlendFactorOne,
BlendFactorSourceAlpha: BlendFactorOne,
BlendFactorDestinationRGB: BlendFactorZero,
BlendFactorDestinationAlpha: BlendFactorZero,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendCopy = internalBlendToBlend(graphicsdriver.BlendCopy)
// BlendDestination is a preset Blend for Porter Duff's 'destination'.
//
// c_out = c_dst
// α_out = α_dst
BlendDestination = Blend{
BlendFactorSourceRGB: BlendFactorZero,
BlendFactorSourceAlpha: BlendFactorZero,
BlendFactorDestinationRGB: BlendFactorOne,
BlendFactorDestinationAlpha: BlendFactorOne,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendDestination = internalBlendToBlend(graphicsdriver.BlendDestination)
// BlendDestinationOver is a preset Blend for Porter Duff's 'destination-over'.
//
// c_out = c_src × (1 - α_dst) + c_dst
// α_out = α_src × (1 - α_dst) + α_dst
BlendDestinationOver = Blend{
BlendFactorSourceRGB: BlendFactorOneMinusDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorOneMinusDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorOne,
BlendFactorDestinationAlpha: BlendFactorOne,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendDestinationOver = internalBlendToBlend(graphicsdriver.BlendDestinationOver)
// BlendSourceIn is a preset Blend for Porter Duff's 'source-in'.
//
// c_out = c_src × α_dst
// α_out = α_src × α_dst
BlendSourceIn = Blend{
BlendFactorSourceRGB: BlendFactorDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorZero,
BlendFactorDestinationAlpha: BlendFactorZero,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendSourceIn = internalBlendToBlend(graphicsdriver.BlendSourceIn)
// BlendDestinationIn is a preset Blend for Porter Duff's 'destination-in'.
//
// c_out = c_dst × α_src
// α_out = α_dst × α_src
BlendDestinationIn = Blend{
BlendFactorSourceRGB: BlendFactorZero,
BlendFactorSourceAlpha: BlendFactorZero,
BlendFactorDestinationRGB: BlendFactorSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendDestinationIn = internalBlendToBlend(graphicsdriver.BlendDestinationIn)
// BlendSourceOut is a preset Blend for Porter Duff's 'source-out'.
//
// c_out = c_src × (1 - α_dst)
// α_out = α_src × (1 - α_dst)
BlendSourceOut = Blend{
BlendFactorSourceRGB: BlendFactorOneMinusDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorOneMinusDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorZero,
BlendFactorDestinationAlpha: BlendFactorZero,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendSourceOut = internalBlendToBlend(graphicsdriver.BlendSourceOut)
// BlendDestinationOut is a preset Blend for Porter Duff's 'destination-out'.
//
// c_out = c_dst × (1 - α_src)
// α_out = α_dst × (1 - α_src)
BlendDestinationOut = Blend{
BlendFactorSourceRGB: BlendFactorZero,
BlendFactorSourceAlpha: BlendFactorZero,
BlendFactorDestinationRGB: BlendFactorOneMinusSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorOneMinusSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendDestinationOut = internalBlendToBlend(graphicsdriver.BlendDestinationOut)
// BlendSourceAtop is a preset Blend for Porter Duff's 'source-atop'.
//
// c_out = c_src × α_dst + c_dst × (1 - α_src)
// α_out = α_src × α_dst + α_dst × (1 - α_src)
BlendSourceAtop = Blend{
BlendFactorSourceRGB: BlendFactorDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorOneMinusSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorOneMinusSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendSourceAtop = internalBlendToBlend(graphicsdriver.BlendSourceAtop)
// BlendDestinationAtop is a preset Blend for Porter Duff's 'destination-atop'.
//
// c_out = c_src × (1 - α_dst) + c_dst × α_src
// α_out = α_src × (1 - α_dst) + α_dst × α_src
BlendDestinationAtop = Blend{
BlendFactorSourceRGB: BlendFactorOneMinusDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorOneMinusDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendDestinationAtop = internalBlendToBlend(graphicsdriver.BlendDestinationAtop)
// BlendXor is a preset Blend for Porter Duff's 'xor'.
//
// c_out = c_src × (1 - α_dst) + c_dst × (1 - α_src)
// α_out = α_src × (1 - α_dst) + α_dst × (1 - α_src)
BlendXor = Blend{
BlendFactorSourceRGB: BlendFactorOneMinusDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorOneMinusDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorOneMinusSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorOneMinusSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendXor = internalBlendToBlend(graphicsdriver.BlendXor)
// BlendLighter is a preset Blend for Porter Duff's 'lighter'.
// This is sum of source and destination (a.k.a. 'plus' or 'additive')
//
// c_out = c_src + c_dst
// α_out = α_src + α_dst
BlendLighter = Blend{
BlendFactorSourceRGB: BlendFactorOne,
BlendFactorSourceAlpha: BlendFactorOne,
BlendFactorDestinationRGB: BlendFactorOne,
BlendFactorDestinationAlpha: BlendFactorOne,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
BlendLighter = internalBlendToBlend(graphicsdriver.BlendLighter)
)
+1 -1
View File
@@ -20,7 +20,7 @@ import (
"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.
const ColorMDim = affine.ColorMDim
+23 -5
View File
@@ -19,17 +19,17 @@ import (
)
// CursorModeType represents a render and coordinate mode of a mouse cursor.
type CursorModeType = ui.CursorMode
type CursorModeType int
// CursorModeTypes
const (
CursorModeVisible CursorModeType = ui.CursorModeVisible
CursorModeHidden CursorModeType = ui.CursorModeHidden
CursorModeCaptured CursorModeType = ui.CursorModeCaptured
CursorModeVisible CursorModeType = CursorModeType(ui.CursorModeVisible)
CursorModeHidden CursorModeType = CursorModeType(ui.CursorModeHidden)
CursorModeCaptured CursorModeType = CursorModeType(ui.CursorModeCaptured)
)
// CursorShapeType represents a shape of a mouse cursor.
type CursorShapeType = ui.CursorShape
type CursorShapeType int
// CursorShapeTypes
const (
@@ -44,3 +44,21 @@ const (
CursorShapeMove CursorShapeType = CursorShapeType(ui.CursorShapeMove)
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 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
//
// `ebitenginedebug` outputs a log of graphics commands. This is useful to know what happens in Ebitengine. In general, the
+21 -52
View File
@@ -15,57 +15,25 @@
package ebiten
import (
"fmt"
"image"
"math"
"sync/atomic"
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
"github.com/hajimehoshi/ebiten/v2/internal/inputstate"
"github.com/hajimehoshi/ebiten/v2/internal/ui"
)
const screenShaderSrc = `//kage:unit pixels
var screenFilterEnabled atomic.Bool
package main
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)
func init() {
screenFilterEnabled.Store(true)
}
type gameForUI struct {
game Game
offscreen *Image
screen *Image
screenShader *Shader
imageDumper imageDumper
transparent bool
}
@@ -75,13 +43,6 @@ func newGameForUI(game Game, transparent bool) *gameForUI {
game: game,
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
}
@@ -129,13 +90,12 @@ func (g *gameForUI) Layout(outsideWidth, outsideHeight float64) (float64, float6
outsideHeight = 1
}
// TODO: Add a new Layout function taking float values (#2285).
sw, sh := g.game.Layout(int(outsideWidth), int(outsideHeight))
return float64(sw), float64(sh)
}
func (g *gameForUI) UpdateInputState(fn func(*ui.InputState)) {
theInputState.update(fn)
inputstate.Get().Update(fn)
}
func (g *gameForUI) Update() error {
@@ -166,21 +126,30 @@ func (g *gameForUI) DrawFinalScreen(scale, offsetX, offsetY float64) {
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 {
case !isScreenFilterEnabled(), math.Floor(scale) == scale:
case !screenFilterEnabled.Load(), math.Floor(scale) == scale:
op := &DrawImageOptions{}
op.GeoM = geoM
g.screen.DrawImage(g.offscreen, op)
screen.DrawImage(offscreen, op)
case scale < 1:
op := &DrawImageOptions{}
op.GeoM = geoM
op.Filter = FilterLinear
g.screen.DrawImage(g.offscreen, op)
screen.DrawImage(offscreen, op)
default:
op := &DrawRectShaderOptions{}
op.Images[0] = g.offscreen
op := &DrawImageOptions{}
op.GeoM = geoM
w, h := g.offscreen.Bounds().Dx(), g.offscreen.Bounds().Dy()
g.screen.DrawRectShader(w, h, g.screenShader, op)
op.Filter = FilterPixelated
screen.DrawImage(offscreen, op)
}
}
+1 -1
View File
@@ -121,7 +121,7 @@ func (g *GeoM) Translate(tx, ty float64) {
g.ty += ty
}
// Rotate rotates the matrix by theta.
// Rotate rotates the matrix clockwise by theta.
// The unit is radian.
func (g *GeoM) Rotate(theta float64) {
if theta == 0 {
+26
View File
@@ -15,6 +15,7 @@
package ebiten
import (
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
"github.com/hajimehoshi/ebiten/v2/internal/builtinshader"
"github.com/hajimehoshi/ebiten/v2/internal/ui"
)
@@ -28,6 +29,10 @@ const (
// FilterLinear represents linear filter
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.
@@ -66,9 +71,30 @@ var _ [GraphicsLibraryAuto]int = [0]int{}
type DebugInfo struct {
// GraphicsLibrary represents the graphics library currently in use.
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 is concurrent-safe.
func ReadDebugInfo(d *DebugInfo) {
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"
"image"
"image/color"
"math"
"slices"
"sync"
"sync/atomic"
"unsafe"
"github.com/hajimehoshi/ebiten/v2/internal/affine"
"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/graphicscommand"
"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/ui"
)
@@ -43,19 +49,56 @@ type Image struct {
// tmpVertices must not be reused until ui.Image.Draw* is called.
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 []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.
// This tends to forget resolving the buffer easily (#2362).
}
type usageCallback struct {
fn func(image *Image)
}
func (i *Image) copyCheck() {
if i.addr != i {
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.
//
// 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
}
i.invokeUsageCallbacks()
i.updateAccessTime()
var crf, cgf, cbf, caf float32
cr, cg, cb, ca := clr.RGBA()
crf = float32(cr) / 0xffff
@@ -97,11 +144,11 @@ func (i *Image) Fill(clr color.Color) {
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 {
return true
}
return geom.det2x2() >= 0.999
return math.Abs(float64(det)) >= 0.999
}
// DrawImageOptions represents options for DrawImage.
@@ -139,6 +186,16 @@ type DrawImageOptions struct {
// Filter is a type of texture filter.
// The default (zero) value is FilterNearest.
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.
@@ -220,6 +277,12 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
return
}
i.invokeUsageCallbacks()
img.invokeUsageCallbacks()
i.updateAccessTime()
img.updateAccessTime()
if options == nil {
options = &DrawImageOptions{}
}
@@ -237,6 +300,10 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
geoM.Translate(float64(offsetX), float64(offsetY))
}
a, b, c, d, tx, ty := geoM.elements32()
det := a*d - b*c
if det == 0 {
return
}
bounds := img.Bounds()
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())
cr, cg, cb, ca = options.ColorScale.apply(cr, cg, cb, ca)
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()
srcs := [graphics.ShaderImageCount]*ui.Image{img.image}
srcs := [graphics.ShaderSrcImageCount]*ui.Image{img.image}
useColorM := !colorm.IsIdentity()
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.
@@ -274,6 +370,10 @@ type Vertex struct {
// SrcX and SrcY represents a point on a source image.
// 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).
//
// 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
SrcY float32
@@ -291,8 +391,20 @@ type Vertex struct {
ColorG float32
ColorB 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.
type Address int
@@ -308,19 +420,46 @@ const (
)
// FillRule is the rule whether an overlapped region is rendered with DrawTriangles(Shader).
//
// Deprecated: as of v2.9.
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 (
// 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.
// 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.
// 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.
@@ -371,11 +510,13 @@ type DrawTrianglesOptions struct {
// 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.
// 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
// 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.
// 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
// 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.
@@ -428,6 +581,36 @@ const MaxVertexCount = graphicscommand.MaxVertexCount
//
// When the image i is disposed, DrawTriangles does nothing.
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()
if img != nil && img.isDisposed() {
@@ -437,6 +620,16 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
return
}
if len(indices) == 0 {
return
}
i.invokeUsageCallbacks()
img.invokeUsageCallbacks()
img.updateAccessTime()
i.updateAccessTime()
if len(vertices) > graphicscommand.MaxVertexCount {
// The last part cannot be specified by indices. Just omit them.
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)
dst := i
if options.ColorScaleMode == ColorScaleModeStraightAlpha {
for i, v := range vertices {
dx, dy := dst.adjustPositionF32(v.DstX, v.DstY)
vs[i*graphics.VertexFloatCount] = dx
vs[i*graphics.VertexFloatCount+1] = dy
sx, sy := img.adjustPositionF32(v.SrcX, v.SrcY)
vs[i*graphics.VertexFloatCount+2] = sx
vs[i*graphics.VertexFloatCount+3] = sy
vs[i*graphics.VertexFloatCount+4] = v.ColorR * v.ColorA * cr
vs[i*graphics.VertexFloatCount+5] = v.ColorG * v.ColorA * cg
vs[i*graphics.VertexFloatCount+6] = v.ColorB * v.ColorA * cb
vs[i*graphics.VertexFloatCount+7] = v.ColorA * ca
// Avoid using `for i, v := range vertices` as adding `v` creates a copy from `vertices` unnecessarily on each loop (#3103).
for i := range vertices {
// Create a temporary slice to reduce boundary checks.
vs := vs[i*graphics.VertexFloatCount : i*graphics.VertexFloatCount+8]
dx, dy := dst.adjustPositionF32(vertices[i].DstX, vertices[i].DstY)
vs[0] = dx
vs[1] = dy
sx, sy := img.adjustPositionF32(vertices[i].SrcX, vertices[i].SrcY)
vs[2] = sx
vs[3] = sy
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 {
for i, v := range vertices {
dx, dy := dst.adjustPositionF32(v.DstX, v.DstY)
vs[i*graphics.VertexFloatCount] = dx
vs[i*graphics.VertexFloatCount+1] = dy
sx, sy := img.adjustPositionF32(v.SrcX, v.SrcY)
vs[i*graphics.VertexFloatCount+2] = sx
vs[i*graphics.VertexFloatCount+3] = sy
vs[i*graphics.VertexFloatCount+4] = v.ColorR * cr
vs[i*graphics.VertexFloatCount+5] = v.ColorG * cg
vs[i*graphics.VertexFloatCount+6] = v.ColorB * cb
vs[i*graphics.VertexFloatCount+7] = v.ColorA * ca
// See comment above (#3103).
for i := range vertices {
// Create a temporary slice to reduce boundary checks.
vs := vs[i*graphics.VertexFloatCount : i*graphics.VertexFloatCount+8]
dx, dy := dst.adjustPositionF32(vertices[i].DstX, vertices[i].DstY)
vs[0] = dx
vs[1] = dy
sx, sy := img.adjustPositionF32(vertices[i].SrcX, vertices[i].SrcY)
vs[2] = sx
vs[3] = sy
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()
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.
@@ -533,7 +732,7 @@ type DrawTrianglesShaderOptions struct {
// Uniforms is a set of uniform variables for the shader.
// 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,
// 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.
@@ -542,16 +741,19 @@ type DrawTrianglesShaderOptions struct {
Uniforms map[string]any
// 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
// 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.
// 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
// 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.
//
// The default (zero) value is false.
//
// Deprecated: as of v2.9. Use [github.com/hajimehoshi/ebiten/v2/vector.FillPath] instead.
AntiAlias bool
}
// 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.
//
@@ -592,6 +796,39 @@ var _ [len(DrawTrianglesShaderOptions{}.Images) - graphics.ShaderImageCount]stru
//
// When the image i is disposed, DrawTrianglesShader does nothing.
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()
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")
}
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 {
// The last part cannot be specified by indices. Just omit them.
vertices = vertices[:graphicscommand.MaxVertexCount]
@@ -629,28 +890,30 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
vs := i.ensureTmpVertices(len(vertices) * graphics.VertexFloatCount)
dst := i
src := options.Images[0]
for i, v := range vertices {
dx, dy := dst.adjustPositionF32(v.DstX, v.DstY)
vs[i*graphics.VertexFloatCount] = dx
vs[i*graphics.VertexFloatCount+1] = dy
sx, sy := v.SrcX, v.SrcY
// Avoid using `for i, v := range vertices` as adding `v` creates a copy from `vertices` unnecessarily on each loop (#3103).
for i := range vertices {
// Create a temporary slice to reduce boundary checks.
vs := vs[i*graphics.VertexFloatCount : i*graphics.VertexFloatCount+12]
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 {
sx, sy = src.adjustPositionF32(sx, sy)
}
vs[i*graphics.VertexFloatCount+2] = sx
vs[i*graphics.VertexFloatCount+3] = sy
vs[i*graphics.VertexFloatCount+4] = v.ColorR
vs[i*graphics.VertexFloatCount+5] = v.ColorG
vs[i*graphics.VertexFloatCount+6] = v.ColorB
vs[i*graphics.VertexFloatCount+7] = v.ColorA
vs[2] = sx
vs[3] = sy
vs[4] = vertices[i].ColorR
vs[5] = vertices[i].ColorG
vs[6] = vertices[i].ColorB
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))
for i := range is {
is[i] = uint32(indices[i])
}
var imgs [graphics.ShaderImageCount]*ui.Image
var imgs [graphics.ShaderSrcImageCount]*ui.Image
var imgSize image.Point
for i, img := range options.Images {
if img == nil {
@@ -672,7 +935,7 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
imgs[i] = img.image
}
var srcRegions [graphics.ShaderImageCount]image.Rectangle
var srcRegions [graphics.ShaderSrcImageCount]image.Rectangle
for i, img := range options.Images {
if img == nil {
continue
@@ -683,7 +946,7 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
i.tmpUniforms = i.tmpUniforms[:0]
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.
@@ -710,7 +973,7 @@ type DrawRectShaderOptions struct {
// Uniforms is a set of uniform variables for the shader.
// 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,
// 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.
@@ -724,7 +987,7 @@ type DrawRectShaderOptions struct {
}
// 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.
//
@@ -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")
}
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 {
options = &DrawRectShaderOptions{}
}
@@ -771,7 +1054,7 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR
blend = options.CompositeMode.blend().internalBlend()
}
var imgs [graphics.ShaderImageCount]*ui.Image
var imgs [graphics.ShaderSrcImageCount]*ui.Image
for i, img := range options.Images {
if img == nil {
continue
@@ -785,7 +1068,7 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR
imgs[i] = img.image
}
var srcRegions [graphics.ShaderImageCount]image.Rectangle
var srcRegions [graphics.ShaderSrcImageCount]image.Rectangle
for i, img := range options.Images {
if img == nil {
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))
}
a, b, c, d, tx, ty := geoM.elements32()
if det := a*d - b*c; det == 0 {
return
}
cr, cg, cb, ca := options.ColorScale.elements()
vs := i.ensureTmpVertices(4 * graphics.VertexFloatCount)
// 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+width), float32(srcRegions[0].Min.Y+height),
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 = 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.
@@ -838,24 +1131,53 @@ func (i *Image) SubImage(r image.Rectangle) image.Image {
return nil
}
if i.isSubImage() {
return i.original.SubImage(r.Intersect(i.Bounds()))
}
r = r.Intersect(i.Bounds())
// Need to check Empty explicitly. See the standard image package implementations.
if r.Empty() {
r = image.ZR
r = image.Rectangle{}
}
var orig = i
if i.isSubImage() {
orig = i.original
i.subImageCacheM.Lock()
defer i.subImageCacheM.Unlock()
// 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{
image: i.image,
bounds: r,
original: orig,
original: i,
}
img.addr = img
if i.subImageCache == nil {
i.subImageCache = map[image.Rectangle]*Image{}
}
i.subImageCache[r] = img
img.updateAccessTime()
return img
}
@@ -906,6 +1228,8 @@ func (i *Image) ReadPixels(pixels []byte) {
return
}
i.invokeUsageCallbacks()
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
}
i.invokeUsageCallbacks()
x, y = i.adjustPosition(x, y)
var pix [4]byte
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.
//
// 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,
// the value is kept and is not clamped.
//
// 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) {
i.copyCheck()
if i.isDisposed() {
return
}
i.invokeUsageCallbacks()
i.updateAccessTime()
if !image.Pt(x, y).In(i.Bounds()) {
return
}
@@ -1003,6 +1338,10 @@ func (i *Image) Dispose() {
}
i.image.Deallocate()
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.
@@ -1026,6 +1365,7 @@ func (i *Image) Deallocate() {
return
}
i.image.Deallocate()
i.usageCallbacks = nil
}
// WritePixels replaces the pixels of the image.
@@ -1048,6 +1388,8 @@ func (i *Image) WritePixels(pixels []byte) {
return
}
i.invokeUsageCallbacks()
// Do not need to copy pixels here.
// * In internal/mipmap, pixels are copied when necessary.
// * 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 {
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()
@@ -1246,6 +1588,102 @@ func (i *Image) ensureTmpVertices(n int) []float32 {
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.
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)
}
+3
View File
@@ -41,6 +41,9 @@ func takeScreenshot(screen *Image, transparent bool) error {
}
name = filepath.Join(home, name)
}
screen.invokeUsageCallbacks()
dumpedName, err := screen.image.DumpScreenshot(name, !transparent)
if err != nil {
return err
+7 -8
View File
@@ -42,11 +42,13 @@ func imageToBytes(img image.Image) []byte {
palette := make([]uint8, len(img.Palette)*4)
for i, c := range img.Palette {
// Create a temporary slice to reduce boundary checks.
pl := palette[4*i : 4*i+4]
rgba := color.RGBAModel.Convert(c).(color.RGBA)
palette[4*i] = rgba.R
palette[4*i+1] = rgba.G
palette[4*i+2] = rgba.B
palette[4*i+3] = rgba.A
pl[0] = rgba.R
pl[1] = rgba.G
pl[2] = rgba.B
pl[3] = rgba.A
}
// Even img is a subimage of another image, Pix starts with 0-th index.
idx0 := 0
@@ -55,10 +57,7 @@ func imageToBytes(img image.Image) []byte {
for j := 0; j < y1-y0; j++ {
for i := 0; i < x1-x0; i++ {
p := int(img.Pix[idx0])
bs[idx1] = palette[4*p]
bs[idx1+1] = palette[4*p+1]
bs[idx1+2] = palette[4*p+2]
bs[idx1+3] = palette[4*p+3]
copy(bs[idx1:idx1+4], palette[4*p:4*p+4])
idx0++
idx1 += 4
}
+10 -106
View File
@@ -15,11 +15,9 @@
package ebiten
import (
"io/fs"
"sync"
"github.com/hajimehoshi/ebiten/v2/internal/gamepad"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
"github.com/hajimehoshi/ebiten/v2/internal/inputstate"
"github.com/hajimehoshi/ebiten/v2/internal/ui"
)
@@ -36,7 +34,7 @@ import (
//
// On Android (ebitenmobile), EbitenView must be focusable to enable to handle keyboard keys.
func AppendInputChars(runes []rune) []rune {
return theInputState.appendInputChars(runes)
return inputstate.Get().AppendInputChars(runes)
}
// InputChars return "printable" runes read from the keyboard at the time Update is called.
@@ -58,7 +56,7 @@ func InputChars() []rune {
//
// On Android (ebitenmobile), EbitenView must be focusable to enable to handle keyboard keys.
func IsKeyPressed(key Key) bool {
return theInputState.isKeyPressed(key)
return inputstate.Get().IsKeyPressed(ui.Key(key))
}
// KeyName returns a key name for the current keyboard layout.
@@ -79,11 +77,11 @@ func KeyName(key Key) string {
//
// CursorPosition returns (0, 0) before the main loop on desktops and browsers.
//
// CursorPosition always returns (0, 0) on mobiles.
// CursorPosition always returns (0, 0) on mobile native applications.
//
// CursorPosition is concurrent-safe.
func CursorPosition() (x, y int) {
cx, cy := theInputState.cursorPosition()
cx, cy := inputstate.Get().CursorPosition()
return int(cx), int(cy)
}
@@ -92,7 +90,7 @@ func CursorPosition() (x, y int) {
//
// Wheel is concurrent-safe.
func Wheel() (xoff, yoff float64) {
return theInputState.wheel()
return inputstate.Get().Wheel()
}
// IsMouseButtonPressed returns a boolean indicating whether mouseButton is pressed.
@@ -102,7 +100,7 @@ func Wheel() (xoff, yoff float64) {
//
// IsMouseButtonPressed is concurrent-safe.
func IsMouseButtonPressed(mouseButton MouseButton) bool {
return theInputState.isMouseButtonPressed(mouseButton)
return inputstate.Get().IsMouseButtonPressed(ui.MouseButton(mouseButton))
}
// GamepadID represents a gamepad identifier.
@@ -350,7 +348,7 @@ func UpdateStandardGamepadLayoutMappings(mappings string) (bool, error) {
}
// TouchID represents a touch's identifier.
type TouchID = ui.TouchID
type TouchID int
// AppendTouchIDs appends the current touch states to touches, and returns the extended buffer.
// Giving a slice that already has enough capacity works efficiently.
@@ -363,7 +361,7 @@ type TouchID = ui.TouchID
//
// AppendTouchIDs is concurrent-safe.
func AppendTouchIDs(touches []TouchID) []TouchID {
return theInputState.appendTouchIDs(touches)
return inputstate.AppendTouchIDs(touches)
}
// TouchIDs returns the current touch states.
@@ -379,99 +377,5 @@ func TouchIDs() []TouchID {
//
// TouchPosition is concurrent-safe.
func TouchPosition(id TouchID) (int, int) {
return theInputState.touchPosition(id)
}
var theInputState inputState
type inputState struct {
state ui.InputState
m sync.Mutex
}
func (i *inputState) update(fn func(*ui.InputState)) {
i.m.Lock()
defer i.m.Unlock()
fn(&i.state)
}
func (i *inputState) appendInputChars(runes []rune) []rune {
i.m.Lock()
defer i.m.Unlock()
return append(runes, i.state.Runes...)
}
func (i *inputState) isKeyPressed(key Key) bool {
if !key.isValid() {
return false
}
i.m.Lock()
defer i.m.Unlock()
switch key {
case KeyAlt:
return i.state.KeyPressed[ui.KeyAltLeft] || i.state.KeyPressed[ui.KeyAltRight]
case KeyControl:
return i.state.KeyPressed[ui.KeyControlLeft] || i.state.KeyPressed[ui.KeyControlRight]
case KeyShift:
return i.state.KeyPressed[ui.KeyShiftLeft] || i.state.KeyPressed[ui.KeyShiftRight]
case KeyMeta:
return i.state.KeyPressed[ui.KeyMetaLeft] || i.state.KeyPressed[ui.KeyMetaRight]
default:
return i.state.KeyPressed[key]
}
}
func (i *inputState) cursorPosition() (float64, float64) {
i.m.Lock()
defer i.m.Unlock()
return i.state.CursorX, i.state.CursorY
}
func (i *inputState) wheel() (float64, float64) {
i.m.Lock()
defer i.m.Unlock()
return i.state.WheelX, i.state.WheelY
}
func (i *inputState) isMouseButtonPressed(mouseButton MouseButton) bool {
i.m.Lock()
defer i.m.Unlock()
return i.state.MouseButtonPressed[mouseButton]
}
func (i *inputState) appendTouchIDs(touches []TouchID) []TouchID {
i.m.Lock()
defer i.m.Unlock()
for _, t := range i.state.Touches {
touches = append(touches, t.ID)
}
return touches
}
func (i *inputState) touchPosition(id TouchID) (int, int) {
i.m.Lock()
defer i.m.Unlock()
for _, t := range i.state.Touches {
if id != t.ID {
continue
}
return t.X, t.Y
}
return 0, 0
}
func (i *inputState) windowBeingClosed() bool {
i.m.Lock()
defer i.m.Unlock()
return i.state.WindowBeingClosed
}
func (i *inputState) droppedFiles() fs.FS {
i.m.Lock()
defer i.m.Unlock()
return i.state.DroppedFiles
return inputstate.Get().TouchPosition(ui.TouchID(id))
}
+151 -264
View File
@@ -16,39 +16,32 @@
package inpututil
import (
"sort"
"slices"
"sync"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/internal/hook"
"github.com/hajimehoshi/ebiten/v2/internal/inputstate"
"github.com/hajimehoshi/ebiten/v2/internal/ui"
)
type pos struct {
type gamepadState struct {
buttonDurations [ebiten.GamepadButtonMax + 1]int
standardButtonDurations [ebiten.StandardGamepadButtonMax + 1]int
}
type touchState struct {
duration int
x int
y int
}
type inputState struct {
keyDurations []int
prevKeyDurations []int
gamepadStates map[ebiten.GamepadID]gamepadState
prevGamepadStates map[ebiten.GamepadID]gamepadState
mouseButtonDurations map[ebiten.MouseButton]int
prevMouseButtonDurations map[ebiten.MouseButton]int
gamepadIDs map[ebiten.GamepadID]struct{}
prevGamepadIDs map[ebiten.GamepadID]struct{}
gamepadButtonDurations map[ebiten.GamepadID][]int
prevGamepadButtonDurations map[ebiten.GamepadID][]int
standardGamepadButtonDurations map[ebiten.GamepadID][]int
prevStandardGamepadButtonDurations map[ebiten.GamepadID][]int
touchIDs map[ebiten.TouchID]struct{}
touchDurations map[ebiten.TouchID]int
touchPositions map[ebiten.TouchID]pos
prevTouchDurations map[ebiten.TouchID]int
prevTouchPositions map[ebiten.TouchID]pos
touchStates map[ebiten.TouchID]touchState
prevTouchStates map[ebiten.TouchID]touchState
gamepadIDsBuf []ebiten.GamepadID
touchIDsBuf []ebiten.TouchID
@@ -57,26 +50,10 @@ type inputState struct {
}
var theInputState = &inputState{
keyDurations: make([]int, ebiten.KeyMax+1),
prevKeyDurations: make([]int, ebiten.KeyMax+1),
mouseButtonDurations: map[ebiten.MouseButton]int{},
prevMouseButtonDurations: map[ebiten.MouseButton]int{},
gamepadIDs: map[ebiten.GamepadID]struct{}{},
prevGamepadIDs: map[ebiten.GamepadID]struct{}{},
gamepadButtonDurations: map[ebiten.GamepadID][]int{},
prevGamepadButtonDurations: map[ebiten.GamepadID][]int{},
standardGamepadButtonDurations: map[ebiten.GamepadID][]int{},
prevStandardGamepadButtonDurations: map[ebiten.GamepadID][]int{},
touchIDs: map[ebiten.TouchID]struct{}{},
touchDurations: map[ebiten.TouchID]int{},
touchPositions: map[ebiten.TouchID]pos{},
prevTouchDurations: map[ebiten.TouchID]int{},
prevTouchPositions: map[ebiten.TouchID]pos{},
gamepadStates: map[ebiten.GamepadID]gamepadState{},
prevGamepadStates: map[ebiten.GamepadID]gamepadState{},
touchStates: map[ebiten.TouchID]touchState{},
prevTouchStates: map[ebiten.TouchID]touchState{},
}
func init() {
@@ -90,121 +67,64 @@ func (i *inputState) update() {
i.m.Lock()
defer i.m.Unlock()
// Keyboard
copy(i.prevKeyDurations, i.keyDurations)
for k := ebiten.Key(0); k <= ebiten.KeyMax; k++ {
if ebiten.IsKeyPressed(k) {
i.keyDurations[k]++
} else {
i.keyDurations[k] = 0
}
}
// Mouse
for b := ebiten.MouseButton(0); b <= ebiten.MouseButtonMax; b++ {
i.prevMouseButtonDurations[b] = i.mouseButtonDurations[b]
if ebiten.IsMouseButtonPressed(b) {
i.mouseButtonDurations[b]++
} else {
i.mouseButtonDurations[b] = 0
}
}
// Gamepads
// Copy the gamepad IDs.
for id := range i.prevGamepadIDs {
delete(i.prevGamepadIDs, id)
}
for id := range i.gamepadIDs {
i.prevGamepadIDs[id] = struct{}{}
// Copy the gamepad states.
clear(i.prevGamepadStates)
for id, s := range i.gamepadStates {
i.prevGamepadStates[id] = s
}
// Copy the gamepad button durations.
for id := range i.prevGamepadButtonDurations {
delete(i.prevGamepadButtonDurations, id)
}
for id, ds := range i.gamepadButtonDurations {
i.prevGamepadButtonDurations[id] = append([]int{}, ds...)
}
for id := range i.prevStandardGamepadButtonDurations {
delete(i.prevStandardGamepadButtonDurations, id)
}
for id, ds := range i.standardGamepadButtonDurations {
i.prevStandardGamepadButtonDurations[id] = append([]int{}, ds...)
}
for id := range i.gamepadIDs {
delete(i.gamepadIDs, id)
}
i.gamepadIDsBuf = ebiten.AppendGamepadIDs(i.gamepadIDsBuf[:0])
for _, id := range i.gamepadIDsBuf {
i.gamepadIDs[id] = struct{}{}
state := i.gamepadStates[id]
if _, ok := i.gamepadButtonDurations[id]; !ok {
i.gamepadButtonDurations[id] = make([]int, ebiten.GamepadButtonMax+1)
}
for b := ebiten.GamepadButton(0); b <= ebiten.GamepadButtonMax; b++ {
if ebiten.IsGamepadButtonPressed(id, b) {
i.gamepadButtonDurations[id][b]++
for b := range i.gamepadStates[id].buttonDurations {
if ebiten.IsGamepadButtonPressed(id, ebiten.GamepadButton(b)) {
state.buttonDurations[b]++
} else {
i.gamepadButtonDurations[id][b] = 0
state.buttonDurations[b] = 0
}
}
if _, ok := i.standardGamepadButtonDurations[id]; !ok {
i.standardGamepadButtonDurations[id] = make([]int, ebiten.StandardGamepadButtonMax+1)
}
for b := ebiten.StandardGamepadButton(0); b <= ebiten.StandardGamepadButtonMax; b++ {
if ebiten.IsStandardGamepadButtonPressed(id, b) {
i.standardGamepadButtonDurations[id][b]++
for b := range i.gamepadStates[id].standardButtonDurations {
if ebiten.IsStandardGamepadButtonPressed(id, ebiten.StandardGamepadButton(b)) {
state.standardButtonDurations[b]++
} else {
i.standardGamepadButtonDurations[id][b] = 0
state.standardButtonDurations[b] = 0
}
}
i.gamepadStates[id] = state
}
for id := range i.gamepadButtonDurations {
if _, ok := i.gamepadIDs[id]; !ok {
delete(i.gamepadButtonDurations, id)
}
}
for id := range i.standardGamepadButtonDurations {
if _, ok := i.gamepadIDs[id]; !ok {
delete(i.standardGamepadButtonDurations, id)
// Remove disconnected gamepads.
for id := range i.gamepadStates {
if !slices.Contains(i.gamepadIDsBuf, id) {
delete(i.gamepadStates, id)
}
}
// Touches
// Copy the touch durations and positions.
for id := range i.prevTouchDurations {
delete(i.prevTouchDurations, id)
}
for id := range i.touchDurations {
i.prevTouchDurations[id] = i.touchDurations[id]
}
for id := range i.prevTouchPositions {
delete(i.prevTouchPositions, id)
}
for id := range i.touchPositions {
i.prevTouchPositions[id] = i.touchPositions[id]
clear(i.prevTouchStates)
for id, state := range i.touchStates {
i.prevTouchStates[id] = state
}
for id := range i.touchIDs {
delete(i.touchIDs, id)
}
i.touchIDsBuf = ebiten.AppendTouchIDs(i.touchIDsBuf[:0])
for _, id := range i.touchIDsBuf {
i.touchIDs[id] = struct{}{}
i.touchDurations[id]++
x, y := ebiten.TouchPosition(id)
i.touchPositions[id] = pos{x: x, y: y}
state := i.touchStates[id]
state.duration++
state.x, state.y = ebiten.TouchPosition(id)
i.touchStates[id] = state
}
for id := range i.touchDurations {
if _, ok := i.touchIDs[id]; !ok {
delete(i.touchDurations, id)
delete(i.touchPositions, id)
// Remove released touches.
for id := range i.touchStates {
if !slices.Contains(i.touchIDsBuf, id) {
delete(i.touchStates, id)
}
}
}
@@ -216,16 +136,7 @@ func (i *inputState) update() {
//
// AppendPressedKeys is concurrent safe.
func AppendPressedKeys(keys []ebiten.Key) []ebiten.Key {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
for i, d := range theInputState.keyDurations {
if d == 0 {
continue
}
keys = append(keys, ebiten.Key(i))
}
return keys
return inputstate.AppendPressedKeys(keys)
}
// PressedKeys returns a set of currently pressed keyboard keys.
@@ -244,16 +155,7 @@ func PressedKeys() []ebiten.Key {
//
// AppendJustPressedKeys is concurrent safe.
func AppendJustPressedKeys(keys []ebiten.Key) []ebiten.Key {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
for i, d := range theInputState.keyDurations {
if d != 1 {
continue
}
keys = append(keys, ebiten.Key(i))
}
return keys
return inputstate.AppendJustPressedKeys(keys)
}
// AppendJustReleasedKeys append just released keyboard keys to keys and returns the extended buffer.
@@ -263,19 +165,7 @@ func AppendJustPressedKeys(keys []ebiten.Key) []ebiten.Key {
//
// AppendJustReleasedKeys is concurrent safe.
func AppendJustReleasedKeys(keys []ebiten.Key) []ebiten.Key {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
for k := ebiten.Key(0); k <= ebiten.KeyMax; k++ {
if theInputState.keyDurations[k] != 0 {
continue
}
if theInputState.prevKeyDurations[k] == 0 {
continue
}
keys = append(keys, k)
}
return keys
return inputstate.AppendJustReleasedKeys(keys)
}
// IsKeyJustPressed returns a boolean value indicating
@@ -285,7 +175,7 @@ func AppendJustReleasedKeys(keys []ebiten.Key) []ebiten.Key {
//
// IsKeyJustPressed is concurrent safe.
func IsKeyJustPressed(key ebiten.Key) bool {
return KeyPressDuration(key) == 1
return inputstate.Get().IsKeyJustPressed(ui.Key(key))
}
// IsKeyJustReleased returns a boolean value indicating
@@ -295,10 +185,7 @@ func IsKeyJustPressed(key ebiten.Key) bool {
//
// IsKeyJustReleased is concurrent safe.
func IsKeyJustReleased(key ebiten.Key) bool {
theInputState.m.RLock()
r := theInputState.keyDurations[key] == 0 && theInputState.prevKeyDurations[key] > 0
theInputState.m.RUnlock()
return r
return inputstate.Get().IsKeyJustReleased(ui.Key(key))
}
// KeyPressDuration returns how long the key is pressed in ticks (Update).
@@ -307,10 +194,7 @@ func IsKeyJustReleased(key ebiten.Key) bool {
//
// KeyPressDuration is concurrent safe.
func KeyPressDuration(key ebiten.Key) int {
theInputState.m.RLock()
s := theInputState.keyDurations[key]
theInputState.m.RUnlock()
return s
return int(inputstate.Get().KeyPressDuration(ui.Key(key)))
}
// IsMouseButtonJustPressed returns a boolean value indicating
@@ -320,7 +204,7 @@ func KeyPressDuration(key ebiten.Key) int {
//
// IsMouseButtonJustPressed is concurrent safe.
func IsMouseButtonJustPressed(button ebiten.MouseButton) bool {
return MouseButtonPressDuration(button) == 1
return inputstate.Get().IsMouseButtonJustPressed(ui.MouseButton(button))
}
// IsMouseButtonJustReleased returns a boolean value indicating
@@ -330,11 +214,7 @@ func IsMouseButtonJustPressed(button ebiten.MouseButton) bool {
//
// IsMouseButtonJustReleased is concurrent safe.
func IsMouseButtonJustReleased(button ebiten.MouseButton) bool {
theInputState.m.RLock()
r := theInputState.mouseButtonDurations[button] == 0 &&
theInputState.prevMouseButtonDurations[button] > 0
theInputState.m.RUnlock()
return r
return inputstate.Get().IsMouseButtonJustReleased(ui.MouseButton(button))
}
// MouseButtonPressDuration returns how long the mouse button is pressed in ticks (Update).
@@ -343,10 +223,7 @@ func IsMouseButtonJustReleased(button ebiten.MouseButton) bool {
//
// MouseButtonPressDuration is concurrent safe.
func MouseButtonPressDuration(button ebiten.MouseButton) int {
theInputState.m.RLock()
s := theInputState.mouseButtonDurations[button]
theInputState.m.RUnlock()
return s
return int(inputstate.Get().MouseButtonPressDuration(ui.MouseButton(button)))
}
// AppendJustConnectedGamepadIDs appends gamepad IDs that are connected just in the current tick to gamepadIDs,
@@ -357,18 +234,17 @@ func MouseButtonPressDuration(button ebiten.MouseButton) int {
//
// AppendJustConnectedGamepadIDs is concurrent safe.
func AppendJustConnectedGamepadIDs(gamepadIDs []ebiten.GamepadID) []ebiten.GamepadID {
origLen := len(gamepadIDs)
theInputState.m.RLock()
for id := range theInputState.gamepadIDs {
if _, ok := theInputState.prevGamepadIDs[id]; !ok {
defer theInputState.m.RUnlock()
origLen := len(gamepadIDs)
for id := range theInputState.gamepadStates {
if _, ok := theInputState.prevGamepadStates[id]; !ok {
gamepadIDs = append(gamepadIDs, id)
}
}
theInputState.m.RUnlock()
s := gamepadIDs[origLen:]
sort.Slice(s, func(a, b int) bool {
return s[a] < s[b]
})
slices.Sort(gamepadIDs[origLen:])
return gamepadIDs
}
@@ -389,10 +265,11 @@ func JustConnectedGamepadIDs() []ebiten.GamepadID {
// IsGamepadJustDisconnected is concurrent safe.
func IsGamepadJustDisconnected(id ebiten.GamepadID) bool {
theInputState.m.RLock()
_, prev := theInputState.prevGamepadIDs[id]
_, current := theInputState.gamepadIDs[id]
theInputState.m.RUnlock()
return prev && !current
defer theInputState.m.RUnlock()
_, current := theInputState.gamepadStates[id]
_, prev := theInputState.prevGamepadStates[id]
return !current && prev
}
// AppendPressedGamepadButtons append currently pressed gamepad buttons to buttons and returns the extended buffer.
@@ -405,11 +282,12 @@ func AppendPressedGamepadButtons(id ebiten.GamepadID, buttons []ebiten.GamepadBu
theInputState.m.RLock()
defer theInputState.m.RUnlock()
if _, ok := theInputState.gamepadButtonDurations[id]; !ok {
state, ok := theInputState.gamepadStates[id]
if !ok {
return buttons
}
for b, d := range theInputState.gamepadButtonDurations[id] {
for b, d := range state.buttonDurations {
if d == 0 {
continue
}
@@ -429,11 +307,12 @@ func AppendJustPressedGamepadButtons(id ebiten.GamepadID, buttons []ebiten.Gamep
theInputState.m.RLock()
defer theInputState.m.RUnlock()
if _, ok := theInputState.gamepadButtonDurations[id]; !ok {
state, ok := theInputState.gamepadStates[id]
if !ok {
return buttons
}
for b, d := range theInputState.gamepadButtonDurations[id] {
for b, d := range state.buttonDurations {
if d != 1 {
continue
}
@@ -453,23 +332,23 @@ func AppendJustReleasedGamepadButtons(id ebiten.GamepadID, buttons []ebiten.Game
theInputState.m.RLock()
defer theInputState.m.RUnlock()
if _, ok := theInputState.gamepadButtonDurations[id]; !ok {
state, ok := theInputState.gamepadStates[id]
if !ok {
return buttons
}
if _, ok := theInputState.prevGamepadButtonDurations[id]; !ok {
prevState, ok := theInputState.prevGamepadStates[id]
if !ok {
return buttons
}
for b := ebiten.GamepadButton(0); b <= ebiten.GamepadButtonMax; b++ {
if theInputState.gamepadButtonDurations[id][b] == 0 {
for b := range state.buttonDurations {
if state.buttonDurations[b] != 0 {
continue
}
if theInputState.prevGamepadButtonDurations[id][b] > 0 {
if prevState.buttonDurations[b] == 0 {
continue
}
buttons = append(buttons, b)
buttons = append(buttons, ebiten.GamepadButton(b))
}
return buttons
@@ -493,16 +372,18 @@ func IsGamepadButtonJustPressed(id ebiten.GamepadID, button ebiten.GamepadButton
// IsGamepadButtonJustReleased is concurrent safe.
func IsGamepadButtonJustReleased(id ebiten.GamepadID, button ebiten.GamepadButton) bool {
theInputState.m.RLock()
prev := 0
if _, ok := theInputState.prevGamepadButtonDurations[id]; ok {
prev = theInputState.prevGamepadButtonDurations[id][button]
defer theInputState.m.RUnlock()
state, ok := theInputState.gamepadStates[id]
if !ok {
return false
}
current := 0
if _, ok := theInputState.gamepadButtonDurations[id]; ok {
current = theInputState.gamepadButtonDurations[id][button]
prevState, ok := theInputState.prevGamepadStates[id]
if !ok {
return false
}
theInputState.m.RUnlock()
return current == 0 && prev > 0
return state.buttonDurations[button] == 0 && prevState.buttonDurations[button] > 0
}
// GamepadButtonPressDuration returns how long the gamepad button of the gamepad id is pressed in ticks (Update).
@@ -512,12 +393,14 @@ func IsGamepadButtonJustReleased(id ebiten.GamepadID, button ebiten.GamepadButto
// GamepadButtonPressDuration is concurrent safe.
func GamepadButtonPressDuration(id ebiten.GamepadID, button ebiten.GamepadButton) int {
theInputState.m.RLock()
s := 0
if _, ok := theInputState.gamepadButtonDurations[id]; ok {
s = theInputState.gamepadButtonDurations[id][button]
defer theInputState.m.RUnlock()
state, ok := theInputState.gamepadStates[id]
if !ok {
return 0
}
theInputState.m.RUnlock()
return s
return state.buttonDurations[button]
}
// AppendPressedStandardGamepadButtons append currently pressed standard gamepad buttons to buttons and returns the extended buffer.
@@ -530,15 +413,16 @@ func AppendPressedStandardGamepadButtons(id ebiten.GamepadID, buttons []ebiten.S
theInputState.m.RLock()
defer theInputState.m.RUnlock()
if _, ok := theInputState.standardGamepadButtonDurations[id]; !ok {
state, ok := theInputState.gamepadStates[id]
if !ok {
return buttons
}
for b, d := range theInputState.standardGamepadButtonDurations[id] {
for i, d := range state.standardButtonDurations {
if d == 0 {
continue
}
buttons = append(buttons, ebiten.StandardGamepadButton(b))
buttons = append(buttons, ebiten.StandardGamepadButton(i))
}
return buttons
@@ -554,11 +438,12 @@ func AppendJustPressedStandardGamepadButtons(id ebiten.GamepadID, buttons []ebit
theInputState.m.RLock()
defer theInputState.m.RUnlock()
if _, ok := theInputState.gamepadButtonDurations[id]; !ok {
state, ok := theInputState.gamepadStates[id]
if !ok {
return buttons
}
for b, d := range theInputState.standardGamepadButtonDurations[id] {
for b, d := range state.standardButtonDurations {
if d != 1 {
continue
}
@@ -578,23 +463,23 @@ func AppendJustReleasedStandardGamepadButtons(id ebiten.GamepadID, buttons []ebi
theInputState.m.RLock()
defer theInputState.m.RUnlock()
if _, ok := theInputState.gamepadButtonDurations[id]; !ok {
state, ok := theInputState.gamepadStates[id]
if !ok {
return buttons
}
if _, ok := theInputState.prevGamepadButtonDurations[id]; !ok {
prevState, ok := theInputState.prevGamepadStates[id]
if !ok {
return buttons
}
for b := ebiten.StandardGamepadButton(0); b <= ebiten.StandardGamepadButtonMax; b++ {
if theInputState.standardGamepadButtonDurations[id][b] == 0 {
for b := range state.standardButtonDurations {
if state.standardButtonDurations[b] != 0 {
continue
}
if theInputState.prevStandardGamepadButtonDurations[id][b] > 0 {
if prevState.standardButtonDurations[b] == 0 {
continue
}
buttons = append(buttons, b)
buttons = append(buttons, ebiten.StandardGamepadButton(b))
}
return buttons
@@ -620,15 +505,16 @@ func IsStandardGamepadButtonJustReleased(id ebiten.GamepadID, button ebiten.Stan
theInputState.m.RLock()
defer theInputState.m.RUnlock()
var prev int
if _, ok := theInputState.prevStandardGamepadButtonDurations[id]; ok {
prev = theInputState.prevStandardGamepadButtonDurations[id][button]
state, ok := theInputState.gamepadStates[id]
if !ok {
return false
}
var current int
if _, ok := theInputState.standardGamepadButtonDurations[id]; ok {
current = theInputState.standardGamepadButtonDurations[id][button]
prevState, ok := theInputState.prevGamepadStates[id]
if !ok {
return false
}
return current == 0 && prev > 0
return state.standardButtonDurations[button] == 0 && prevState.standardButtonDurations[button] > 0
}
// StandardGamepadButtonPressDuration returns how long the standard gamepad button of the gamepad id is pressed in ticks (Update).
@@ -640,12 +526,14 @@ func StandardGamepadButtonPressDuration(id ebiten.GamepadID, button ebiten.Stand
theInputState.m.RLock()
defer theInputState.m.RUnlock()
if _, ok := theInputState.standardGamepadButtonDurations[id]; ok {
return theInputState.standardGamepadButtonDurations[id][button]
}
state, ok := theInputState.gamepadStates[id]
if !ok {
return 0
}
return state.standardButtonDurations[button]
}
// AppendJustPressedTouchIDs append touch IDs that are created just in the current tick to touchIDs,
// and returns the extended buffer.
// Giving a slice that already has enough capacity works efficiently.
@@ -658,17 +546,14 @@ func AppendJustPressedTouchIDs(touchIDs []ebiten.TouchID) []ebiten.TouchID {
defer theInputState.m.RUnlock()
origLen := len(touchIDs)
for id, s := range theInputState.touchDurations {
if s == 1 {
for id, state := range theInputState.touchStates {
if state.duration != 1 {
continue
}
touchIDs = append(touchIDs, id)
}
}
s := touchIDs[origLen:]
sort.Slice(s, func(a, b int) bool {
return s[a] < s[b]
})
slices.Sort(touchIDs[origLen:])
return touchIDs
}
@@ -693,17 +578,18 @@ func AppendJustReleasedTouchIDs(touchIDs []ebiten.TouchID) []ebiten.TouchID {
defer theInputState.m.RUnlock()
origLen := len(touchIDs)
for id := range theInputState.prevTouchDurations {
if theInputState.touchDurations[id] == 0 && theInputState.prevTouchDurations[id] > 0 {
// Iterate prevTouchStates instead of touchStates since touchStates doesn't have released touches.
for id, state := range theInputState.prevTouchStates {
if state.duration == 0 {
continue
}
if theInputState.touchStates[id].duration != 0 {
continue
}
touchIDs = append(touchIDs, id)
}
}
s := touchIDs[origLen:]
sort.Slice(s, func(a, b int) bool {
return s[a] < s[b]
})
slices.Sort(touchIDs[origLen:])
return touchIDs
}
@@ -717,7 +603,9 @@ func IsTouchJustReleased(id ebiten.TouchID) bool {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
return theInputState.touchDurations[id] == 0 && theInputState.prevTouchDurations[id] > 0
current := theInputState.touchStates[id]
prev := theInputState.prevTouchStates[id]
return current.duration == 0 && prev.duration > 0
}
// TouchPressDuration returns how long the touch remains in ticks (Update).
@@ -727,9 +615,8 @@ func IsTouchJustReleased(id ebiten.TouchID) bool {
// TouchPressDuration is concurrent safe.
func TouchPressDuration(id ebiten.TouchID) int {
theInputState.m.RLock()
s := theInputState.touchDurations[id]
theInputState.m.RUnlock()
return s
defer theInputState.m.RUnlock()
return theInputState.touchStates[id].duration
}
// TouchPositionInPreviousTick returns the position in the previous tick.
@@ -742,6 +629,6 @@ func TouchPositionInPreviousTick(id ebiten.TouchID) (int, int) {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
p := theInputState.prevTouchPositions[id]
return p.x, p.y
state := theInputState.prevTouchStates[id]
return state.x, state.y
}
-9
View File
@@ -783,12 +783,3 @@ func ChangeHSV(c ColorM, hueTheta float64, saturationScale float32, valueScale f
c = c.Concat(yCbCrToRgb)
return c
}
type cachedScalingColorMKey struct {
r, g, b, a float32
}
type cachedScalingColorMValue struct {
c *colorMImplScale
atime uint64
}
+133 -218
View File
@@ -22,11 +22,10 @@ import (
"runtime"
"sync"
"github.com/hajimehoshi/ebiten/v2/internal/debug"
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/packing"
"github.com/hajimehoshi/ebiten/v2/internal/restorable"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
)
@@ -36,30 +35,6 @@ var (
maxSize = 0
)
func max(a, b int) int {
if a > b {
return a
}
return b
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
// quadVertices returns vertices to render a quad. These values are passed to graphicscommand.Image.
func quadVertices(dx0, dy0, dx1, dy1, sx0, sy0, sx1, sy1, cr, cg, cb, ca float32) []float32 {
return []float32{
dx0, dy0, sx0, sy0, cr, cg, cb, ca,
dx1, dy0, sx1, sy0, cr, cg, cb, ca,
dx0, dy1, sx0, sy1, cr, cg, cb, ca,
dx1, dy1, sx1, sy1, cr, cg, cb, ca,
}
}
func appendDeferred(f func()) {
deferredM.Lock()
defer deferredM.Unlock()
@@ -81,9 +56,14 @@ func flushDeferred() {
// Actual time duration is increased in an exponential way for each usage as a rendering target.
const baseCountToPutOnSourceBackend = 10
func putImagesOnSourceBackend(graphicsDriver graphicsdriver.Graphics) {
func putImagesOnSourceBackend() {
// The counter usedAsDestinationCount is updated at most once per frame (#2676).
imagesUsedAsDestination.forEach(func(i *Image) {
// i.backend can be nil after deallocate is called.
if i.backend == nil {
i.usedAsDestinationCount = 0
return
}
// This counter is not updated when the backend is created in this frame.
if !i.backendCreatedInThisFrame && i.usedAsDestinationCount < math.MaxInt {
i.usedAsDestinationCount++
@@ -93,29 +73,34 @@ func putImagesOnSourceBackend(graphicsDriver graphicsdriver.Graphics) {
imagesUsedAsDestination.clear()
imagesToPutOnSourceBackend.forEach(func(i *Image) {
// i.backend can be nil after deallocate is called.
if i.backend == nil {
i.usedAsSourceCount = 0
return
}
if i.usedAsSourceCount < math.MaxInt {
i.usedAsSourceCount++
}
if int64(i.usedAsSourceCount) >= int64(baseCountToPutOnSourceBackend*(1<<uint(min(i.usedAsDestinationCount, 31)))) {
i.putOnSourceBackend(graphicsDriver)
if i.usedAsSourceCount >= baseCountToPutOnSourceBackend*(1<<uint(min(i.usedAsDestinationCount, 31))) {
i.putOnSourceBackend()
i.usedAsSourceCount = 0
}
})
imagesToPutOnSourceBackend.clear()
}
// backend is a big texture atlas that can have multiple images.
// backend is a texture in GPU.
type backend struct {
// image is an atlas on which there might be multiple images.
image *graphicscommand.Image
width int
height int
// restorable is an atlas on which there might be multiple images.
restorable *restorable.Image
// page is an atlas map. Each part is called a node.
// If page is nil, the backend's image is isolated and not on an atlas.
page *packing.Page
// source reports whether this backend is mainly used a rendering source, but this is not 100%.
//
// If a non-source (destination) image is used as a source many times,
// the image's backend might be turned into a source backend to optimize draw calls.
source bool
@@ -135,73 +120,11 @@ func (b *backend) tryAlloc(width, height int) (*packing.Node, bool) {
return nil, false
}
b.extendIfNeeded(b.page.Size())
b.restorable = b.restorable.Extend(b.page.Size())
return n, true
}
// extendIfNeeded extends the image by the given size if necessary.
// extendIfNeeded creates a new image with the given size and copies the pixels of the given source image.
// extendIfNeeded disposes an old image after its call when a new image is created.
func (b *backend) extendIfNeeded(width, height int) {
if b.width >= width && b.height >= height {
return
}
// Assume that the screen image is never extended.
newImg := newClearedImage(width, height, false)
// Use DrawTriangles instead of WritePixels because the image i might be stale and not have its pixels
// information.
srcs := [graphics.ShaderImageCount]*graphicscommand.Image{b.image}
sw, sh := b.image.InternalSize()
vs := quadVertices(0, 0, float32(sw), float32(sh), 0, 0, float32(sw), float32(sh), 1, 1, 1, 1)
is := graphics.QuadIndices()
dr := image.Rect(0, 0, sw, sh)
newImg.DrawTriangles(srcs, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, NearestFilterShader.ensureShader(), nil, graphicsdriver.FillAll)
b.image.Dispose()
b.image = newImg
b.width = width
b.height = height
}
// newClearedImage creates an emtpy image with the given size.
//
// Note that Dispose is not called automatically.
func newClearedImage(width, height int, screen bool) *graphicscommand.Image {
i := graphicscommand.NewImage(width, height, screen)
// This needs to use 'InternalSize' to render the whole region, or edges are unexpectedly cleared on some
// devices.
iw, ih := i.InternalSize()
clearImage(i, image.Rect(0, 0, iw, ih))
return i
}
func clearImage(i *graphicscommand.Image, region image.Rectangle) {
vs := quadVertices(float32(region.Min.X), float32(region.Min.Y), float32(region.Max.X), float32(region.Max.Y), 0, 0, 0, 0, 0, 0, 0, 0)
is := graphics.QuadIndices()
i.DrawTriangles([graphics.ShaderImageCount]*graphicscommand.Image{}, vs, is, graphicsdriver.BlendClear, region, [graphics.ShaderImageCount]image.Rectangle{}, clearShader.ensureShader(), nil, graphicsdriver.FillAll)
}
func (b *backend) clearPixels(region image.Rectangle) {
if region.Dx() <= 0 || region.Dy() <= 0 {
panic("atlas: width/height must be positive")
}
clearImage(b.image, region.Intersect(image.Rect(0, 0, b.width, b.height)))
}
func (b *backend) writePixels(pixels *graphics.ManagedBytes, region image.Rectangle) {
if region.Dx() <= 0 || region.Dy() <= 0 {
panic("atlas: width/height must be positive")
}
if !region.In(image.Rect(0, 0, b.width, b.height)) {
panic(fmt.Sprintf("atlas: out of range %v", region))
}
b.image.WritePixels(pixels, region)
}
var (
// backendsM is a mutex for critical sections of the backend and packing.Node objects.
backendsM sync.Mutex
@@ -215,11 +138,9 @@ var (
// theBackends is a set of atlases.
theBackends []*backend
imagesToPutOnSourceBackend smallImageSet
imagesToPutOnSourceBackend imageSmallSet
imagesUsedAsDestination smallImageSet
graphicsDriverInitialized bool
imagesUsedAsDestination imageSmallSet
deferred []func()
@@ -227,17 +148,33 @@ var (
deferredM sync.Mutex
)
// ImageType represents the type of an image.
type ImageType int
const (
// ImageTypeRegular is a regular image, that can be on a big texture atlas (backend).
ImageTypeRegular ImageType = iota
// ImageTypeScreen is a screen image that is not on an atlas.
// A screen image is also unmanaged.
ImageTypeScreen
// ImageTypeVolatile is a volatile image that is cleared every frame.
// A volatile image is also unmanaged.
ImageTypeVolatile
// ImageTypeUnmanaged is an unmanaged image that is not on an atlas.
ImageTypeUnmanaged
)
// Image is a rectangle pixel set that might be on an atlas.
type Image struct {
*imageImpl
cleanup runtime.Cleanup
}
type imageImpl struct {
width int
height int
imageType ImageType
@@ -250,13 +187,15 @@ type Image struct {
// usedAsSourceCount represents how long the image is used as a rendering source and kept not modified with
// DrawTriangles.
// In the current implementation, if an image is being modified by DrawTriangles, the image is separated from
// a graphicscommand.Image on an atlas by ensureIsolatedFromSource.
// a restorable image on an atlas by ensureIsolatedFromSource.
//
// The type is int64 instead of int to avoid overflow when comparing the limitation.
//
// usedAsSourceCount is increased if the image is used as a rendering source, or set to 0 if the image is
// modified.
//
// WritePixels doesn't affect this value since WritePixels can be done on images on an atlas.
usedAsSourceCount int
usedAsSourceCount int64
// usedAsDestinationCount represents how many times an image is used as a rendering destination at DrawTriangles.
// usedAsDestinationCount affects the calculation when to put the image onto a texture atlas again.
@@ -270,15 +209,21 @@ type Image struct {
//
// moveTo is similar to C++'s move semantics.
func (i *Image) moveTo(dst *Image) {
dst.deallocate()
*dst = *i
dst.deallocateImpl()
dst.cleanup.Stop()
impl := *i.imageImpl
dst.imageImpl = &impl
if dst.backend != nil {
dst.cleanup = runtime.AddCleanup(dst, (*imageImpl).cleanup, dst.imageImpl)
}
// i is no longer available but the finalizer must not be called
// since i and dst share the same backend and the same node.
runtime.SetFinalizer(i, nil)
i.cleanup.Stop()
}
func (i *Image) isOnAtlas() bool {
func (i *imageImpl) isOnAtlas() bool {
return i.node != nil
}
@@ -294,7 +239,7 @@ func (i *Image) resetUsedAsSourceCount() {
imagesToPutOnSourceBackend.remove(i)
}
func (i *Image) paddingSize() int {
func (i *imageImpl) paddingSize() int {
if i.imageType == ImageTypeRegular {
return 1
}
@@ -351,15 +296,16 @@ func (i *Image) ensureIsolatedFromSource(backends []*backend) {
w, h := float32(i.width), float32(i.height)
vs := make([]float32, 4*graphics.VertexFloatCount)
graphics.QuadVertices(vs, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
graphics.QuadVerticesFromDstAndSrc(vs, 0, 0, w, h, 0, 0, w, h, 1, 1, 1, 1)
is := graphics.QuadIndices()
dr := image.Rect(0, 0, i.width, i.height)
sr := image.Rect(0, 0, i.width, i.height)
newI.drawTriangles([graphics.ShaderImageCount]*Image{i}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillAll)
newI.drawTriangles([graphics.ShaderSrcImageCount]*Image{i}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintOverwriteDstRegion)
newI.moveTo(i)
}
func (i *Image) putOnSourceBackend(graphicsDriver graphicsdriver.Graphics) {
func (i *Image) putOnSourceBackend() {
if i.backend == nil {
i.allocate(nil, true)
return
@@ -382,10 +328,11 @@ func (i *Image) putOnSourceBackend(graphicsDriver graphicsdriver.Graphics) {
w, h := float32(i.width), float32(i.height)
vs := make([]float32, 4*graphics.VertexFloatCount)
graphics.QuadVertices(vs, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
graphics.QuadVerticesFromDstAndSrc(vs, 0, 0, w, h, 0, 0, w, h, 1, 1, 1, 1)
is := graphics.QuadIndices()
dr := image.Rect(0, 0, i.width, i.height)
newI.drawTriangles([graphics.ShaderImageCount]*Image{i}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillAll)
sr := image.Rect(0, 0, i.width, i.height)
newI.drawTriangles([graphics.ShaderSrcImageCount]*Image{i}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintOverwriteDstRegion)
newI.moveTo(i)
i.usedAsSourceCount = 0
@@ -395,7 +342,7 @@ func (i *Image) putOnSourceBackend(graphicsDriver graphicsdriver.Graphics) {
}
}
func (i *Image) regionWithPadding() image.Rectangle {
func (i *imageImpl) regionWithPadding() image.Rectangle {
if i.backend == nil {
panic("atlas: backend must not be nil: not allocated yet?")
}
@@ -417,7 +364,7 @@ func (i *Image) regionWithPadding() image.Rectangle {
// 5: Color G
// 6: Color B
// 7: Color Y
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) {
func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, hint restorable.Hint) {
backendsM.Lock()
defer backendsM.Unlock()
@@ -430,19 +377,15 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices [
copy(us, uniforms)
appendDeferred(func() {
i.drawTriangles(srcs, vs, is, blend, dstRegion, srcRegions, shader, us, fillRule)
i.drawTriangles(srcs, vs, is, blend, dstRegion, srcRegions, shader, us, fillRule, hint)
})
return
}
i.drawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule)
}
func (i *Image) drawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) {
if len(vertices) == 0 {
return
i.drawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, hint)
}
func (i *Image) drawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, hint restorable.Hint) {
backends := make([]*backend, 0, len(srcs))
for _, src := range srcs {
if src == nil {
@@ -462,7 +405,7 @@ func (i *Image) drawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices [
for _, src := range srcs {
// Compare i and source images after ensuring i is not on an atlas, or
// i and a source image might share the same atlas even though i != src.
if src != nil && i.backend.image == src.backend.image {
if src != nil && i.backend.restorable == src.backend.restorable {
panic("atlas: Image.DrawTriangles: source must be different from the receiver")
}
}
@@ -484,8 +427,8 @@ func (i *Image) drawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices [
vertices[i+2] += oxf
vertices[i+3] += oyf
}
if shader.ir.Unit == shaderir.Texels {
sw, sh := srcs[0].backend.image.InternalSize()
if shader.unit == shaderir.Texels {
sw, sh := srcs[0].backend.restorable.InternalSize()
swf, shf := float32(sw), float32(sh)
for i := 0; i < n; i += graphics.VertexFloatCount {
vertices[i+2] /= swf
@@ -500,6 +443,7 @@ func (i *Image) drawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices [
}
}
var imgs [graphics.ShaderSrcImageCount]*restorable.Image
for i, src := range srcs {
if src == nil {
continue
@@ -507,33 +451,19 @@ func (i *Image) drawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices [
// A source region can be deliberately empty when this is not needed in order to avoid unexpected
// performance issue (#1293).
if srcRegions[i].Empty() {
continue
}
// TODO: This should no longer be needed but is kept just in case. Remove this later.
if !srcRegions[i].Empty() {
r := src.regionWithPadding()
srcRegions[i] = srcRegions[i].Add(r.Min)
}
var imgs [graphics.ShaderImageCount]*graphicscommand.Image
for i, src := range srcs {
if src == nil {
continue
}
imgs[i] = src.backend.image
}
i.backend.image.DrawTriangles(imgs, vertices, indices, blend, dstRegion, srcRegions, shader.ensureShader(), uniforms, fillRule)
for _, src := range srcs {
if src == nil {
continue
}
imgs[i] = src.backend.restorable
if !src.isOnSourceBackend() && src.canBePutOnAtlas() {
// src might already registered, but assigning it again is not harmful.
imagesToPutOnSourceBackend.add(src)
}
}
i.backend.restorable.DrawTriangles(imgs, vertices, indices, blend, dstRegion, srcRegions, shader.ensureShader(), uniforms, fillRule, hint)
}
// WritePixels replaces the pixels on the image.
@@ -575,7 +505,7 @@ func (i *Image) writePixels(pix []byte, region image.Rectangle) {
region = region.Add(r.Min)
if pix == nil {
i.backend.clearPixels(region)
i.backend.restorable.ClearPixels(region)
return
}
@@ -583,7 +513,7 @@ func (i *Image) writePixels(pix []byte, region image.Rectangle) {
pix2 := graphics.NewManagedBytes(len(pix), func(bs []byte) {
copy(bs, pix)
})
i.backend.writePixels(pix2, region)
i.backend.restorable.WritePixels(pix2, region)
return
}
@@ -612,7 +542,7 @@ func (i *Image) writePixels(pix []byte, region image.Rectangle) {
copy(bs[4*j*r.Dx():], pix[4*j*region.Dx():4*(j+1)*region.Dx()])
}
})
i.backend.writePixels(pixb, r)
i.backend.restorable.WritePixels(pixb, r)
}
func (i *Image) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte, region image.Rectangle) (ok bool, err error) {
@@ -628,58 +558,45 @@ func (i *Image) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte
// To prevent memory leaks, flush the deferred functions here.
flushDeferred()
if err := i.readPixels(graphicsDriver, pixels, region); err != nil {
if i.backend == nil || i.backend.restorable == nil {
for i := range pixels {
pixels[i] = 0
}
return true, nil
}
if err := i.backend.restorable.ReadPixels(graphicsDriver, pixels, region.Add(i.regionWithPadding().Min)); err != nil {
return false, err
}
return true, nil
}
func (i *Image) readPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte, region image.Rectangle) error {
if i.backend == nil || i.backend.image == nil {
for i := range pixels {
pixels[i] = 0
}
return nil
}
if err := i.backend.image.ReadPixels(graphicsDriver, []graphicsdriver.PixelsArgs{
{
Pixels: pixels,
Region: region.Add(i.regionWithPadding().Min),
},
}); err != nil {
return err
}
return nil
}
// Deallocate deallocates the internal state.
// Even after this call, the image is still available as a new cleared image.
func (i *Image) Deallocate() {
i.cleanup.Stop()
backendsM.Lock()
defer backendsM.Unlock()
if !inFrame {
appendDeferred(func() {
i.deallocate()
runtime.SetFinalizer(i, nil)
i.deallocateImpl()
})
return
}
i.deallocate()
runtime.SetFinalizer(i, nil)
i.deallocateImpl()
}
func (i *Image) deallocate() {
func (i *imageImpl) deallocateImpl() {
defer func() {
i.backend = nil
i.node = nil
}()
i.resetUsedAsSourceCount()
i.usedAsSourceCount = 0
i.usedAsDestinationCount = 0
imagesUsedAsDestination.remove(i)
if i.backend == nil {
// Not allocated yet.
@@ -691,13 +608,12 @@ func (i *Image) deallocate() {
if !i.backend.page.IsEmpty() {
// As this part can be reused, this should be cleared explicitly.
r := i.regionWithPadding()
i.backend.clearPixels(r)
i.backend.restorable.ClearPixels(r)
return
}
}
i.backend.image.Dispose()
i.backend.image = nil
i.backend.restorable.Dispose()
for idx, sh := range theBackends {
if sh == i.backend {
@@ -714,9 +630,11 @@ func (i *Image) deallocate() {
func NewImage(width, height int, imageType ImageType) *Image {
// Actual allocation is done lazily, and the lock is not needed.
return &Image{
imageImpl: &imageImpl{
width: width,
height: height,
imageType: imageType,
},
}
}
@@ -730,25 +648,20 @@ func (i *Image) canBePutOnAtlas() bool {
return i.width+i.paddingSize() <= maxSize && i.height+i.paddingSize() <= maxSize
}
func (i *Image) finalize() {
func (i *imageImpl) cleanup() {
// A function from finalizer must not be blocked, but disposing operation can be blocked.
// Defer this operation until it becomes safe. (#913)
appendDeferred(func() {
i.deallocate()
runtime.SetFinalizer(i, nil)
i.deallocateImpl()
})
}
func (i *Image) allocate(forbiddenBackends []*backend, asSource bool) {
if !graphicsDriverInitialized {
panic("atlas: graphics driver must be ready at allocate but not")
}
if i.backend != nil {
panic("atlas: the image is already allocated")
}
runtime.SetFinalizer(i, (*Image).finalize)
i.cleanup = runtime.AddCleanup(i, (*imageImpl).cleanup, i.imageImpl)
if i.imageType == ImageTypeScreen {
if asSource {
@@ -756,9 +669,7 @@ func (i *Image) allocate(forbiddenBackends []*backend, asSource bool) {
}
// A screen image doesn't have a padding.
i.backend = &backend{
image: newClearedImage(i.width, i.height, true),
width: i.width,
height: i.height,
restorable: restorable.NewImage(i.width, i.height, restorable.ImageTypeScreen),
}
theBackends = append(theBackends, i.backend)
return
@@ -772,11 +683,13 @@ func (i *Image) allocate(forbiddenBackends []*backend, asSource bool) {
panic(fmt.Sprintf("atlas: the image being put on an atlas is too big: width: %d, height: %d", i.width, i.height))
}
typ := restorable.ImageTypeRegular
if i.imageType == ImageTypeVolatile {
typ = restorable.ImageTypeVolatile
}
i.backend = &backend{
image: newClearedImage(wp, hp, false),
width: wp,
height: hp,
source: asSource && i.imageType == ImageTypeRegular,
restorable: restorable.NewImage(wp, hp, typ),
source: asSource && typ == restorable.ImageTypeRegular,
}
theBackends = append(theBackends, i.backend)
return
@@ -820,10 +733,12 @@ loop:
height *= 2
}
typ := restorable.ImageTypeRegular
if i.imageType == ImageTypeVolatile {
typ = restorable.ImageTypeVolatile
}
b := &backend{
image: newClearedImage(width, height, false),
width: width,
height: height,
restorable: restorable.NewImage(width, height, typ),
page: packing.NewPage(width, height, maxSize),
source: asSource,
}
@@ -845,7 +760,7 @@ func (i *Image) DumpScreenshot(graphicsDriver graphicsdriver.Graphics, path stri
panic("atlas: DumpScreenshots must be called in between BeginFrame and EndFrame")
}
return i.backend.image.Dump(graphicsDriver, path, blackbg, image.Rect(0, 0, i.width, i.height))
return i.backend.restorable.Dump(graphicsDriver, path, blackbg, image.Rect(0, 0, i.width, i.height))
}
func EndFrame() error {
@@ -876,18 +791,7 @@ func SwapBuffers(graphicsDriver graphicsdriver.Graphics) error {
}
}()
if debug.IsDebug {
debug.Logf("Internal image sizes:\n")
imgs := make([]*graphicscommand.Image, 0, len(theBackends))
for _, backend := range theBackends {
if backend.image == nil {
continue
}
imgs = append(imgs, backend.image)
}
graphicscommand.LogImagesInfo(imgs)
}
if err := graphicscommand.FlushCommands(graphicsDriver, true); err != nil {
if err := restorable.SwapBuffers(graphicsDriver); err != nil {
return err
}
return nil
@@ -912,7 +816,7 @@ func BeginFrame(graphicsDriver graphicsdriver.Graphics) error {
var err error
initOnce.Do(func() {
err = graphicscommand.InitializeGraphicsDriverState(graphicsDriver)
err = restorable.InitializeGraphicsDriverState(graphicsDriver)
if err != nil {
return
}
@@ -928,17 +832,20 @@ func BeginFrame(graphicsDriver graphicsdriver.Graphics) error {
minDestinationSize = 16
}
if maxSize == 0 {
maxSize = floorPowerOf2(graphicscommand.MaxImageSize(graphicsDriver))
maxSize = floorPowerOf2(restorable.MaxImageSize(graphicsDriver))
}
graphicsDriverInitialized = true
})
if err != nil {
return err
}
// Restore images first before other image manipulations (#2075).
if err := restorable.RestoreIfNeeded(graphicsDriver); err != nil {
return err
}
flushDeferred()
putImagesOnSourceBackend(graphicsDriver)
putImagesOnSourceBackend()
return nil
}
@@ -951,12 +858,20 @@ func DumpImages(graphicsDriver graphicsdriver.Graphics, dir string) (string, err
panic("atlas: DumpImages must be called in between BeginFrame and EndFrame")
}
images := make([]*graphicscommand.Image, 0, len(theBackends))
for _, backend := range theBackends {
if backend.image == nil {
return restorable.DumpImages(graphicsDriver, dir)
}
func TotalGPUImageMemoryUsageInBytes() int64 {
backendsM.Lock()
defer backendsM.Unlock()
var sum int64
for _, b := range theBackends {
if b.restorable == nil {
continue
}
images = append(images, backend.image)
w, h := b.restorable.InternalSize()
sum += 4 * int64(w) * int64(h)
}
return graphicscommand.DumpImages(images, graphicsDriver, dir)
return sum
}

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