diff --git a/audio/internal/cbackend/context.go b/audio/internal/cbackend/context.go new file mode 100644 index 000000000..2f8d3fb7a --- /dev/null +++ b/audio/internal/cbackend/context.go @@ -0,0 +1,329 @@ +// Copyright 2021 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. + +//go:build ebitencbackend +// +build ebitencbackend + +package cbackend + +import ( + "io" + "runtime" + "sync" + + "github.com/hajimehoshi/ebiten/v2/internal/cbackend" +) + +type Context struct { + sampleRate int + channelNum int + bitDepthInBytes int +} + +func NewContext(sampleRate, channelNum, bitDepthInBytes int) (*Context, chan struct{}, error) { + cbackend.OpenAudio(sampleRate, channelNum, bitDepthInBytes) + c := &Context{ + sampleRate: sampleRate, + channelNum: channelNum, + bitDepthInBytes: bitDepthInBytes, + } + ready := make(chan struct{}) + close(ready) + return c, ready, nil +} + +func (c *Context) NewPlayer(r io.Reader) *Player { + cond := sync.NewCond(&sync.Mutex{}) + p := &Player{ + context: c, + src: r, + volume: 1, + cond: cond, + onWritten: cond.Signal, + } + runtime.SetFinalizer(p, (*Player).Close) + return p +} + +func (c *Context) Suspend() error { + // Do nothing so far. + return nil +} + +func (c *Context) Resume() error { + // Do nothing so far. + return nil +} + +func (c *Context) Err() error { + return nil +} + +func (c *Context) oneBufferSize() int { + // TODO: This must be audio.oneBufferSize(p.context.sampleRate). Avoid the duplication. + return c.sampleRate * c.channelNum * c.bitDepthInBytes / 4 +} + +func (c *Context) MaxBufferSize() int { + // TODO: This must be audio.maxBufferSize(p.context.sampleRate). Avoid the duplication. + return c.oneBufferSize() * 2 +} + +type playerState int + +const ( + playerStatePaused playerState = iota + playerStatePlaying + playerStateClosed +) + +type Player struct { + context *Context + src io.Reader + v *cbackend.AudioPlayer + state playerState + volume float64 + cond *sync.Cond + err error + buf []byte + + onWritten func() +} + +func (p *Player) Pause() { + p.cond.L.Lock() + defer p.cond.L.Unlock() + + if p.state == playerStateClosed { + return + } + if p.v == nil { + return + } + + p.v.Pause() + p.state = playerStatePaused + p.cond.Signal() +} + +func (p *Player) Play() { + p.cond.L.Lock() + defer p.cond.L.Unlock() + + if p.state == playerStateClosed { + return + } + + var runloop bool + if p.v == nil { + p.v = cbackend.CreateAudioPlayer(p.onWritten) + runloop = true + } + + p.v.SetVolume(p.volume) + p.v.Play() + + // Prepare the first data as soon as possible, or the audio can get stuck. + // TODO: Get the appropriate buffer size from the C++ side. + if p.buf == nil { + n := p.context.oneBufferSize() + if max := p.context.MaxBufferSize() - p.UnplayedBufferSize(); n > max { + n = max + } + p.buf = make([]byte, n) + } + n, err := p.src.Read(p.buf) + if err != nil && err != io.EOF { + p.setErrorImpl(err) + return + } + if n > 0 { + p.writeImpl(p.buf[:n]) + } + + if runloop { + go p.loop() + } + p.state = playerStatePlaying + p.cond.Signal() +} + +func (p *Player) IsPlaying() bool { + p.cond.L.Lock() + defer p.cond.L.Unlock() + + return p.state == playerStatePlaying +} + +func (p *Player) Reset() { + p.cond.L.Lock() + defer p.cond.L.Unlock() + + if p.state == playerStateClosed { + return + } + p.state = playerStatePaused + + if p.v == nil { + return + } + + p.v.Close(true) + p.v = nil + p.cond.Signal() +} + +func (p *Player) Volume() float64 { + p.cond.L.Lock() + defer p.cond.L.Unlock() + + if p.v == nil { + return p.volume + } + return p.v.Volume() +} + +func (p *Player) SetVolume(volume float64) { + p.cond.L.Lock() + defer p.cond.L.Unlock() + + p.volume = volume + if p.v == nil { + return + } + p.v.SetVolume(volume) +} + +func (p *Player) UnplayedBufferSize() int { + if p.v == nil { + return 0 + } + return p.v.UnplayedBufferSize() +} + +func (p *Player) Err() error { + p.cond.L.Lock() + defer p.cond.L.Unlock() + + return p.err +} + +func (p *Player) Close() error { + runtime.SetFinalizer(p, nil) + return p.close(true) +} + +func (p *Player) close(remove bool) error { + p.cond.L.Lock() + defer p.cond.L.Unlock() + return p.closeImpl(remove) +} + +func (p *Player) closeImpl(remove bool) error { + if p.state == playerStateClosed { + return p.err + } + + if p.v != nil { + p.v.Close(false) + p.v = nil + } + if remove { + p.state = playerStateClosed + p.onWritten = nil + } else { + p.state = playerStatePaused + } + p.cond.Signal() + return p.err +} + +func (p *Player) setError(err error) { + p.cond.L.Lock() + defer p.cond.L.Unlock() + p.setErrorImpl(err) +} + +func (p *Player) setErrorImpl(err error) { + if p.state != playerStateClosed && p.v != nil { + p.v.Close(true) + p.v = nil + } + p.err = err + p.state = playerStateClosed + p.cond.Signal() +} + +func (p *Player) shouldWait() bool { + if p.v == nil { + return false + } + switch p.state { + case playerStatePaused: + return true + case playerStatePlaying: + return p.v.UnplayedBufferSize() >= p.context.MaxBufferSize() + } + return false +} + +func (p *Player) waitUntilUnpaused() bool { + p.cond.L.Lock() + defer p.cond.L.Unlock() + + for p.shouldWait() { + p.cond.Wait() + } + return p.v != nil && p.state == playerStatePlaying +} + +func (p *Player) writeImpl(buf []byte) { + if p.state == playerStateClosed { + return + } + if p.v == nil { + return + } + p.v.Write(buf) +} + +func (p *Player) loop() { + const readChunkSize = 4096 + + buf := make([]byte, readChunkSize) + + for { + if !p.waitUntilUnpaused() { + return + } + + p.cond.L.Lock() + n, err := p.src.Read(buf) + if err != nil && err != io.EOF { + p.setErrorImpl(err) + p.cond.L.Unlock() + return + } + if n > 0 { + p.writeImpl(buf[:n]) + } + + if err == io.EOF { + p.closeImpl(false) + p.cond.L.Unlock() + return + } + p.cond.L.Unlock() + } +} diff --git a/audio/oto.go b/audio/oto.go index 3b628542b..6cc050b5f 100644 --- a/audio/oto.go +++ b/audio/oto.go @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !ebitencbackend +// +build !ebitencbackend + package audio import ( @@ -33,7 +36,7 @@ type contextProxy struct { otoContext } -// contextProxy implements context. +// NewPlayer implements context. func (c *contextProxy) NewPlayer(r io.Reader) player { return c.otoContext.NewPlayer(r) } diff --git a/audio/player_cbackend.go b/audio/player_cbackend.go new file mode 100644 index 000000000..867df085b --- /dev/null +++ b/audio/player_cbackend.go @@ -0,0 +1,39 @@ +// Copyright 2021 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. + +//go:build ebitencbackend +// +build ebitencbackend + +package audio + +import ( + "io" + + "github.com/hajimehoshi/ebiten/v2/audio/internal/cbackend" +) + +func newContext(sampleRate, channelNum, bitDepthInBytes int) (context, chan struct{}, error) { + ctx, ready, err := cbackend.NewContext(sampleRate, channelNum, bitDepthInBytes) + return &contextProxy{ctx}, ready, err +} + +// contextProxy is a proxy between cbackend.Context and context. +type contextProxy struct { + *cbackend.Context +} + +// NewPlayer implements context. +func (c *contextProxy) NewPlayer(r io.Reader) player { + return c.Context.NewPlayer(r) +} diff --git a/audio/player_js.go b/audio/player_js.go index d2b91d066..1570b65f7 100644 --- a/audio/player_js.go +++ b/audio/player_js.go @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !ebitencbackend +// +build !ebitencbackend + package audio import ( diff --git a/audio/player_notjs.go b/audio/player_notjs.go index 864982b9a..ddb219f22 100644 --- a/audio/player_notjs.go +++ b/audio/player_notjs.go @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build !js -// +build !js +//go:build !js && !ebitencbackend +// +build !js,!ebitencbackend package audio diff --git a/internal/cbackend/cbackend.go b/internal/cbackend/cbackend.go new file mode 100644 index 000000000..b815dff8b --- /dev/null +++ b/internal/cbackend/cbackend.go @@ -0,0 +1,243 @@ +// Copyright 2021 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. + +//go:build ebitencbackend +// +build ebitencbackend + +package cbackend + +// #cgo !darwin LDFLAGS: -Wl,-unresolved-symbols=ignore-all +// #cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup +// +// #include +// +// struct Gamepad { +// int id; +// char standard; +// int button_num; +// int axis_num; +// char button_pressed[32]; +// float button_values[32]; +// float axis_values[16]; +// }; +// +// struct Touch { +// int id; +// int x; +// int y; +// }; +// +// // UI +// void EbitenInitializeGame(); +// void EbitenGetScreenSize(int* width, int* height); +// void EbitenBeginFrame(); +// void EbitenEndFrame(); +// +// // Input +// int EbitenGetGamepadNum(); +// void EbitenGetGamepads(struct Gamepad* gamepads); +// int EbitenGetTouchNum(); +// void EbitenGetTouches(struct Touch* touches); +// +// // Audio +// typedef void (*OnWrittenCallback)(int id); +// void EbitenOpenAudio(int sample_rate, int channel_num, int bit_depth_in_bytes); +// void EbitenCloseAudio(); +// int EbitenCreateAudioPlayer(OnWrittenCallback on_written_callback); +// void EbitenAudioPlayerPlay(int id); +// void EbitenAudioPlayerPause(int id); +// void EbitenAudioPlayerWrite(int id, uint8_t* data, int length); +// void EbitenAudioPlayerClose(int id, int immediately); +// double EbitenAudioPlayerGetVolume(int id); +// void EbitenAudioPlayerSetVolume(int id, double volume); +// int EbitenAudioPlayerGetUnplayedBufferSize(int id); +// +// void EbitenAudioPlayerOnWrittenCallback(int id); +// static int EbitenCreateAudioPlayerProxy() { +// return EbitenCreateAudioPlayer(EbitenAudioPlayerOnWrittenCallback); +// } +import "C" + +import ( + "runtime" + "sync" + "unsafe" + + "github.com/hajimehoshi/ebiten/v2/internal/driver" +) + +type Gamepad struct { + ID driver.GamepadID + Standard bool + ButtonNum int + AxisNum int + ButtonPressed [32]bool + ButtonValues [32]float64 + AxisValues [16]float64 +} + +type Touch struct { + ID driver.TouchID + X int + Y int +} + +func InitializeGame() { + C.EbitenInitializeGame() +} + +func ScreenSize() (int, int) { + var width, height C.int + C.EbitenGetScreenSize(&width, &height) + return int(width), int(height) +} + +func BeginFrame() { + C.EbitenBeginFrame() +} + +func EndFrame() { + C.EbitenEndFrame() +} + +var cGamepads []C.struct_Gamepad + +func AppendGamepads(gamepads []Gamepad) []Gamepad { + n := int(C.EbitenGetGamepadNum()) + if cap(cGamepads) < n { + cGamepads = append(cGamepads, make([]C.struct_Gamepad, n)...) + } else { + cGamepads = cGamepads[:n] + } + if n > 0 { + C.EbitenGetGamepads(&cGamepads[0]) + } + + for _, g := range cGamepads { + gamepad := Gamepad{ + ID: driver.GamepadID(g.id), + Standard: g.standard != 0, + ButtonNum: int(g.button_num), + AxisNum: int(g.axis_num), + } + for i := 0; i < gamepad.ButtonNum; i++ { + gamepad.ButtonPressed[i] = g.button_pressed[i] != 0 + gamepad.ButtonValues[i] = float64(g.button_values[i]) + } + for i := 0; i < gamepad.AxisNum; i++ { + gamepad.AxisValues[i] = float64(g.axis_values[i]) + } + + gamepads = append(gamepads, gamepad) + } + return gamepads +} + +var cTouches []C.struct_Touch + +func AppendTouches(touches []Touch) []Touch { + n := int(C.EbitenGetTouchNum()) + cTouches = cTouches[:0] + if cap(cTouches) < n { + cTouches = append(cTouches, make([]C.struct_Touch, n)...) + } else { + cTouches = cTouches[:n] + } + if n > 0 { + C.EbitenGetTouches(&cTouches[0]) + } + + for _, t := range cTouches { + touches = append(touches, Touch{ + ID: driver.TouchID(t.id), + X: int(t.x), + Y: int(t.y), + }) + } + return touches +} + +func OpenAudio(sampleRate, channelNum, bitDepthInBytes int) { + C.EbitenOpenAudio(C.int(sampleRate), C.int(channelNum), C.int(bitDepthInBytes)) +} + +func CloseAudio() { + C.EbitenCloseAudio() +} + +func CreateAudioPlayer(onWritten func()) *AudioPlayer { + id := C.EbitenCreateAudioPlayerProxy() + p := &AudioPlayer{ + id: id, + } + onWrittenCallbacksM.Lock() + defer onWrittenCallbacksM.Unlock() + onWrittenCallbacks[id] = onWritten + return p +} + +type AudioPlayer struct { + id C.int +} + +func (p *AudioPlayer) Play() { + C.EbitenAudioPlayerPlay(p.id) +} + +func (p *AudioPlayer) Pause() { + C.EbitenAudioPlayerPause(p.id) +} + +func (p *AudioPlayer) Write(buf []byte) { + C.EbitenAudioPlayerWrite(p.id, (*C.uint8_t)(unsafe.Pointer(&buf[0])), C.int(len(buf))) + runtime.KeepAlive(buf) +} + +func (p *AudioPlayer) Close(immediately bool) { + var i C.int + if immediately { + i = 1 + } + C.EbitenAudioPlayerClose(p.id, i) + + onWrittenCallbacksM.Lock() + defer onWrittenCallbacksM.Unlock() + delete(onWrittenCallbacks, p.id) +} + +func (p *AudioPlayer) Volume() float64 { + return float64(C.EbitenAudioPlayerGetVolume(p.id)) +} + +func (p *AudioPlayer) SetVolume(volume float64) { + C.EbitenAudioPlayerSetVolume(p.id, C.double(volume)) +} + +func (p *AudioPlayer) UnplayedBufferSize() int { + return int(C.EbitenAudioPlayerGetUnplayedBufferSize(p.id)) +} + +var ( + onWrittenCallbacks = map[C.int]func(){} + onWrittenCallbacksM sync.Mutex +) + +//export EbitenAudioPlayerOnWrittenCallback +func EbitenAudioPlayerOnWrittenCallback(id C.int) { + onWrittenCallbacksM.Lock() + defer onWrittenCallbacksM.Unlock() + if c, ok := onWrittenCallbacks[id]; ok { + c() + } +} diff --git a/internal/graphicsdriver/opengl/gl/package_notwindows.go b/internal/graphicsdriver/opengl/gl/package_notwindows.go index f940c3cb4..d6755404e 100644 --- a/internal/graphicsdriver/opengl/gl/package_notwindows.go +++ b/internal/graphicsdriver/opengl/gl/package_notwindows.go @@ -6,7 +6,7 @@ package gl // #cgo darwin LDFLAGS: -framework OpenGL -// #cgo linux freebsd openbsd pkg-config: gl +// #cgo linux,!ebitencbackend freebsd,!ebitencbackend openbsd,!ebitencbackend pkg-config: gl // // #ifndef APIENTRY // #define APIENTRY diff --git a/internal/graphicsdriver/opengl/gl/procaddr_notwindows.go b/internal/graphicsdriver/opengl/gl/procaddr_notwindows.go index 316cba786..140c31ee6 100644 --- a/internal/graphicsdriver/opengl/gl/procaddr_notwindows.go +++ b/internal/graphicsdriver/opengl/gl/procaddr_notwindows.go @@ -21,9 +21,11 @@ package gl #cgo darwin CFLAGS: -DTAG_DARWIN #cgo darwin LDFLAGS: -framework OpenGL #cgo linux freebsd openbsd CFLAGS: -DTAG_POSIX -#cgo linux freebsd openbsd pkg-config: gl +#cgo linux,!ebitencbackend freebsd,!ebitencbackend openbsd,!ebitencbackend pkg-config: gl #cgo egl CFLAGS: -DTAG_EGL -#cgo egl pkg-config: egl +#cgo egl,!ebitencbackend pkg-config: egl +#cgo !darwin ebitencbackend LDFLAGS: -Wl,-unresolved-symbols=ignore-all +#cgo darwin ebitencbackend LDFLAGS: -Wl,-undefined,dynamic_lookup // Check the EGL tag first as it takes priority over the platform's default // configuration of WGL/GLX/CGL. #if defined(TAG_EGL) diff --git a/internal/uidriver/cbackend/input.go b/internal/uidriver/cbackend/input.go new file mode 100644 index 000000000..7fd9dd4d1 --- /dev/null +++ b/internal/uidriver/cbackend/input.go @@ -0,0 +1,271 @@ +// Copyright 2021 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. + +//go:build ebitencbackend +// +build ebitencbackend + +package cbackend + +import ( + "sync" + "time" + + "github.com/hajimehoshi/ebiten/v2/internal/cbackend" + "github.com/hajimehoshi/ebiten/v2/internal/driver" +) + +type Input struct { + gamepads []cbackend.Gamepad + touches []cbackend.Touch + + m sync.Mutex +} + +func (i *Input) update(context driver.UIContext) { + i.m.Lock() + defer i.m.Unlock() + + i.gamepads = i.gamepads[:0] + i.gamepads = cbackend.AppendGamepads(i.gamepads) + + i.touches = i.touches[:0] + i.touches = cbackend.AppendTouches(i.touches) + + for idx, t := range i.touches { + x, y := context.AdjustPosition(float64(t.X), float64(t.Y), deviceScaleFactor) + i.touches[idx].X = int(x) + i.touches[idx].Y = int(y) + } +} + +func (i *Input) AppendInputChars(runes []rune) []rune { + return nil +} + +func (i *Input) AppendGamepadIDs(gamepadIDs []driver.GamepadID) []driver.GamepadID { + i.m.Lock() + defer i.m.Unlock() + + for _, g := range i.gamepads { + gamepadIDs = append(gamepadIDs, g.ID) + } + return gamepadIDs +} + +func (i *Input) AppendTouchIDs(touchIDs []driver.TouchID) []driver.TouchID { + i.m.Lock() + defer i.m.Unlock() + + for _, t := range i.touches { + touchIDs = append(touchIDs, t.ID) + } + return touchIDs +} + +func (i *Input) CursorPosition() (x, y int) { + return 0, 0 +} + +func (i *Input) GamepadSDLID(id driver.GamepadID) string { + return "" +} + +func (i *Input) GamepadName(id driver.GamepadID) string { + return "" +} + +func (i *Input) GamepadAxisValue(id driver.GamepadID, axis int) float64 { + i.m.Lock() + defer i.m.Unlock() + + for _, g := range i.gamepads { + if g.ID != id { + continue + } + if axis < 0 { + return 0 + } + if g.AxisNum <= axis { + return 0 + } + if len(g.AxisValues) <= axis { + return 0 + } + return g.AxisValues[axis] + } + return 0 +} + +func (i *Input) GamepadAxisNum(id driver.GamepadID) int { + i.m.Lock() + defer i.m.Unlock() + + for _, g := range i.gamepads { + if g.ID != id { + continue + } + return g.AxisNum + } + return 0 +} + +func (i *Input) GamepadButtonNum(id driver.GamepadID) int { + i.m.Lock() + defer i.m.Unlock() + + for _, g := range i.gamepads { + if g.ID != id { + continue + } + return g.ButtonNum + } + return 0 +} + +func (i *Input) IsGamepadButtonPressed(id driver.GamepadID, button driver.GamepadButton) bool { + i.m.Lock() + defer i.m.Unlock() + + for _, g := range i.gamepads { + if g.ID != id { + continue + } + if button < 0 { + return false + } + if g.ButtonNum <= int(button) { + return false + } + if len(g.ButtonPressed) <= int(button) { + return false + } + return g.ButtonPressed[button] + } + return false +} + +func (i *Input) IsKeyPressed(key driver.Key) bool { + return false +} + +func (i *Input) IsMouseButtonPressed(button driver.MouseButton) bool { + return false +} + +func (i *Input) IsStandardGamepadButtonPressed(id driver.GamepadID, button driver.StandardGamepadButton) bool { + i.m.Lock() + defer i.m.Unlock() + + for _, g := range i.gamepads { + if g.ID != id { + continue + } + if !g.Standard { + return false + } + if button < 0 { + return false + } + if g.ButtonNum <= int(button) { + return false + } + if len(g.ButtonPressed) <= int(button) { + return false + } + return g.ButtonPressed[button] + } + return false +} + +func (i *Input) IsStandardGamepadLayoutAvailable(id driver.GamepadID) bool { + i.m.Lock() + defer i.m.Unlock() + + for _, g := range i.gamepads { + if g.ID != id { + continue + } + return g.Standard + } + return false +} + +func (i *Input) StandardGamepadAxisValue(id driver.GamepadID, axis driver.StandardGamepadAxis) float64 { + i.m.Lock() + defer i.m.Unlock() + + for _, g := range i.gamepads { + if g.ID != id { + continue + } + if !g.Standard { + return 0 + } + if axis < 0 { + return 0 + } + if g.AxisNum <= int(axis) { + return 0 + } + if len(g.AxisValues) <= int(axis) { + return 0 + } + return g.AxisValues[axis] + } + return 0 +} + +func (i *Input) StandardGamepadButtonValue(id driver.GamepadID, button driver.StandardGamepadButton) float64 { + i.m.Lock() + defer i.m.Unlock() + + for _, g := range i.gamepads { + if g.ID != id { + continue + } + if !g.Standard { + return 0 + } + if button < 0 { + return 0 + } + if g.ButtonNum <= int(button) { + return 0 + } + if len(g.ButtonValues) <= int(button) { + return 0 + } + return g.ButtonValues[button] + } + return 0 +} + +func (i *Input) TouchPosition(id driver.TouchID) (x, y int) { + i.m.Lock() + defer i.m.Unlock() + + for _, t := range i.touches { + if t.ID == id { + return t.X, t.Y + } + } + return 0, 0 +} + +func (i *Input) VibrateGamepad(id driver.GamepadID, duration time.Duration, strongMagnitude float64, weakMagnitude float64) { +} + +func (i *Input) Wheel() (xoff, yoff float64) { + return 0, 0 +} diff --git a/internal/uidriver/cbackend/ui.go b/internal/uidriver/cbackend/ui.go new file mode 100644 index 000000000..15f3e7232 --- /dev/null +++ b/internal/uidriver/cbackend/ui.go @@ -0,0 +1,142 @@ +// Copyright 2021 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. + +//go:build ebitencbackend +// +build ebitencbackend + +package cbackend + +import ( + "runtime" + "time" + + "github.com/hajimehoshi/ebiten/v2/internal/cbackend" + "github.com/hajimehoshi/ebiten/v2/internal/driver" + "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl" +) + +const deviceScaleFactor = 1 + +func init() { + runtime.LockOSThread() +} + +type UserInterface struct { + input Input +} + +var theUserInterface UserInterface + +func Get() *UserInterface { + return &theUserInterface +} + +func (u *UserInterface) Run(context driver.UIContext) error { + cbackend.InitializeGame() + for { + w, h := cbackend.ScreenSize() + context.Layout(float64(w), float64(h)) + + cbackend.BeginFrame() + u.input.update(context) + + if err := context.UpdateFrame(); err != nil { + return err + } + + cbackend.EndFrame() + } +} + +func (*UserInterface) RunWithoutMainLoop(context driver.UIContext) { + panic("cbackend: RunWithoutMainLoop is not implemented") +} + +func (*UserInterface) DeviceScaleFactor() float64 { + return deviceScaleFactor +} + +func (*UserInterface) IsFocused() bool { + return true +} + +func (*UserInterface) ScreenSizeInFullscreen() (int, int) { + return 0, 0 +} + +func (*UserInterface) ResetForFrame() { +} + +func (*UserInterface) CursorMode() driver.CursorMode { + return driver.CursorModeHidden +} + +func (*UserInterface) SetCursorMode(mode driver.CursorMode) { +} + +func (*UserInterface) CursorShape() driver.CursorShape { + return driver.CursorShapeDefault +} + +func (*UserInterface) SetCursorShape(shape driver.CursorShape) { +} + +func (*UserInterface) IsFullscreen() bool { + return false +} + +func (*UserInterface) SetFullscreen(fullscreen bool) { +} + +func (*UserInterface) IsRunnableOnUnfocused() bool { + return false +} + +func (*UserInterface) SetRunnableOnUnfocused(runnableOnUnfocused bool) { +} + +func (*UserInterface) FPSMode() driver.FPSMode { + return driver.FPSModeVsyncOn +} + +func (*UserInterface) SetFPSMode(mode driver.FPSMode) { +} + +func (*UserInterface) ScheduleFrame() { +} + +func (*UserInterface) IsScreenTransparent() bool { + return false +} + +func (*UserInterface) SetScreenTransparent(transparent bool) { +} + +func (*UserInterface) SetInitFocused(focused bool) { +} + +func (*UserInterface) Vibrate(duration time.Duration, intensity float64) { +} + +func (*UserInterface) Input() driver.Input { + return &theUserInterface.input +} + +func (*UserInterface) Window() driver.Window { + return nil +} + +func (*UserInterface) Graphics() driver.Graphics { + return opengl.Get() +} diff --git a/uidriver_cbackend.go b/uidriver_cbackend.go new file mode 100644 index 000000000..333afda14 --- /dev/null +++ b/uidriver_cbackend.go @@ -0,0 +1,27 @@ +// Copyright 2021 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. + +//go:build ebitencbackend +// +build ebitencbackend + +package ebiten + +import ( + "github.com/hajimehoshi/ebiten/v2/internal/driver" + "github.com/hajimehoshi/ebiten/v2/internal/uidriver/cbackend" +) + +func uiDriver() driver.UI { + return cbackend.Get() +} diff --git a/uidriver_glfw.go b/uidriver_glfw.go index 396456a45..ed6006881 100644 --- a/uidriver_glfw.go +++ b/uidriver_glfw.go @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build !android && !js && !ios -// +build !android,!js,!ios +//go:build !android && !js && !ios && !ebitencbackend +// +build !android,!js,!ios,!ebitencbackend package ebiten diff --git a/uidriver_js.go b/uidriver_js.go index 8d02b67dd..fdefd7130 100644 --- a/uidriver_js.go +++ b/uidriver_js.go @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !ebitencbackend +// +build !ebitencbackend + package ebiten import ( diff --git a/uidriver_mobile.go b/uidriver_mobile.go index 0bea4b1ef..d864784c3 100644 --- a/uidriver_mobile.go +++ b/uidriver_mobile.go @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build android || ios +//go:build (android || ios) && !ebitencbackend // +build android ios +// +build !ebitencbackend package ebiten