mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
Compare commits
1 Commits
083b9f98db
...
3bd9149d4b
Author | SHA1 | Date | |
---|---|---|---|
|
3bd9149d4b |
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -7,7 +7,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
go: ['1.22.x', '1.23.x']
|
||||
go: ['1.19.x', '1.20.x', '1.21.x', '1.22.x', '1.23.x']
|
||||
name: Test with Go ${{ matrix.go }} on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
|
@ -188,7 +188,14 @@ func (c *Context) addPlayingPlayer(p *playerImpl) {
|
||||
defer c.m.Unlock()
|
||||
c.playingPlayers[p] = struct{}{}
|
||||
|
||||
if !reflect.ValueOf(p.sourceIdent()).Comparable() {
|
||||
// (reflect.Type).Comparable() is enough here, as reflect.TypeOf should always return a dynamic (non-interface) type.
|
||||
// If reflect.TypeOf returned an interface type, this check would be meaningless.
|
||||
// See these for more details:
|
||||
// * https://pkg.go.dev/reflect#TypeOf
|
||||
// * https://pkg.go.dev/reflect#Type.Comparable
|
||||
//
|
||||
// (*reflect.Value).Comparable() is more intuitive but this was introduced in Go 1.20.
|
||||
if !reflect.TypeOf(p.sourceIdent()).Comparable() {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ func newContext(sampleRate int) (context, chan struct{}, error) {
|
||||
ChannelCount: channelCount,
|
||||
Format: oto.FormatFloat32LE,
|
||||
})
|
||||
err = addErrorInfo(err)
|
||||
err = addErrorInfoForContextCreation(err)
|
||||
return &contextProxy{ctx}, ready, err
|
||||
}
|
||||
|
||||
|
@ -35,9 +35,9 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// addErrorInfo adds an additional information to the error when creating an audio context.
|
||||
// See also ebitengine/oto#93.
|
||||
func addErrorInfo(err error) error {
|
||||
// addErrorInfoForContextCreation adds an additional information to the error when creating an audio context.
|
||||
// See also hajimehoshi/oto#93.
|
||||
func addErrorInfoForContextCreation(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -16,6 +16,6 @@
|
||||
|
||||
package audio
|
||||
|
||||
func addErrorInfo(err error) error {
|
||||
func addErrorInfoForContextCreation(err error) error {
|
||||
return err
|
||||
}
|
||||
|
@ -93,3 +93,10 @@ func (r *float32BytesReader) Seek(offset int64, whence int) (int64, error) {
|
||||
}
|
||||
return n / 2 * 4, nil
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ package convert_test
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"math/rand/v2"
|
||||
"math/rand" // TODO: Use math/rand/v2 when the minimum supported version becomes Go 1.22.
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
@ -27,7 +27,7 @@ import (
|
||||
func randInt16s(n int) []int16 {
|
||||
r := make([]int16, n)
|
||||
for i := range r {
|
||||
r[i] = int16(rand.IntN(1<<16) - (1 << 15))
|
||||
r[i] = int16(rand.Intn(1<<16) - (1 << 15))
|
||||
}
|
||||
return r
|
||||
}
|
||||
@ -69,14 +69,15 @@ func TestFloat32(t *testing.T) {
|
||||
name = "seek"
|
||||
}
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// Note that unsafe.SliceData is available as of Go 1.20.
|
||||
var in, out []byte
|
||||
if len(c.In) > 0 {
|
||||
outF32 := make([]float32, len(c.In))
|
||||
for i := range c.In {
|
||||
outF32[i] = float32(c.In[i]) / (1 << 15)
|
||||
}
|
||||
in = unsafe.Slice((*byte)(unsafe.Pointer(unsafe.SliceData(c.In))), len(c.In)*2)
|
||||
out = unsafe.Slice((*byte)(unsafe.Pointer(unsafe.SliceData(outF32))), len(outF32)*4)
|
||||
in = unsafe.Slice((*byte)(unsafe.Pointer(&c.In[0])), len(c.In)*2)
|
||||
out = unsafe.Slice((*byte)(unsafe.Pointer(&outF32[0])), len(outF32)*4)
|
||||
}
|
||||
r := convert.NewFloat32BytesReaderFromInt16BytesReader(bytes.NewReader(in)).(io.ReadSeeker)
|
||||
var got []byte
|
||||
|
@ -19,7 +19,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/rand/v2"
|
||||
"math/rand" // TODO: Use math/rand/v2 when the minimum supported version becomes Go 1.22.
|
||||
"testing"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/audio/internal/convert"
|
||||
|
@ -17,8 +17,8 @@ package convert_test
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand/v2"
|
||||
"io" // TODO: Use math/rand/v2 when the minimum supported version becomes Go 1.22.
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/audio/internal/convert"
|
||||
@ -101,7 +101,7 @@ func TestStereoI16(t *testing.T) {
|
||||
func randBytes(n int) []byte {
|
||||
r := make([]byte, n)
|
||||
for i := range r {
|
||||
r[i] = byte(rand.IntN(256))
|
||||
r[i] = byte(rand.Intn(256))
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ func (f *playerFactory) suspend() error {
|
||||
if f.context == nil {
|
||||
return nil
|
||||
}
|
||||
return addErrorInfo(f.context.Suspend())
|
||||
return f.context.Suspend()
|
||||
}
|
||||
|
||||
func (f *playerFactory) resume() error {
|
||||
@ -122,7 +122,7 @@ func (f *playerFactory) resume() error {
|
||||
if f.context == nil {
|
||||
return nil
|
||||
}
|
||||
return addErrorInfo(f.context.Resume())
|
||||
return f.context.Resume()
|
||||
}
|
||||
|
||||
func (f *playerFactory) error() error {
|
||||
@ -132,7 +132,7 @@ func (f *playerFactory) error() error {
|
||||
if f.context == nil {
|
||||
return nil
|
||||
}
|
||||
return addErrorInfo(f.context.Err())
|
||||
return f.context.Err()
|
||||
}
|
||||
|
||||
func (f *playerFactory) initContextIfNeeded() (<-chan struct{}, error) {
|
||||
@ -263,7 +263,7 @@ func (p *playerImpl) Close() error {
|
||||
}()
|
||||
p.player.Pause()
|
||||
p.stopwatch.stop()
|
||||
return addErrorInfo(p.player.Close())
|
||||
return p.player.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -293,7 +293,7 @@ func (p *playerImpl) SetPosition(offset time.Duration) error {
|
||||
|
||||
pos := p.stream.timeDurationToPos(offset)
|
||||
if _, err := p.player.Seek(pos, io.SeekStart); err != nil {
|
||||
return addErrorInfo(err)
|
||||
return err
|
||||
}
|
||||
p.lastSamples = -1
|
||||
// Just after setting a position, the buffer size should be 0 as no data is sent.
|
||||
@ -312,7 +312,7 @@ func (p *playerImpl) Err() error {
|
||||
if p.player == nil {
|
||||
return nil
|
||||
}
|
||||
return addErrorInfo(p.player.Err())
|
||||
return p.player.Err()
|
||||
}
|
||||
|
||||
func (p *playerImpl) SetBufferSize(bufferSize time.Duration) {
|
||||
|
@ -34,6 +34,13 @@ type int16BytesReader struct {
|
||||
fbuf []float32
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a < b {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (r *int16BytesReader) Read(buf []byte) (int, error) {
|
||||
if r.eof {
|
||||
return 0, io.EOF
|
||||
|
@ -29,12 +29,13 @@ import {{.JavaPkg}}.ebitenmobileview.Renderer;
|
||||
import {{.JavaPkg}}.{{.PrefixLower}}.EbitenView;
|
||||
|
||||
class EbitenSurfaceView extends GLSurfaceView implements Renderer {
|
||||
// As GLSurfaceView can be recreated, the states must be static (#3097).
|
||||
static private boolean errored_ = false;
|
||||
static private boolean onceSurfaceCreated_ = false;
|
||||
static private boolean contextLost_ = false;
|
||||
|
||||
private class EbitenRenderer implements GLSurfaceView.Renderer {
|
||||
|
||||
private boolean errored_ = false;
|
||||
private boolean onceSurfaceCreated_ = false;
|
||||
private boolean contextLost_ = false;
|
||||
|
||||
@Override
|
||||
public void onDrawFrame(GL10 gl) {
|
||||
if (errored_) {
|
||||
@ -106,7 +107,7 @@ class EbitenSurfaceView extends GLSurfaceView implements Renderer {
|
||||
}
|
||||
|
||||
private void onContextLost() {
|
||||
Log.e("Go", "The application was killed due to context loss");
|
||||
Log.v("Go", "Kill the application due to a context lost");
|
||||
// TODO: Relaunch this application for better UX (#805).
|
||||
Runtime.getRuntime().exit(0);
|
||||
}
|
||||
|
@ -161,12 +161,6 @@ import (
|
||||
return tmp, err
|
||||
}
|
||||
}
|
||||
// runtime.Version() is the current executing Go version. For example, this is the version of the toolchain directive in go.mod.
|
||||
// This might differ from the Go command version under the temporary directory.
|
||||
// To avoid the version mismatch, set the toolchain explicitly (#3086).
|
||||
if err := runGo("mod", "edit", "-toolchain="+runtime.Version()); err != nil {
|
||||
return tmp, err
|
||||
}
|
||||
if err := runGo("mod", "tidy"); err != nil {
|
||||
return tmp, err
|
||||
}
|
||||
|
28
cursor.go
28
cursor.go
@ -19,17 +19,17 @@ import (
|
||||
)
|
||||
|
||||
// CursorModeType represents a render and coordinate mode of a mouse cursor.
|
||||
type CursorModeType int
|
||||
type CursorModeType = ui.CursorMode
|
||||
|
||||
// CursorModeTypes
|
||||
const (
|
||||
CursorModeVisible CursorModeType = CursorModeType(ui.CursorModeVisible)
|
||||
CursorModeHidden CursorModeType = CursorModeType(ui.CursorModeHidden)
|
||||
CursorModeCaptured CursorModeType = CursorModeType(ui.CursorModeCaptured)
|
||||
CursorModeVisible CursorModeType = ui.CursorModeVisible
|
||||
CursorModeHidden CursorModeType = ui.CursorModeHidden
|
||||
CursorModeCaptured CursorModeType = ui.CursorModeCaptured
|
||||
)
|
||||
|
||||
// CursorShapeType represents a shape of a mouse cursor.
|
||||
type CursorShapeType int
|
||||
type CursorShapeType = ui.CursorShape
|
||||
|
||||
// CursorShapeTypes
|
||||
const (
|
||||
@ -44,21 +44,3 @@ 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))
|
||||
}
|
||||
|
@ -15,9 +15,16 @@
|
||||
package twenty48
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
const (
|
||||
ScreenWidth = 420
|
||||
ScreenHeight = 600
|
||||
|
@ -19,7 +19,7 @@ import (
|
||||
"errors"
|
||||
"image/color"
|
||||
"log"
|
||||
"math/rand/v2"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
@ -261,9 +261,9 @@ func addRandomTile(tiles map[*Tile]struct{}, size int) error {
|
||||
if len(availableCells) == 0 {
|
||||
return errors.New("twenty48: there is no space to add a new tile")
|
||||
}
|
||||
c := availableCells[rand.IntN(len(availableCells))]
|
||||
c := availableCells[rand.Intn(len(availableCells))]
|
||||
v := 2
|
||||
if rand.IntN(10) == 0 {
|
||||
if rand.Intn(10) == 0 {
|
||||
v = 4
|
||||
}
|
||||
x := c % size
|
||||
|
@ -163,9 +163,19 @@ func loadImage(data []byte) (*ebiten.Image, error) {
|
||||
}
|
||||
|
||||
// max returns the largest of x or y.
|
||||
func max(x, y int) int {
|
||||
if x > y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
// maxSide returns the largest side of a or b images.
|
||||
func maxSide(a, b *ebiten.Image) int {
|
||||
return max(a.Bounds().Dx(), b.Bounds().Dx(), a.Bounds().Dy(), b.Bounds().Dy())
|
||||
return max(
|
||||
max(a.Bounds().Dx(), b.Bounds().Dx()),
|
||||
max(a.Bounds().Dy(), b.Bounds().Dy()),
|
||||
)
|
||||
}
|
||||
|
||||
// drawCenteredText is a util function for drawing blend mode description.
|
||||
|
@ -187,6 +187,13 @@ func (f *Field) Update() {
|
||||
}
|
||||
}
|
||||
|
||||
func min(a, b float64) float64 {
|
||||
if a > b {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func flushingColor(rate float64) colorm.ColorM {
|
||||
var clr colorm.ColorM
|
||||
alpha := min(1, rate*2)
|
||||
|
@ -20,8 +20,9 @@ import (
|
||||
"image/color"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
"math/rand/v2"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/colorm"
|
||||
@ -138,6 +139,10 @@ type GameScene struct {
|
||||
gameover bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func NewGameScene() *GameScene {
|
||||
return &GameScene{
|
||||
field: &Field{},
|
||||
@ -189,7 +194,7 @@ const (
|
||||
|
||||
func (s *GameScene) choosePiece() *Piece {
|
||||
num := int(BlockTypeMax)
|
||||
blockType := BlockType(rand.IntN(num) + 1)
|
||||
blockType := BlockType(rand.Intn(num) + 1)
|
||||
return Pieces[blockType]
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"log"
|
||||
"math/rand/v2"
|
||||
"math/rand"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||
|
@ -17,7 +17,8 @@ package main
|
||||
import (
|
||||
"image/color"
|
||||
"log"
|
||||
"math/rand/v2"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
@ -101,7 +102,7 @@ func (g *Game) updateFireIntensityPerPixel(currentPixelIndex int) {
|
||||
return
|
||||
}
|
||||
|
||||
d := rand.IntN(3)
|
||||
d := rand.Intn(3)
|
||||
newI := int(g.indices[below]) - d
|
||||
if newI < 0 {
|
||||
newI = 0
|
||||
@ -138,6 +139,8 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
||||
}
|
||||
|
||||
func main() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
ebiten.SetWindowSize(screenWidth*6, screenHeight*6)
|
||||
ebiten.SetWindowTitle("Doom Fire (Ebitengine Demo)")
|
||||
if err := ebiten.RunGame(NewGame()); err != nil {
|
||||
|
@ -20,7 +20,8 @@ import (
|
||||
"image/color"
|
||||
_ "image/png"
|
||||
"log"
|
||||
"math/rand/v2"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||
@ -28,6 +29,10 @@ import (
|
||||
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
const (
|
||||
screenWidth = 640
|
||||
screenHeight = 480
|
||||
@ -191,8 +196,8 @@ func NewGame() *Game {
|
||||
s := &Sprite{
|
||||
image: ebitenImage,
|
||||
alphaImage: ebitenAlphaImage,
|
||||
x: rand.IntN(screenWidth - w),
|
||||
y: rand.IntN(screenHeight - h),
|
||||
x: rand.Intn(screenWidth - w),
|
||||
y: rand.Intn(screenHeight - h),
|
||||
}
|
||||
sprites = append(sprites, s)
|
||||
}
|
||||
|
@ -24,7 +24,8 @@ import (
|
||||
_ "image/png"
|
||||
"log"
|
||||
"math"
|
||||
"math/rand/v2"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/audio"
|
||||
@ -43,6 +44,10 @@ var flagCRT = flag.Bool("crt", false, "enable the CRT effect")
|
||||
//go:embed crt.go
|
||||
var crtGo []byte
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func floorDiv(x, y int) int {
|
||||
d := x / y
|
||||
if d*y == x || x >= 0 {
|
||||
@ -145,7 +150,7 @@ func (g *Game) init() {
|
||||
g.cameraY = 0
|
||||
g.pipeTileYs = make([]int, 256)
|
||||
for i := range g.pipeTileYs {
|
||||
g.pipeTileYs[i] = rand.IntN(6) + 2
|
||||
g.pipeTileYs[i] = rand.Intn(6) + 2
|
||||
}
|
||||
|
||||
if g.audioContext == nil {
|
||||
|
@ -19,7 +19,8 @@ import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"log"
|
||||
"math/rand/v2"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/examples/resources/fonts"
|
||||
@ -93,6 +94,10 @@ func init() {
|
||||
mplusFaceSource = s
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
type Game struct {
|
||||
counter int
|
||||
kanjiText string
|
||||
@ -105,14 +110,14 @@ func (g *Game) Update() error {
|
||||
g.kanjiText = ""
|
||||
for j := 0; j < 6; j++ {
|
||||
for i := 0; i < 12; i++ {
|
||||
g.kanjiText += string(jaKanjis[rand.IntN(len(jaKanjis))])
|
||||
g.kanjiText += string(jaKanjis[rand.Intn(len(jaKanjis))])
|
||||
}
|
||||
g.kanjiText += "\n"
|
||||
}
|
||||
|
||||
g.kanjiTextColor.R = 0x80 + uint8(rand.IntN(0x7f))
|
||||
g.kanjiTextColor.G = 0x80 + uint8(rand.IntN(0x7f))
|
||||
g.kanjiTextColor.B = 0x80 + uint8(rand.IntN(0x7f))
|
||||
g.kanjiTextColor.R = 0x80 + uint8(rand.Intn(0x7f))
|
||||
g.kanjiTextColor.G = 0x80 + uint8(rand.Intn(0x7f))
|
||||
g.kanjiTextColor.B = 0x80 + uint8(rand.Intn(0x7f))
|
||||
g.kanjiTextColor.A = 0xff
|
||||
}
|
||||
g.counter++
|
||||
|
@ -16,7 +16,8 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Level represents a Game level.
|
||||
@ -55,6 +56,9 @@ func NewLevel() (*Level, error) {
|
||||
return nil, fmt.Errorf("failed to load embedded spritesheet: %s", err)
|
||||
}
|
||||
|
||||
// Generate a unique permutation each time.
|
||||
r := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
|
||||
|
||||
// Fill each tile with one or more sprites randomly.
|
||||
l.tiles = make([][]*Tile, l.h)
|
||||
for y := 0; y < l.h; y++ {
|
||||
@ -62,7 +66,7 @@ func NewLevel() (*Level, error) {
|
||||
for x := 0; x < l.w; x++ {
|
||||
t := &Tile{}
|
||||
isBorderSpace := x == 0 || y == 0 || x == l.w-1 || y == l.h-1
|
||||
val := rand.IntN(1000)
|
||||
val := r.Intn(1000)
|
||||
switch {
|
||||
case isBorderSpace || val < 275:
|
||||
t.AddSprite(ss.Wall)
|
||||
|
@ -8,11 +8,16 @@ package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math/rand/v2"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// World represents the game state.
|
||||
type World struct {
|
||||
area []bool
|
||||
@ -34,8 +39,8 @@ func NewWorld(width, height int, maxInitLiveCells int) *World {
|
||||
// init inits world with a random state.
|
||||
func (w *World) init(maxLiveCells int) {
|
||||
for i := 0; i < maxLiveCells; i++ {
|
||||
x := rand.IntN(w.width)
|
||||
y := rand.IntN(w.height)
|
||||
x := rand.Intn(w.width)
|
||||
y := rand.Intn(w.height)
|
||||
w.area[y*w.width+x] = true
|
||||
}
|
||||
}
|
||||
@ -91,6 +96,20 @@ func (w *World) Draw(pix []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a < b {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// neighbourCount calculates the Moore neighborhood of (x, y).
|
||||
func neighbourCount(a []bool, width, height, x, y int) int {
|
||||
c := 0
|
||||
|
@ -27,6 +27,13 @@ import (
|
||||
"github.com/hajimehoshi/ebiten/v2/vector"
|
||||
)
|
||||
|
||||
func min(x, y int) int {
|
||||
if x < y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
var (
|
||||
whiteImage = ebiten.NewImage(3, 3)
|
||||
|
||||
|
@ -21,7 +21,8 @@ import (
|
||||
"image"
|
||||
_ "image/png"
|
||||
"log"
|
||||
"math/rand/v2"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
rmascot "github.com/hajimehoshi/ebiten/v2/examples/resources/images/mascot"
|
||||
@ -59,6 +60,10 @@ func init() {
|
||||
gopher3 = ebiten.NewImageFromImage(img3)
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
type mascot struct {
|
||||
x16 int
|
||||
y16 int
|
||||
@ -100,8 +105,8 @@ func (m *mascot) Update() error {
|
||||
}
|
||||
|
||||
// If the mascto is on the ground, cause an action in random.
|
||||
if rand.IntN(60) == 0 && m.y16 == 0 {
|
||||
switch rand.IntN(2) {
|
||||
if rand.Intn(60) == 0 && m.y16 == 0 {
|
||||
switch rand.Intn(2) {
|
||||
case 0:
|
||||
// Jump.
|
||||
m.vy16 = -240
|
||||
|
@ -145,6 +145,20 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
||||
return screenWidth, screenHeight
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a < b {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func main() {
|
||||
ebiten.SetWindowSize(screenWidth*2, screenHeight*2)
|
||||
ebiten.SetWindowTitle("Masking (Ebitengine Demo)")
|
||||
|
@ -32,7 +32,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
screenWidth = 1000
|
||||
screenWidth = 800
|
||||
screenHeight = 480
|
||||
)
|
||||
|
||||
@ -44,28 +44,20 @@ type Game struct {
|
||||
rotate bool
|
||||
clip bool
|
||||
counter int
|
||||
pause bool
|
||||
}
|
||||
|
||||
func (g *Game) Update() error {
|
||||
g.counter++
|
||||
if g.counter == 480 {
|
||||
g.counter = 0
|
||||
}
|
||||
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyR) {
|
||||
g.rotate = !g.rotate
|
||||
}
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyC) {
|
||||
g.clip = !g.clip
|
||||
}
|
||||
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
|
||||
g.pause = !g.pause
|
||||
}
|
||||
|
||||
if g.pause {
|
||||
return nil
|
||||
}
|
||||
|
||||
g.counter++
|
||||
if g.counter == 480 {
|
||||
g.counter = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -73,8 +65,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
|
||||
s := 1.5 / math.Pow(1.01, float64(g.counter))
|
||||
|
||||
clippedGophersImage := gophersImage.SubImage(image.Rect(100, 100, 200, 200)).(*ebiten.Image)
|
||||
for i := range 3 {
|
||||
//for i, f := range []ebiten.Filter{ebiten.FilterNearest, ebiten.FilterLinear} {
|
||||
for i, f := range []ebiten.Filter{ebiten.FilterNearest, ebiten.FilterLinear} {
|
||||
w, h := gophersImage.Bounds().Dx(), gophersImage.Bounds().Dy()
|
||||
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
@ -84,15 +75,8 @@ func (g *Game) Draw(screen *ebiten.Image) {
|
||||
op.GeoM.Translate(float64(w)/2, float64(h)/2)
|
||||
}
|
||||
op.GeoM.Scale(s, s)
|
||||
op.GeoM.Translate(32+float64(i*w)*s+float64(i*4), 100)
|
||||
if i == 0 {
|
||||
op.Filter = ebiten.FilterNearest
|
||||
} else {
|
||||
op.Filter = ebiten.FilterLinear
|
||||
}
|
||||
if i == 2 {
|
||||
op.DisableMipmaps = true
|
||||
}
|
||||
op.GeoM.Translate(32+float64(i*w)*s+float64(i*4), 64)
|
||||
op.Filter = f
|
||||
if g.clip {
|
||||
screen.DrawImage(clippedGophersImage, op)
|
||||
} else {
|
||||
@ -100,10 +84,9 @@ func (g *Game) Draw(screen *ebiten.Image) {
|
||||
}
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf(`Minifying images (Nearest filter, Linear filter (w/ mipmaps), and Linear Filter (w/o mipmaps)):
|
||||
msg := fmt.Sprintf(`Minifying images (Nearest filter vs Linear filter):
|
||||
Press R to rotate the images.
|
||||
Press C to clip the images.
|
||||
Click to pause and resume.
|
||||
Scale: %0.2f`, s)
|
||||
ebitenutil.DebugPrint(screen, msg)
|
||||
}
|
||||
|
@ -23,13 +23,18 @@ import (
|
||||
_ "image/png"
|
||||
"log"
|
||||
"math"
|
||||
"math/rand/v2"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||
"github.com/hajimehoshi/ebiten/v2/examples/resources/images"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
const (
|
||||
screenWidth = 640
|
||||
screenHeight = 480
|
||||
@ -105,7 +110,7 @@ func (s *sprite) draw(screen *ebiten.Image) {
|
||||
}
|
||||
|
||||
func newSprite(img *ebiten.Image) *sprite {
|
||||
c := rand.IntN(50) + 300
|
||||
c := rand.Intn(50) + 300
|
||||
dir := rand.Float64() * 2 * math.Pi
|
||||
a := rand.Float64() * 2 * math.Pi
|
||||
s := rand.Float64()*0.1 + 0.4
|
||||
@ -131,7 +136,7 @@ func (g *Game) Update() error {
|
||||
g.sprites = list.New()
|
||||
}
|
||||
|
||||
if g.sprites.Len() < 500 && rand.IntN(4) < 3 {
|
||||
if g.sprites.Len() < 500 && rand.Intn(4) < 3 {
|
||||
// Emit
|
||||
g.sprites.PushBack(newSprite(smokeImage))
|
||||
}
|
||||
|
@ -18,7 +18,8 @@ import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"log"
|
||||
"math/rand/v2"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||
@ -29,6 +30,10 @@ const (
|
||||
screenHeight = 240
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
type Game struct {
|
||||
offscreen *ebiten.Image
|
||||
}
|
||||
@ -41,12 +46,12 @@ func NewGame() *Game {
|
||||
|
||||
func (g *Game) Update() error {
|
||||
s := g.offscreen.Bounds().Size()
|
||||
x := rand.IntN(s.X)
|
||||
y := rand.IntN(s.Y)
|
||||
x := rand.Intn(s.X)
|
||||
y := rand.Intn(s.Y)
|
||||
c := color.RGBA{
|
||||
byte(rand.IntN(256)),
|
||||
byte(rand.IntN(256)),
|
||||
byte(rand.IntN(256)),
|
||||
byte(rand.Intn(256)),
|
||||
byte(rand.Intn(256)),
|
||||
byte(rand.Intn(256)),
|
||||
byte(0xff),
|
||||
}
|
||||
g.offscreen.Set(x, y, c)
|
||||
|
@ -18,7 +18,8 @@ import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"log"
|
||||
"math/rand/v2"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||
@ -58,6 +59,10 @@ type Game struct {
|
||||
level int
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func (g *Game) collidesWithApple() bool {
|
||||
return g.snakeBody[0].X == g.apple.X &&
|
||||
g.snakeBody[0].Y == g.apple.Y
|
||||
@ -125,8 +130,8 @@ func (g *Game) Update() error {
|
||||
}
|
||||
|
||||
if g.collidesWithApple() {
|
||||
g.apple.X = rand.IntN(xGridCountInScreen - 1)
|
||||
g.apple.Y = rand.IntN(yGridCountInScreen - 1)
|
||||
g.apple.X = rand.Intn(xGridCountInScreen - 1)
|
||||
g.apple.Y = rand.Intn(yGridCountInScreen - 1)
|
||||
g.snakeBody = append(g.snakeBody, Position{
|
||||
X: g.snakeBody[len(g.snakeBody)-1].X,
|
||||
Y: g.snakeBody[len(g.snakeBody)-1].Y,
|
||||
|
@ -21,7 +21,7 @@ import (
|
||||
_ "image/png"
|
||||
"log"
|
||||
"math"
|
||||
"math/rand/v2"
|
||||
"math/rand"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||
@ -119,9 +119,9 @@ func (g *Game) init() {
|
||||
g.sprites.num = 500
|
||||
for i := range g.sprites.sprites {
|
||||
w, h := ebitenImage.Bounds().Dx(), ebitenImage.Bounds().Dy()
|
||||
x, y := rand.IntN(screenWidth-w), rand.IntN(screenHeight-h)
|
||||
vx, vy := 2*rand.IntN(2)-1, 2*rand.IntN(2)-1
|
||||
a := rand.IntN(maxAngle)
|
||||
x, y := rand.Intn(screenWidth-w), rand.Intn(screenHeight-h)
|
||||
vx, vy := 2*rand.Intn(2)-1, 2*rand.Intn(2)-1
|
||||
a := rand.Intn(maxAngle)
|
||||
g.sprites.sprites[i] = &Sprite{
|
||||
imageWidth: w,
|
||||
imageHeight: h,
|
||||
|
@ -21,7 +21,7 @@ import (
|
||||
_ "image/png"
|
||||
"log"
|
||||
"math"
|
||||
"math/rand/v2"
|
||||
"math/rand"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||
@ -117,9 +117,9 @@ func (g *Game) init() {
|
||||
g.sprites.num = 500
|
||||
for i := range g.sprites.sprites {
|
||||
w, h := ebitenImage.Bounds().Dx(), ebitenImage.Bounds().Dy()
|
||||
x, y := rand.IntN(screenWidth-w), rand.IntN(screenHeight-h)
|
||||
vx, vy := 2*rand.IntN(2)-1, 2*rand.IntN(2)-1
|
||||
a := rand.IntN(maxAngle)
|
||||
x, y := rand.Intn(screenWidth-w), rand.Intn(screenHeight-h)
|
||||
vx, vy := 2*rand.Intn(2)-1, 2*rand.Intn(2)-1
|
||||
a := rand.Intn(maxAngle)
|
||||
g.sprites.sprites[i] = &Sprite{
|
||||
imageWidth: w,
|
||||
imageHeight: h,
|
||||
|
@ -20,7 +20,8 @@ import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"log"
|
||||
"math/rand/v2"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||
@ -126,8 +127,8 @@ type squiral struct {
|
||||
func (s *squiral) spawn(game *Game) {
|
||||
s.dead = false
|
||||
|
||||
rx := rand.IntN(width-4) + 2
|
||||
ry := rand.IntN(height-4) + 2
|
||||
rx := rand.Intn(width-4) + 2
|
||||
ry := rand.Intn(height-4) + 2
|
||||
|
||||
for dx := -2; dx <= 2; dx++ {
|
||||
for dy := -2; dy <= 2; dy++ {
|
||||
@ -139,15 +140,15 @@ func (s *squiral) spawn(game *Game) {
|
||||
}
|
||||
}
|
||||
|
||||
s.speed = rand.IntN(5) + 1
|
||||
s.speed = rand.Intn(5) + 1
|
||||
s.pos.x = rx
|
||||
s.pos.y = ry
|
||||
s.dir = rand.IntN(4)
|
||||
s.dir = rand.Intn(4)
|
||||
|
||||
game.colorCycle = (game.colorCycle + 1) % len(palettes[game.selectedPalette].colors)
|
||||
s.col = palettes[game.selectedPalette].colors[game.colorCycle]
|
||||
|
||||
s.rot = rand.IntN(2)
|
||||
s.rot = rand.Intn(2)
|
||||
}
|
||||
|
||||
func (s *squiral) step(game *Game) {
|
||||
@ -156,7 +157,7 @@ func (s *squiral) step(game *Game) {
|
||||
}
|
||||
x, y := s.pos.x, s.pos.y // shorthands
|
||||
|
||||
change := rand.IntN(1000)
|
||||
change := rand.Intn(1000)
|
||||
if change < 2 {
|
||||
// On 0.2% of iterations, switch rotation direction.
|
||||
s.rot = (s.rot + 1) % 2
|
||||
@ -266,6 +267,10 @@ func (a *automaton) step(game *Game) {
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
type Game struct {
|
||||
selectedPalette int
|
||||
colorCycle int
|
||||
|
@ -17,7 +17,8 @@ package main
|
||||
import (
|
||||
"image/color"
|
||||
"log"
|
||||
"math/rand/v2"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/vector"
|
||||
@ -96,6 +97,7 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
||||
}
|
||||
|
||||
func main() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
ebiten.SetWindowSize(screenWidth, screenHeight)
|
||||
ebiten.SetWindowTitle("Stars (Ebitengine Demo)")
|
||||
if err := ebiten.RunGame(NewGame()); err != nil {
|
||||
|
@ -22,11 +22,12 @@ import (
|
||||
_ "image/jpeg"
|
||||
"log"
|
||||
"math"
|
||||
"math/rand/v2"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||
@ -54,6 +55,7 @@ var (
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
const (
|
||||
@ -69,9 +71,9 @@ var (
|
||||
func createRandomIconImage() image.Image {
|
||||
const size = 32
|
||||
|
||||
rf := float64(rand.IntN(0x100))
|
||||
gf := float64(rand.IntN(0x100))
|
||||
bf := float64(rand.IntN(0x100))
|
||||
rf := float64(rand.Intn(0x100))
|
||||
gf := float64(rand.Intn(0x100))
|
||||
bf := float64(rand.Intn(0x100))
|
||||
img := ebiten.NewImage(size, size)
|
||||
pix := make([]byte, 4*size*size)
|
||||
for j := 0; j < size; j++ {
|
||||
|
2
geom.go
2
geom.go
@ -121,7 +121,7 @@ func (g *GeoM) Translate(tx, ty float64) {
|
||||
g.ty += ty
|
||||
}
|
||||
|
||||
// Rotate rotates the matrix clockwise by theta.
|
||||
// Rotate rotates the matrix by theta.
|
||||
// The unit is radian.
|
||||
func (g *GeoM) Rotate(theta float64) {
|
||||
if theta == 0 {
|
||||
|
14
go.mod
14
go.mod
@ -1,29 +1,29 @@
|
||||
module github.com/hajimehoshi/ebiten/v2
|
||||
|
||||
go 1.22.0
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/ebitengine/gomobile v0.0.0-20240911145611-4856209ac325
|
||||
github.com/ebitengine/gomobile v0.0.0-20240825043811-96c531f5bd83
|
||||
github.com/ebitengine/hideconsole v1.0.0
|
||||
github.com/ebitengine/oto/v3 v3.3.0-alpha.4
|
||||
github.com/ebitengine/purego v0.8.0-alpha.5
|
||||
github.com/gen2brain/mpeg v0.3.2-0.20240412154320-a2ac4fc8a46f
|
||||
github.com/go-text/typesetting v0.1.1-0.20240522210117-2c045476f496
|
||||
github.com/go-text/typesetting v0.1.1
|
||||
github.com/hajimehoshi/bitmapfont/v3 v3.2.0-alpha.5
|
||||
github.com/hajimehoshi/go-mp3 v0.3.4
|
||||
github.com/jakecoffman/cp v1.2.1
|
||||
github.com/jezek/xgb v1.1.1
|
||||
github.com/jfreymuth/oggvorbis v1.0.5
|
||||
github.com/kisielk/errcheck v1.7.0
|
||||
golang.org/x/image v0.20.0
|
||||
golang.org/x/image v0.19.0
|
||||
golang.org/x/sync v0.8.0
|
||||
golang.org/x/sys v0.25.0
|
||||
golang.org/x/text v0.18.0
|
||||
golang.org/x/tools v0.25.0
|
||||
golang.org/x/text v0.17.0
|
||||
golang.org/x/tools v0.24.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/jfreymuth/vorbis v1.0.2 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
golang.org/x/mod v0.21.0 // indirect
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
)
|
||||
|
25
go.sum
25
go.sum
@ -1,5 +1,5 @@
|
||||
github.com/ebitengine/gomobile v0.0.0-20240911145611-4856209ac325 h1:Gk1XUEttOk0/hb6Tq3WkmutWa0ZLhNn/6fc6XZpM7tM=
|
||||
github.com/ebitengine/gomobile v0.0.0-20240911145611-4856209ac325/go.mod h1:ulhSQcbPioQrallSuIzF8l1NKQoD7xmMZc5NxzibUMY=
|
||||
github.com/ebitengine/gomobile v0.0.0-20240825043811-96c531f5bd83 h1:yA0CtFKYZI/db1snCOInRS0Z18QGZU6aBYkqUT0H6RI=
|
||||
github.com/ebitengine/gomobile v0.0.0-20240825043811-96c531f5bd83/go.mod h1:n2NbB/F4d9wOXFzC7FT1ipERidmYWC5I4YNOYRs5N7I=
|
||||
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/oto/v3 v3.3.0-alpha.4 h1:w9SD7kK4GgJULkh5pWVTToMA5Ia1bP7VxD4rIjQqb8M=
|
||||
@ -8,10 +8,9 @@ github.com/ebitengine/purego v0.8.0-alpha.5 h1:M0+PSgsdVNczTB8ijX89HmYqCfb2HUuBE
|
||||
github.com/ebitengine/purego v0.8.0-alpha.5/go.mod h1:SQ56/omnSL8DdaBSKswoBvsMjgaWQyxyeMtb48sOskI=
|
||||
github.com/gen2brain/mpeg v0.3.2-0.20240412154320-a2ac4fc8a46f h1:ysqRe+lvUiL0dH5XzkH0Bz68bFMPJ4f5Si4L/HD9SGk=
|
||||
github.com/gen2brain/mpeg v0.3.2-0.20240412154320-a2ac4fc8a46f/go.mod h1:i/ebyRRv/IoHixuZ9bElZnXbmfoUVPGQpdsJ4sVuX38=
|
||||
github.com/go-text/typesetting v0.1.1-0.20240522210117-2c045476f496 h1:zgx3rOyOdRoA2GXWpfJkH7Zg248ookseRifdn9VSp5g=
|
||||
github.com/go-text/typesetting v0.1.1-0.20240522210117-2c045476f496/go.mod h1:2+owI/sxa73XA581LAzVuEBZ3WEEV2pXeDswCH/3i1I=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66 h1:GUrm65PQPlhFSKjLPGOZNPNxLCybjzjYBzjfoBGaDUY=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
||||
github.com/go-text/typesetting v0.1.1 h1:bGAesCuo85nXnEN5LmFMVGAGpGkCPtHrZLi//qD7EJo=
|
||||
github.com/go-text/typesetting v0.1.1/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04 h1:zBx+p/W2aQYtNuyZNcTfinWvXBQwYtDfme051PR/lAY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/hajimehoshi/bitmapfont/v3 v3.2.0-alpha.5 h1:gtIcN2INlD2qlfUiECuvbI0moNIoANgIY7MwgW4cFGE=
|
||||
@ -38,16 +37,15 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ=
|
||||
golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys=
|
||||
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
|
||||
golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
@ -94,15 +92,14 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
|
||||
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
85
image.go
85
image.go
@ -144,16 +144,6 @@ 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.
|
||||
@ -283,11 +273,7 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
|
||||
hint = restorable.HintOverwriteDstRegion
|
||||
}
|
||||
|
||||
skipMipmap := options.DisableMipmaps
|
||||
if !skipMipmap {
|
||||
skipMipmap = canSkipMipmap(geoM, filter)
|
||||
}
|
||||
i.image.DrawTriangles(srcs, vs, is, blend, dr, [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRuleFillAll, skipMipmap, false, hint)
|
||||
i.image.DrawTriangles(srcs, vs, is, blend, dr, [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRuleFillAll, canSkipMipmap(geoM, filter), false, hint)
|
||||
}
|
||||
|
||||
// overwritesDstRegion reports whether the given parameters overwrite the destination region completely.
|
||||
@ -461,16 +447,6 @@ type DrawTrianglesOptions struct {
|
||||
//
|
||||
// The default (zero) value is false.
|
||||
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.
|
||||
@ -554,32 +530,30 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
|
||||
vs := i.ensureTmpVertices(len(vertices) * graphics.VertexFloatCount)
|
||||
dst := i
|
||||
if options.ColorScaleMode == ColorScaleModeStraightAlpha {
|
||||
// Avoid using `for i, v := range vertices` as adding `v` creates a copy from `vertices` unnecessarily on each loop (#3103).
|
||||
for i := range vertices {
|
||||
dx, dy := dst.adjustPositionF32(vertices[i].DstX, vertices[i].DstY)
|
||||
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(vertices[i].SrcX, vertices[i].SrcY)
|
||||
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] = vertices[i].ColorR * vertices[i].ColorA * cr
|
||||
vs[i*graphics.VertexFloatCount+5] = vertices[i].ColorG * vertices[i].ColorA * cg
|
||||
vs[i*graphics.VertexFloatCount+6] = vertices[i].ColorB * vertices[i].ColorA * cb
|
||||
vs[i*graphics.VertexFloatCount+7] = vertices[i].ColorA * ca
|
||||
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
|
||||
}
|
||||
} else {
|
||||
// See comment above (#3103).
|
||||
for i := range vertices {
|
||||
dx, dy := dst.adjustPositionF32(vertices[i].DstX, vertices[i].DstY)
|
||||
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(vertices[i].SrcX, vertices[i].SrcY)
|
||||
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] = vertices[i].ColorR * cr
|
||||
vs[i*graphics.VertexFloatCount+5] = vertices[i].ColorG * cg
|
||||
vs[i*graphics.VertexFloatCount+6] = vertices[i].ColorB * cb
|
||||
vs[i*graphics.VertexFloatCount+7] = vertices[i].ColorA * ca
|
||||
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
|
||||
}
|
||||
}
|
||||
is := i.ensureTmpIndices(len(indices))
|
||||
@ -602,11 +576,7 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
|
||||
})
|
||||
}
|
||||
|
||||
skipMipmap := options.DisableMipmaps
|
||||
if !skipMipmap {
|
||||
skipMipmap = filter != builtinshader.FilterLinear
|
||||
}
|
||||
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), skipMipmap, options.AntiAlias, restorable.HintNone)
|
||||
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), filter != builtinshader.FilterLinear, options.AntiAlias, restorable.HintNone)
|
||||
}
|
||||
|
||||
// DrawTrianglesShaderOptions represents options for DrawTrianglesShader.
|
||||
@ -720,25 +690,24 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
|
||||
vs := i.ensureTmpVertices(len(vertices) * graphics.VertexFloatCount)
|
||||
dst := i
|
||||
src := options.Images[0]
|
||||
// Avoid using `for i, v := range vertices` as adding `v` creates a copy from `vertices` unnecessarily on each loop (#3103).
|
||||
for i := range vertices {
|
||||
dx, dy := dst.adjustPositionF32(vertices[i].DstX, vertices[i].DstY)
|
||||
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 := vertices[i].SrcX, vertices[i].SrcY
|
||||
sx, sy := v.SrcX, v.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] = vertices[i].ColorR
|
||||
vs[i*graphics.VertexFloatCount+5] = vertices[i].ColorG
|
||||
vs[i*graphics.VertexFloatCount+6] = vertices[i].ColorB
|
||||
vs[i*graphics.VertexFloatCount+7] = vertices[i].ColorA
|
||||
vs[i*graphics.VertexFloatCount+8] = vertices[i].Custom0
|
||||
vs[i*graphics.VertexFloatCount+9] = vertices[i].Custom1
|
||||
vs[i*graphics.VertexFloatCount+10] = vertices[i].Custom2
|
||||
vs[i*graphics.VertexFloatCount+11] = vertices[i].Custom3
|
||||
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[i*graphics.VertexFloatCount+8] = v.Custom0
|
||||
vs[i*graphics.VertexFloatCount+9] = v.Custom1
|
||||
vs[i*graphics.VertexFloatCount+10] = v.Custom2
|
||||
vs[i*graphics.VertexFloatCount+11] = v.Custom3
|
||||
}
|
||||
|
||||
is := i.ensureTmpIndices(len(indices))
|
||||
|
@ -22,9 +22,10 @@ import (
|
||||
"image/draw"
|
||||
_ "image/png"
|
||||
"math"
|
||||
"math/rand/v2"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/examples/resources/images"
|
||||
@ -36,6 +37,10 @@ import (
|
||||
// maxImageSize is a maximum image size that should work in almost every environment.
|
||||
const maxImageSize = 4096 - 2
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func skipTooSlowTests(t *testing.T) bool {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode")
|
||||
@ -343,6 +348,26 @@ func TestImageDeallocate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type ordered interface {
|
||||
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string
|
||||
}
|
||||
|
||||
// TODO: Use the built-in function min from Go 1.21.
|
||||
func min[T ordered](a, b T) T {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// TODO: Use the built-in function max from Go 1.21.
|
||||
func max[T ordered](a, b T) T {
|
||||
if a < b {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func TestImageBlendLighter(t *testing.T) {
|
||||
img0, _, err := openEbitenImage()
|
||||
if err != nil {
|
||||
@ -4267,7 +4292,7 @@ func TestImageAntiAlias(t *testing.T) {
|
||||
ebiten.BlendXor,
|
||||
ebiten.BlendLighter,
|
||||
} {
|
||||
rnd := rand.New(rand.NewPCG(0, 0))
|
||||
rnd := rand.New(rand.NewSource(0))
|
||||
max := func(x, y, z byte) byte {
|
||||
if x >= y && x >= z {
|
||||
return x
|
||||
|
8
input.go
8
input.go
@ -79,7 +79,7 @@ func KeyName(key Key) string {
|
||||
//
|
||||
// CursorPosition returns (0, 0) before the main loop on desktops and browsers.
|
||||
//
|
||||
// CursorPosition always returns (0, 0) on mobile native applications.
|
||||
// CursorPosition always returns (0, 0) on mobiles.
|
||||
//
|
||||
// CursorPosition is concurrent-safe.
|
||||
func CursorPosition() (x, y int) {
|
||||
@ -350,7 +350,7 @@ func UpdateStandardGamepadLayoutMappings(mappings string) (bool, error) {
|
||||
}
|
||||
|
||||
// TouchID represents a touch's identifier.
|
||||
type TouchID int
|
||||
type TouchID = ui.TouchID
|
||||
|
||||
// AppendTouchIDs appends the current touch states to touches, and returns the extended buffer.
|
||||
// Giving a slice that already has enough capacity works efficiently.
|
||||
@ -446,7 +446,7 @@ func (i *inputState) appendTouchIDs(touches []TouchID) []TouchID {
|
||||
defer i.m.Unlock()
|
||||
|
||||
for _, t := range i.state.Touches {
|
||||
touches = append(touches, TouchID(t.ID))
|
||||
touches = append(touches, t.ID)
|
||||
}
|
||||
return touches
|
||||
}
|
||||
@ -456,7 +456,7 @@ func (i *inputState) touchPosition(id TouchID) (int, int) {
|
||||
defer i.m.Unlock()
|
||||
|
||||
for _, t := range i.state.Touches {
|
||||
if id != TouchID(t.ID) {
|
||||
if id != t.ID {
|
||||
continue
|
||||
}
|
||||
return t.X, t.Y
|
||||
|
@ -16,7 +16,7 @@ package affine_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand/v2"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/affine"
|
||||
|
@ -35,6 +35,13 @@ var (
|
||||
maxSize = 0
|
||||
)
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func appendDeferred(f func()) {
|
||||
deferredM.Lock()
|
||||
defer deferredM.Unlock()
|
||||
|
@ -314,5 +314,8 @@ func (i *Image) syncPixelsIfNeeded() {
|
||||
blend := graphicsdriver.BlendCopy
|
||||
i.img.DrawTriangles(srcs, vs, is, blend, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||
|
||||
clear(i.dotsBuffer)
|
||||
// TODO: Use clear if Go 1.21 is available.
|
||||
for pos := range i.dotsBuffer {
|
||||
delete(i.dotsBuffer, pos)
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +64,13 @@ func ActualTPS() float64 {
|
||||
return actualTPS
|
||||
}
|
||||
|
||||
func max(a, b int64) int64 {
|
||||
if a < b {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func calcCountFromTPS(tps int64, now int64) int {
|
||||
if tps == 0 {
|
||||
return 0
|
||||
|
@ -616,5 +616,6 @@ func bytePtrToString(p *byte) string {
|
||||
ptr = unsafe.Add(ptr, 1)
|
||||
}
|
||||
|
||||
return unsafe.String(p, n)
|
||||
// unsafe.String(p, n) is available as of Go 1.20.
|
||||
return string(unsafe.Slice(p, n))
|
||||
}
|
||||
|
@ -507,6 +507,13 @@ func roundUpPower2(x int) int {
|
||||
return p2
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a < b {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (b *uint32sBuffer) alloc(n int) []uint32 {
|
||||
buf := b.buf
|
||||
if len(buf)+n > cap(buf) {
|
||||
|
@ -17,6 +17,8 @@ package gl
|
||||
import (
|
||||
"fmt"
|
||||
"syscall/js"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/jsutil"
|
||||
)
|
||||
|
||||
type defaultContext struct {
|
||||
@ -286,7 +288,7 @@ func (c *defaultContext) BufferInit(target uint32, size int, usage uint32) {
|
||||
|
||||
func (c *defaultContext) BufferSubData(target uint32, offset int, data []byte) {
|
||||
l := len(data)
|
||||
arr := tmpUint8ArrayFromUint8Slice(l, data)
|
||||
arr := jsutil.TemporaryUint8ArrayFromUint8Slice(l, data)
|
||||
c.fnBufferSubData.Invoke(target, offset, arr, 0, l)
|
||||
}
|
||||
|
||||
@ -492,7 +494,7 @@ func (c *defaultContext) ReadPixels(dst []byte, x int32, y int32, width int32, h
|
||||
c.fnReadPixels.Invoke(x, y, width, height, format, xtype, 0)
|
||||
return
|
||||
}
|
||||
p := tmpUint8ArrayFromUint8Slice(len(dst), nil)
|
||||
p := jsutil.TemporaryUint8ArrayFromUint8Slice(len(dst), nil)
|
||||
c.fnReadPixels.Invoke(x, y, width, height, format, xtype, p)
|
||||
js.CopyBytesToGo(dst, p)
|
||||
}
|
||||
@ -529,7 +531,7 @@ func (c *defaultContext) TexParameteri(target uint32, pname uint32, param int32)
|
||||
}
|
||||
|
||||
func (c *defaultContext) TexSubImage2D(target uint32, level int32, xoffset int32, yoffset int32, width int32, height int32, format uint32, xtype uint32, pixels []byte) {
|
||||
arr := tmpUint8ArrayFromUint8Slice(len(pixels), pixels)
|
||||
arr := jsutil.TemporaryUint8ArrayFromUint8Slice(len(pixels), pixels)
|
||||
// void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
|
||||
// GLsizei width, GLsizei height,
|
||||
// GLenum format, GLenum type, ArrayBufferView pixels, srcOffset);
|
||||
@ -538,7 +540,7 @@ func (c *defaultContext) TexSubImage2D(target uint32, level int32, xoffset int32
|
||||
|
||||
func (c *defaultContext) Uniform1fv(location int32, value []float32) {
|
||||
l := c.getUniformLocation(location)
|
||||
arr := tmpFloat32ArrayFromFloat32Slice(len(value), value)
|
||||
arr := jsutil.TemporaryFloat32Array(len(value), value)
|
||||
c.fnUniform1fv.Invoke(l, arr, 0, len(value))
|
||||
}
|
||||
|
||||
@ -549,61 +551,61 @@ func (c *defaultContext) Uniform1i(location int32, v0 int32) {
|
||||
|
||||
func (c *defaultContext) Uniform1iv(location int32, value []int32) {
|
||||
l := c.getUniformLocation(location)
|
||||
arr := tmpInt32ArrayFromInt32Slice(len(value), value)
|
||||
arr := jsutil.TemporaryInt32Array(len(value), value)
|
||||
c.fnUniform1iv.Invoke(l, arr, 0, len(value))
|
||||
}
|
||||
|
||||
func (c *defaultContext) Uniform2fv(location int32, value []float32) {
|
||||
l := c.getUniformLocation(location)
|
||||
arr := tmpFloat32ArrayFromFloat32Slice(len(value), value)
|
||||
arr := jsutil.TemporaryFloat32Array(len(value), value)
|
||||
c.fnUniform2fv.Invoke(l, arr, 0, len(value))
|
||||
}
|
||||
|
||||
func (c *defaultContext) Uniform2iv(location int32, value []int32) {
|
||||
l := c.getUniformLocation(location)
|
||||
arr := tmpInt32ArrayFromInt32Slice(len(value), value)
|
||||
arr := jsutil.TemporaryInt32Array(len(value), value)
|
||||
c.fnUniform2iv.Invoke(l, arr, 0, len(value))
|
||||
}
|
||||
|
||||
func (c *defaultContext) Uniform3fv(location int32, value []float32) {
|
||||
l := c.getUniformLocation(location)
|
||||
arr := tmpFloat32ArrayFromFloat32Slice(len(value), value)
|
||||
arr := jsutil.TemporaryFloat32Array(len(value), value)
|
||||
c.fnUniform3fv.Invoke(l, arr, 0, len(value))
|
||||
}
|
||||
|
||||
func (c *defaultContext) Uniform3iv(location int32, value []int32) {
|
||||
l := c.getUniformLocation(location)
|
||||
arr := tmpInt32ArrayFromInt32Slice(len(value), value)
|
||||
arr := jsutil.TemporaryInt32Array(len(value), value)
|
||||
c.fnUniform3iv.Invoke(l, arr, 0, len(value))
|
||||
}
|
||||
|
||||
func (c *defaultContext) Uniform4fv(location int32, value []float32) {
|
||||
l := c.getUniformLocation(location)
|
||||
arr := tmpFloat32ArrayFromFloat32Slice(len(value), value)
|
||||
arr := jsutil.TemporaryFloat32Array(len(value), value)
|
||||
c.fnUniform4fv.Invoke(l, arr, 0, len(value))
|
||||
}
|
||||
|
||||
func (c *defaultContext) Uniform4iv(location int32, value []int32) {
|
||||
l := c.getUniformLocation(location)
|
||||
arr := tmpInt32ArrayFromInt32Slice(len(value), value)
|
||||
arr := jsutil.TemporaryInt32Array(len(value), value)
|
||||
c.fnUniform4iv.Invoke(l, arr, 0, len(value))
|
||||
}
|
||||
|
||||
func (c *defaultContext) UniformMatrix2fv(location int32, value []float32) {
|
||||
l := c.getUniformLocation(location)
|
||||
arr := tmpFloat32ArrayFromFloat32Slice(len(value), value)
|
||||
arr := jsutil.TemporaryFloat32Array(len(value), value)
|
||||
c.fnUniformMatrix2fv.Invoke(l, false, arr, 0, len(value))
|
||||
}
|
||||
|
||||
func (c *defaultContext) UniformMatrix3fv(location int32, value []float32) {
|
||||
l := c.getUniformLocation(location)
|
||||
arr := tmpFloat32ArrayFromFloat32Slice(len(value), value)
|
||||
arr := jsutil.TemporaryFloat32Array(len(value), value)
|
||||
c.fnUniformMatrix3fv.Invoke(l, false, arr, 0, len(value))
|
||||
}
|
||||
|
||||
func (c *defaultContext) UniformMatrix4fv(location int32, value []float32) {
|
||||
l := c.getUniformLocation(location)
|
||||
arr := tmpFloat32ArrayFromFloat32Slice(len(value), value)
|
||||
arr := jsutil.TemporaryFloat32Array(len(value), value)
|
||||
c.fnUniformMatrix4fv.Invoke(l, false, arr, 0, len(value))
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,8 @@ func (c *defaultContext) init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("gl: failed to load: OpenGL.framework: %w, OpenGLES.framework: %w", errGL, errGLES)
|
||||
// TODO: Use multiple %w-s as of Go 1.20
|
||||
return fmt.Errorf("gl: failed to load: OpenGL.framework: %v, OpenGLES.framework: %v", errGL, errGLES)
|
||||
}
|
||||
|
||||
func (c *defaultContext) getProcAddress(name string) (uintptr, error) {
|
||||
|
@ -17,8 +17,8 @@
|
||||
package gl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ebitengine/purego"
|
||||
)
|
||||
@ -29,7 +29,8 @@ var (
|
||||
)
|
||||
|
||||
func (c *defaultContext) init() error {
|
||||
var errs []error
|
||||
// TODO: Use multiple %w-s as of Go 1.20.
|
||||
var errors []string
|
||||
|
||||
// Try OpenGL ES first. Some machines like Android and Raspberry Pi might work only with OpenGL ES.
|
||||
for _, name := range []string{"libGLESv2.so", "libGLESv2.so.2", "libGLESv2.so.1", "libGLESv2.so.0"} {
|
||||
@ -39,7 +40,7 @@ func (c *defaultContext) init() error {
|
||||
c.isES = true
|
||||
return nil
|
||||
}
|
||||
errs = append(errs, fmt.Errorf("gl: Dlopen failed: name: %s: %w", name, err))
|
||||
errors = append(errors, fmt.Sprintf("%s: %v", name, err))
|
||||
}
|
||||
|
||||
// Try OpenGL next.
|
||||
@ -53,11 +54,10 @@ func (c *defaultContext) init() error {
|
||||
libGL = lib
|
||||
return nil
|
||||
}
|
||||
errs = append(errs, fmt.Errorf("gl: Dlopen failed: name: %s: %w", name, err))
|
||||
errors = append(errors, fmt.Sprintf("%s: %v", name, err))
|
||||
}
|
||||
|
||||
errs = append([]error{fmt.Errorf("gl: failed to load libGL.so and libGLESv2.so: ")}, errs...)
|
||||
return errors.Join(errs...)
|
||||
return fmt.Errorf("gl: failed to load libGL.so and libGLESv2.so: %s", strings.Join(errors, ", "))
|
||||
}
|
||||
|
||||
func (c *defaultContext) getProcAddress(name string) (uintptr, error) {
|
||||
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package gl
|
||||
package jsutil
|
||||
|
||||
import (
|
||||
"syscall/js"
|
||||
@ -27,77 +27,77 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
tmpArrayBufferByteLength = 16
|
||||
temporaryArrayBufferByteLength = 16
|
||||
|
||||
// tmpArrayBuffer is a temporary buffer used at gl.readPixels or gl.texSubImage2D.
|
||||
// temporaryArrayBuffer is a temporary buffer used at gl.readPixels or gl.texSubImage2D.
|
||||
// The read data is converted to Go's byte slice as soon as possible.
|
||||
// To avoid often allocating ArrayBuffer, reuse the buffer whenever possible.
|
||||
tmpArrayBuffer = arrayBuffer.New(tmpArrayBufferByteLength)
|
||||
temporaryArrayBuffer = arrayBuffer.New(temporaryArrayBufferByteLength)
|
||||
|
||||
// tmpUint8Array is a Uint8ArrayBuffer whose underlying buffer is always temporaryArrayBuffer.
|
||||
tmpUint8Array = uint8Array.New(tmpArrayBuffer)
|
||||
// temporaryUint8Array is a Uint8ArrayBuffer whose underlying buffer is always temporaryArrayBuffer.
|
||||
temporaryUint8Array = uint8Array.New(temporaryArrayBuffer)
|
||||
|
||||
// tmpFloat32Array is a Float32ArrayBuffer whose underlying buffer is always temporaryArrayBuffer.
|
||||
tmpFloat32Array = float32Array.New(tmpArrayBuffer)
|
||||
// temporaryFloat32Array is a Float32ArrayBuffer whose underlying buffer is always temporaryArrayBuffer.
|
||||
temporaryFloat32Array = float32Array.New(temporaryArrayBuffer)
|
||||
|
||||
// tmpInt32Array is a Float32ArrayBuffer whose underlying buffer is always temporaryArrayBuffer.
|
||||
tmpInt32Array = int32Array.New(tmpArrayBuffer)
|
||||
// temporaryInt32Array is a Float32ArrayBuffer whose underlying buffer is always temporaryArrayBuffer.
|
||||
temporaryInt32Array = int32Array.New(temporaryArrayBuffer)
|
||||
)
|
||||
|
||||
func ensureTemporaryArrayBufferSize(byteLength int) {
|
||||
if bufl := tmpArrayBufferByteLength; bufl < byteLength {
|
||||
if bufl := temporaryArrayBufferByteLength; bufl < byteLength {
|
||||
for bufl < byteLength {
|
||||
bufl *= 2
|
||||
}
|
||||
tmpArrayBufferByteLength = bufl
|
||||
tmpArrayBuffer = arrayBuffer.New(bufl)
|
||||
tmpUint8Array = uint8Array.New(tmpArrayBuffer)
|
||||
tmpFloat32Array = float32Array.New(tmpArrayBuffer)
|
||||
tmpInt32Array = int32Array.New(tmpArrayBuffer)
|
||||
temporaryArrayBufferByteLength = bufl
|
||||
temporaryArrayBuffer = arrayBuffer.New(bufl)
|
||||
temporaryUint8Array = uint8Array.New(temporaryArrayBuffer)
|
||||
temporaryFloat32Array = float32Array.New(temporaryArrayBuffer)
|
||||
temporaryInt32Array = int32Array.New(temporaryArrayBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
// tmpUint8ArrayFromUint8Slice returns a Uint8Array whose length is at least minLength from an uint8 slice.
|
||||
// TemporaryUint8ArrayFromUint8Slice returns a Uint8Array whose length is at least minLength from an uint8 slice.
|
||||
// Be careful that the length can exceed the given minLength.
|
||||
// data must be a slice of a numeric type for initialization, or nil if you don't need initialization.
|
||||
func tmpUint8ArrayFromUint8Slice(minLength int, data []uint8) js.Value {
|
||||
func TemporaryUint8ArrayFromUint8Slice(minLength int, data []uint8) js.Value {
|
||||
ensureTemporaryArrayBufferSize(minLength)
|
||||
copyUint8SliceToTemporaryArrayBuffer(data)
|
||||
return tmpUint8Array
|
||||
return temporaryUint8Array
|
||||
}
|
||||
|
||||
// tmpUint8ArrayFromUint16Slice returns a Uint8Array whose length is at least minLength from an uint16 slice.
|
||||
// TemporaryUint8ArrayFromUint16Slice returns a Uint8Array whose length is at least minLength from an uint16 slice.
|
||||
// Be careful that the length can exceed the given minLength.
|
||||
// data must be a slice of a numeric type for initialization, or nil if you don't need initialization.
|
||||
func tmpUint8ArrayFromUint16Slice(minLength int, data []uint16) js.Value {
|
||||
func TemporaryUint8ArrayFromUint16Slice(minLength int, data []uint16) js.Value {
|
||||
ensureTemporaryArrayBufferSize(minLength * 2)
|
||||
copySliceToTemporaryArrayBuffer(data)
|
||||
return tmpUint8Array
|
||||
return temporaryUint8Array
|
||||
}
|
||||
|
||||
// tmpUint8ArrayFromFloat32Slice returns a Uint8Array whose length is at least minLength from a float32 slice.
|
||||
// TemporaryUint8ArrayFromFloat32Slice returns a Uint8Array whose length is at least minLength from a float32 slice.
|
||||
// Be careful that the length can exceed the given minLength.
|
||||
// data must be a slice of a numeric type for initialization, or nil if you don't need initialization.
|
||||
func tmpUint8ArrayFromFloat32Slice(minLength int, data []float32) js.Value {
|
||||
func TemporaryUint8ArrayFromFloat32Slice(minLength int, data []float32) js.Value {
|
||||
ensureTemporaryArrayBufferSize(minLength * 4)
|
||||
copySliceToTemporaryArrayBuffer(data)
|
||||
return tmpUint8Array
|
||||
return temporaryUint8Array
|
||||
}
|
||||
|
||||
// tmpFloat32ArrayFromFloat32Slice returns a Float32Array whose length is at least minLength.
|
||||
// TemporaryFloat32Array returns a Float32Array whose length is at least minLength.
|
||||
// Be careful that the length can exceed the given minLength.
|
||||
// data must be a slice of a numeric type for initialization, or nil if you don't need initialization.
|
||||
func tmpFloat32ArrayFromFloat32Slice(minLength int, data []float32) js.Value {
|
||||
func TemporaryFloat32Array(minLength int, data []float32) js.Value {
|
||||
ensureTemporaryArrayBufferSize(minLength * 4)
|
||||
copySliceToTemporaryArrayBuffer(data)
|
||||
return tmpFloat32Array
|
||||
return temporaryFloat32Array
|
||||
}
|
||||
|
||||
// tmpInt32ArrayFromInt32Slice returns a Int32Array whose length is at least minLength.
|
||||
// TemporaryInt32Array returns a Int32Array whose length is at least minLength.
|
||||
// Be careful that the length can exceed the given minLength.
|
||||
// data must be a slice of a numeric type for initialization, or nil if you don't need initialization.
|
||||
func tmpInt32ArrayFromInt32Slice(minLength int, data []int32) js.Value {
|
||||
func TemporaryInt32Array(minLength int, data []int32) js.Value {
|
||||
ensureTemporaryArrayBufferSize(minLength * 4)
|
||||
copySliceToTemporaryArrayBuffer(data)
|
||||
return tmpInt32Array
|
||||
return temporaryInt32Array
|
||||
}
|
19
internal/jsutil/doc_js.go
Normal file
19
internal/jsutil/doc_js.go
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2019 The Ebiten Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This can be compiled in non-JS environments to avoid a mysterious error: 'no Go source files'
|
||||
// See https://travis-ci.org/hajimehoshi/ebiten/builds/603539948
|
||||
|
||||
// Package jsutil offers utility functions for Wasm.
|
||||
package jsutil
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package gl
|
||||
package jsutil
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
@ -24,7 +24,7 @@ func copyUint8SliceToTemporaryArrayBuffer(src []uint8) {
|
||||
if len(src) == 0 {
|
||||
return
|
||||
}
|
||||
js.CopyBytesToJS(tmpUint8Array, src)
|
||||
js.CopyBytesToJS(temporaryUint8Array, src)
|
||||
}
|
||||
|
||||
type numeric interface {
|
||||
@ -35,6 +35,6 @@ func copySliceToTemporaryArrayBuffer[T numeric](src []T) {
|
||||
if len(src) == 0 {
|
||||
return
|
||||
}
|
||||
js.CopyBytesToJS(tmpUint8Array, unsafe.Slice((*byte)(unsafe.Pointer(&src[0])), len(src)*int(unsafe.Sizeof(T(0)))))
|
||||
js.CopyBytesToJS(temporaryUint8Array, unsafe.Slice((*byte)(unsafe.Pointer(&src[0])), len(src)*int(unsafe.Sizeof(T(0)))))
|
||||
runtime.KeepAlive(src)
|
||||
}
|
@ -41,12 +41,7 @@ type Mipmap struct {
|
||||
height int
|
||||
imageType atlas.ImageType
|
||||
orig *buffered.Image
|
||||
imgs map[int]imageWithDirtyFlag
|
||||
}
|
||||
|
||||
type imageWithDirtyFlag struct {
|
||||
img *buffered.Image
|
||||
dirty bool
|
||||
imgs map[int]*buffered.Image
|
||||
}
|
||||
|
||||
func New(width, height int, imageType atlas.ImageType) *Mipmap {
|
||||
@ -64,14 +59,7 @@ func (m *Mipmap) DumpScreenshot(graphicsDriver graphicsdriver.Graphics, name str
|
||||
|
||||
func (m *Mipmap) WritePixels(pix []byte, region image.Rectangle) {
|
||||
m.orig.WritePixels(pix, region)
|
||||
m.markDirty()
|
||||
}
|
||||
|
||||
func (m *Mipmap) markDirty() {
|
||||
for i, img := range m.imgs {
|
||||
img.dirty = true
|
||||
m.imgs[i] = img
|
||||
}
|
||||
m.deallocateMipmaps()
|
||||
}
|
||||
|
||||
func (m *Mipmap) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte, region image.Rectangle) (ok bool, err error) {
|
||||
@ -137,17 +125,14 @@ func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Mipmap, verti
|
||||
}
|
||||
|
||||
m.orig.DrawTriangles(imgs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, hint)
|
||||
m.markDirty()
|
||||
m.deallocateMipmaps()
|
||||
}
|
||||
|
||||
func (m *Mipmap) setImg(level int, img *buffered.Image) {
|
||||
if m.imgs == nil {
|
||||
m.imgs = map[int]imageWithDirtyFlag{}
|
||||
}
|
||||
m.imgs[level] = imageWithDirtyFlag{
|
||||
img: img,
|
||||
dirty: false,
|
||||
m.imgs = map[int]*buffered.Image{}
|
||||
}
|
||||
m.imgs[level] = img
|
||||
}
|
||||
|
||||
func (m *Mipmap) level(level int) *buffered.Image {
|
||||
@ -159,63 +144,54 @@ func (m *Mipmap) level(level int) *buffered.Image {
|
||||
panic("mipmap: mipmap images for a screen image is not implemented yet")
|
||||
}
|
||||
|
||||
img, ok := m.imgs[level]
|
||||
if ok && !img.dirty {
|
||||
return img.img
|
||||
if img, ok := m.imgs[level]; ok {
|
||||
return img
|
||||
}
|
||||
|
||||
var srcW, srcH int
|
||||
var src *buffered.Image
|
||||
vs := make([]float32, 4*graphics.VertexFloatCount)
|
||||
switch {
|
||||
case level == 1:
|
||||
src = m.orig
|
||||
srcW = m.width
|
||||
srcH = m.height
|
||||
graphics.QuadVerticesFromSrcAndMatrix(vs, 0, 0, float32(m.width), float32(m.height), 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1)
|
||||
case level > 1:
|
||||
src = m.level(level - 1)
|
||||
if src == nil {
|
||||
m.setImg(level, nil)
|
||||
return nil
|
||||
}
|
||||
srcW = sizeForLevel(m.width, level-1)
|
||||
srcH = sizeForLevel(m.height, level-1)
|
||||
w := sizeForLevel(m.width, level-1)
|
||||
h := sizeForLevel(m.height, level-1)
|
||||
graphics.QuadVerticesFromSrcAndMatrix(vs, 0, 0, float32(w), float32(h), 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1)
|
||||
default:
|
||||
panic(fmt.Sprintf("mipmap: invalid level: %d", level))
|
||||
}
|
||||
|
||||
graphics.QuadVerticesFromSrcAndMatrix(vs, 0, 0, float32(srcW), float32(srcH), 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1)
|
||||
|
||||
is := graphics.QuadIndices()
|
||||
|
||||
dstW := sizeForLevel(m.width, level)
|
||||
dstH := sizeForLevel(m.height, level)
|
||||
if dstW == 0 || dstH == 0 {
|
||||
w2 := sizeForLevel(m.width, level)
|
||||
h2 := sizeForLevel(m.height, level)
|
||||
if w2 == 0 || h2 == 0 {
|
||||
m.setImg(level, nil)
|
||||
return nil
|
||||
}
|
||||
// buffered.NewImage panics with a too big size when actual allocation happens.
|
||||
// 4096 should be a safe size in most environments (#1399).
|
||||
// Unfortunately a precise max image size cannot be obtained here since this requires GPU access.
|
||||
if dstW > 4096 || dstH > 4096 {
|
||||
if w2 > 4096 || h2 > 4096 {
|
||||
m.setImg(level, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
var s *buffered.Image
|
||||
if img.img != nil {
|
||||
// As s is overwritten, this doesn't have to be cleared.
|
||||
s = img.img
|
||||
} else {
|
||||
s = buffered.NewImage(dstW, dstH, m.imageType)
|
||||
}
|
||||
s := buffered.NewImage(w2, h2, m.imageType)
|
||||
|
||||
dstRegion := image.Rect(0, 0, dstW, dstH)
|
||||
srcRegion := image.Rect(0, 0, srcW, srcH)
|
||||
dstRegion := image.Rect(0, 0, w2, h2)
|
||||
w := sizeForLevel(m.width, level-1)
|
||||
h := sizeForLevel(m.height, level-1)
|
||||
srcRegion := image.Rect(0, 0, w, h)
|
||||
s.DrawTriangles([graphics.ShaderSrcImageCount]*buffered.Image{src}, vs, is, graphicsdriver.BlendCopy, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{srcRegion}, atlas.LinearFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintOverwriteDstRegion)
|
||||
m.setImg(level, s)
|
||||
|
||||
return m.imgs[level].img
|
||||
return m.imgs[level]
|
||||
}
|
||||
|
||||
func sizeForLevel(x int, level int) int {
|
||||
@ -229,16 +205,19 @@ func sizeForLevel(x int, level int) int {
|
||||
}
|
||||
|
||||
func (m *Mipmap) Deallocate() {
|
||||
m.deallocateMipmaps()
|
||||
m.orig.Deallocate()
|
||||
}
|
||||
|
||||
func (m *Mipmap) deallocateMipmaps() {
|
||||
for _, img := range m.imgs {
|
||||
if img.img == nil {
|
||||
continue
|
||||
if img != nil {
|
||||
img.Deallocate()
|
||||
}
|
||||
img.img.Deallocate()
|
||||
}
|
||||
for k := range m.imgs {
|
||||
delete(m.imgs, k)
|
||||
}
|
||||
m.orig.Deallocate()
|
||||
}
|
||||
|
||||
// mipmapLevel returns an appropriate mipmap level for the given distance.
|
||||
|
@ -136,6 +136,18 @@ func run() error {
|
||||
fmt.Fprintln(w)
|
||||
format.Node(w, fset, tree)
|
||||
|
||||
if f == "reader.go" {
|
||||
// The min function was removed as of Go 1.22, but this is needed for old Go.
|
||||
// TODO: Remove this when Go 1.21 is the minimum supported version.
|
||||
fmt.Fprintln(w, `
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}`)
|
||||
}
|
||||
|
||||
if err := w.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1054,3 +1054,10 @@ func DecodeConfig(r io.Reader) (image.Config, error) {
|
||||
func init() {
|
||||
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ type context struct {
|
||||
offscreenHeight float64
|
||||
|
||||
isOffscreenModified bool
|
||||
lastSwapBufferTime time.Time
|
||||
lastDrawTime time.Time
|
||||
|
||||
skipCount int
|
||||
|
||||
@ -70,14 +70,7 @@ func newContext(game Game) *context {
|
||||
|
||||
func (c *context) updateFrame(graphicsDriver graphicsdriver.Graphics, outsideWidth, outsideHeight float64, deviceScaleFactor float64, ui *UserInterface) error {
|
||||
// TODO: If updateCount is 0 and vsync is disabled, swapping buffers can be skipped.
|
||||
needsSwapBuffers, err := c.updateFrameImpl(graphicsDriver, clock.UpdateFrame(), outsideWidth, outsideHeight, deviceScaleFactor, ui, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.swapBuffersOrWait(needsSwapBuffers, graphicsDriver, ui.FPSMode() == FPSModeVsyncOn); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return c.updateFrameImpl(graphicsDriver, clock.UpdateFrame(), outsideWidth, outsideHeight, deviceScaleFactor, ui, false)
|
||||
}
|
||||
|
||||
func (c *context) forceUpdateFrame(graphicsDriver graphicsdriver.Graphics, outsideWidth, outsideHeight float64, deviceScaleFactor float64, ui *UserInterface) error {
|
||||
@ -88,32 +81,33 @@ func (c *context) forceUpdateFrame(graphicsDriver graphicsdriver.Graphics, outsi
|
||||
n = 2
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
needsSwapBuffers, err := c.updateFrameImpl(graphicsDriver, 1, outsideWidth, outsideHeight, deviceScaleFactor, ui, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.swapBuffersOrWait(needsSwapBuffers, graphicsDriver, ui.FPSMode() == FPSModeVsyncOn); err != nil {
|
||||
if err := c.updateFrameImpl(graphicsDriver, 1, outsideWidth, outsideHeight, deviceScaleFactor, ui, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, updateCount int, outsideWidth, outsideHeight float64, deviceScaleFactor float64, ui *UserInterface, forceDraw bool) (needsSwapBuffers bool, err error) {
|
||||
func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, updateCount int, outsideWidth, outsideHeight float64, deviceScaleFactor float64, ui *UserInterface, forceDraw bool) (err error) {
|
||||
// The given outside size can be 0 e.g. just after restoring from the fullscreen mode on Windows (#1589)
|
||||
// Just ignore such cases. Otherwise, creating a zero-sized framebuffer causes a panic.
|
||||
if outsideWidth == 0 || outsideHeight == 0 {
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
debug.FrameLogf("----\n")
|
||||
|
||||
if err := atlas.BeginFrame(graphicsDriver); err != nil {
|
||||
return false, err
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err1 := atlas.EndFrame(); err1 != nil && err == nil {
|
||||
needsSwapBuffers = false
|
||||
err = err1
|
||||
return
|
||||
}
|
||||
|
||||
if err1 := atlas.SwapBuffers(graphicsDriver); err1 != nil && err == nil {
|
||||
err = err1
|
||||
return
|
||||
}
|
||||
@ -121,17 +115,17 @@ func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, update
|
||||
|
||||
// Flush deferred functions, like reading pixels from GPU.
|
||||
if err := c.processFuncsInFrame(ui); err != nil {
|
||||
return false, err
|
||||
return err
|
||||
}
|
||||
|
||||
// ForceUpdate can be invoked even if the context is not initialized yet (#1591).
|
||||
if w, h := c.layoutGame(outsideWidth, outsideHeight, deviceScaleFactor); w == 0 || h == 0 {
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update the input state after the layout is updated as a cursor position is affected by the layout.
|
||||
if err := ui.updateInputState(); err != nil {
|
||||
return false, err
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure that Update is called once before Draw so that Update can be used for initialization.
|
||||
@ -149,15 +143,15 @@ func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, update
|
||||
})
|
||||
|
||||
if err := hook.RunBeforeUpdateHooks(); err != nil {
|
||||
return false, err
|
||||
return err
|
||||
}
|
||||
if err := c.game.Update(); err != nil {
|
||||
return false, err
|
||||
return err
|
||||
}
|
||||
|
||||
// Catch the error that happened at (*Image).At.
|
||||
if err := ui.error(); err != nil {
|
||||
return false, err
|
||||
return err
|
||||
}
|
||||
|
||||
ui.tick.Add(1)
|
||||
@ -166,39 +160,13 @@ func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, update
|
||||
// Update window icons during a frame, since an icon might be *ebiten.Image and
|
||||
// getting pixels from it needs to be in a frame (#1468).
|
||||
if err := ui.updateIconIfNeeded(); err != nil {
|
||||
return false, err
|
||||
return err
|
||||
}
|
||||
|
||||
// Draw the game.
|
||||
return c.drawGame(graphicsDriver, ui, forceDraw)
|
||||
}
|
||||
|
||||
func (c *context) swapBuffersOrWait(needsSwapBuffers bool, graphicsDriver graphicsdriver.Graphics, vsyncEnabled bool) error {
|
||||
now := time.Now()
|
||||
defer func() {
|
||||
c.lastSwapBufferTime = now
|
||||
}()
|
||||
|
||||
if needsSwapBuffers {
|
||||
if err := atlas.SwapBuffers(graphicsDriver); err != nil {
|
||||
if err := c.drawGame(graphicsDriver, ui, forceDraw); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var waitTime time.Duration
|
||||
if !needsSwapBuffers {
|
||||
// When swapping buffers is skipped and Draw is called too early, sleep for a while to suppress CPU usages (#2890).
|
||||
waitTime = time.Second / 60
|
||||
} else if vsyncEnabled {
|
||||
// In some environments, e.g. Linux on Parallels, SwapBuffers doesn't wait for the vsync (#2952).
|
||||
// In the case when the display has high refresh rates like 240 [Hz], the wait time should be small.
|
||||
waitTime = time.Millisecond
|
||||
}
|
||||
if waitTime > 0 {
|
||||
if delta := waitTime - now.Sub(c.lastSwapBufferTime); delta > 0 {
|
||||
time.Sleep(delta)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -211,7 +179,7 @@ func (c *context) newOffscreenImage(w, h int) *Image {
|
||||
return img
|
||||
}
|
||||
|
||||
func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics, ui *UserInterface, forceDraw bool) (needSwapBuffers bool, err error) {
|
||||
func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics, ui *UserInterface, forceDraw bool) error {
|
||||
if (c.offscreen.imageType == atlas.ImageTypeVolatile) != ui.IsScreenClearedEveryFrame() {
|
||||
w, h := c.offscreen.width, c.offscreen.height
|
||||
c.offscreen.Deallocate()
|
||||
@ -229,7 +197,7 @@ func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics, ui *UserInter
|
||||
}
|
||||
|
||||
if err := c.game.DrawOffscreen(); err != nil {
|
||||
return false, err
|
||||
return err
|
||||
}
|
||||
|
||||
const maxSkipCount = 4
|
||||
@ -242,10 +210,12 @@ func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics, ui *UserInter
|
||||
c.skipCount = 0
|
||||
}
|
||||
|
||||
if c.skipCount >= maxSkipCount {
|
||||
return false, nil
|
||||
}
|
||||
now := time.Now()
|
||||
defer func() {
|
||||
c.lastDrawTime = now
|
||||
}()
|
||||
|
||||
if c.skipCount < maxSkipCount {
|
||||
if graphicsDriver.NeedsClearingScreen() {
|
||||
// This clear is needed for fullscreen mode or some mobile platforms (#622).
|
||||
c.screen.clear()
|
||||
@ -256,7 +226,12 @@ func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics, ui *UserInter
|
||||
// The final screen is never used as the rendering source.
|
||||
// Flush its buffer here just in case.
|
||||
c.screen.flushBufferIfNeeded()
|
||||
return true, nil
|
||||
} else if delta := time.Second/60 - now.Sub(c.lastDrawTime); delta > 0 {
|
||||
// When swapping buffers is skipped and Draw is called too early, sleep for a while to suppress CPU usages (#2890).
|
||||
time.Sleep(delta)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) layoutGame(outsideWidth, outsideHeight float64, deviceScaleFactor float64) (int, int) {
|
||||
@ -265,13 +240,8 @@ func (c *context) layoutGame(outsideWidth, outsideHeight float64, deviceScaleFac
|
||||
panic("ui: Layout must return positive numbers")
|
||||
}
|
||||
|
||||
screenWidth := outsideWidth * deviceScaleFactor
|
||||
screenHeight := outsideHeight * deviceScaleFactor
|
||||
if c.screenWidth != screenWidth || c.screenHeight != screenHeight {
|
||||
c.skipCount = 0
|
||||
}
|
||||
c.screenWidth = screenWidth
|
||||
c.screenHeight = screenHeight
|
||||
c.screenWidth = outsideWidth * deviceScaleFactor
|
||||
c.screenHeight = outsideHeight * deviceScaleFactor
|
||||
c.offscreenWidth = owf
|
||||
c.offscreenHeight = ohf
|
||||
|
||||
|
@ -18,19 +18,15 @@ package ui
|
||||
#include <jni.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// The following JNI code works as this pseudo Java code:
|
||||
// Basically same as:
|
||||
//
|
||||
// WindowService windowService = context.getSystemService(Context.WINDOW_SERVICE);
|
||||
// Display display = windowManager.getDefaultDisplay();
|
||||
// DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
// display.getRealMetrics(displayMetrics);
|
||||
// return displayMetrics.widthPixels, displayMetrics.heightPixels, displayMetrics.density;
|
||||
// this.deviceScale = displayMetrics.density;
|
||||
//
|
||||
static void displayInfo(int* width, int* height, float* scale, uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx) {
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
*scale = 1;
|
||||
|
||||
static float deviceScale(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx) {
|
||||
JavaVM* vm = (JavaVM*)java_vm;
|
||||
JNIEnv* env = (JNIEnv*)jni_env;
|
||||
jobject context = (jobject)ctx;
|
||||
@ -68,15 +64,7 @@ static void displayInfo(int* width, int* height, float* scale, uintptr_t java_vm
|
||||
env, display,
|
||||
(*env)->GetMethodID(env, android_view_Display, "getRealMetrics", "(Landroid/util/DisplayMetrics;)V"),
|
||||
displayMetrics);
|
||||
*width =
|
||||
(*env)->GetIntField(
|
||||
env, displayMetrics,
|
||||
(*env)->GetFieldID(env, android_util_DisplayMetrics, "widthPixels", "I"));
|
||||
*height =
|
||||
(*env)->GetIntField(
|
||||
env, displayMetrics,
|
||||
(*env)->GetFieldID(env, android_util_DisplayMetrics, "heightPixels", "I"));
|
||||
*scale =
|
||||
const float density =
|
||||
(*env)->GetFloatField(
|
||||
env, displayMetrics,
|
||||
(*env)->GetFieldID(env, android_util_DisplayMetrics, "density", "F"));
|
||||
@ -90,12 +78,15 @@ static void displayInfo(int* width, int* height, float* scale, uintptr_t java_vm
|
||||
(*env)->DeleteLocalRef(env, windowManager);
|
||||
(*env)->DeleteLocalRef(env, display);
|
||||
(*env)->DeleteLocalRef(env, displayMetrics);
|
||||
|
||||
return density;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ebitengine/gomobile/app"
|
||||
|
||||
@ -128,27 +119,18 @@ func (*graphicsDriverCreatorImpl) newPlayStation5() (graphicsdriver.Graphics, er
|
||||
return nil, errors.New("ui: PlayStation 5 is not supported in this environment")
|
||||
}
|
||||
|
||||
func deviceScaleFactorImpl() float64 {
|
||||
var s float64
|
||||
if err := app.RunOnJVM(func(vm, env, ctx uintptr) error {
|
||||
// TODO: This might be crash when this is called from init(). How can we detect this?
|
||||
s = float64(C.deviceScale(C.uintptr_t(vm), C.uintptr_t(env), C.uintptr_t(ctx)))
|
||||
return nil
|
||||
}); err != nil {
|
||||
panic(fmt.Sprintf("devicescale: error %v", err))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func dipToNativePixels(x float64, scale float64) float64 {
|
||||
return x * scale
|
||||
}
|
||||
|
||||
func dipFromNativePixels(x float64, scale float64) float64 {
|
||||
return x / scale
|
||||
}
|
||||
|
||||
func (u *UserInterface) displayInfo() (int, int, float64, bool) {
|
||||
var cWidth, cHeight C.int
|
||||
var cScale C.float
|
||||
if err := app.RunOnJVM(func(vm, env, ctx uintptr) error {
|
||||
C.displayInfo(&cWidth, &cHeight, &cScale, C.uintptr_t(vm), C.uintptr_t(env), C.uintptr_t(ctx))
|
||||
return nil
|
||||
}); err != nil {
|
||||
// JVM is not ready yet.
|
||||
// TODO: Fix gomobile to detect the error type for this case.
|
||||
return 0, 0, 1, false
|
||||
}
|
||||
scale := float64(cScale)
|
||||
width := int(dipFromNativePixels(float64(cWidth), scale))
|
||||
height := int(dipFromNativePixels(float64(cHeight), scale))
|
||||
return width, height, scale, true
|
||||
}
|
||||
|
@ -81,8 +81,6 @@ type userInterfaceImpl struct {
|
||||
initWindowMaximized bool
|
||||
initWindowMousePassthrough bool
|
||||
|
||||
initUnfocused bool
|
||||
|
||||
// bufferOnceSwapped must be accessed from the main thread.
|
||||
bufferOnceSwapped bool
|
||||
|
||||
@ -1121,7 +1119,6 @@ func (u *UserInterface) initOnMainThread(options *RunOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
u.initUnfocused = options.InitUnfocused
|
||||
focused := glfw.True
|
||||
if options.InitUnfocused {
|
||||
focused = glfw.False
|
||||
@ -1323,11 +1320,9 @@ func (u *UserInterface) update() (float64, float64, error) {
|
||||
if err = u.window.Show(); err != nil {
|
||||
return
|
||||
}
|
||||
if !u.initUnfocused {
|
||||
if err = u.window.Focus(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
|
||||
return
|
||||
@ -1388,9 +1383,7 @@ func (u *UserInterface) update() (float64, float64, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// If isRunnableOnUnfocused is false and the window is not focused, wait here.
|
||||
// For the first update, skip this check as the window might not be seen yet in some environments like ChromeOS (#3091).
|
||||
for !u.isRunnableOnUnfocused() && u.bufferOnceSwapped {
|
||||
for !u.isRunnableOnUnfocused() {
|
||||
// In the initial state on macOS, the window is not shown (#2620).
|
||||
visible, err := u.window.GetAttrib(glfw.Visible)
|
||||
if err != nil {
|
||||
|
@ -19,43 +19,8 @@ package ui
|
||||
//
|
||||
// #import <UIKit/UIKit.h>
|
||||
//
|
||||
// static void displayInfoOnMainThread(float* width, float* height, float* scale, UIView* view) {
|
||||
// *width = 0;
|
||||
// *height = 0;
|
||||
// *scale = 1;
|
||||
// UIWindow* window = view.window;
|
||||
// if (!window) {
|
||||
// return;
|
||||
// }
|
||||
// UIWindowScene* scene = window.windowScene;
|
||||
// if (!scene) {
|
||||
// return;
|
||||
// }
|
||||
// CGRect bounds = scene.screen.bounds;
|
||||
// *width = bounds.size.width;
|
||||
// *height = bounds.size.height;
|
||||
// *scale = scene.screen.nativeScale;
|
||||
// }
|
||||
//
|
||||
// static void displayInfo(float* width, float* height, float* scale, uintptr_t viewPtr) {
|
||||
// *width = 0;
|
||||
// *height = 0;
|
||||
// *scale = 1;
|
||||
// if (!viewPtr) {
|
||||
// return;
|
||||
// }
|
||||
// UIView* view = (__bridge UIView*)(void*)viewPtr;
|
||||
// if ([NSThread isMainThread]) {
|
||||
// displayInfoOnMainThread(width, height, scale, view);
|
||||
// return;
|
||||
// }
|
||||
// __block float w, h, s;
|
||||
// dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
// displayInfoOnMainThread(&w, &h, &s, view);
|
||||
// });
|
||||
// *width = w;
|
||||
// *height = h;
|
||||
// *scale = s;
|
||||
// static double devicePixelRatio() {
|
||||
// return [[UIScreen mainScreen] nativeScale];
|
||||
// }
|
||||
import "C"
|
||||
|
||||
@ -101,7 +66,6 @@ func (*graphicsDriverCreatorImpl) newPlayStation5() (graphicsdriver.Graphics, er
|
||||
}
|
||||
|
||||
func (u *UserInterface) SetUIView(uiview uintptr) error {
|
||||
u.uiView.Store(uiview)
|
||||
select {
|
||||
case err := <-u.errCh:
|
||||
return err
|
||||
@ -125,24 +89,11 @@ func (u *UserInterface) IsGL() (bool, error) {
|
||||
return u.GraphicsLibrary() == GraphicsLibraryOpenGL, nil
|
||||
}
|
||||
|
||||
func deviceScaleFactorImpl() float64 {
|
||||
// TODO: Can this be called from non-main threads?
|
||||
return float64(C.devicePixelRatio())
|
||||
}
|
||||
|
||||
func dipToNativePixels(x float64, scale float64) float64 {
|
||||
return x
|
||||
}
|
||||
|
||||
func dipFromNativePixels(x float64, scale float64) float64 {
|
||||
return x
|
||||
}
|
||||
|
||||
func (u *UserInterface) displayInfo() (int, int, float64, bool) {
|
||||
view := u.uiView.Load()
|
||||
if view == 0 {
|
||||
return 0, 0, 1, false
|
||||
}
|
||||
|
||||
var cWidth, cHeight, cScale C.float
|
||||
C.displayInfo(&cWidth, &cHeight, &cScale, C.uintptr_t(view))
|
||||
scale := float64(cScale)
|
||||
width := int(dipFromNativePixels(float64(cWidth), scale))
|
||||
height := int(dipFromNativePixels(float64(cHeight), scale))
|
||||
return width, height, scale, true
|
||||
}
|
||||
|
@ -539,10 +539,6 @@ func (u *UserInterface) init() error {
|
||||
}))
|
||||
document.Call("addEventListener", "pointerlockerror", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
js.Global().Get("console").Call("error", "pointerlockerror event is fired. 'sandbox=\"allow-pointer-lock\"' might be required at an iframe. This function on browsers must be called as a result of a gestural interaction or orientation change.")
|
||||
if u.cursorMode == CursorModeCaptured {
|
||||
u.recoverCursorMode()
|
||||
}
|
||||
u.recoverCursorPosition()
|
||||
return nil
|
||||
}))
|
||||
document.Call("addEventListener", "fullscreenerror", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||
|
@ -104,9 +104,6 @@ type userInterfaceImpl struct {
|
||||
strictContextRestoration atomic.Bool
|
||||
strictContextRestorationOnce sync.Once
|
||||
|
||||
// uiView is used only on iOS.
|
||||
uiView atomic.Uintptr
|
||||
|
||||
m sync.RWMutex
|
||||
}
|
||||
|
||||
@ -268,10 +265,8 @@ func (u *UserInterface) Window() Window {
|
||||
}
|
||||
|
||||
type Monitor struct {
|
||||
width int
|
||||
height int
|
||||
deviceScaleFactor float64
|
||||
inited atomic.Bool
|
||||
deviceScaleFactorOnce sync.Once
|
||||
|
||||
m sync.Mutex
|
||||
}
|
||||
@ -282,35 +277,22 @@ func (m *Monitor) Name() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Monitor) ensureInit() {
|
||||
if m.inited.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Monitor) DeviceScaleFactor() float64 {
|
||||
m.m.Lock()
|
||||
defer m.m.Unlock()
|
||||
// Re-check the state since the state might be changed while locking.
|
||||
if m.inited.Load() {
|
||||
return
|
||||
}
|
||||
width, height, scale, ok := theUI.displayInfo()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
m.width = width
|
||||
m.height = height
|
||||
m.deviceScaleFactor = scale
|
||||
m.inited.Store(true)
|
||||
}
|
||||
|
||||
func (m *Monitor) DeviceScaleFactor() float64 {
|
||||
m.ensureInit()
|
||||
// The device scale factor can be obtained after the main function starts, especially on Android.
|
||||
// Initialize this lazily.
|
||||
m.deviceScaleFactorOnce.Do(func() {
|
||||
// Assume that the device scale factor never changes on mobiles.
|
||||
m.deviceScaleFactor = deviceScaleFactorImpl()
|
||||
})
|
||||
return m.deviceScaleFactor
|
||||
}
|
||||
|
||||
func (m *Monitor) Size() (int, int) {
|
||||
m.ensureInit()
|
||||
return m.width, m.height
|
||||
// TODO: Return a valid value.
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor {
|
||||
|
@ -32,7 +32,8 @@ func (m *MonitorType) Name() string {
|
||||
// DeviceScaleFactor returns a meaningful value on high-DPI display environment,
|
||||
// otherwise DeviceScaleFactor returns 1.
|
||||
//
|
||||
// On mobiles, DeviceScaleFactor returns 1 before the game starts e.g. in init functions.
|
||||
// DeviceScaleFactor might panic on init function on some devices like Android.
|
||||
// Then, it is not recommended to call DeviceScaleFactor from init functions.
|
||||
func (m *MonitorType) DeviceScaleFactor() float64 {
|
||||
return (*ui.Monitor)(m).DeviceScaleFactor()
|
||||
}
|
||||
@ -41,7 +42,7 @@ func (m *MonitorType) DeviceScaleFactor() float64 {
|
||||
// This is the same as the screen size in fullscreen mode.
|
||||
// The returned value can be given to SetSize function if the perfectly fit fullscreen is needed.
|
||||
//
|
||||
// On mobiles, Size returns (0, 0) before the game starts e.g. in init functions.
|
||||
// On mobiles, Size returns (0, 0) so far.
|
||||
//
|
||||
// Size's use cases are limited. If you are making a fullscreen application, you can use RunGame and
|
||||
// the Game interface's Layout function instead. If you are making a not-fullscreen application but the application's
|
||||
|
@ -19,7 +19,7 @@ import (
|
||||
)
|
||||
|
||||
// A MouseButton represents a mouse button.
|
||||
type MouseButton int
|
||||
type MouseButton = ui.MouseButton
|
||||
|
||||
// MouseButtons
|
||||
const (
|
||||
@ -27,10 +27,10 @@ const (
|
||||
MouseButtonMiddle MouseButton = MouseButton1
|
||||
MouseButtonRight MouseButton = MouseButton2
|
||||
|
||||
MouseButton0 MouseButton = MouseButton(ui.MouseButton0)
|
||||
MouseButton1 MouseButton = MouseButton(ui.MouseButton1)
|
||||
MouseButton2 MouseButton = MouseButton(ui.MouseButton2)
|
||||
MouseButton3 MouseButton = MouseButton(ui.MouseButton3)
|
||||
MouseButton4 MouseButton = MouseButton(ui.MouseButton4)
|
||||
MouseButton0 MouseButton = ui.MouseButton0
|
||||
MouseButton1 MouseButton = ui.MouseButton1
|
||||
MouseButton2 MouseButton = ui.MouseButton2
|
||||
MouseButton3 MouseButton = ui.MouseButton3
|
||||
MouseButton4 MouseButton = ui.MouseButton4
|
||||
MouseButtonMax MouseButton = MouseButton4
|
||||
)
|
||||
|
79
run.go
79
run.go
@ -297,6 +297,27 @@ type RunGameOptions struct {
|
||||
|
||||
// X11InstanceName is an instance name in the ICCCM WM_CLASS window property.
|
||||
X11InstanceName string
|
||||
|
||||
// StrictContextRestration indicates whether the context lost should be restored strictly by Ebitengine or not.
|
||||
//
|
||||
// StrictContextRestration is available only on Android. Otherwise, StrictContextRestration is ignored.
|
||||
//
|
||||
// In Android, Ebitengien uses `GLSurfaceView`'s `setPreserveEGLContextOnPause(true)`.
|
||||
// This works in most cases, but it is still possible that the context is lost in some minor cases.
|
||||
//
|
||||
// When StrictContextRestration is true, Ebitengine tries to restore the context more strictly
|
||||
// for such minor cases.
|
||||
// However, this might cause a performance issue since Ebitengine tries to keep all the information
|
||||
// to restore the context.
|
||||
//
|
||||
// When StrictContextRestration is false, Ebitengine does nothing special to restore the context and
|
||||
// relies on the OS's behavior.
|
||||
//
|
||||
// As a side note, especially when StrictContextRestration is false, the activity's launch mode should
|
||||
// be singleInstance, or the activity no longer works correctly after the context is lost.
|
||||
//
|
||||
// The default (zero) value is false.
|
||||
StrictContextRestration bool
|
||||
}
|
||||
|
||||
// RunGameWithOptions starts the main loop and runs the game with the specified options.
|
||||
@ -384,7 +405,7 @@ func ScreenSizeInFullscreen() (int, int) {
|
||||
//
|
||||
// CursorMode is concurrent-safe.
|
||||
func CursorMode() CursorModeType {
|
||||
return CursorModeType(ui.Get().CursorMode())
|
||||
return ui.Get().CursorMode()
|
||||
}
|
||||
|
||||
// SetCursorMode sets the render and capture mode of the mouse cursor.
|
||||
@ -406,7 +427,25 @@ func CursorMode() CursorModeType {
|
||||
//
|
||||
// SetCursorMode is concurrent-safe.
|
||||
func SetCursorMode(mode CursorModeType) {
|
||||
ui.Get().SetCursorMode(ui.CursorMode(mode))
|
||||
ui.Get().SetCursorMode(mode)
|
||||
}
|
||||
|
||||
// CursorShape returns the current cursor shape.
|
||||
//
|
||||
// CursorShape returns CursorShapeDefault on mobiles.
|
||||
//
|
||||
// CursorShape is concurrent-safe.
|
||||
func CursorShape() CursorShapeType {
|
||||
return 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(shape)
|
||||
}
|
||||
|
||||
// IsFullscreen reports whether the current mode is fullscreen or not.
|
||||
@ -510,14 +549,14 @@ func SetVsyncEnabled(enabled bool) {
|
||||
// FPSModeType is a type of FPS modes.
|
||||
//
|
||||
// Deprecated: as of v2.5. Use SetVsyncEnabled instead.
|
||||
type FPSModeType int
|
||||
type FPSModeType = ui.FPSModeType
|
||||
|
||||
const (
|
||||
// FPSModeVsyncOn indicates that the game tries to sync the display's refresh rate.
|
||||
// FPSModeVsyncOn is the default mode.
|
||||
//
|
||||
// Deprecated: as of v2.5. Use SetVsyncEnabled(true) instead.
|
||||
FPSModeVsyncOn FPSModeType = FPSModeType(ui.FPSModeVsyncOn)
|
||||
FPSModeVsyncOn FPSModeType = ui.FPSModeVsyncOn
|
||||
|
||||
// FPSModeVsyncOffMaximum indicates that the game doesn't sync with vsync, and
|
||||
// the game is updated whenever possible.
|
||||
@ -528,7 +567,7 @@ const (
|
||||
// The game's Update is called based on the specified TPS.
|
||||
//
|
||||
// Deprecated: as of v2.5. Use SetVsyncEnabled(false) instead.
|
||||
FPSModeVsyncOffMaximum FPSModeType = FPSModeType(ui.FPSModeVsyncOffMaximum)
|
||||
FPSModeVsyncOffMaximum FPSModeType = ui.FPSModeVsyncOffMaximum
|
||||
|
||||
// FPSModeVsyncOffMinimum indicates that the game doesn't sync with vsync, and
|
||||
// the game is updated only when necessary.
|
||||
@ -541,7 +580,7 @@ const (
|
||||
//
|
||||
// Deprecated: as of v2.5. Use SetScreenClearedEveryFrame(false) instead.
|
||||
// See examples/skipdraw for GPU optimization with SetScreenClearedEveryFrame(false).
|
||||
FPSModeVsyncOffMinimum FPSModeType = FPSModeType(ui.FPSModeVsyncOffMinimum)
|
||||
FPSModeVsyncOffMinimum FPSModeType = ui.FPSModeVsyncOffMinimum
|
||||
)
|
||||
|
||||
// FPSMode returns the current FPS mode.
|
||||
@ -550,7 +589,7 @@ const (
|
||||
//
|
||||
// Deprecated: as of v2.5. Use SetVsyncEnabled instead.
|
||||
func FPSMode() FPSModeType {
|
||||
return FPSModeType(ui.Get().FPSMode())
|
||||
return ui.Get().FPSMode()
|
||||
}
|
||||
|
||||
// SetFPSMode sets the FPS mode.
|
||||
@ -560,7 +599,7 @@ func FPSMode() FPSModeType {
|
||||
//
|
||||
// Deprecated: as of v2.5. Use SetVsyncEnabled instead.
|
||||
func SetFPSMode(mode FPSModeType) {
|
||||
ui.Get().SetFPSMode(ui.FPSModeType(mode))
|
||||
ui.Get().SetFPSMode(mode)
|
||||
}
|
||||
|
||||
// ScheduleFrame schedules a next frame when the current FPS mode is FPSModeVsyncOffMinimum.
|
||||
@ -697,29 +736,6 @@ func toUIRunOptions(options *RunGameOptions) *ui.RunOptions {
|
||||
if options.X11InstanceName == "" {
|
||||
options.X11InstanceName = defaultX11InstanceName
|
||||
}
|
||||
|
||||
// ui.RunOptions.StrictContextRestoration is not used so far (#3098).
|
||||
// This might be reused in the future.
|
||||
// The original comment for StrictContextRestration is as follows:
|
||||
//
|
||||
// StrictContextRestration indicates whether the context lost should be restored strictly by Ebitengine or not.
|
||||
//
|
||||
// StrictContextRestration is available only on Android. Otherwise, StrictContextRestration is ignored.
|
||||
// Thus, StrictContextRestration should be used with mobile.SetGameWithOptions, rather than RunGameWithOptions.
|
||||
//
|
||||
// In Android, Ebitengien uses `GLSurfaceView`'s `setPreserveEGLContextOnPause(true)`.
|
||||
// This works in most cases, but it is still possible that the context is lost in some minor cases.
|
||||
//
|
||||
// When StrictContextRestration is true, Ebitengine tries to restore the context more strictly
|
||||
// for such minor cases.
|
||||
// However, this might cause a performance issue since Ebitengine tries to keep all the information
|
||||
// to restore the context.
|
||||
//
|
||||
// When StrictContextRestration is false, Ebitengine does nothing special to restore the context and
|
||||
// relies on the OS's behavior.
|
||||
//
|
||||
// The default (zero) value is false.
|
||||
|
||||
return &ui.RunOptions{
|
||||
GraphicsLibrary: ui.GraphicsLibrary(options.GraphicsLibrary),
|
||||
InitUnfocused: options.InitUnfocused,
|
||||
@ -730,6 +746,7 @@ func toUIRunOptions(options *RunGameOptions) *ui.RunOptions {
|
||||
ColorSpace: graphicsdriver.ColorSpace(options.ColorSpace),
|
||||
X11ClassName: options.X11ClassName,
|
||||
X11InstanceName: options.X11InstanceName,
|
||||
StrictContextRestoration: options.StrictContextRestration,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,8 +20,9 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-text/typesetting/di"
|
||||
"github.com/go-text/typesetting/font"
|
||||
glanguage "github.com/go-text/typesetting/language"
|
||||
"github.com/go-text/typesetting/opentype/api/font"
|
||||
"github.com/go-text/typesetting/opentype/loader"
|
||||
"github.com/go-text/typesetting/shaping"
|
||||
"golang.org/x/image/math/fixed"
|
||||
"golang.org/x/text/language"
|
||||
@ -88,7 +89,7 @@ func (g *GoTextFace) SetVariation(tag Tag, value float32) {
|
||||
g.variations = append(g.variations, font.Variation{})
|
||||
copy(g.variations[idx+1:], g.variations[idx:])
|
||||
g.variations[idx] = font.Variation{
|
||||
Tag: font.Tag(tag),
|
||||
Tag: loader.Tag(tag),
|
||||
Value: value,
|
||||
}
|
||||
g.variationsString = ""
|
||||
@ -135,7 +136,7 @@ func (g *GoTextFace) SetFeature(tag Tag, value uint32) {
|
||||
g.features = append(g.features, shaping.FontFeature{})
|
||||
copy(g.features[idx+1:], g.features[idx:])
|
||||
g.features[idx] = shaping.FontFeature{
|
||||
Tag: font.Tag(tag),
|
||||
Tag: loader.Tag(tag),
|
||||
Value: value,
|
||||
}
|
||||
g.featuresString = ""
|
||||
@ -201,21 +202,6 @@ func (g *GoTextFace) Metrics() Metrics {
|
||||
m.VDescent = float64(-v.Descender) * scale
|
||||
}
|
||||
|
||||
m.XHeight = float64(g.Source.f.LineMetric(font.XHeight)) * scale
|
||||
m.CapHeight = float64(g.Source.f.LineMetric(font.CapHeight)) * scale
|
||||
|
||||
// XHeight and CapHeight might not be correct for some old fonts (go-text/typesetting#169).
|
||||
if m.XHeight <= 0 {
|
||||
if _, gs := g.Source.shape("x", g); len(gs) > 0 {
|
||||
m.XHeight = fixed26_6ToFloat64(-gs[0].bounds.Min.Y)
|
||||
}
|
||||
}
|
||||
if m.CapHeight <= 0 {
|
||||
if _, gs := g.Source.shape("H", g); len(gs) > 0 {
|
||||
m.CapHeight = fixed26_6ToFloat64(-gs[0].bounds.Min.Y)
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
|
@ -20,8 +20,10 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/go-text/typesetting/font"
|
||||
"github.com/go-text/typesetting/font/opentype"
|
||||
"github.com/go-text/typesetting/language"
|
||||
"github.com/go-text/typesetting/opentype/api"
|
||||
ofont "github.com/go-text/typesetting/opentype/api/font"
|
||||
"github.com/go-text/typesetting/opentype/loader"
|
||||
"github.com/go-text/typesetting/shaping"
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
@ -40,7 +42,7 @@ type glyph struct {
|
||||
shapingGlyph *shaping.Glyph
|
||||
startIndex int
|
||||
endIndex int
|
||||
scaledSegments []opentype.Segment
|
||||
scaledSegments []api.Segment
|
||||
bounds fixed.Rectangle26_6
|
||||
}
|
||||
|
||||
@ -51,7 +53,7 @@ type goTextOutputCacheValue struct {
|
||||
}
|
||||
|
||||
type goTextGlyphImageCacheKey struct {
|
||||
gid opentype.GID
|
||||
gid api.GID
|
||||
xoffset fixed.Int26_6
|
||||
yoffset fixed.Int26_6
|
||||
variations string
|
||||
@ -59,7 +61,7 @@ type goTextGlyphImageCacheKey struct {
|
||||
|
||||
// GoTextFaceSource is a source of a GoTextFace. This can be shared by multiple GoTextFace objects.
|
||||
type GoTextFaceSource struct {
|
||||
f *font.Face
|
||||
f font.Face
|
||||
metadata Metadata
|
||||
|
||||
outputCache map[goTextOutputCacheKey]*goTextOutputCacheValue
|
||||
@ -97,18 +99,18 @@ func NewGoTextFaceSource(source io.Reader) (*GoTextFaceSource, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l, err := opentype.NewLoader(src)
|
||||
l, err := loader.NewLoader(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f, err := font.NewFont(l)
|
||||
f, err := ofont.NewFont(l)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &GoTextFaceSource{
|
||||
f: font.NewFace(f),
|
||||
f: &ofont.Face{Font: f},
|
||||
}
|
||||
s.addr = s
|
||||
s.metadata = metadataFromLoader(l)
|
||||
@ -123,19 +125,19 @@ func NewGoTextFaceSourcesFromCollection(source io.Reader) ([]*GoTextFaceSource,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ls, err := opentype.NewLoaders(src)
|
||||
ls, err := loader.NewLoaders(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sources := make([]*GoTextFaceSource, len(ls))
|
||||
for i, l := range ls {
|
||||
f, err := font.NewFont(l)
|
||||
f, err := ofont.NewFont(l)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := &GoTextFaceSource{
|
||||
f: &font.Face{Font: f},
|
||||
f: &ofont.Face{Font: f},
|
||||
}
|
||||
s.addr = s
|
||||
s.metadata = metadataFromLoader(l)
|
||||
@ -156,12 +158,9 @@ func (g *GoTextFaceSource) Metadata() Metadata {
|
||||
}
|
||||
|
||||
// UnsafeInternal returns its font.Face.
|
||||
// The return value type is any since github.com/go-text/typesettings's API is now unstable.
|
||||
//
|
||||
// UnsafeInternal is unsafe since this might make internal cache states out of sync.
|
||||
//
|
||||
// UnsafeInternal might have breaking changes even in the same major version.
|
||||
func (g *GoTextFaceSource) UnsafeInternal() any {
|
||||
// This is unsafe since this might make internal cache states out of sync.
|
||||
func (g *GoTextFaceSource) UnsafeInternal() font.Face {
|
||||
return g.f
|
||||
}
|
||||
|
||||
@ -219,22 +218,22 @@ func (g *GoTextFaceSource) shape(text string, face *GoTextFace) ([]shaping.Outpu
|
||||
|
||||
for _, gl := range out.Glyphs {
|
||||
gl := gl
|
||||
var segs []opentype.Segment
|
||||
var segs []api.Segment
|
||||
switch data := g.f.GlyphData(gl.GlyphID).(type) {
|
||||
case font.GlyphOutline:
|
||||
case api.GlyphOutline:
|
||||
if out.Direction.IsSideways() {
|
||||
data.Sideways(fixed26_6ToFloat32(-gl.YOffset) / fixed26_6ToFloat32(out.Size) * float32(f.Upem()))
|
||||
}
|
||||
segs = data.Segments
|
||||
case font.GlyphSVG:
|
||||
case api.GlyphSVG:
|
||||
segs = data.Outline.Segments
|
||||
case font.GlyphBitmap:
|
||||
case api.GlyphBitmap:
|
||||
if data.Outline != nil {
|
||||
segs = data.Outline.Segments
|
||||
}
|
||||
}
|
||||
|
||||
scaledSegs := make([]opentype.Segment, len(segs))
|
||||
scaledSegs := make([]api.Segment, len(segs))
|
||||
scale := float32(g.scale(fixed26_6ToFloat64(out.Size)))
|
||||
for i, seg := range segs {
|
||||
scaledSegs[i] = seg
|
||||
@ -292,9 +291,9 @@ func (g *GoTextFaceSource) getOrCreateGlyphImage(goTextFace *GoTextFace, key goT
|
||||
}
|
||||
|
||||
type singleFontmap struct {
|
||||
face *font.Face
|
||||
face font.Face
|
||||
}
|
||||
|
||||
func (s *singleFontmap) ResolveFace(r rune) *font.Face {
|
||||
func (s *singleFontmap) ResolveFace(r rune) font.Face {
|
||||
return s.face
|
||||
}
|
||||
|
@ -19,14 +19,15 @@ import (
|
||||
"image/draw"
|
||||
"math"
|
||||
|
||||
"github.com/go-text/typesetting/font/opentype"
|
||||
"golang.org/x/image/math/fixed"
|
||||
gvector "golang.org/x/image/vector"
|
||||
|
||||
"github.com/go-text/typesetting/opentype/api"
|
||||
"golang.org/x/image/math/fixed"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/vector"
|
||||
)
|
||||
|
||||
func segmentsToBounds(segs []opentype.Segment) fixed.Rectangle26_6 {
|
||||
func segmentsToBounds(segs []api.Segment) fixed.Rectangle26_6 {
|
||||
if len(segs) == 0 {
|
||||
return fixed.Rectangle26_6{}
|
||||
}
|
||||
@ -39,9 +40,9 @@ func segmentsToBounds(segs []opentype.Segment) fixed.Rectangle26_6 {
|
||||
for _, seg := range segs {
|
||||
n := 1
|
||||
switch seg.Op {
|
||||
case opentype.SegmentOpQuadTo:
|
||||
case api.SegmentOpQuadTo:
|
||||
n = 2
|
||||
case opentype.SegmentOpCubeTo:
|
||||
case api.SegmentOpCubeTo:
|
||||
n = 3
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
@ -74,7 +75,7 @@ func segmentsToBounds(segs []opentype.Segment) fixed.Rectangle26_6 {
|
||||
}
|
||||
}
|
||||
|
||||
func segmentsToImage(a *glyphAtlas, segs []opentype.Segment, subpixelOffset fixed.Point26_6, glyphBounds fixed.Rectangle26_6) *glyphImage {
|
||||
func segmentsToImage(a *glyphAtlas, segs []api.Segment, subpixelOffset fixed.Point26_6, glyphBounds fixed.Rectangle26_6) *glyphImage {
|
||||
if len(segs) == 0 {
|
||||
return nil
|
||||
}
|
||||
@ -96,16 +97,16 @@ func segmentsToImage(a *glyphAtlas, segs []opentype.Segment, subpixelOffset fixe
|
||||
rast.DrawOp = draw.Src
|
||||
for _, seg := range segs {
|
||||
switch seg.Op {
|
||||
case opentype.SegmentOpMoveTo:
|
||||
case api.SegmentOpMoveTo:
|
||||
rast.MoveTo(seg.Args[0].X+biasX, seg.Args[0].Y+biasY)
|
||||
case opentype.SegmentOpLineTo:
|
||||
case api.SegmentOpLineTo:
|
||||
rast.LineTo(seg.Args[0].X+biasX, seg.Args[0].Y+biasY)
|
||||
case opentype.SegmentOpQuadTo:
|
||||
case api.SegmentOpQuadTo:
|
||||
rast.QuadTo(
|
||||
seg.Args[0].X+biasX, seg.Args[0].Y+biasY,
|
||||
seg.Args[1].X+biasX, seg.Args[1].Y+biasY,
|
||||
)
|
||||
case opentype.SegmentOpCubeTo:
|
||||
case api.SegmentOpCubeTo:
|
||||
rast.CubeTo(
|
||||
seg.Args[0].X+biasX, seg.Args[0].Y+biasY,
|
||||
seg.Args[1].X+biasX, seg.Args[1].Y+biasY,
|
||||
@ -127,19 +128,19 @@ func segmentsToImage(a *glyphAtlas, segs []opentype.Segment, subpixelOffset fixe
|
||||
return img
|
||||
}
|
||||
|
||||
func appendVectorPathFromSegments(path *vector.Path, segs []opentype.Segment, x, y float32) {
|
||||
func appendVectorPathFromSegments(path *vector.Path, segs []api.Segment, x, y float32) {
|
||||
for _, seg := range segs {
|
||||
switch seg.Op {
|
||||
case opentype.SegmentOpMoveTo:
|
||||
case api.SegmentOpMoveTo:
|
||||
path.MoveTo(seg.Args[0].X+x, seg.Args[0].Y+y)
|
||||
case opentype.SegmentOpLineTo:
|
||||
case api.SegmentOpLineTo:
|
||||
path.LineTo(seg.Args[0].X+x, seg.Args[0].Y+y)
|
||||
case opentype.SegmentOpQuadTo:
|
||||
case api.SegmentOpQuadTo:
|
||||
path.QuadTo(
|
||||
seg.Args[0].X+x, seg.Args[0].Y+y,
|
||||
seg.Args[1].X+x, seg.Args[1].Y+y,
|
||||
)
|
||||
case opentype.SegmentOpCubeTo:
|
||||
case api.SegmentOpCubeTo:
|
||||
path.CubicTo(
|
||||
seg.Args[0].X+x, seg.Args[0].Y+y,
|
||||
seg.Args[1].X+x, seg.Args[1].Y+y,
|
||||
|
@ -44,8 +44,6 @@ type GoXFace struct {
|
||||
|
||||
glyphImageCache glyphImageCache[goXFaceGlyphImageCacheKey]
|
||||
|
||||
cachedMetrics Metrics
|
||||
|
||||
addr *GoXFace
|
||||
}
|
||||
|
||||
@ -70,35 +68,17 @@ func (s *GoXFace) copyCheck() {
|
||||
func (s *GoXFace) Metrics() Metrics {
|
||||
s.copyCheck()
|
||||
|
||||
if s.cachedMetrics != (Metrics{}) {
|
||||
return s.cachedMetrics
|
||||
m := s.f.Metrics()
|
||||
return Metrics{
|
||||
HLineGap: fixed26_6ToFloat64(m.Height - m.Ascent - m.Descent),
|
||||
HAscent: fixed26_6ToFloat64(m.Ascent),
|
||||
HDescent: fixed26_6ToFloat64(m.Descent),
|
||||
}
|
||||
|
||||
fm := s.f.Metrics()
|
||||
m := Metrics{
|
||||
HLineGap: fixed26_6ToFloat64(fm.Height - fm.Ascent - fm.Descent),
|
||||
HAscent: fixed26_6ToFloat64(fm.Ascent),
|
||||
HDescent: fixed26_6ToFloat64(fm.Descent),
|
||||
XHeight: fixed26_6ToFloat64(fm.XHeight),
|
||||
CapHeight: fixed26_6ToFloat64(fm.CapHeight),
|
||||
}
|
||||
|
||||
// There is an issue that XHeight and CapHeight are negative for some old fonts (golang/go#69378).
|
||||
if fm.XHeight < 0 {
|
||||
m.XHeight *= -1
|
||||
}
|
||||
if fm.CapHeight < 0 {
|
||||
m.CapHeight *= -1
|
||||
}
|
||||
s.cachedMetrics = m
|
||||
return m
|
||||
}
|
||||
|
||||
// UnsafeInternal returns its internal font.Face.
|
||||
//
|
||||
// UnsafeInternal is unsafe since this might make internal cache states out of sync.
|
||||
//
|
||||
// UnsafeInternal might have breaking changes even in the same major version.
|
||||
// This is unsafe since this might make internal cache states out of sync.
|
||||
func (s *GoXFace) UnsafeInternal() font.Face {
|
||||
s.copyCheck()
|
||||
return s.f.f
|
||||
|
@ -15,8 +15,8 @@
|
||||
package text
|
||||
|
||||
import (
|
||||
"github.com/go-text/typesetting/font"
|
||||
"github.com/go-text/typesetting/font/opentype"
|
||||
"github.com/go-text/typesetting/opentype/api/metadata"
|
||||
"github.com/go-text/typesetting/opentype/loader"
|
||||
)
|
||||
|
||||
// Metadata represents a font face's metadata.
|
||||
@ -27,47 +27,47 @@ type Metadata struct {
|
||||
Stretch Stretch
|
||||
}
|
||||
|
||||
func metadataFromLoader(l *opentype.Loader) Metadata {
|
||||
d, _ := font.Describe(l, nil)
|
||||
func metadataFromLoader(l *loader.Loader) Metadata {
|
||||
f, a, _ := metadata.Describe(l, nil)
|
||||
return Metadata{
|
||||
Family: d.Family,
|
||||
Style: Style(d.Aspect.Style),
|
||||
Weight: Weight(d.Aspect.Weight),
|
||||
Stretch: Stretch(d.Aspect.Stretch),
|
||||
Family: f,
|
||||
Style: Style(a.Style),
|
||||
Weight: Weight(a.Weight),
|
||||
Stretch: Stretch(a.Stretch),
|
||||
}
|
||||
}
|
||||
|
||||
type Style uint8
|
||||
|
||||
const (
|
||||
StyleNormal Style = Style(font.StyleNormal)
|
||||
StyleItalic Style = Style(font.StyleItalic)
|
||||
StyleNormal Style = Style(metadata.StyleNormal)
|
||||
StyleItalic Style = Style(metadata.StyleItalic)
|
||||
)
|
||||
|
||||
type Weight float32
|
||||
|
||||
const (
|
||||
WeightThin Weight = Weight(font.WeightThin)
|
||||
WeightExtraLight Weight = Weight(font.WeightExtraLight)
|
||||
WeightLight Weight = Weight(font.WeightLight)
|
||||
WeightNormal Weight = Weight(font.WeightNormal)
|
||||
WeightMedium Weight = Weight(font.WeightMedium)
|
||||
WeightSemibold Weight = Weight(font.WeightSemibold)
|
||||
WeightBold Weight = Weight(font.WeightBold)
|
||||
WeightExtraBold Weight = Weight(font.WeightExtraBold)
|
||||
WeightBlack Weight = Weight(font.WeightBlack)
|
||||
WeightThin Weight = Weight(metadata.WeightThin)
|
||||
WeightExtraLight Weight = Weight(metadata.WeightExtraLight)
|
||||
WeightLight Weight = Weight(metadata.WeightLight)
|
||||
WeightNormal Weight = Weight(metadata.WeightNormal)
|
||||
WeightMedium Weight = Weight(metadata.WeightMedium)
|
||||
WeightSemibold Weight = Weight(metadata.WeightSemibold)
|
||||
WeightBold Weight = Weight(metadata.WeightBold)
|
||||
WeightExtraBold Weight = Weight(metadata.WeightExtraBold)
|
||||
WeightBlack Weight = Weight(metadata.WeightBlack)
|
||||
)
|
||||
|
||||
type Stretch float32
|
||||
|
||||
const (
|
||||
StretchUltraCondensed Stretch = Stretch(font.StretchUltraCondensed)
|
||||
StretchExtraCondensed Stretch = Stretch(font.StretchExtraCondensed)
|
||||
StretchCondensed Stretch = Stretch(font.StretchCondensed)
|
||||
StretchSemiCondensed Stretch = Stretch(font.StretchSemiCondensed)
|
||||
StretchNormal Stretch = Stretch(font.StretchNormal)
|
||||
StretchSemiExpanded Stretch = Stretch(font.StretchSemiExpanded)
|
||||
StretchExpanded Stretch = Stretch(font.StretchExpanded)
|
||||
StretchExtraExpanded Stretch = Stretch(font.StretchExtraExpanded)
|
||||
StretchUltraExpanded Stretch = Stretch(font.StretchUltraExpanded)
|
||||
StretchUltraCondensed Stretch = Stretch(metadata.StretchUltraCondensed)
|
||||
StretchExtraCondensed Stretch = Stretch(metadata.StretchExtraCondensed)
|
||||
StretchCondensed Stretch = Stretch(metadata.StretchCondensed)
|
||||
StretchSemiCondensed Stretch = Stretch(metadata.StretchSemiCondensed)
|
||||
StretchNormal Stretch = Stretch(metadata.StretchNormal)
|
||||
StretchSemiExpanded Stretch = Stretch(metadata.StretchSemiExpanded)
|
||||
StretchExpanded Stretch = Stretch(metadata.StretchExpanded)
|
||||
StretchExtraExpanded Stretch = Stretch(metadata.StretchExtraExpanded)
|
||||
StretchUltraExpanded Stretch = Stretch(metadata.StretchUltraExpanded)
|
||||
)
|
||||
|
@ -75,12 +75,6 @@ func (m *MultiFace) Metrics() Metrics {
|
||||
if mt1.VDescent > mt.VDescent {
|
||||
mt.VDescent = mt1.VDescent
|
||||
}
|
||||
if mt1.XHeight > mt.XHeight {
|
||||
mt.XHeight = mt1.XHeight
|
||||
}
|
||||
if mt1.CapHeight > mt.CapHeight {
|
||||
mt.CapHeight = mt1.CapHeight
|
||||
}
|
||||
}
|
||||
return mt
|
||||
}
|
||||
|
310
text/v2/testdata/LICENSE.md
vendored
310
text/v2/testdata/LICENSE.md
vendored
@ -1,310 +0,0 @@
|
||||
# Licenses
|
||||
|
||||
## `MPLUS1p-Regular.ttf`
|
||||
|
||||
https://fonts.google.com/specimen/M+PLUS+1p
|
||||
|
||||
```
|
||||
Copyright 2016 The M+ Project Authors.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
```
|
||||
|
||||
## `Roboto-Regular.ttf`
|
||||
|
||||
https://fonts.google.com/specimen/Roboto
|
||||
|
||||
|
||||
```
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
```
|
BIN
text/v2/testdata/MPLUS1p-Regular.ttf
vendored
BIN
text/v2/testdata/MPLUS1p-Regular.ttf
vendored
Binary file not shown.
BIN
text/v2/testdata/Roboto-Regular.ttf
vendored
BIN
text/v2/testdata/Roboto-Regular.ttf
vendored
Binary file not shown.
@ -69,12 +69,6 @@ type Metrics struct {
|
||||
// VDescent is the distance in pixels from the top of a line to its baseline for vertical lines.
|
||||
// If the face is GoXFace or the font doesn't support a vertical direction, VDescent is 0.
|
||||
VDescent float64
|
||||
|
||||
// XHeight is the distance in pixels from the baseline to the top of the lower case letters.
|
||||
XHeight float64
|
||||
|
||||
// CapHeight is the distance in pixels from the baseline to the top of the capital letters.
|
||||
CapHeight float64
|
||||
}
|
||||
|
||||
func fixed26_6ToFloat32(x fixed.Int26_6) float32 {
|
||||
|
@ -18,9 +18,6 @@ import (
|
||||
"bytes"
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
@ -28,7 +25,6 @@ import (
|
||||
"github.com/hajimehoshi/bitmapfont/v3"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/gofont/goregular"
|
||||
"golang.org/x/image/font/opentype"
|
||||
"golang.org/x/image/math/fixed"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
@ -378,78 +374,6 @@ func TestDrawOptionsNotModified(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoXFaceMetrics(t *testing.T) {
|
||||
const size = 100
|
||||
|
||||
fontFiles := []string{
|
||||
// MPLUS1p-Regular.ttf is an old version of M+ 1p font, and this doesn't have metadata.
|
||||
"MPLUS1p-Regular.ttf",
|
||||
"Roboto-Regular.ttf",
|
||||
}
|
||||
|
||||
for _, fontFile := range fontFiles {
|
||||
fontFile := fontFile
|
||||
t.Run(fontFile, func(t *testing.T) {
|
||||
fontdata, err := os.ReadFile(filepath.Join("testdata", fontFile))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sfntFont, err := opentype.Parse(fontdata)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
opentypeFace, err := opentype.NewFace(sfntFont, &opentype.FaceOptions{
|
||||
Size: size,
|
||||
DPI: 72,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
goXFace := text.NewGoXFace(opentypeFace)
|
||||
goXMetrics := goXFace.Metrics()
|
||||
if goXMetrics.XHeight <= 0 {
|
||||
t.Errorf("GoXFace's XHeight must be positive but not: %f", goXMetrics.XHeight)
|
||||
}
|
||||
if goXMetrics.CapHeight <= 0 {
|
||||
t.Errorf("GoXFace's CapHeight must be positive but not: %f", goXMetrics.CapHeight)
|
||||
}
|
||||
|
||||
goTextFaceSource, err := text.NewGoTextFaceSource(bytes.NewBuffer(fontdata))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
goTextFace := &text.GoTextFace{
|
||||
Source: goTextFaceSource,
|
||||
Size: size,
|
||||
}
|
||||
goTextMetrics := goTextFace.Metrics()
|
||||
if goTextMetrics.XHeight <= 0 {
|
||||
t.Errorf("GoTextFace's XHeight must be positive but not: %f", goTextMetrics.XHeight)
|
||||
}
|
||||
if goTextMetrics.CapHeight <= 0 {
|
||||
t.Errorf("GoTextFace's CapHeight must be positive but not: %f", goTextMetrics.CapHeight)
|
||||
}
|
||||
|
||||
if math.Abs(goXMetrics.XHeight-goTextMetrics.XHeight) >= 0.1 {
|
||||
t.Errorf("XHeight values don't match: %f (GoXFace) vs %f (GoTextFace)", goXMetrics.XHeight, goTextMetrics.XHeight)
|
||||
}
|
||||
if math.Abs(goXMetrics.CapHeight-goTextMetrics.CapHeight) >= 0.1 {
|
||||
t.Errorf("CapHeight values don't match: %f (GoXFace) vs %f (GoTextFace)", goXMetrics.CapHeight, goTextMetrics.CapHeight)
|
||||
}
|
||||
|
||||
// Check that a MultiFace should have the same metrics.
|
||||
multiFace, err := text.NewMultiFace(goTextFace)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := multiFace.Metrics(); got != goTextMetrics {
|
||||
t.Errorf("got: %v, want: %v", got, goTextMetrics)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDrawText(b *testing.B) {
|
||||
var txt string
|
||||
for i := 0; i < 32; i++ {
|
||||
|
12
window.go
12
window.go
@ -25,21 +25,21 @@ import (
|
||||
//
|
||||
// Regardless of the resizing mode, an Ebitengine application can still change the window size or make
|
||||
// the window fullscreen by calling Ebitengine functions.
|
||||
type WindowResizingModeType int
|
||||
type WindowResizingModeType = ui.WindowResizingMode
|
||||
|
||||
// WindowResizingModeTypes
|
||||
const (
|
||||
// WindowResizingModeDisabled indicates the mode to disallow resizing the window by a user.
|
||||
WindowResizingModeDisabled WindowResizingModeType = WindowResizingModeType(ui.WindowResizingModeDisabled)
|
||||
WindowResizingModeDisabled WindowResizingModeType = ui.WindowResizingModeDisabled
|
||||
|
||||
// WindowResizingModeOnlyFullscreenEnabled indicates the mode to disallow resizing the window,
|
||||
// but allow to make the window fullscreen by a user.
|
||||
// This works only on macOS so far.
|
||||
// On the other platforms, this is the same as WindowResizingModeDisabled.
|
||||
WindowResizingModeOnlyFullscreenEnabled WindowResizingModeType = WindowResizingModeType(ui.WindowResizingModeOnlyFullscreenEnabled)
|
||||
WindowResizingModeOnlyFullscreenEnabled WindowResizingModeType = ui.WindowResizingModeOnlyFullscreenEnabled
|
||||
|
||||
// WindowResizingModeEnabled indicates the mode to allow resizing the window by a user.
|
||||
WindowResizingModeEnabled WindowResizingModeType = WindowResizingModeType(ui.WindowResizingModeEnabled)
|
||||
WindowResizingModeEnabled WindowResizingModeType = ui.WindowResizingModeEnabled
|
||||
)
|
||||
|
||||
// IsWindowDecorated reports whether the window is decorated.
|
||||
@ -67,14 +67,14 @@ func SetWindowDecorated(decorated bool) {
|
||||
//
|
||||
// WindowResizingMode is concurrent-safe.
|
||||
func WindowResizingMode() WindowResizingModeType {
|
||||
return WindowResizingModeType(ui.Get().Window().ResizingMode())
|
||||
return ui.Get().Window().ResizingMode()
|
||||
}
|
||||
|
||||
// SetWindowResizingMode sets the mode in which a user resizes the window.
|
||||
//
|
||||
// SetWindowResizingMode is concurrent-safe.
|
||||
func SetWindowResizingMode(mode WindowResizingModeType) {
|
||||
ui.Get().Window().SetResizingMode(ui.WindowResizingMode(mode))
|
||||
ui.Get().Window().SetResizingMode(mode)
|
||||
}
|
||||
|
||||
// IsWindowResizable reports whether the window is resizable by the user's dragging on desktops.
|
||||
|
Loading…
Reference in New Issue
Block a user