updated ebiten version from 2.7.9 to 2.9.9
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
+92
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
+6
@@ -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 */
|
||||
}
|
||||
|
||||
+6
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
+71
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 isn’t SSE or any other
|
||||
// eightbyte isn’t 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
+4014
File diff suppressed because it is too large
Load Diff
+1
@@ -0,0 +1 @@
|
||||
CommentPragmas: '^go:build'
|
||||
@@ -7,3 +7,8 @@
|
||||
.vscode
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
*.fxc
|
||||
!dummy.fxc
|
||||
*.metallib
|
||||
!dummy.metallib
|
||||
|
||||
+131
-2
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user