vendor dependencies, make some changes to how input is done

This commit is contained in:
2026-06-15 18:54:00 +02:00
parent 535130933c
commit 4800eb28d9
759 changed files with 360941 additions and 30 deletions
+9
View File
@@ -0,0 +1,9 @@
.DS_Store
*~
*.aar
*.apk
*.tar.gz
.idea
.vscode
go.work
go.work.sum
+3
View File
@@ -0,0 +1,3 @@
# Code of Conduct
We adhere to the [Go Community Code of Conduct](https://go.dev/conduct).
+56
View File
@@ -0,0 +1,56 @@
# Contributing to Ebitengine
Ebitengine is an open source project and we appreciate your contributions!
There are some rules for Ebitengine contribution.
## Filing an issue in the GitHub issue
File an issue in the GitHub issues, if you find a problem or have a feature request but this is not filed.
Except for very trivial fixes like fixing misspelling, it is a must to have an issue.
## Asking us when you are not sure
In Ebitengine, problems might not be documented very well in GitHub issues. Please ask us before you work on such issue.
You can ask us at these communities:
* [Ebitengine Discord Server](https://discord.gg/3tVdM5H8cC)
* `#ebitengine` channel in [Gophers Slack](https://invite.slack.golangbridge.org/)
* [GitHub Discussion](https://github.com/hajimehoshi/ebiten/discussions)
## Following the Go convention
Please follow the Go convention like [Effective Go](https://golang.org/doc/effective_go.html).
For example, formatting by `go fmt` is required.
## Adding copyright comments to each file
```go
// Copyright [YYYY] The Ebitengine 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.
```
You don't have to update existing files' license comments.
## Implementation details
See the [documents](https://ebitengine.org/en/documents/implementation.html) about internal implementation.
## Fixing the documentation
Ebitengine's documentations are managed at [ebitengine/ebitengine.org](https://github.com/ebitengine/ebitengine.org).
Before submitting a documentation PR, read through the [README](https://github.com/ebitengine/ebitengine.org/blob/main/README.md) in that repo.
+201
View File
@@ -0,0 +1,201 @@
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.
+63
View File
@@ -0,0 +1,63 @@
# Ebitengine (v2)
[![Go Reference](https://pkg.go.dev/badge/github.com/hajimehoshi/ebiten/v2.svg)](https://pkg.go.dev/github.com/hajimehoshi/ebiten/v2)
[![Build Status](https://github.com/hajimehoshi/ebiten/actions/workflows/test.yml/badge.svg)](https://github.com/hajimehoshi/ebiten/actions?query=workflow%3Atest)
**A dead simple 2D game engine for Go**
Ebitengine (formerly known as Ebiten) is an open source game engine for the Go programming language. Ebitengine's simple API allows you to quickly and easily develop 2D games that can be deployed across multiple platforms.
* [Website (ebitengine.org)](https://ebitengine.org)
* [API Reference](https://pkg.go.dev/github.com/hajimehoshi/ebiten/v2)
* [Cheat Sheet](https://ebitengine.org/en/documents/cheatsheet.html)
* [Awesome Ebitengine](https://github.com/sedyh/awesome-ebitengine)
![Overview](https://ebitengine.org/images/overview2.png)
## Platforms
* [Windows](https://ebitengine.org/en/documents/install.html?os=windows) (No Cgo required!)
* [macOS](https://ebitengine.org/en/documents/install.html?os=darwin)
* [Linux](https://ebitengine.org/en/documents/install.html?os=linux)
* [FreeBSD](https://ebitengine.org/en/documents/install.html?os=freebsd)
* [Android](https://ebitengine.org/en/documents/mobile.html)
* [iOS](https://ebitengine.org/en/documents/mobile.html)
* [WebAssembly](https://ebitengine.org/en/documents/webassembly.html)
* Nintendo Switch
* Xbox
For installation on desktops, see [the installation instruction](https://ebitengine.org/en/documents/install.html).
## Features
* 2D Graphics (Geometry and color transformation by matrices, Various composition modes, Offscreen rendering, Text rendering, Automatic batches, Automatic texture atlas, Custom shaders)
* Input (Mouse, Keyboard, Gamepads, Touches)
* Audio (Ogg/Vorbis, MP3, WAV, PCM)
## Packages
* [ebiten](https://pkg.go.dev/github.com/hajimehoshi/ebiten/v2)
* [audio](https://pkg.go.dev/github.com/hajimehoshi/ebiten/v2/audio)
* [mp3](https://pkg.go.dev/github.com/hajimehoshi/ebiten/v2/audio/mp3)
* [vorbis](https://pkg.go.dev/github.com/hajimehoshi/ebiten/v2/audio/vorbis)
* [wav](https://pkg.go.dev/github.com/hajimehoshi/ebiten/v2/audio/wav)
* [colorm](https://pkg.go.dev/github.com/hajimehoshi/ebiten/v2/colorm)
* [ebitenutil](https://pkg.go.dev/github.com/hajimehoshi/ebiten/v2/ebitenutil)
* [inpututil](https://pkg.go.dev/github.com/hajimehoshi/ebiten/v2/inpututil)
* [mobile](https://pkg.go.dev/github.com/hajimehoshi/ebiten/v2/mobile)
* [text/v2](https://pkg.go.dev/github.com/hajimehoshi/ebiten/v2/text/v2)
* [vector](https://pkg.go.dev/github.com/hajimehoshi/ebiten/v2/vector)
* [exp/textinput](https://pkg.go.dev/github.com/hajimehoshi/ebiten/v2/exp/textinput)
## Community
- [Discord](https://discord.gg/3tVdM5H8cC)
- `#ebitengine` channel in [Gophers Slack](https://blog.gopheracademy.com/gophers-slack-community/)
- [GitHub Discussion](https://github.com/hajimehoshi/ebiten/discussions)
- [`r/ebitengine` in Reddit](https://www.reddit.com/r/ebitengine/)
## License
Ebitengine is licensed under Apache license version 2.0. See [LICENSE](LICENSE) file.
[The Ebitengine logo](https://ebitengine.org/images/logo.png) by Hajime Hoshi is licensed under [the Creative Commons Attribution-NoDerivatives 4.0](https://creativecommons.org/licenses/by-nd/4.0/).
+410
View File
@@ -0,0 +1,410 @@
// Copyright 2022 The Ebitengine 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.
package ebiten
import (
"fmt"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
)
// Blend is a blending way of the source color and the destination color.
//
// The final color is calculated like this:
//
// c_src: source RGB values
// c_dst: destination RGB values
// c_out: result RGB values
// α_src: source alpha values
// α_dst: destination alpha values
// α_out: result alpha values
//
// c_out = BlendOperationRGB((BlendFactorSourceRGB) × c_src, (BlendFactorDestinationRGB) × c_dst)
// α_out = BlendOperationAlpha((BlendFactorSourceAlpha) × α_src, (BlendFactorDestinationAlpha) × α_dst)
//
// A blend factor is a factor for source and color destination color values.
// The default is source-over (regular alpha blending).
//
// A blend operation is a binary operator of a source color and a destination color.
// The default is adding.
type Blend struct {
// BlendFactorSourceRGB is a factor for source RGB values.
BlendFactorSourceRGB BlendFactor
// BlendFactorSourceAlpha is a factor for source alpha values.
BlendFactorSourceAlpha BlendFactor
// BlendFactorDestinationRGB is a factor for destination RGB values.
BlendFactorDestinationRGB BlendFactor
// BlendFactorDestinationAlpha is a factor for destination alpha values.
BlendFactorDestinationAlpha BlendFactor
// BlendOperationRGB is an operation for source and destination RGB values.
BlendOperationRGB BlendOperation
// BlendOperationAlpha is an operation for source and destination alpha values.
BlendOperationAlpha BlendOperation
}
var (
defaultBlendInternalBlend = graphicsdriver.Blend{
BlendFactorSourceRGB: BlendFactorDefault.internalBlendFactor(true),
BlendFactorSourceAlpha: BlendFactorDefault.internalBlendFactor(true),
BlendFactorDestinationRGB: BlendFactorDefault.internalBlendFactor(false),
BlendFactorDestinationAlpha: BlendFactorDefault.internalBlendFactor(false),
BlendOperationRGB: BlendOperationAdd.internalBlendOperation(),
BlendOperationAlpha: BlendOperationAdd.internalBlendOperation(),
}
)
func (b Blend) internalBlend() graphicsdriver.Blend {
// A shortcut for the most common blend.
if b == (Blend{}) {
return defaultBlendInternalBlend
}
return graphicsdriver.Blend{
BlendFactorSourceRGB: b.BlendFactorSourceRGB.internalBlendFactor(true),
BlendFactorSourceAlpha: b.BlendFactorSourceAlpha.internalBlendFactor(true),
BlendFactorDestinationRGB: b.BlendFactorDestinationRGB.internalBlendFactor(false),
BlendFactorDestinationAlpha: b.BlendFactorDestinationAlpha.internalBlendFactor(false),
BlendOperationRGB: b.BlendOperationRGB.internalBlendOperation(),
BlendOperationAlpha: b.BlendOperationAlpha.internalBlendOperation(),
}
}
// BlendFactor is a factor for source and destination color values.
type BlendFactor byte
const (
// BlendFactorDefault is the default factor value.
// The actual value depends on which source or destination this value is used.
BlendFactorDefault BlendFactor = iota
// BlendFactorZero is a factor:
//
// 0
BlendFactorZero
// BlendFactorOne is a factor:
//
// 1
BlendFactorOne
// BlendFactorSourceColor is a factor:
//
// (source RGBA)
BlendFactorSourceColor
// BlendFactorOneMinusSourceColor is a factor:
//
// 1 - (source color)
BlendFactorOneMinusSourceColor
// BlendFactorSourceAlpha is a factor:
//
// (source alpha)
BlendFactorSourceAlpha
// BlendFactorOneMinusSourceAlpha is a factor:
//
// 1 - (source alpha)
BlendFactorOneMinusSourceAlpha
// BlendFactorDestinationColor is a factor:
//
// (destination RGBA)
BlendFactorDestinationColor
// BlendFactorOneMinusDestinationColor is a factor:
//
// 1 - (destination RGBA)
BlendFactorOneMinusDestinationColor
// BlendFactorDestinationAlpha is a factor:
//
// (destination alpha)
BlendFactorDestinationAlpha
// BlendFactorOneMinusDestinationAlpha is a factor:
//
// 1 - (destination alpha)
BlendFactorOneMinusDestinationAlpha
// TODO: Add BlendFactorSourceAlphaSaturated. This might not work well on some platforms like Steam SDK (#2382).
)
func (b BlendFactor) internalBlendFactor(source bool) graphicsdriver.BlendFactor {
switch b {
case BlendFactorDefault:
// The default is the source-over composition (regular alpha blending).
if source {
return graphicsdriver.BlendFactorOne
}
return graphicsdriver.BlendFactorOneMinusSourceAlpha
case BlendFactorZero:
return graphicsdriver.BlendFactorZero
case BlendFactorOne:
return graphicsdriver.BlendFactorOne
case BlendFactorSourceColor:
return graphicsdriver.BlendFactorSourceColor
case BlendFactorOneMinusSourceColor:
return graphicsdriver.BlendFactorOneMinusSourceColor
case BlendFactorSourceAlpha:
return graphicsdriver.BlendFactorSourceAlpha
case BlendFactorOneMinusSourceAlpha:
return graphicsdriver.BlendFactorOneMinusSourceAlpha
case BlendFactorDestinationColor:
return graphicsdriver.BlendFactorDestinationColor
case BlendFactorOneMinusDestinationColor:
return graphicsdriver.BlendFactorOneMinusDestinationColor
case BlendFactorDestinationAlpha:
return graphicsdriver.BlendFactorDestinationAlpha
case BlendFactorOneMinusDestinationAlpha:
return graphicsdriver.BlendFactorOneMinusDestinationAlpha
default:
panic(fmt.Sprintf("ebiten: invalid blend factor: %d", b))
}
}
// BlendOperation is an operation for source and destination color values.
type BlendOperation byte
const (
// BlendOperationAdd represents adding the source and destination color.
//
// c_out = (BlendFactorSourceRGB) × c_src + (BlendFactorDestinationRGB) × c_dst
// α_out = (BlendFactorSourceAlpha) × α_src + (BlendFactorDestinationAlpha) × α_dst
BlendOperationAdd BlendOperation = iota
// BlendOperationSubtract represents subtracting the source and destination color.
//
// c_out = (BlendFactorSourceRGB) × c_src - (BlendFactorDestinationRGB) × c_dst
// α_out = (BlendFactorSourceAlpha) × α_src - (BlendFactorDestinationAlpha) × α_dst
BlendOperationSubtract
// BlendOperationReverseSubtract represents subtracting the source and destination color in a reversed order.
//
// c_out = (BlendFactorDestinationRGB) × c_dst - (BlendFactorSourceRGB) × c_src
// α_out = (BlendFactorDestinationAlpha) × α_dst - (BlendFactorSourceAlpha) × α_src
BlendOperationReverseSubtract
// BlendOperationMin represents a minimum function for the source and destination color.
// If BlendOperationMin is specified, blend factors are not used.
//
// c_out = min(c_dst, c_src)
// α_out = min(α_dst, α_src)
BlendOperationMin
// BlendOperationMax represents a maximum function for the source and destination color.
// If BlendOperationMax is specified, blend factors are not used.
//
// c_out = max(c_dst, c_src)
// α_out = max(α_dst, α_src)
BlendOperationMax
)
func (b BlendOperation) internalBlendOperation() graphicsdriver.BlendOperation {
switch b {
case BlendOperationAdd:
return graphicsdriver.BlendOperationAdd
case BlendOperationSubtract:
return graphicsdriver.BlendOperationSubtract
case BlendOperationReverseSubtract:
return graphicsdriver.BlendOperationReverseSubtract
case BlendOperationMin:
return graphicsdriver.BlendOperationMin
case BlendOperationMax:
return graphicsdriver.BlendOperationMax
default:
panic(fmt.Sprintf("ebiten: invalid blend operation: %d", b))
}
}
// This name convention follows CSS compositing: https://drafts.fxtf.org/compositing-2/.
//
// In the comments,
// c_src, c_dst and c_out represent alpha-premultiplied RGB values of source, destination and output respectively. α_src and α_dst represent alpha values of source and destination respectively.
var (
// BlendSourceOver is a preset Blend for the regular alpha blending.
//
// c_out = c_src + c_dst × (1 - α_src)
// α_out = α_src + α_dst × (1 - α_src)
BlendSourceOver = Blend{
BlendFactorSourceRGB: BlendFactorOne,
BlendFactorSourceAlpha: BlendFactorOne,
BlendFactorDestinationRGB: BlendFactorOneMinusSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorOneMinusSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendClear is a preset Blend for Porter Duff's 'clear'.
//
// c_out = 0
// α_out = 0
BlendClear = Blend{
BlendFactorSourceRGB: BlendFactorZero,
BlendFactorSourceAlpha: BlendFactorZero,
BlendFactorDestinationRGB: BlendFactorZero,
BlendFactorDestinationAlpha: BlendFactorZero,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendCopy is a preset Blend for Porter Duff's 'copy'.
//
// c_out = c_src
// α_out = α_src
BlendCopy = Blend{
BlendFactorSourceRGB: BlendFactorOne,
BlendFactorSourceAlpha: BlendFactorOne,
BlendFactorDestinationRGB: BlendFactorZero,
BlendFactorDestinationAlpha: BlendFactorZero,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendDestination is a preset Blend for Porter Duff's 'destination'.
//
// c_out = c_dst
// α_out = α_dst
BlendDestination = Blend{
BlendFactorSourceRGB: BlendFactorZero,
BlendFactorSourceAlpha: BlendFactorZero,
BlendFactorDestinationRGB: BlendFactorOne,
BlendFactorDestinationAlpha: BlendFactorOne,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendDestinationOver is a preset Blend for Porter Duff's 'destination-over'.
//
// c_out = c_src × (1 - α_dst) + c_dst
// α_out = α_src × (1 - α_dst) + α_dst
BlendDestinationOver = Blend{
BlendFactorSourceRGB: BlendFactorOneMinusDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorOneMinusDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorOne,
BlendFactorDestinationAlpha: BlendFactorOne,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendSourceIn is a preset Blend for Porter Duff's 'source-in'.
//
// c_out = c_src × α_dst
// α_out = α_src × α_dst
BlendSourceIn = Blend{
BlendFactorSourceRGB: BlendFactorDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorZero,
BlendFactorDestinationAlpha: BlendFactorZero,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendDestinationIn is a preset Blend for Porter Duff's 'destination-in'.
//
// c_out = c_dst × α_src
// α_out = α_dst × α_src
BlendDestinationIn = Blend{
BlendFactorSourceRGB: BlendFactorZero,
BlendFactorSourceAlpha: BlendFactorZero,
BlendFactorDestinationRGB: BlendFactorSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendSourceOut is a preset Blend for Porter Duff's 'source-out'.
//
// c_out = c_src × (1 - α_dst)
// α_out = α_src × (1 - α_dst)
BlendSourceOut = Blend{
BlendFactorSourceRGB: BlendFactorOneMinusDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorOneMinusDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorZero,
BlendFactorDestinationAlpha: BlendFactorZero,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendDestinationOut is a preset Blend for Porter Duff's 'destination-out'.
//
// c_out = c_dst × (1 - α_src)
// α_out = α_dst × (1 - α_src)
BlendDestinationOut = Blend{
BlendFactorSourceRGB: BlendFactorZero,
BlendFactorSourceAlpha: BlendFactorZero,
BlendFactorDestinationRGB: BlendFactorOneMinusSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorOneMinusSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendSourceAtop is a preset Blend for Porter Duff's 'source-atop'.
//
// c_out = c_src × α_dst + c_dst × (1 - α_src)
// α_out = α_src × α_dst + α_dst × (1 - α_src)
BlendSourceAtop = Blend{
BlendFactorSourceRGB: BlendFactorDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorOneMinusSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorOneMinusSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendDestinationAtop is a preset Blend for Porter Duff's 'destination-atop'.
//
// c_out = c_src × (1 - α_dst) + c_dst × α_src
// α_out = α_src × (1 - α_dst) + α_dst × α_src
BlendDestinationAtop = Blend{
BlendFactorSourceRGB: BlendFactorOneMinusDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorOneMinusDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendXor is a preset Blend for Porter Duff's 'xor'.
//
// c_out = c_src × (1 - α_dst) + c_dst × (1 - α_src)
// α_out = α_src × (1 - α_dst) + α_dst × (1 - α_src)
BlendXor = Blend{
BlendFactorSourceRGB: BlendFactorOneMinusDestinationAlpha,
BlendFactorSourceAlpha: BlendFactorOneMinusDestinationAlpha,
BlendFactorDestinationRGB: BlendFactorOneMinusSourceAlpha,
BlendFactorDestinationAlpha: BlendFactorOneMinusSourceAlpha,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
// BlendLighter is a preset Blend for Porter Duff's 'lighter'.
// This is sum of source and destination (a.k.a. 'plus' or 'additive')
//
// c_out = c_src + c_dst
// α_out = α_src + α_dst
BlendLighter = Blend{
BlendFactorSourceRGB: BlendFactorOne,
BlendFactorSourceAlpha: BlendFactorOne,
BlendFactorDestinationRGB: BlendFactorOne,
BlendFactorDestinationAlpha: BlendFactorOne,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
)
+160
View File
@@ -0,0 +1,160 @@
// Copyright 2014 Hajime Hoshi
//
// 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.
package ebiten
import (
"image/color"
"github.com/hajimehoshi/ebiten/v2/internal/affine"
)
// ColorMDim is a dimension of a ColorM.
//
// Deprecated: as of v2.5. Use the colorm package instead.
const ColorMDim = affine.ColorMDim
// A ColorM represents a matrix to transform coloring when rendering an image.
//
// A ColorM is applied to the straight alpha color
// while an Image's pixels' format is alpha premultiplied.
// Before applying a matrix, a color is un-multiplied, and after applying the matrix,
// the color is multiplied again.
//
// The initial value is identity.
//
// Deprecated: as of v2.5. Use the colorm package instead.
type ColorM struct {
impl affine.ColorM
_ [0]func() // Marks as non-comparable.
}
func (c *ColorM) affineColorM() affine.ColorM {
if c.impl != nil {
return c.impl
}
return affine.ColorMIdentity{}
}
// String returns a string representation of ColorM.
//
// Deprecated: as of v2.5. Use the colorm package instead.
func (c *ColorM) String() string {
return c.affineColorM().String()
}
// Reset resets the ColorM as identity.
//
// Deprecated: as of v2.5. Use the colorm package instead.
func (c *ColorM) Reset() {
c.impl = affine.ColorMIdentity{}
}
// Apply pre-multiplies a vector (r, g, b, a, 1) by the matrix
// where r, g, b, and a are clr's values in straight-alpha format.
// In other words, Apply calculates ColorM * (r, g, b, a, 1)^T.
//
// Deprecated: as of v2.5. Use the colorm package instead.
func (c *ColorM) Apply(clr color.Color) color.Color {
return c.affineColorM().Apply(clr)
}
// Concat multiplies a color matrix with the other color matrix.
// This is same as multiplying the matrix other and the matrix c in this order.
//
// Deprecated: as of v2.5. Use the colorm package instead.
func (c *ColorM) Concat(other ColorM) {
o := other.impl
if o == nil {
return
}
c.impl = c.affineColorM().Concat(o)
}
// Scale scales the matrix by (r, g, b, a).
//
// Deprecated: as of v2.5. Use ColorScale or the colorm package instead.
func (c *ColorM) Scale(r, g, b, a float64) {
c.impl = c.affineColorM().Scale(float32(r), float32(g), float32(b), float32(a))
}
// ScaleWithColor scales the matrix by clr.
//
// Deprecated: as of v2.5. Use ColorScale or the colorm package instead.
func (c *ColorM) ScaleWithColor(clr color.Color) {
cr, cg, cb, ca := clr.RGBA()
if ca == 0 {
c.Scale(0, 0, 0, 0)
return
}
c.Scale(float64(cr)/float64(ca), float64(cg)/float64(ca), float64(cb)/float64(ca), float64(ca)/0xffff)
}
// Translate translates the matrix by (r, g, b, a).
//
// Deprecated: as of v2.5. Use the colorm package instead.
func (c *ColorM) Translate(r, g, b, a float64) {
c.impl = c.affineColorM().Translate(float32(r), float32(g), float32(b), float32(a))
}
// RotateHue rotates the hue.
// theta represents rotating angle in radian.
//
// Deprecated: as of v2.5. Use the colorm package instead.
func (c *ColorM) RotateHue(theta float64) {
c.ChangeHSV(theta, 1, 1)
}
// ChangeHSV changes HSV (Hue-Saturation-Value) values.
// hueTheta is a radian value to rotate hue.
// saturationScale is a value to scale saturation.
// valueScale is a value to scale value (a.k.a. brightness).
//
// This conversion uses RGB to/from YCrCb conversion.
//
// Deprecated: as of v2.5. Use the colorm package instead.
func (c *ColorM) ChangeHSV(hueTheta float64, saturationScale float64, valueScale float64) {
c.impl = affine.ChangeHSV(c.affineColorM(), hueTheta, float32(saturationScale), float32(valueScale))
}
// Element returns a value of a matrix at (i, j).
//
// Deprecated: as of v2.5. Use the colorm package instead.
func (c *ColorM) Element(i, j int) float64 {
return float64(c.affineColorM().At(i, j))
}
// SetElement sets an element at (i, j).
//
// Deprecated: as of v2.5. Use the colorm package instead.
func (c *ColorM) SetElement(i, j int, element float64) {
c.impl = affine.ColorMSetElement(c.affineColorM(), i, j, float32(element))
}
// IsInvertible returns a boolean value indicating
// whether the matrix c is invertible or not.
//
// Deprecated: as of v2.5. Use the colorm package instead.
func (c *ColorM) IsInvertible() bool {
return c.affineColorM().IsInvertible()
}
// Invert inverts the matrix.
// If c is not invertible, Invert panics.
//
// Deprecated: as of v2.5. Use the colorm package instead.
func (c *ColorM) Invert() {
c.impl = c.affineColorM().Invert()
}
+125
View File
@@ -0,0 +1,125 @@
// Copyright 2022 The Ebitengine 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.
package ebiten
import (
"fmt"
"image/color"
)
// ColorScale represents a scale of RGBA color.
// ColorScale is intended to be applied to a premultiplied-alpha color value.
//
// The initial (zero) value of ColorScale is an identity scale (1, 1, 1, 1).
type ColorScale struct {
// These values are adjusted by -1 from the actual values.
// It's because the initial value should be 1 instead of 0.
r_1, g_1, b_1, a_1 float32
}
// String returns a string representing the color scale.
func (c *ColorScale) String() string {
return fmt.Sprintf("(%f,%f,%f,%f)", c.r_1+1, c.g_1+1, c.b_1+1, c.a_1+1)
}
// Reset resets the ColorScale as identity.
func (c *ColorScale) Reset() {
c.r_1 = 0
c.g_1 = 0
c.b_1 = 0
c.a_1 = 0
}
// R returns the red scale.
func (c *ColorScale) R() float32 {
return c.r_1 + 1
}
// G returns the green scale.
func (c *ColorScale) G() float32 {
return c.g_1 + 1
}
// B returns the blue scale.
func (c *ColorScale) B() float32 {
return c.b_1 + 1
}
// A returns the alpha scale.
func (c *ColorScale) A() float32 {
return c.a_1 + 1
}
func (c *ColorScale) elements() (float32, float32, float32, float32) {
return c.r_1 + 1, c.g_1 + 1, c.b_1 + 1, c.a_1 + 1
}
// SetR overwrites the current red value with r.
func (c *ColorScale) SetR(r float32) {
c.r_1 = r - 1
}
// SetG overwrites the current green value with g.
func (c *ColorScale) SetG(g float32) {
c.g_1 = g - 1
}
// SetB overwrites the current blue value with b.
func (c *ColorScale) SetB(b float32) {
c.b_1 = b - 1
}
// SetA overwrites the current alpha value with a.
func (c *ColorScale) SetA(a float32) {
c.a_1 = a - 1
}
// Scale multiplies the given values to the current scale.
//
// Scale is slightly different from colorm.ColorM's Scale in terms of alphas.
// ColorScale is applied to premultiplied-alpha colors, while colorm.ColorM is applied to straight-alpha colors.
// Thus, ColorM.Scale(r, g, b, a) equals to ColorScale.Scale(r*a, g*a, b*a, a).
func (c *ColorScale) Scale(r, g, b, a float32) {
c.r_1 = (c.r_1+1)*r - 1
c.g_1 = (c.g_1+1)*g - 1
c.b_1 = (c.b_1+1)*b - 1
c.a_1 = (c.a_1+1)*a - 1
}
// ScaleAlpha multiplies the given alpha value to the current scale.
func (c *ColorScale) ScaleAlpha(a float32) {
c.r_1 = (c.r_1+1)*a - 1
c.g_1 = (c.g_1+1)*a - 1
c.b_1 = (c.b_1+1)*a - 1
c.a_1 = (c.a_1+1)*a - 1
}
// ScaleWithColor multiplies the given color values to the current scale.
func (c *ColorScale) ScaleWithColor(clr color.Color) {
cr, cg, cb, ca := clr.RGBA()
c.Scale(float32(cr)/0xffff, float32(cg)/0xffff, float32(cb)/0xffff, float32(ca)/0xffff)
}
// ScaleWithColorScale multiplies the given color scale values to the current scale.
func (c *ColorScale) ScaleWithColorScale(colorScale ColorScale) {
c.r_1 = (c.r_1+1)*(colorScale.r_1+1) - 1
c.g_1 = (c.g_1+1)*(colorScale.g_1+1) - 1
c.b_1 = (c.b_1+1)*(colorScale.b_1+1) - 1
c.a_1 = (c.a_1+1)*(colorScale.a_1+1) - 1
}
func (c *ColorScale) apply(r, g, b, a float32) (float32, float32, float32, float32) {
return (c.r_1 + 1) * r, (c.g_1 + 1) * g, (c.b_1 + 1) * b, (c.a_1 + 1) * a
}
+113
View File
@@ -0,0 +1,113 @@
// Copyright 2023 The Ebitengine 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.
package ebiten
import (
"fmt"
)
// CompositeMode represents Porter-Duff composition mode.
//
// Deprecated: as of v2.5. Use Blend instead.
type CompositeMode int
const (
// CompositeModeCustom indicates to refer Blend.
CompositeModeCustom CompositeMode = iota
// Deprecated: as of v2.5. Use BlendSourceOver instead.
CompositeModeSourceOver
// Deprecated: as of v2.5. Use BlendClear instead.
CompositeModeClear
// Deprecated: as of v2.5. Use BlendCopy instead.
CompositeModeCopy
// Deprecated: as of v2.5. Use BlendDestination instead.
CompositeModeDestination
// Deprecated: as of v2.5. Use BlendDestinationOver instead.
CompositeModeDestinationOver
// Deprecated: as of v2.5. Use BlendSourceIn instead.
CompositeModeSourceIn
// Deprecated: as of v2.5. Use BlendDestinationIn instead.
CompositeModeDestinationIn
// Deprecated: as of v2.5. Use BlendSourceOut instead.
CompositeModeSourceOut
// Deprecated: as of v2.5. Use BlendDestinationOut instead.
CompositeModeDestinationOut
// Deprecated: as of v2.5. Use BlendSourceAtop instead.
CompositeModeSourceAtop
// Deprecated: as of v2.5. Use BlendDestinationAtop instead.
CompositeModeDestinationAtop
// Deprecated: as of v2.5. Use BlendXor instead.
CompositeModeXor
// Deprecated: as of v2.5. Use BlendLighter instead.
CompositeModeLighter
// Deprecated: as of v2.5. Use Blend with BlendFactorDestinationColor and BlendFactorZero instead.
CompositeModeMultiply
)
func (c CompositeMode) blend() Blend {
switch c {
case CompositeModeSourceOver:
return BlendSourceOver
case CompositeModeClear:
return BlendClear
case CompositeModeCopy:
return BlendCopy
case CompositeModeDestination:
return BlendDestination
case CompositeModeDestinationOver:
return BlendDestinationOver
case CompositeModeSourceIn:
return BlendSourceIn
case CompositeModeDestinationIn:
return BlendDestinationIn
case CompositeModeSourceOut:
return BlendSourceOut
case CompositeModeDestinationOut:
return BlendDestinationOut
case CompositeModeSourceAtop:
return BlendSourceAtop
case CompositeModeDestinationAtop:
return BlendDestinationAtop
case CompositeModeXor:
return BlendXor
case CompositeModeLighter:
return BlendLighter
case CompositeModeMultiply:
return Blend{
BlendFactorSourceRGB: BlendFactorDestinationColor,
BlendFactorSourceAlpha: BlendFactorDestinationColor,
BlendFactorDestinationRGB: BlendFactorZero,
BlendFactorDestinationAlpha: BlendFactorZero,
BlendOperationRGB: BlendOperationAdd,
BlendOperationAlpha: BlendOperationAdd,
}
default:
panic(fmt.Sprintf("ebiten: invalid composite mode: %d", c))
}
}
+46
View File
@@ -0,0 +1,46 @@
// 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.
package ebiten
import (
"github.com/hajimehoshi/ebiten/v2/internal/ui"
)
// CursorModeType represents a render and coordinate mode of a mouse cursor.
type CursorModeType = ui.CursorMode
// CursorModeTypes
const (
CursorModeVisible CursorModeType = ui.CursorModeVisible
CursorModeHidden CursorModeType = ui.CursorModeHidden
CursorModeCaptured CursorModeType = ui.CursorModeCaptured
)
// CursorShapeType represents a shape of a mouse cursor.
type CursorShapeType = ui.CursorShape
// CursorShapeTypes
const (
CursorShapeDefault CursorShapeType = CursorShapeType(ui.CursorShapeDefault)
CursorShapeText CursorShapeType = CursorShapeType(ui.CursorShapeText)
CursorShapeCrosshair CursorShapeType = CursorShapeType(ui.CursorShapeCrosshair)
CursorShapePointer CursorShapeType = CursorShapeType(ui.CursorShapePointer)
CursorShapeEWResize CursorShapeType = CursorShapeType(ui.CursorShapeEWResize)
CursorShapeNSResize CursorShapeType = CursorShapeType(ui.CursorShapeNSResize)
CursorShapeNESWResize CursorShapeType = CursorShapeType(ui.CursorShapeNESWResize)
CursorShapeNWSEResize CursorShapeType = CursorShapeType(ui.CursorShapeNWSEResize)
CursorShapeMove CursorShapeType = CursorShapeType(ui.CursorShapeMove)
CursorShapeNotAllowed CursorShapeType = CursorShapeType(ui.CursorShapeNotAllowed)
)
+122
View File
@@ -0,0 +1,122 @@
// Copyright 2014 Hajime Hoshi
//
// 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.
// Package ebiten provides graphics and input API to develop a 2D game.
//
// You can start the game by calling the function RunGame.
//
// // Game implements ebiten.Game interface.
// type Game struct{}
//
// // Update proceeds the game state.
// // Update is called every tick (1/60 [s] by default).
// func (g *Game) Update() error {
// // Write your game's logical update.
// return nil
// }
//
// // Draw draws the game screen.
// // Draw is called every frame (typically 1/60[s] for 60Hz display).
// func (g *Game) Draw(screen *ebiten.Image) {
// // Write your game's rendering.
// }
//
// // Layout takes the outside size (e.g., the window size) and returns the (logical) screen size.
// // If you don't have to adjust the screen size with the outside size, just return a fixed size.
// func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
// return 320, 240
// }
//
// func main() {
// game := &Game{}
// // Specify the window size as you like. Here, a doubled size is specified.
// ebiten.SetWindowSize(640, 480)
// ebiten.SetWindowTitle("Your game's title")
// // Call ebiten.RunGame to start your game loop.
// if err := ebiten.RunGame(game); err != nil {
// log.Fatal(err)
// }
// }
//
// In the API document, 'the main thread' means the goroutine in init(), main() and their callees without 'go'
// statement. It is assured that 'the main thread' runs on the OS main thread. There are some Ebitengine functions (e.g.,
// DeviceScaleFactor) that must be called on the main thread under some conditions (typically, before ebiten.RunGame
// is called).
//
// # Environment variables
//
// `EBITENGINE_SCREENSHOT_KEY` environment variable specifies the key
// to take a screenshot. For example, if you run your game with
// `EBITENGINE_SCREENSHOT_KEY=q`, you can take a game screen's screenshot
// by pressing Q key. This works only on desktops and browsers.
//
// `EBITENGINE_INTERNAL_IMAGES_KEY` environment variable specifies the key
// to dump all the internal images. This is valid only when the build tag
// 'ebitenginedebug' is specified. This works only on desktops and browsers.
//
// `EBITENGINE_GRAPHICS_LIBRARY` environment variable specifies the graphics library.
// If the specified graphics library is not available, RunGame returns an error.
// This environment variable works when RunGame is called or RunGameWithOptions is called with GraphicsLibraryAuto.
// This can take one of the following value:
//
// "auto": Ebitengine chooses the graphics library automatically. This is the default value.
// "opengl": OpenGL, OpenGL ES, or WebGL.
// "directx": DirectX. This works only on Windows.
// "metal": Metal. This works only on macOS or iOS.
// "playstation5": PlayStation 5. This works only on PlayStation 5.
//
// `EBITENGINE_DIRECTX` environment variable specifies various parameters for DirectX.
// You can specify multiple values separated by a comma. The default value is empty (i.e. no parameters).
//
// "debug": Use a debug layer.
// "warp": Use WARP (i.e. software rendering).
// "version=VERSION": Specify a DirectX version (e.g. 11).
// "featurelevel=FEATURE_LEVEL": Specify a feature level (e.g. 11_0). This is for DirectX 12.
//
// The options taking arguments are exclusive, and if multiples are specified, the lastly specified value is adopted.
//
// The possible values for the option "version" are "11" and "12".
// If the version is not specified, the default version 11 is adopted.
// On Xbox, the "version" option is ignored and DirectX 12 is always adopted.
//
// The option "featurelevel" is valid only for DirectX 12.
// The possible values are "11_0", "11_1", "12_0", "12_1", and "12_2". The default value is "11_0".
//
// `EBITENGINE_OPENGL` environment variable specifies various parameters for OpenGL.
// You can specify multiple values separated by a comma. The default value is empty (i.e. no parameters).
//
// "es": Use OpenGL ES. Without this, OpenGL and OpenGL ES are automatically chosen.
//
// # Build tags
//
// `ebitenginedebug` outputs a log of graphics commands. This is useful to know what happens in Ebitengine. In general, the
// number of graphics commands affects the performance of your game.
//
// `ebitenginegldebug` enables a debug mode for OpenGL. This is valid only when the graphics library is OpenGL.
// This affects performance very much.
//
// `ebitenginesinglethread` disables Ebitengine's thread safety to unlock maximum performance. If you use this you will have
// to manage threads yourself. Functions like `SetWindowSize` will no longer be concurrent-safe with this build tag.
// They must be called from the main thread or the same goroutine as the given game's callback functions like Update
// `ebitenginesinglethread` works only with desktops and consoles.
// `ebitenginesinglethread` was deprecated as of v2.7. Use RunGameOptions.SingleThread instead.
//
// `microsoftgdk` is for Microsoft GDK (e.g. Xbox).
//
// `nintendosdk` is for NintendoSDK (e.g. Nintendo Switch).
//
// `nintendosdkprofile` enables a profiler for NintendoSDK.
//
// `playstation5` is for PlayStation 5.
package ebiten
+186
View File
@@ -0,0 +1,186 @@
// Copyright 2014 Hajime Hoshi
//
// 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.
package ebiten
import (
"fmt"
"image"
"math"
"sync/atomic"
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
"github.com/hajimehoshi/ebiten/v2/internal/ui"
)
const screenShaderSrc = `//kage:unit pixels
package main
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
// Blend source colors in a square region, which size is 1/scale.
scale := imageDstSize()/imageSrc0Size()
pos := srcPos
p0 := pos - 1/2.0/scale
p1 := pos + 1/2.0/scale
// Texels must be in the source rect, so it is not necessary to check.
c0 := imageSrc0UnsafeAt(p0)
c1 := imageSrc0UnsafeAt(vec2(p1.x, p0.y))
c2 := imageSrc0UnsafeAt(vec2(p0.x, p1.y))
c3 := imageSrc0UnsafeAt(p1)
// p is the p1 value in one pixel assuming that the pixel's upper-left is (0, 0) and the lower-right is (1, 1).
rate := clamp(fract(p1)*scale, 0, 1)
return mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y)
}
`
var screenFilterEnabled = int32(1)
func isScreenFilterEnabled() bool {
return atomic.LoadInt32(&screenFilterEnabled) != 0
}
func setScreenFilterEnabled(enabled bool) {
v := int32(0)
if enabled {
v = 1
}
atomic.StoreInt32(&screenFilterEnabled, v)
}
type gameForUI struct {
game Game
offscreen *Image
screen *Image
screenShader *Shader
imageDumper imageDumper
transparent bool
}
func newGameForUI(game Game, transparent bool) *gameForUI {
g := &gameForUI{
game: game,
transparent: transparent,
}
s, err := NewShader([]byte(screenShaderSrc))
if err != nil {
panic(fmt.Sprintf("ebiten: compiling the screen shader failed: %v", err))
}
g.screenShader = s
return g
}
func (g *gameForUI) NewOffscreenImage(width, height int) *ui.Image {
if g.offscreen != nil {
g.offscreen.Deallocate()
g.offscreen = nil
}
// Keep the offscreen an unmanaged image that is always isolated from an atlas (#1938).
// The shader program for the screen is special and doesn't work well with an image on an atlas.
// An image on an atlas is surrounded by a transparent edge,
// and the shader program unexpectedly picks the pixel on the edges.
imageType := atlas.ImageTypeUnmanaged
if ui.Get().IsScreenClearedEveryFrame() {
// A volatile image is also always isolated.
imageType = atlas.ImageTypeVolatile
}
g.offscreen = newImage(image.Rect(0, 0, width, height), imageType)
return g.offscreen.image
}
func (g *gameForUI) NewScreenImage(width, height int) *ui.Image {
if g.screen != nil {
g.screen.Deallocate()
g.screen = nil
}
g.screen = newImage(image.Rect(0, 0, width, height), atlas.ImageTypeScreen)
return g.screen.image
}
func (g *gameForUI) Layout(outsideWidth, outsideHeight float64) (float64, float64) {
if l, ok := g.game.(LayoutFer); ok {
return l.LayoutF(outsideWidth, outsideHeight)
}
// Even if the original value is less than 1, the value must be a positive integer (#2340).
// This is for a simple implementation of Layout, which returns the argument values without modifications.
// TODO: Remove this hack when Game.Layout takes floats instead of integers.
if outsideWidth < 1 {
outsideWidth = 1
}
if outsideHeight < 1 {
outsideHeight = 1
}
// TODO: Add a new Layout function taking float values (#2285).
sw, sh := g.game.Layout(int(outsideWidth), int(outsideHeight))
return float64(sw), float64(sh)
}
func (g *gameForUI) UpdateInputState(fn func(*ui.InputState)) {
theInputState.update(fn)
}
func (g *gameForUI) Update() error {
if err := g.game.Update(); err != nil {
return err
}
if err := g.imageDumper.update(); err != nil {
return err
}
return nil
}
func (g *gameForUI) DrawOffscreen() error {
g.game.Draw(g.offscreen)
if err := g.imageDumper.dump(g.offscreen, g.transparent); err != nil {
return err
}
return nil
}
func (g *gameForUI) DrawFinalScreen(scale, offsetX, offsetY float64) {
var geoM GeoM
geoM.Scale(scale, scale)
geoM.Translate(offsetX, offsetY)
if d, ok := g.game.(FinalScreenDrawer); ok {
d.DrawFinalScreen(g.screen, g.offscreen, geoM)
return
}
switch {
case !isScreenFilterEnabled(), math.Floor(scale) == scale:
op := &DrawImageOptions{}
op.GeoM = geoM
g.screen.DrawImage(g.offscreen, op)
case scale < 1:
op := &DrawImageOptions{}
op.GeoM = geoM
op.Filter = FilterLinear
g.screen.DrawImage(g.offscreen, op)
default:
op := &DrawRectShaderOptions{}
op.Images[0] = g.offscreen
op.GeoM = geoM
w, h := g.offscreen.Bounds().Dx(), g.offscreen.Bounds().Dy()
g.screen.DrawRectShader(w, h, g.screenShader, op)
}
}
+107
View File
@@ -0,0 +1,107 @@
// Copyright 2015 Hajime Hoshi
//
// 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.
package ebiten
import (
"github.com/hajimehoshi/ebiten/v2/internal/gamepad"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
)
// GamepadButton represents a gamepad button.
type GamepadButton = gamepad.Button
// GamepadButtons
const (
GamepadButton0 GamepadButton = gamepad.Button0
GamepadButton1 GamepadButton = gamepad.Button1
GamepadButton2 GamepadButton = gamepad.Button2
GamepadButton3 GamepadButton = gamepad.Button3
GamepadButton4 GamepadButton = gamepad.Button4
GamepadButton5 GamepadButton = gamepad.Button5
GamepadButton6 GamepadButton = gamepad.Button6
GamepadButton7 GamepadButton = gamepad.Button7
GamepadButton8 GamepadButton = gamepad.Button8
GamepadButton9 GamepadButton = gamepad.Button9
GamepadButton10 GamepadButton = gamepad.Button10
GamepadButton11 GamepadButton = gamepad.Button11
GamepadButton12 GamepadButton = gamepad.Button12
GamepadButton13 GamepadButton = gamepad.Button13
GamepadButton14 GamepadButton = gamepad.Button14
GamepadButton15 GamepadButton = gamepad.Button15
GamepadButton16 GamepadButton = gamepad.Button16
GamepadButton17 GamepadButton = gamepad.Button17
GamepadButton18 GamepadButton = gamepad.Button18
GamepadButton19 GamepadButton = gamepad.Button19
GamepadButton20 GamepadButton = gamepad.Button20
GamepadButton21 GamepadButton = gamepad.Button21
GamepadButton22 GamepadButton = gamepad.Button22
GamepadButton23 GamepadButton = gamepad.Button23
GamepadButton24 GamepadButton = gamepad.Button24
GamepadButton25 GamepadButton = gamepad.Button25
GamepadButton26 GamepadButton = gamepad.Button26
GamepadButton27 GamepadButton = gamepad.Button27
GamepadButton28 GamepadButton = gamepad.Button28
GamepadButton29 GamepadButton = gamepad.Button29
GamepadButton30 GamepadButton = gamepad.Button30
GamepadButton31 GamepadButton = gamepad.Button31
GamepadButtonMax GamepadButton = GamepadButton31
)
// GamepadAxisType represents a gamepad axis.
// This is the same as int for backward compatibility in v2.
type GamepadAxisType = int
// StandardGamepadButton represents a gamepad button in the standard layout.
//
// The layout and the button values are based on the web standard.
// See https://www.w3.org/TR/gamepad/#remapping.
type StandardGamepadButton = gamepaddb.StandardButton
// StandardGamepadButtons
const (
StandardGamepadButtonRightBottom StandardGamepadButton = gamepaddb.StandardButtonRightBottom
StandardGamepadButtonRightRight StandardGamepadButton = gamepaddb.StandardButtonRightRight
StandardGamepadButtonRightLeft StandardGamepadButton = gamepaddb.StandardButtonRightLeft
StandardGamepadButtonRightTop StandardGamepadButton = gamepaddb.StandardButtonRightTop
StandardGamepadButtonFrontTopLeft StandardGamepadButton = gamepaddb.StandardButtonFrontTopLeft
StandardGamepadButtonFrontTopRight StandardGamepadButton = gamepaddb.StandardButtonFrontTopRight
StandardGamepadButtonFrontBottomLeft StandardGamepadButton = gamepaddb.StandardButtonFrontBottomLeft
StandardGamepadButtonFrontBottomRight StandardGamepadButton = gamepaddb.StandardButtonFrontBottomRight
StandardGamepadButtonCenterLeft StandardGamepadButton = gamepaddb.StandardButtonCenterLeft
StandardGamepadButtonCenterRight StandardGamepadButton = gamepaddb.StandardButtonCenterRight
StandardGamepadButtonLeftStick StandardGamepadButton = gamepaddb.StandardButtonLeftStick
StandardGamepadButtonRightStick StandardGamepadButton = gamepaddb.StandardButtonRightStick
StandardGamepadButtonLeftTop StandardGamepadButton = gamepaddb.StandardButtonLeftTop
StandardGamepadButtonLeftBottom StandardGamepadButton = gamepaddb.StandardButtonLeftBottom
StandardGamepadButtonLeftLeft StandardGamepadButton = gamepaddb.StandardButtonLeftLeft
StandardGamepadButtonLeftRight StandardGamepadButton = gamepaddb.StandardButtonLeftRight
StandardGamepadButtonCenterCenter StandardGamepadButton = gamepaddb.StandardButtonCenterCenter
StandardGamepadButtonMax StandardGamepadButton = StandardGamepadButtonCenterCenter
)
// StandardGamepadAxis represents a gamepad axis in the standard layout.
//
// The layout and the button values are based on the web standard.
// See https://www.w3.org/TR/gamepad/#remapping.
type StandardGamepadAxis = gamepaddb.StandardAxis
// StandardGamepadAxes
const (
StandardGamepadAxisLeftStickHorizontal StandardGamepadAxis = gamepaddb.StandardAxisLeftStickHorizontal
StandardGamepadAxisLeftStickVertical StandardGamepadAxis = gamepaddb.StandardAxisLeftStickVertical
StandardGamepadAxisRightStickHorizontal StandardGamepadAxis = gamepaddb.StandardAxisRightStickHorizontal
StandardGamepadAxisRightStickVertical StandardGamepadAxis = gamepaddb.StandardAxisRightStickVertical
StandardGamepadAxisMax StandardGamepadAxis = StandardGamepadAxisRightStickVertical
)
+22
View File
@@ -0,0 +1,22 @@
// Copyright 2015 Hajime Hoshi
//
// 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.
package ebiten
//go:generate go run genkeys.go
//go:generate gofmt -s -w .
// TODO: Add this after event package is added again (#926).
//
// go:generate go run genevents.go
+220
View File
@@ -0,0 +1,220 @@
// Copyright 2014 Hajime Hoshi
//
// 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.
package ebiten
import (
"fmt"
"math"
)
// GeoMDim is a dimension of a GeoM.
const GeoMDim = 3
// A GeoM represents a matrix to transform geometry when rendering an image.
//
// The initial value is identity.
type GeoM struct {
a_1 float64 // The actual 'a' value minus 1
b float64
c float64
d_1 float64 // The actual 'd' value minus 1
tx float64
ty float64
}
// String returns a string representation of GeoM.
func (g *GeoM) String() string {
return fmt.Sprintf("[[%f, %f, %f], [%f, %f, %f]]", g.a_1+1, g.b, g.tx, g.c, g.d_1+1, g.ty)
}
// Reset resets the GeoM as identity.
func (g *GeoM) Reset() {
g.a_1 = 0
g.b = 0
g.c = 0
g.d_1 = 0
g.tx = 0
g.ty = 0
}
// Apply pre-multiplies a vector (x, y, 1) by the matrix.
// In other words, Apply calculates GeoM * (x, y, 1)^T.
// The return value is x and y values of the result vector.
func (g *GeoM) Apply(x, y float64) (float64, float64) {
return (g.a_1+1)*x + g.b*y + g.tx, g.c*x + (g.d_1+1)*y + g.ty
}
func (g *GeoM) elements32() (a, b, c, d, tx, ty float32) {
return float32(g.a_1) + 1, float32(g.b), float32(g.c), float32(g.d_1) + 1, float32(g.tx), float32(g.ty)
}
// Element returns a value of a matrix at (i, j).
func (g *GeoM) Element(i, j int) float64 {
switch {
case i == 0 && j == 0:
return g.a_1 + 1
case i == 0 && j == 1:
return g.b
case i == 0 && j == 2:
return g.tx
case i == 1 && j == 0:
return g.c
case i == 1 && j == 1:
return g.d_1 + 1
case i == 1 && j == 2:
return g.ty
default:
panic("ebiten: i or j is out of index")
}
}
// Concat multiplies a geometry matrix with the other geometry matrix.
// This is same as multiplying the matrix other and the matrix g in this order.
func (g *GeoM) Concat(other GeoM) {
a := (other.a_1+1)*(g.a_1+1) + other.b*g.c
b := (other.a_1+1)*g.b + other.b*(g.d_1+1)
tx := (other.a_1+1)*g.tx + other.b*g.ty + other.tx
c := other.c*(g.a_1+1) + (other.d_1+1)*g.c
d := other.c*g.b + (other.d_1+1)*(g.d_1+1)
ty := other.c*g.tx + (other.d_1+1)*g.ty + other.ty
g.a_1 = a - 1
g.b = b
g.c = c
g.d_1 = d - 1
g.tx = tx
g.ty = ty
}
// Scale scales the matrix by (x, y).
func (g *GeoM) Scale(x, y float64) {
a := (g.a_1 + 1) * x
b := g.b * x
tx := g.tx * x
c := g.c * y
d := (g.d_1 + 1) * y
ty := g.ty * y
g.a_1 = a - 1
g.b = b
g.c = c
g.d_1 = d - 1
g.tx = tx
g.ty = ty
}
// Translate translates the matrix by (tx, ty).
func (g *GeoM) Translate(tx, ty float64) {
g.tx += tx
g.ty += ty
}
// Rotate rotates the matrix by theta.
// The unit is radian.
func (g *GeoM) Rotate(theta float64) {
if theta == 0 {
return
}
sin, cos := math.Sincos(theta)
a := cos*(g.a_1+1) - sin*g.c
b := cos*g.b - sin*(g.d_1+1)
tx := cos*g.tx - sin*g.ty
c := sin*(g.a_1+1) + cos*g.c
d := sin*g.b + cos*(g.d_1+1)
ty := sin*g.tx + cos*g.ty
g.a_1 = a - 1
g.b = b
g.c = c
g.d_1 = d - 1
g.tx = tx
g.ty = ty
}
// Skew skews the matrix by (skewX, skewY). The unit is radian.
func (g *GeoM) Skew(skewX, skewY float64) {
sx := math.Tan(skewX)
sy := math.Tan(skewY)
a := (g.a_1 + 1) + g.c*sx
b := g.b + (g.d_1+1)*sx
c := (g.a_1+1)*sy + g.c
d := g.b*sy + (g.d_1 + 1)
tx := g.tx + g.ty*sx
ty := g.ty + g.tx*sy
g.a_1 = a - 1
g.b = b
g.c = c
g.d_1 = d - 1
g.tx = tx
g.ty = ty
}
func (g *GeoM) det2x2() float64 {
return (g.a_1+1)*(g.d_1+1) - g.b*g.c
}
// IsInvertible returns a boolean value indicating
// whether the matrix g is invertible or not.
func (g *GeoM) IsInvertible() bool {
return g.det2x2() != 0
}
// Invert inverts the matrix.
// If g is not invertible, Invert panics.
func (g *GeoM) Invert() {
det := g.det2x2()
if det == 0 {
panic("ebiten: g is not invertible")
}
a := (g.d_1 + 1) / det
b := -g.b / det
c := -g.c / det
d := (g.a_1 + 1) / det
tx := (-(g.d_1+1)*g.tx + g.b*g.ty) / det
ty := (g.c*g.tx + -(g.a_1+1)*g.ty) / det
g.a_1 = a - 1
g.b = b
g.c = c
g.d_1 = d - 1
g.tx = tx
g.ty = ty
}
// SetElement sets an element at (i, j).
func (g *GeoM) SetElement(i, j int, element float64) {
e := element
switch {
case i == 0 && j == 0:
g.a_1 = e - 1
case i == 0 && j == 1:
g.b = e
case i == 0 && j == 2:
g.tx = e
case i == 1 && j == 0:
g.c = e
case i == 1 && j == 1:
g.d_1 = e - 1
case i == 1 && j == 2:
g.ty = e
default:
panic("ebiten: i or j is out of index")
}
}
+74
View File
@@ -0,0 +1,74 @@
// Copyright 2014 Hajime Hoshi
//
// 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.
package ebiten
import (
"github.com/hajimehoshi/ebiten/v2/internal/builtinshader"
"github.com/hajimehoshi/ebiten/v2/internal/ui"
)
// Filter represents the type of texture filter to be used when an image is magnified or minified.
type Filter int
const (
// FilterNearest represents nearest (crisp-edged) filter
FilterNearest Filter = Filter(builtinshader.FilterNearest)
// FilterLinear represents linear filter
FilterLinear Filter = Filter(builtinshader.FilterLinear)
)
// GraphicsLibrary represents graphics libraries supported by the engine.
type GraphicsLibrary int
const (
// GraphicsLibraryAuto represents the automatic choose of graphics library by Ebitengine.
GraphicsLibraryAuto GraphicsLibrary = GraphicsLibrary(ui.GraphicsLibraryAuto)
// GraphicsLibraryUnknown represents the state at which graphics library cannot be determined,
// e.g. hasn't loaded yet or failed to initialize.
GraphicsLibraryUnknown GraphicsLibrary = GraphicsLibrary(ui.GraphicsLibraryUnknown)
// GraphicsLibraryOpenGL represents the graphics library OpenGL.
GraphicsLibraryOpenGL GraphicsLibrary = GraphicsLibrary(ui.GraphicsLibraryOpenGL)
// GraphicsLibraryDirectX represents the graphics library Microsoft DirectX.
GraphicsLibraryDirectX GraphicsLibrary = GraphicsLibrary(ui.GraphicsLibraryDirectX)
// GraphicsLibraryMetal represents the graphics library Apple's Metal.
GraphicsLibraryMetal GraphicsLibrary = GraphicsLibrary(ui.GraphicsLibraryMetal)
// GraphicsLibraryMetal represents the graphics library PlayStation 5.
GraphicsLibraryPlayStation5 GraphicsLibrary = GraphicsLibrary(ui.GraphicsLibraryPlayStation5)
)
// String returns a string representing the graphics library.
func (g GraphicsLibrary) String() string {
return ui.GraphicsLibrary(g).String()
}
// Ensures GraphicsLibraryAuto is zero (the default value for RunOptions).
var _ [GraphicsLibraryAuto]int = [0]int{}
// DebugInfo is a struct to store debug info about the graphics.
type DebugInfo struct {
// GraphicsLibrary represents the graphics library currently in use.
GraphicsLibrary GraphicsLibrary
}
// ReadDebugInfo writes debug info (e.g. current graphics library) into a provided struct.
func ReadDebugInfo(d *DebugInfo) {
d.GraphicsLibrary = GraphicsLibrary(ui.Get().GraphicsLibrary())
}
+1251
View File
File diff suppressed because it is too large Load Diff
+173
View File
@@ -0,0 +1,173 @@
// Copyright 2018 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.
package ebiten
import (
"fmt"
"os"
"path/filepath"
"runtime"
"time"
"github.com/hajimehoshi/ebiten/v2/internal/debug"
"github.com/hajimehoshi/ebiten/v2/internal/ui"
)
func datetimeForFilename() string {
const datetimeFormat = "20060102150405"
now := time.Now()
return now.Format(datetimeFormat)
}
func takeScreenshot(screen *Image, transparent bool) error {
name := "screenshot_" + datetimeForFilename() + ".png"
// Use the home directory for mobiles as a provisional implementation.
if runtime.GOOS == "android" || runtime.GOOS == "ios" {
home, err := os.UserHomeDir()
if err != nil {
return err
}
name = filepath.Join(home, name)
}
dumpedName, err := screen.image.DumpScreenshot(name, !transparent)
if err != nil {
return err
}
if _, err := fmt.Fprintf(os.Stderr, "Saved screenshot: %s\n", dumpedName); err != nil {
return err
}
return nil
}
func dumpInternalImages() error {
dumpedDir, err := ui.Get().DumpImages("internalimages_" + datetimeForFilename())
if err != nil {
return err
}
// Use the home directory for mobiles as a provisional implementation.
if runtime.GOOS == "android" || runtime.GOOS == "ios" {
home, err := os.UserHomeDir()
if err != nil {
return err
}
dumpedDir = filepath.Join(home, dumpedDir)
}
if _, err := fmt.Fprintf(os.Stderr, "Dumped the internal images at: %s\n", dumpedDir); err != nil {
return err
}
return nil
}
type imageDumper struct {
keyState map[Key]int
hasScreenshotKey bool
screenshotKey Key
toTakeScreenshot bool
hasDumpInternalImagesKey bool
dumpInternalImagesKey Key
toDumpInternalImages bool
err error
}
func envScreenshotKey() string {
if env := os.Getenv("EBITENGINE_SCREENSHOT_KEY"); env != "" {
return env
}
// For backward compatibility, read the EBITEN_ version.
return os.Getenv("EBITEN_SCREENSHOT_KEY")
}
func envInternalImagesKey() string {
if env := os.Getenv("EBITENGINE_INTERNAL_IMAGES_KEY"); env != "" {
return env
}
// For backward compatibility, read the EBITEN_ version.
return os.Getenv("EBITEN_INTERNAL_IMAGES_KEY")
}
func (i *imageDumper) update() error {
if i.err != nil {
return i.err
}
// If keyState is nil, all values are not initialized.
if i.keyState == nil {
i.keyState = map[Key]int{}
if keyname := envScreenshotKey(); keyname != "" {
if key, ok := keyNameToKeyCode(keyname); ok {
i.hasScreenshotKey = true
i.screenshotKey = key
}
}
if keyname := envInternalImagesKey(); keyname != "" {
if debug.IsDebug {
if key, ok := keyNameToKeyCode(keyname); ok {
i.hasDumpInternalImagesKey = true
i.dumpInternalImagesKey = key
}
} else {
fmt.Fprintf(os.Stderr, "EBITENGINE_INTERNAL_IMAGES_KEY is disabled. Specify a build tag 'ebitenginedebug' to enable it.\n")
}
}
}
keys := map[Key]struct{}{}
if i.hasScreenshotKey {
keys[i.screenshotKey] = struct{}{}
}
if i.hasDumpInternalImagesKey {
keys[i.dumpInternalImagesKey] = struct{}{}
}
for key := range keys {
if IsKeyPressed(key) {
i.keyState[key]++
if i.keyState[key] == 1 {
if i.hasScreenshotKey && key == i.screenshotKey {
i.toTakeScreenshot = true
}
if i.hasDumpInternalImagesKey && key == i.dumpInternalImagesKey {
i.toDumpInternalImages = true
}
}
} else {
i.keyState[key] = 0
}
}
return nil
}
func (i *imageDumper) dump(screen *Image, transparent bool) error {
if i.toTakeScreenshot {
i.toTakeScreenshot = false
if err := takeScreenshot(screen, transparent); err != nil {
return err
}
}
if i.toDumpInternalImages {
i.toDumpInternalImages = false
if err := dumpInternalImages(); err != nil {
return err
}
}
return nil
}
+90
View File
@@ -0,0 +1,90 @@
// Copyright 2017 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.
package ebiten
import (
"image"
"image/color"
"image/draw"
)
// imageToBytes gets RGBA bytes from img.
//
// Basically imageToBytes just calls draw.Draw.
// If img is a paletted image, an optimized copying method is used.
//
// If img is *image.RGBA and its length is same as 4*width*height, imageToBytes returns its Pix.
func imageToBytes(img image.Image) []byte {
size := img.Bounds().Size()
w, h := size.X, size.Y
switch img := img.(type) {
case *image.Paletted:
bs := make([]byte, 4*w*h)
b := img.Bounds()
x0 := b.Min.X
y0 := b.Min.Y
x1 := b.Max.X
y1 := b.Max.Y
palette := make([]uint8, len(img.Palette)*4)
for i, c := range img.Palette {
rgba := color.RGBAModel.Convert(c).(color.RGBA)
palette[4*i] = rgba.R
palette[4*i+1] = rgba.G
palette[4*i+2] = rgba.B
palette[4*i+3] = rgba.A
}
// Even img is a subimage of another image, Pix starts with 0-th index.
idx0 := 0
idx1 := 0
d := img.Stride - (x1 - x0)
for j := 0; j < y1-y0; j++ {
for i := 0; i < x1-x0; i++ {
p := int(img.Pix[idx0])
bs[idx1] = palette[4*p]
bs[idx1+1] = palette[4*p+1]
bs[idx1+2] = palette[4*p+2]
bs[idx1+3] = palette[4*p+3]
idx0++
idx1 += 4
}
idx0 += d
}
return bs
case *image.RGBA:
if len(img.Pix) == 4*w*h {
return img.Pix
}
return imageToBytesSlow(img)
default:
return imageToBytesSlow(img)
}
}
func imageToBytesSlow(img image.Image) []byte {
size := img.Bounds().Size()
w, h := size.X, size.Y
bs := make([]byte, 4*w*h)
dstImg := &image.RGBA{
Pix: bs,
Stride: 4 * w,
Rect: image.Rect(0, 0, w, h),
}
draw.Draw(dstImg, image.Rect(0, 0, w, h), img, img.Bounds().Min, draw.Src)
return bs
}
+477
View File
@@ -0,0 +1,477 @@
// Copyright 2015 Hajime Hoshi
//
// 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.
package ebiten
import (
"io/fs"
"sync"
"github.com/hajimehoshi/ebiten/v2/internal/gamepad"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
"github.com/hajimehoshi/ebiten/v2/internal/ui"
)
// AppendInputChars appends "printable" runes, read from the keyboard at the time Update is called, to runes,
// and returns the extended buffer.
// Giving a slice that already has enough capacity works efficiently.
//
// AppendInputChars represents the environment's locale-dependent translation of keyboard
// input to Unicode characters. On the other hand, Key represents a physical key of US keyboard layout
//
// "Control" and modifier keys should be handled with IsKeyPressed.
//
// AppendInputChars is concurrent-safe.
//
// On Android (ebitenmobile), EbitenView must be focusable to enable to handle keyboard keys.
func AppendInputChars(runes []rune) []rune {
return theInputState.appendInputChars(runes)
}
// InputChars return "printable" runes read from the keyboard at the time Update is called.
//
// Deprecated: as of v2.2. Use AppendInputChars instead.
func InputChars() []rune {
return AppendInputChars(nil)
}
// IsKeyPressed returns a boolean indicating whether key is pressed.
//
// If you want to know whether the key started being pressed in the current tick,
// use inpututil.IsKeyJustPressed
//
// Note that a Key represents a physical key of US keyboard layout.
// For example, KeyQ represents Q key on US keyboards and ' (quote) key on Dvorak keyboards.
//
// IsKeyPressed is concurrent-safe.
//
// On Android (ebitenmobile), EbitenView must be focusable to enable to handle keyboard keys.
func IsKeyPressed(key Key) bool {
return theInputState.isKeyPressed(key)
}
// KeyName returns a key name for the current keyboard layout.
// For example, KeyName(KeyQ) returns 'q' for a QWERTY keyboard, and returns 'a' for an AZERTY keyboard.
//
// KeyName returns an empty string if 1) the key doesn't have a physical key name, 2) the platform doesn't support KeyName,
// or 3) the main loop doesn't start yet.
//
// KeyName is supported by desktops and browsers.
//
// KeyName is concurrent-safe.
func KeyName(key Key) string {
return ui.Get().KeyName(ui.Key(key))
}
// CursorPosition returns a position of a mouse cursor relative to the game screen (window). The cursor position is
// 'logical' position and this considers the scale of the screen.
//
// CursorPosition returns (0, 0) before the main loop on desktops and browsers.
//
// CursorPosition always returns (0, 0) on mobiles.
//
// CursorPosition is concurrent-safe.
func CursorPosition() (x, y int) {
cx, cy := theInputState.cursorPosition()
return int(cx), int(cy)
}
// Wheel returns x and y offsets of the mouse wheel or touchpad scroll.
// It returns 0 if the wheel isn't being rolled.
//
// Wheel is concurrent-safe.
func Wheel() (xoff, yoff float64) {
return theInputState.wheel()
}
// IsMouseButtonPressed returns a boolean indicating whether mouseButton is pressed.
//
// If you want to know whether the mouseButton started being pressed in the current tick,
// use inpututil.IsMouseButtonJustPressed
//
// IsMouseButtonPressed is concurrent-safe.
func IsMouseButtonPressed(mouseButton MouseButton) bool {
return theInputState.isMouseButtonPressed(mouseButton)
}
// GamepadID represents a gamepad identifier.
type GamepadID = gamepad.ID
// GamepadSDLID returns a string with the GUID generated in the same way as SDL.
// To detect devices, see also the community project of gamepad devices database: https://github.com/gabomdq/SDL_GameControllerDB
//
// GamepadSDLID always returns an empty string on browsers and mobiles.
//
// GamepadSDLID is concurrent-safe.
func GamepadSDLID(id GamepadID) string {
g := gamepad.Get(id)
if g == nil {
return ""
}
return g.SDLID()
}
// GamepadName returns a string with the name.
// This function may vary in how it returns descriptions for the same device across platforms.
// for example the following drivers/platforms see an Xbox One controller as the following:
//
// - Windows: "Xbox Controller"
// - Chrome: "Xbox 360 Controller (XInput STANDARD GAMEPAD)"
// - Firefox: "xinput"
//
// GamepadName is concurrent-safe.
func GamepadName(id GamepadID) string {
g := gamepad.Get(id)
if g == nil {
return ""
}
return g.Name()
}
// AppendGamepadIDs appends available gamepad IDs to gamepadIDs, and returns the extended buffer.
// Giving a slice that already has enough capacity works efficiently.
//
// AppendGamepadIDs is concurrent-safe.
func AppendGamepadIDs(gamepadIDs []GamepadID) []GamepadID {
return gamepad.AppendGamepadIDs(gamepadIDs)
}
// GamepadIDs returns a slice indicating available gamepad IDs.
//
// Deprecated: as of v2.2. Use AppendGamepadIDs instead.
func GamepadIDs() []GamepadID {
return AppendGamepadIDs(nil)
}
// GamepadAxisCount returns the number of axes of the gamepad (id).
//
// GamepadAxisCount is concurrent-safe.
func GamepadAxisCount(id GamepadID) int {
g := gamepad.Get(id)
if g == nil {
return 0
}
return g.AxisCount()
}
// GamepadAxisNum returns the number of axes of the gamepad (id).
//
// Deprecated: as of v2.4. Use GamepadAxisCount instead.
func GamepadAxisNum(id GamepadID) int {
return GamepadAxisCount(id)
}
// GamepadAxisValue returns a float value [-1.0 - 1.0] of the given gamepad (id)'s axis (axis).
//
// GamepadAxisValue is concurrent-safe.
func GamepadAxisValue(id GamepadID, axis GamepadAxisType) float64 {
g := gamepad.Get(id)
if g == nil {
return 0
}
return g.Axis(int(axis))
}
// GamepadAxis returns a float value [-1.0 - 1.0] of the given gamepad (id)'s axis (axis).
//
// Deprecated: as of v2.2. Use GamepadAxisValue instead.
func GamepadAxis(id GamepadID, axis GamepadAxisType) float64 {
return GamepadAxisValue(id, axis)
}
// GamepadButtonCount returns the number of the buttons of the given gamepad (id).
//
// GamepadButtonCount is concurrent-safe.
func GamepadButtonCount(id GamepadID) int {
g := gamepad.Get(id)
if g == nil {
return 0
}
// For backward compatibility, hats are treated as buttons in GLFW.
return g.ButtonCount() + g.HatCount()*4
}
// GamepadButtonNum returns the number of the buttons of the given gamepad (id).
//
// Deprecated: as of v2.4. Use GamepadButtonCount instead.
func GamepadButtonNum(id GamepadID) int {
return GamepadButtonCount(id)
}
// IsGamepadButtonPressed reports whether the given button of the gamepad (id) is pressed or not.
//
// If you want to know whether the given button of gamepad (id) started being pressed in the current tick,
// use inpututil.IsGamepadButtonJustPressed
//
// IsGamepadButtonPressed is concurrent-safe.
//
// The relationships between physical buttons and button IDs depend on environments.
// There can be differences even between Chrome and Firefox.
func IsGamepadButtonPressed(id GamepadID, button GamepadButton) bool {
g := gamepad.Get(id)
if g == nil {
return false
}
nbuttons := g.ButtonCount()
if int(button) < nbuttons {
return g.Button(int(button))
}
// For backward compatibility, hats are treated as buttons in GLFW.
if hat := (int(button) - nbuttons) / 4; hat < g.HatCount() {
dir := (int(button) - nbuttons) % 4
return g.Hat(hat)&(1<<dir) != 0
}
return false
}
// StandardGamepadAxisValue returns a float value [-1.0 - 1.0] of the given gamepad (id)'s standard axis (axis).
//
// StandardGamepadAxisValue returns 0 when the gamepad doesn't have a standard gamepad layout mapping.
//
// StandardGamepadAxisValue is concurrent safe.
func StandardGamepadAxisValue(id GamepadID, axis StandardGamepadAxis) float64 {
g := gamepad.Get(id)
if g == nil {
return 0
}
return g.StandardAxisValue(axis)
}
// StandardGamepadButtonValue returns a float value [0.0 - 1.0] of the given gamepad (id)'s standard button (button).
//
// StandardGamepadButtonValue returns 0 when the gamepad doesn't have a standard gamepad layout mapping.
//
// StandardGamepadButtonValue is concurrent safe.
func StandardGamepadButtonValue(id GamepadID, button StandardGamepadButton) float64 {
g := gamepad.Get(id)
if g == nil {
return 0
}
return g.StandardButtonValue(button)
}
// IsStandardGamepadButtonPressed reports whether the given gamepad (id)'s standard gamepad button (button) is pressed.
//
// IsStandardGamepadButtonPressed returns false when the gamepad doesn't have a standard gamepad layout mapping.
//
// IsStandardGamepadButtonPressed is concurrent safe.
func IsStandardGamepadButtonPressed(id GamepadID, button StandardGamepadButton) bool {
g := gamepad.Get(id)
if g == nil {
return false
}
return g.IsStandardButtonPressed(button)
}
// IsStandardGamepadLayoutAvailable reports whether the gamepad (id) has a standard gamepad layout mapping.
//
// IsStandardGamepadLayoutAvailable is concurrent-safe.
func IsStandardGamepadLayoutAvailable(id GamepadID) bool {
g := gamepad.Get(id)
if g == nil {
return false
}
return g.IsStandardLayoutAvailable()
}
// IsStandardGamepadAxisAvailable reports whether the standard gamepad axis is available on the gamepad (id).
//
// IsStandardGamepadAxisAvailable is concurrent-safe.
func IsStandardGamepadAxisAvailable(id GamepadID, axis StandardGamepadAxis) bool {
g := gamepad.Get(id)
if g == nil {
return false
}
return g.IsStandardAxisAvailable(axis)
}
// IsStandardGamepadButtonAvailable reports whether the standard gamepad button is available on the gamepad (id).
//
// IsStandardGamepadButtonAvailable is concurrent-safe.
func IsStandardGamepadButtonAvailable(id GamepadID, button StandardGamepadButton) bool {
g := gamepad.Get(id)
if g == nil {
return false
}
return g.IsStandardButtonAvailable(button)
}
// UpdateStandardGamepadLayoutMappings parses the specified string mappings in SDL_GameControllerDB format and
// updates the gamepad layout definitions.
//
// UpdateStandardGamepadLayoutMappings reports whether the mappings were applied,
// and returns an error in case any occurred while parsing the mappings.
//
// One or more input definitions can be provided separated by newlines.
// In particular, it is valid to pass an entire gamecontrollerdb.txt file.
// Note though that Ebitengine already includes its own copy of this file,
// so this call should only be necessary to add mappings for hardware not supported yet;
// ideally games using the StandardGamepad* functions should allow the user to provide mappings and
// then call this function if provided.
// When using this facility to support new hardware, please also send a pull request to
// https://github.com/gabomdq/SDL_GameControllerDB to make your mapping available to everyone else.
//
// A platform field in a line corresponds with a GOOS like the following:
//
// "Windows": GOOS=windows
// "Mac OS X": GOOS=darwin (not ios)
// "Linux": GOOS=linux (not android)
// "Android": GOOS=android
// "iOS": GOOS=ios
// "": Any GOOS
//
// On platforms where gamepad mappings are not managed by Ebitengine, this always returns false and nil.
//
// UpdateStandardGamepadLayoutMappings is concurrent-safe.
//
// UpdateStandardGamepadLayoutMappings mappings take effect immediately even for already connected gamepads.
//
// UpdateStandardGamepadLayoutMappings works atomically. If an error happens, nothing is updated.
func UpdateStandardGamepadLayoutMappings(mappings string) (bool, error) {
if err := gamepaddb.Update([]byte(mappings)); err != nil {
return false, err
}
return true, nil
}
// TouchID represents a touch's identifier.
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.
//
// If you want to know whether a touch started being pressed in the current tick,
// use inpututil.JustPressedTouchIDs
//
// AppendTouchIDs doesn't append anything when there are no touches.
// AppendTouchIDs always does nothing on desktops.
//
// AppendTouchIDs is concurrent-safe.
func AppendTouchIDs(touches []TouchID) []TouchID {
return theInputState.appendTouchIDs(touches)
}
// TouchIDs returns the current touch states.
//
// Deprecated: as of v2.2. Use AppendTouchIDs instead.
func TouchIDs() []TouchID {
return AppendTouchIDs(nil)
}
// TouchPosition returns the position for the touch of the specified ID.
//
// If the touch of the specified ID is not present, TouchPosition returns (0, 0).
//
// TouchPosition is concurrent-safe.
func TouchPosition(id TouchID) (int, int) {
return theInputState.touchPosition(id)
}
var theInputState inputState
type inputState struct {
state ui.InputState
m sync.Mutex
}
func (i *inputState) update(fn func(*ui.InputState)) {
i.m.Lock()
defer i.m.Unlock()
fn(&i.state)
}
func (i *inputState) appendInputChars(runes []rune) []rune {
i.m.Lock()
defer i.m.Unlock()
return append(runes, i.state.Runes...)
}
func (i *inputState) isKeyPressed(key Key) bool {
if !key.isValid() {
return false
}
i.m.Lock()
defer i.m.Unlock()
switch key {
case KeyAlt:
return i.state.KeyPressed[ui.KeyAltLeft] || i.state.KeyPressed[ui.KeyAltRight]
case KeyControl:
return i.state.KeyPressed[ui.KeyControlLeft] || i.state.KeyPressed[ui.KeyControlRight]
case KeyShift:
return i.state.KeyPressed[ui.KeyShiftLeft] || i.state.KeyPressed[ui.KeyShiftRight]
case KeyMeta:
return i.state.KeyPressed[ui.KeyMetaLeft] || i.state.KeyPressed[ui.KeyMetaRight]
default:
return i.state.KeyPressed[key]
}
}
func (i *inputState) cursorPosition() (float64, float64) {
i.m.Lock()
defer i.m.Unlock()
return i.state.CursorX, i.state.CursorY
}
func (i *inputState) wheel() (float64, float64) {
i.m.Lock()
defer i.m.Unlock()
return i.state.WheelX, i.state.WheelY
}
func (i *inputState) isMouseButtonPressed(mouseButton MouseButton) bool {
i.m.Lock()
defer i.m.Unlock()
return i.state.MouseButtonPressed[mouseButton]
}
func (i *inputState) appendTouchIDs(touches []TouchID) []TouchID {
i.m.Lock()
defer i.m.Unlock()
for _, t := range i.state.Touches {
touches = append(touches, t.ID)
}
return touches
}
func (i *inputState) touchPosition(id TouchID) (int, int) {
i.m.Lock()
defer i.m.Unlock()
for _, t := range i.state.Touches {
if id != t.ID {
continue
}
return t.X, t.Y
}
return 0, 0
}
func (i *inputState) windowBeingClosed() bool {
i.m.Lock()
defer i.m.Unlock()
return i.state.WindowBeingClosed
}
func (i *inputState) droppedFiles() fs.FS {
i.m.Lock()
defer i.m.Unlock()
return i.state.DroppedFiles
}
+747
View File
@@ -0,0 +1,747 @@
// Copyright 2018 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.
// Package inpututil provides utility functions of input like keyboard or mouse.
package inpututil
import (
"sort"
"sync"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/internal/hook"
)
type pos struct {
x int
y int
}
type inputState struct {
keyDurations []int
prevKeyDurations []int
mouseButtonDurations map[ebiten.MouseButton]int
prevMouseButtonDurations map[ebiten.MouseButton]int
gamepadIDs map[ebiten.GamepadID]struct{}
prevGamepadIDs map[ebiten.GamepadID]struct{}
gamepadButtonDurations map[ebiten.GamepadID][]int
prevGamepadButtonDurations map[ebiten.GamepadID][]int
standardGamepadButtonDurations map[ebiten.GamepadID][]int
prevStandardGamepadButtonDurations map[ebiten.GamepadID][]int
touchIDs map[ebiten.TouchID]struct{}
touchDurations map[ebiten.TouchID]int
touchPositions map[ebiten.TouchID]pos
prevTouchDurations map[ebiten.TouchID]int
prevTouchPositions map[ebiten.TouchID]pos
gamepadIDsBuf []ebiten.GamepadID
touchIDsBuf []ebiten.TouchID
m sync.RWMutex
}
var theInputState = &inputState{
keyDurations: make([]int, ebiten.KeyMax+1),
prevKeyDurations: make([]int, ebiten.KeyMax+1),
mouseButtonDurations: map[ebiten.MouseButton]int{},
prevMouseButtonDurations: map[ebiten.MouseButton]int{},
gamepadIDs: map[ebiten.GamepadID]struct{}{},
prevGamepadIDs: map[ebiten.GamepadID]struct{}{},
gamepadButtonDurations: map[ebiten.GamepadID][]int{},
prevGamepadButtonDurations: map[ebiten.GamepadID][]int{},
standardGamepadButtonDurations: map[ebiten.GamepadID][]int{},
prevStandardGamepadButtonDurations: map[ebiten.GamepadID][]int{},
touchIDs: map[ebiten.TouchID]struct{}{},
touchDurations: map[ebiten.TouchID]int{},
touchPositions: map[ebiten.TouchID]pos{},
prevTouchDurations: map[ebiten.TouchID]int{},
prevTouchPositions: map[ebiten.TouchID]pos{},
}
func init() {
hook.AppendHookOnBeforeUpdate(func() error {
theInputState.update()
return nil
})
}
func (i *inputState) update() {
i.m.Lock()
defer i.m.Unlock()
// Keyboard
copy(i.prevKeyDurations, i.keyDurations)
for k := ebiten.Key(0); k <= ebiten.KeyMax; k++ {
if ebiten.IsKeyPressed(k) {
i.keyDurations[k]++
} else {
i.keyDurations[k] = 0
}
}
// Mouse
for b := ebiten.MouseButton(0); b <= ebiten.MouseButtonMax; b++ {
i.prevMouseButtonDurations[b] = i.mouseButtonDurations[b]
if ebiten.IsMouseButtonPressed(b) {
i.mouseButtonDurations[b]++
} else {
i.mouseButtonDurations[b] = 0
}
}
// Gamepads
// Copy the gamepad IDs.
for id := range i.prevGamepadIDs {
delete(i.prevGamepadIDs, id)
}
for id := range i.gamepadIDs {
i.prevGamepadIDs[id] = struct{}{}
}
// Copy the gamepad button durations.
for id := range i.prevGamepadButtonDurations {
delete(i.prevGamepadButtonDurations, id)
}
for id, ds := range i.gamepadButtonDurations {
i.prevGamepadButtonDurations[id] = append([]int{}, ds...)
}
for id := range i.prevStandardGamepadButtonDurations {
delete(i.prevStandardGamepadButtonDurations, id)
}
for id, ds := range i.standardGamepadButtonDurations {
i.prevStandardGamepadButtonDurations[id] = append([]int{}, ds...)
}
for id := range i.gamepadIDs {
delete(i.gamepadIDs, id)
}
i.gamepadIDsBuf = ebiten.AppendGamepadIDs(i.gamepadIDsBuf[:0])
for _, id := range i.gamepadIDsBuf {
i.gamepadIDs[id] = struct{}{}
if _, ok := i.gamepadButtonDurations[id]; !ok {
i.gamepadButtonDurations[id] = make([]int, ebiten.GamepadButtonMax+1)
}
for b := ebiten.GamepadButton(0); b <= ebiten.GamepadButtonMax; b++ {
if ebiten.IsGamepadButtonPressed(id, b) {
i.gamepadButtonDurations[id][b]++
} else {
i.gamepadButtonDurations[id][b] = 0
}
}
if _, ok := i.standardGamepadButtonDurations[id]; !ok {
i.standardGamepadButtonDurations[id] = make([]int, ebiten.StandardGamepadButtonMax+1)
}
for b := ebiten.StandardGamepadButton(0); b <= ebiten.StandardGamepadButtonMax; b++ {
if ebiten.IsStandardGamepadButtonPressed(id, b) {
i.standardGamepadButtonDurations[id][b]++
} else {
i.standardGamepadButtonDurations[id][b] = 0
}
}
}
for id := range i.gamepadButtonDurations {
if _, ok := i.gamepadIDs[id]; !ok {
delete(i.gamepadButtonDurations, id)
}
}
for id := range i.standardGamepadButtonDurations {
if _, ok := i.gamepadIDs[id]; !ok {
delete(i.standardGamepadButtonDurations, id)
}
}
// Touches
// Copy the touch durations and positions.
for id := range i.prevTouchDurations {
delete(i.prevTouchDurations, id)
}
for id := range i.touchDurations {
i.prevTouchDurations[id] = i.touchDurations[id]
}
for id := range i.prevTouchPositions {
delete(i.prevTouchPositions, id)
}
for id := range i.touchPositions {
i.prevTouchPositions[id] = i.touchPositions[id]
}
for id := range i.touchIDs {
delete(i.touchIDs, id)
}
i.touchIDsBuf = ebiten.AppendTouchIDs(i.touchIDsBuf[:0])
for _, id := range i.touchIDsBuf {
i.touchIDs[id] = struct{}{}
i.touchDurations[id]++
x, y := ebiten.TouchPosition(id)
i.touchPositions[id] = pos{x: x, y: y}
}
for id := range i.touchDurations {
if _, ok := i.touchIDs[id]; !ok {
delete(i.touchDurations, id)
delete(i.touchPositions, id)
}
}
}
// AppendPressedKeys append currently pressed keyboard keys to keys and returns the extended buffer.
// Giving a slice that already has enough capacity works efficiently.
//
// AppendPressedKeys must be called in a game's Update, not Draw.
//
// AppendPressedKeys is concurrent safe.
func AppendPressedKeys(keys []ebiten.Key) []ebiten.Key {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
for i, d := range theInputState.keyDurations {
if d == 0 {
continue
}
keys = append(keys, ebiten.Key(i))
}
return keys
}
// PressedKeys returns a set of currently pressed keyboard keys.
//
// PressedKeys must be called in a game's Update, not Draw.
//
// Deprecated: as of v2.2. Use AppendPressedKeys instead.
func PressedKeys() []ebiten.Key {
return AppendPressedKeys(nil)
}
// AppendJustPressedKeys append just pressed keyboard keys to keys and returns the extended buffer.
// Giving a slice that already has enough capacity works efficiently.
//
// AppendJustPressedKeys must be called in a game's Update, not Draw.
//
// AppendJustPressedKeys is concurrent safe.
func AppendJustPressedKeys(keys []ebiten.Key) []ebiten.Key {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
for i, d := range theInputState.keyDurations {
if d != 1 {
continue
}
keys = append(keys, ebiten.Key(i))
}
return keys
}
// AppendJustReleasedKeys append just released keyboard keys to keys and returns the extended buffer.
// Giving a slice that already has enough capacity works efficiently.
//
// AppendJustReleasedKeys must be called in a game's Update, not Draw.
//
// AppendJustReleasedKeys is concurrent safe.
func AppendJustReleasedKeys(keys []ebiten.Key) []ebiten.Key {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
for k := ebiten.Key(0); k <= ebiten.KeyMax; k++ {
if theInputState.keyDurations[k] != 0 {
continue
}
if theInputState.prevKeyDurations[k] == 0 {
continue
}
keys = append(keys, k)
}
return keys
}
// IsKeyJustPressed returns a boolean value indicating
// whether the given key is pressed just in the current tick.
//
// IsKeyJustPressed must be called in a game's Update, not Draw.
//
// IsKeyJustPressed is concurrent safe.
func IsKeyJustPressed(key ebiten.Key) bool {
return KeyPressDuration(key) == 1
}
// IsKeyJustReleased returns a boolean value indicating
// whether the given key is released just in the current tick.
//
// IsKeyJustReleased must be called in a game's Update, not Draw.
//
// IsKeyJustReleased is concurrent safe.
func IsKeyJustReleased(key ebiten.Key) bool {
theInputState.m.RLock()
r := theInputState.keyDurations[key] == 0 && theInputState.prevKeyDurations[key] > 0
theInputState.m.RUnlock()
return r
}
// KeyPressDuration returns how long the key is pressed in ticks (Update).
//
// KeyPressDuration must be called in a game's Update, not Draw.
//
// KeyPressDuration is concurrent safe.
func KeyPressDuration(key ebiten.Key) int {
theInputState.m.RLock()
s := theInputState.keyDurations[key]
theInputState.m.RUnlock()
return s
}
// IsMouseButtonJustPressed returns a boolean value indicating
// whether the given mouse button is pressed just in the current tick.
//
// IsMouseButtonJustPressed must be called in a game's Update, not Draw.
//
// IsMouseButtonJustPressed is concurrent safe.
func IsMouseButtonJustPressed(button ebiten.MouseButton) bool {
return MouseButtonPressDuration(button) == 1
}
// IsMouseButtonJustReleased returns a boolean value indicating
// whether the given mouse button is released just in the current tick.
//
// IsMouseButtonJustReleased must be called in a game's Update, not Draw.
//
// IsMouseButtonJustReleased is concurrent safe.
func IsMouseButtonJustReleased(button ebiten.MouseButton) bool {
theInputState.m.RLock()
r := theInputState.mouseButtonDurations[button] == 0 &&
theInputState.prevMouseButtonDurations[button] > 0
theInputState.m.RUnlock()
return r
}
// MouseButtonPressDuration returns how long the mouse button is pressed in ticks (Update).
//
// MouseButtonPressDuration must be called in a game's Update, not Draw.
//
// MouseButtonPressDuration is concurrent safe.
func MouseButtonPressDuration(button ebiten.MouseButton) int {
theInputState.m.RLock()
s := theInputState.mouseButtonDurations[button]
theInputState.m.RUnlock()
return s
}
// AppendJustConnectedGamepadIDs appends gamepad IDs that are connected just in the current tick to gamepadIDs,
// and returns the extended buffer.
// Giving a slice that already has enough capacity works efficiently.
//
// AppendJustConnectedGamepadIDs must be called in a game's Update, not Draw.
//
// AppendJustConnectedGamepadIDs is concurrent safe.
func AppendJustConnectedGamepadIDs(gamepadIDs []ebiten.GamepadID) []ebiten.GamepadID {
origLen := len(gamepadIDs)
theInputState.m.RLock()
for id := range theInputState.gamepadIDs {
if _, ok := theInputState.prevGamepadIDs[id]; !ok {
gamepadIDs = append(gamepadIDs, id)
}
}
theInputState.m.RUnlock()
s := gamepadIDs[origLen:]
sort.Slice(s, func(a, b int) bool {
return s[a] < s[b]
})
return gamepadIDs
}
// JustConnectedGamepadIDs returns gamepad IDs that are connected just in the current tick.
//
// JustConnectedGamepadIDs must be called in a game's Update, not Draw.
//
// Deprecated: as of v2.2. Use AppendJustConnectedGamepadIDs instead.
func JustConnectedGamepadIDs() []ebiten.GamepadID {
return AppendJustConnectedGamepadIDs(nil)
}
// IsGamepadJustDisconnected returns a boolean value indicating
// whether the gamepad of the given id is released just in the current tick.
//
// IsGamepadJustDisconnected must be called in a game's Update, not Draw.
//
// IsGamepadJustDisconnected is concurrent safe.
func IsGamepadJustDisconnected(id ebiten.GamepadID) bool {
theInputState.m.RLock()
_, prev := theInputState.prevGamepadIDs[id]
_, current := theInputState.gamepadIDs[id]
theInputState.m.RUnlock()
return prev && !current
}
// AppendPressedGamepadButtons append currently pressed gamepad buttons to buttons and returns the extended buffer.
// Giving a slice that already has enough capacity works efficiently.
//
// AppendPressedGamepadButtons must be called in a game's Update, not Draw.
//
// AppendPressedGamepadButtons is concurrent safe.
func AppendPressedGamepadButtons(id ebiten.GamepadID, buttons []ebiten.GamepadButton) []ebiten.GamepadButton {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
if _, ok := theInputState.gamepadButtonDurations[id]; !ok {
return buttons
}
for b, d := range theInputState.gamepadButtonDurations[id] {
if d == 0 {
continue
}
buttons = append(buttons, ebiten.GamepadButton(b))
}
return buttons
}
// AppendJustPressedGamepadButtons append just pressed gamepad buttons to buttons and returns the extended buffer.
// Giving a slice that already has enough capacity works efficiently.
//
// AppendJustPressedGamepadButtons must be called in a game's Update, not Draw.
//
// AppendJustPressedGamepadButtons is concurrent safe.
func AppendJustPressedGamepadButtons(id ebiten.GamepadID, buttons []ebiten.GamepadButton) []ebiten.GamepadButton {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
if _, ok := theInputState.gamepadButtonDurations[id]; !ok {
return buttons
}
for b, d := range theInputState.gamepadButtonDurations[id] {
if d != 1 {
continue
}
buttons = append(buttons, ebiten.GamepadButton(b))
}
return buttons
}
// AppendJustReleasedGamepadButtons append just released gamepad buttons to buttons and returns the extended buffer.
// Giving a slice that already has enough capacity works efficiently.
//
// AppendJustReleasedGamepadButtons must be called in a game's Update, not Draw.
//
// AppendJustReleasedGamepadButtons is concurrent safe.
func AppendJustReleasedGamepadButtons(id ebiten.GamepadID, buttons []ebiten.GamepadButton) []ebiten.GamepadButton {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
if _, ok := theInputState.gamepadButtonDurations[id]; !ok {
return buttons
}
if _, ok := theInputState.prevGamepadButtonDurations[id]; !ok {
return buttons
}
for b := ebiten.GamepadButton(0); b <= ebiten.GamepadButtonMax; b++ {
if theInputState.gamepadButtonDurations[id][b] == 0 {
continue
}
if theInputState.prevGamepadButtonDurations[id][b] > 0 {
continue
}
buttons = append(buttons, b)
}
return buttons
}
// IsGamepadButtonJustPressed returns a boolean value indicating
// whether the given gamepad button of the gamepad id is pressed just in the current tick.
//
// IsGamepadButtonJustPressed must be called in a game's Update, not Draw.
//
// IsGamepadButtonJustPressed is concurrent safe.
func IsGamepadButtonJustPressed(id ebiten.GamepadID, button ebiten.GamepadButton) bool {
return GamepadButtonPressDuration(id, button) == 1
}
// IsGamepadButtonJustReleased returns a boolean value indicating
// whether the given gamepad button of the gamepad id is released just in the current tick.
//
// IsGamepadButtonJustReleased must be called in a game's Update, not Draw.
//
// IsGamepadButtonJustReleased is concurrent safe.
func IsGamepadButtonJustReleased(id ebiten.GamepadID, button ebiten.GamepadButton) bool {
theInputState.m.RLock()
prev := 0
if _, ok := theInputState.prevGamepadButtonDurations[id]; ok {
prev = theInputState.prevGamepadButtonDurations[id][button]
}
current := 0
if _, ok := theInputState.gamepadButtonDurations[id]; ok {
current = theInputState.gamepadButtonDurations[id][button]
}
theInputState.m.RUnlock()
return current == 0 && prev > 0
}
// GamepadButtonPressDuration returns how long the gamepad button of the gamepad id is pressed in ticks (Update).
//
// GamepadButtonPressDuration must be called in a game's Update, not Draw.
//
// GamepadButtonPressDuration is concurrent safe.
func GamepadButtonPressDuration(id ebiten.GamepadID, button ebiten.GamepadButton) int {
theInputState.m.RLock()
s := 0
if _, ok := theInputState.gamepadButtonDurations[id]; ok {
s = theInputState.gamepadButtonDurations[id][button]
}
theInputState.m.RUnlock()
return s
}
// AppendPressedStandardGamepadButtons append currently pressed standard gamepad buttons to buttons and returns the extended buffer.
// Giving a slice that already has enough capacity works efficiently.
//
// AppendPressedStandardGamepadButtons must be called in a game's Update, not Draw.
//
// AppendPressedStandardGamepadButtons is concurrent safe.
func AppendPressedStandardGamepadButtons(id ebiten.GamepadID, buttons []ebiten.StandardGamepadButton) []ebiten.StandardGamepadButton {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
if _, ok := theInputState.standardGamepadButtonDurations[id]; !ok {
return buttons
}
for b, d := range theInputState.standardGamepadButtonDurations[id] {
if d == 0 {
continue
}
buttons = append(buttons, ebiten.StandardGamepadButton(b))
}
return buttons
}
// AppendJustPressedStandardGamepadButtons append just pressed standard gamepad buttons to buttons and returns the extended buffer.
// Giving a slice that already has enough capacity works efficiently.
//
// AppendJustPressedStandardGamepadButtons must be called in a game's Update, not Draw.
//
// AppendJustPressedStandardGamepadButtons is concurrent safe.
func AppendJustPressedStandardGamepadButtons(id ebiten.GamepadID, buttons []ebiten.StandardGamepadButton) []ebiten.StandardGamepadButton {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
if _, ok := theInputState.gamepadButtonDurations[id]; !ok {
return buttons
}
for b, d := range theInputState.standardGamepadButtonDurations[id] {
if d != 1 {
continue
}
buttons = append(buttons, ebiten.StandardGamepadButton(b))
}
return buttons
}
// AppendJustReleasedStandardGamepadButtons append just released standard gamepad buttons to buttons and returns the extended buffer.
// Giving a slice that already has enough capacity works efficiently.
//
// AppendJustReleasedStandardGamepadButtons must be called in a game's Update, not Draw.
//
// AppendJustReleasedStandardGamepadButtons is concurrent safe.
func AppendJustReleasedStandardGamepadButtons(id ebiten.GamepadID, buttons []ebiten.StandardGamepadButton) []ebiten.StandardGamepadButton {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
if _, ok := theInputState.gamepadButtonDurations[id]; !ok {
return buttons
}
if _, ok := theInputState.prevGamepadButtonDurations[id]; !ok {
return buttons
}
for b := ebiten.StandardGamepadButton(0); b <= ebiten.StandardGamepadButtonMax; b++ {
if theInputState.standardGamepadButtonDurations[id][b] == 0 {
continue
}
if theInputState.prevStandardGamepadButtonDurations[id][b] > 0 {
continue
}
buttons = append(buttons, b)
}
return buttons
}
// IsStandardGamepadButtonJustPressed returns a boolean value indicating
// whether the given standard gamepad button of the gamepad id is pressed just in the current tick.
//
// IsStandardGamepadButtonJustPressed must be called in a game's Update, not Draw.
//
// IsStandardGamepadButtonJustPressed is concurrent safe.
func IsStandardGamepadButtonJustPressed(id ebiten.GamepadID, button ebiten.StandardGamepadButton) bool {
return StandardGamepadButtonPressDuration(id, button) == 1
}
// IsStandardGamepadButtonJustReleased returns a boolean value indicating
// whether the given standard gamepad button of the gamepad id is released just in the current tick.
//
// IsStandardGamepadButtonJustReleased must be called in a game's Update, not Draw.
//
// IsStandardGamepadButtonJustReleased is concurrent safe.
func IsStandardGamepadButtonJustReleased(id ebiten.GamepadID, button ebiten.StandardGamepadButton) bool {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
var prev int
if _, ok := theInputState.prevStandardGamepadButtonDurations[id]; ok {
prev = theInputState.prevStandardGamepadButtonDurations[id][button]
}
var current int
if _, ok := theInputState.standardGamepadButtonDurations[id]; ok {
current = theInputState.standardGamepadButtonDurations[id][button]
}
return current == 0 && prev > 0
}
// StandardGamepadButtonPressDuration returns how long the standard gamepad button of the gamepad id is pressed in ticks (Update).
//
// StandardGamepadButtonPressDuration must be called in a game's Update, not Draw.
//
// StandardGamepadButtonPressDuration is concurrent safe.
func StandardGamepadButtonPressDuration(id ebiten.GamepadID, button ebiten.StandardGamepadButton) int {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
if _, ok := theInputState.standardGamepadButtonDurations[id]; ok {
return theInputState.standardGamepadButtonDurations[id][button]
}
return 0
}
// AppendJustPressedTouchIDs append touch IDs that are created just in the current tick to touchIDs,
// and returns the extended buffer.
// Giving a slice that already has enough capacity works efficiently.
//
// AppendJustPressedTouchIDs must be called in a game's Update, not Draw.
//
// AppendJustPressedTouchIDs is concurrent safe.
func AppendJustPressedTouchIDs(touchIDs []ebiten.TouchID) []ebiten.TouchID {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
origLen := len(touchIDs)
for id, s := range theInputState.touchDurations {
if s == 1 {
touchIDs = append(touchIDs, id)
}
}
s := touchIDs[origLen:]
sort.Slice(s, func(a, b int) bool {
return s[a] < s[b]
})
return touchIDs
}
// JustPressedTouchIDs returns touch IDs that are created just in the current tick.
//
// JustPressedTouchIDs must be called in a game's Update, not Draw.
//
// Deprecated: as of v2.2. Use AppendJustPressedTouchIDs instead.
func JustPressedTouchIDs() []ebiten.TouchID {
return AppendJustPressedTouchIDs(nil)
}
// AppendJustReleasedTouchIDs append touch IDs that are released just in the current tick to touchIDs,
// and returns the extended buffer.
// Giving a slice that already has enough capacity works efficiently.
//
// AppendJustReleasedTouchIDs must be called in a game's Update, not Draw.
//
// AppendJustReleasedTouchIDs is concurrent safe.
func AppendJustReleasedTouchIDs(touchIDs []ebiten.TouchID) []ebiten.TouchID {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
origLen := len(touchIDs)
for id := range theInputState.prevTouchDurations {
if theInputState.touchDurations[id] == 0 && theInputState.prevTouchDurations[id] > 0 {
touchIDs = append(touchIDs, id)
}
}
s := touchIDs[origLen:]
sort.Slice(s, func(a, b int) bool {
return s[a] < s[b]
})
return touchIDs
}
// IsTouchJustReleased returns a boolean value indicating
// whether the given touch is released just in the current tick.
//
// IsTouchJustReleased must be called in a game's Update, not Draw.
//
// IsTouchJustReleased is concurrent safe.
func IsTouchJustReleased(id ebiten.TouchID) bool {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
return theInputState.touchDurations[id] == 0 && theInputState.prevTouchDurations[id] > 0
}
// TouchPressDuration returns how long the touch remains in ticks (Update).
//
// TouchPressDuration must be called in a game's Update, not Draw.
//
// TouchPressDuration is concurrent safe.
func TouchPressDuration(id ebiten.TouchID) int {
theInputState.m.RLock()
s := theInputState.touchDurations[id]
theInputState.m.RUnlock()
return s
}
// TouchPositionInPreviousTick returns the position in the previous tick.
// If the touch is a just-released touch, TouchPositionInPreviousTick returns the last position of the touch.
//
// TouchPositionInPreviousTick must be called in a game's Update, not Draw.
//
// TouchJustReleasedPosition is concurrent safe.
func TouchPositionInPreviousTick(id ebiten.TouchID) (int, int) {
theInputState.m.RLock()
defer theInputState.m.RUnlock()
p := theInputState.prevTouchPositions[id]
return p.x, p.y
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright 2014 Hajime Hoshi
//
// 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.
package affine
func mulSquare(lhs, rhs *[16]float32, dim int) [16]float32 {
result := [16]float32{}
for i := 0; i < dim; i++ {
for j := 0; j < dim; j++ {
e := float32(0.0)
for k := 0; k < dim; k++ {
e += lhs[i*dim+k] * rhs[k*dim+j]
}
result[i*dim+j] = e
}
}
return result
}
+794
View File
@@ -0,0 +1,794 @@
// Copyright 2014 Hajime Hoshi
//
// 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.
package affine
import (
"fmt"
"image/color"
"math"
)
// ColorMDim is a dimension of a ColorM.
const ColorMDim = 5
var (
colorMIdentityBody = [...]float32{
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
}
colorMIdentityTranslate = [...]float32{
0, 0, 0, 0,
}
)
// ColorM represents a matrix to transform coloring when rendering an image.
//
// ColorM is applied to the source alpha color
// while an Image's pixels' format is alpha premultiplied.
// Before applying a matrix, a color is un-multiplied, and after applying the matrix,
// the color is multiplied again.
type ColorM interface {
String() string
IsIdentity() bool
ScaleOnly() bool
At(i, j int) float32
Elements(body []float32, translate []float32)
Apply(clr color.Color) color.Color
// IsInvertible returns a boolean value indicating
// whether the matrix c is invertible or not.
IsInvertible() bool
// Invert inverts the matrix.
// If c is not invertible, Invert panics.
Invert() ColorM
Equals(other ColorM) bool
// Concat multiplies a color matrix with the other color matrix.
// This is same as multiplying the matrix other and the matrix c in this order.
Concat(other ColorM) ColorM
// Scale scales the matrix by (r, g, b, a).
Scale(r, g, b, a float32) ColorM
// Translate translates the matrix by (r, g, b, a).
Translate(r, g, b, a float32) ColorM
scaleElements() (r, g, b, a float32)
}
func colorMString(c ColorM) string {
var b [16]float32
var t [4]float32
c.Elements(b[:], t[:])
return fmt.Sprintf("[[%f, %f, %f, %f, %f], [%f, %f, %f, %f, %f], [%f, %f, %f, %f, %f], [%f, %f, %f, %f, %f]]",
b[0], b[4], b[8], b[12], t[0],
b[1], b[5], b[9], b[13], t[1],
b[2], b[6], b[10], b[14], t[2],
b[3], b[7], b[11], b[15], t[3])
}
type ColorMIdentity struct{}
type colorMImplScale struct {
scale [4]float32
}
type colorMImplBodyTranslate struct {
body [16]float32
translate [4]float32
}
func (c ColorMIdentity) String() string {
return "Identity[]"
}
func (c colorMImplScale) String() string {
return fmt.Sprintf("Scale[%f, %f, %f, %f]", c.scale[0], c.scale[1], c.scale[2], c.scale[3])
}
func (c *colorMImplBodyTranslate) String() string {
return colorMString(c)
}
func clamp(x float32) float32 {
if x > 1 {
return 1
}
if x < 0 {
return 0
}
return x
}
func (c ColorMIdentity) IsIdentity() bool {
return true
}
func (c colorMImplScale) IsIdentity() bool {
return c.scale == [4]float32{1, 1, 1, 1}
}
func (c *colorMImplBodyTranslate) IsIdentity() bool {
return c.body == colorMIdentityBody && c.translate == colorMIdentityTranslate
}
func (c ColorMIdentity) ScaleOnly() bool {
return true
}
func (c colorMImplScale) ScaleOnly() bool {
return true
}
func (c *colorMImplBodyTranslate) ScaleOnly() bool {
if c.body[1] != 0 {
return false
}
if c.body[2] != 0 {
return false
}
if c.body[3] != 0 {
return false
}
if c.body[4] != 0 {
return false
}
if c.body[6] != 0 {
return false
}
if c.body[7] != 0 {
return false
}
if c.body[8] != 0 {
return false
}
if c.body[9] != 0 {
return false
}
if c.body[11] != 0 {
return false
}
if c.body[12] != 0 {
return false
}
if c.body[13] != 0 {
return false
}
if c.body[14] != 0 {
return false
}
for _, e := range c.translate {
if e != 0 {
return false
}
}
return true
}
func (c ColorMIdentity) scaleElements() (r, g, b, a float32) {
return 1, 1, 1, 1
}
func (c colorMImplScale) scaleElements() (r, g, b, a float32) {
return c.scale[0], c.scale[1], c.scale[2], c.scale[3]
}
func (c *colorMImplBodyTranslate) scaleElements() (r, g, b, a float32) {
return c.body[0], c.body[5], c.body[10], c.body[15]
}
func colorToFloat32s(clr color.Color) (float32, float32, float32, float32) {
r, g, b, a := clr.RGBA()
rf, gf, bf, af := float32(0.0), float32(0.0), float32(0.0), float32(0.0)
// Unmultiply alpha
if a > 0 {
rf = float32(r) / float32(a)
gf = float32(g) / float32(a)
bf = float32(b) / float32(a)
af = float32(a) / 0xffff
}
return rf, gf, bf, af
}
func (c ColorMIdentity) Apply(clr color.Color) color.Color {
rf, gf, bf, af := colorToFloat32s(clr)
return color.NRGBA64{
R: uint16(rf * 0xffff),
G: uint16(gf * 0xffff),
B: uint16(bf * 0xffff),
A: uint16(af * 0xffff),
}
}
func (c colorMImplScale) Apply(clr color.Color) color.Color {
rf, gf, bf, af := colorToFloat32s(clr)
rf *= c.scale[0]
gf *= c.scale[1]
bf *= c.scale[2]
af *= c.scale[3]
rf = clamp(rf)
gf = clamp(gf)
bf = clamp(bf)
af = clamp(af)
return color.NRGBA64{
R: uint16(rf * 0xffff),
G: uint16(gf * 0xffff),
B: uint16(bf * 0xffff),
A: uint16(af * 0xffff),
}
}
func (c *colorMImplBodyTranslate) Apply(clr color.Color) color.Color {
rf, gf, bf, af := colorToFloat32s(clr)
eb := &c.body
et := &c.translate
rf2 := eb[0]*rf + eb[4]*gf + eb[8]*bf + eb[12]*af + et[0]
gf2 := eb[1]*rf + eb[5]*gf + eb[9]*bf + eb[13]*af + et[1]
bf2 := eb[2]*rf + eb[6]*gf + eb[10]*bf + eb[14]*af + et[2]
af2 := eb[3]*rf + eb[7]*gf + eb[11]*bf + eb[15]*af + et[3]
rf2 = clamp(rf2)
gf2 = clamp(gf2)
bf2 = clamp(bf2)
af2 = clamp(af2)
return color.NRGBA64{
R: uint16(rf2 * 0xffff),
G: uint16(gf2 * 0xffff),
B: uint16(bf2 * 0xffff),
A: uint16(af2 * 0xffff),
}
}
func (c ColorMIdentity) Elements(body []float32, translate []float32) {
body[0] = 1
body[1] = 0
body[2] = 0
body[3] = 0
body[4] = 0
body[5] = 1
body[6] = 0
body[7] = 0
body[8] = 0
body[9] = 0
body[10] = 1
body[11] = 0
body[12] = 0
body[13] = 0
body[14] = 0
body[15] = 1
translate[0] = 0
translate[1] = 0
translate[2] = 0
translate[3] = 0
}
func (c colorMImplScale) Elements(body []float32, translate []float32) {
body[0] = c.scale[0]
body[1] = 0
body[2] = 0
body[3] = 0
body[4] = 0
body[5] = c.scale[1]
body[6] = 0
body[7] = 0
body[8] = 0
body[9] = 0
body[10] = c.scale[2]
body[11] = 0
body[12] = 0
body[13] = 0
body[14] = 0
body[15] = c.scale[3]
translate[0] = 0
translate[1] = 0
translate[2] = 0
translate[3] = 0
}
func (c *colorMImplBodyTranslate) Elements(body []float32, translate []float32) {
copy(body, c.body[:])
copy(translate, c.translate[:])
}
func (c *colorMImplBodyTranslate) det() float32 {
m00 := c.body[0]
m01 := c.body[1]
m02 := c.body[2]
m03 := c.body[3]
m10 := c.body[4]
m11 := c.body[5]
m12 := c.body[6]
m13 := c.body[7]
m20 := c.body[8]
m21 := c.body[9]
m22 := c.body[10]
m23 := c.body[11]
m30 := c.body[12]
m31 := c.body[13]
m32 := c.body[14]
m33 := c.body[15]
b234234 := m22*m33 - m23*m32
b134234 := m21*m33 - m23*m31
b124234 := m21*m32 - m22*m31
b034234 := m20*m33 - m23*m30
b024234 := m20*m32 - m22*m30
b014234 := m20*m31 - m21*m30
return m00*(m11*b234234-m12*b134234+m13*b124234) -
m01*(m10*b234234-m12*b034234+m13*b024234) +
m02*(m10*b134234-m11*b034234+m13*b014234) -
m03*(m10*b124234-m11*b024234+m12*b014234)
}
func (c ColorMIdentity) IsInvertible() bool {
return true
}
func (c colorMImplScale) IsInvertible() bool {
return c.scale[0] != 0 && c.scale[1] != 0 && c.scale[2] != 0 && c.scale[3] != 0
}
func (c *colorMImplBodyTranslate) IsInvertible() bool {
return c.det() != 0
}
func (c ColorMIdentity) Invert() ColorM {
return c
}
func (c colorMImplScale) Invert() ColorM {
return colorMImplScale{
scale: [4]float32{
1 / c.scale[0],
1 / c.scale[1],
1 / c.scale[2],
1 / c.scale[3],
},
}
}
func (c *colorMImplBodyTranslate) Invert() ColorM {
det := c.det()
if det == 0 {
panic("affine: c is not invertible")
}
m00 := c.body[0]
m01 := c.body[1]
m02 := c.body[2]
m03 := c.body[3]
m10 := c.body[4]
m11 := c.body[5]
m12 := c.body[6]
m13 := c.body[7]
m20 := c.body[8]
m21 := c.body[9]
m22 := c.body[10]
m23 := c.body[11]
m30 := c.body[12]
m31 := c.body[13]
m32 := c.body[14]
m33 := c.body[15]
m40 := c.translate[0]
m41 := c.translate[1]
m42 := c.translate[2]
m43 := c.translate[3]
a2334 := m32*m43 - m33*m42
a1334 := m31*m43 - m33*m41
a1234 := m31*m42 - m32*m41
a0334 := m30*m43 - m33*m40
a0234 := m30*m42 - m32*m40
a0134 := m30*m41 - m31*m40
a2324 := m22*m43 - m23*m42
a1324 := m21*m43 - m23*m41
a1224 := m21*m42 - m22*m41
a0324 := m20*m43 - m23*m40
a0224 := m20*m42 - m22*m40
a0124 := m20*m41 - m21*m40
b234234 := m22*m33 - m23*m32
b134234 := m21*m33 - m23*m31
b124234 := m21*m32 - m22*m31
b123234 := m21*a2334 - m22*a1334 + m23*a1234
b034234 := m20*m33 - m23*m30
b024234 := m20*m32 - m22*m30
b023234 := m20*a2334 - m22*a0334 + m23*a0234
b014234 := m20*m31 - m21*m30
b013234 := m20*a1334 - m21*a0334 + m23*a0134
b012234 := m20*a1234 - m21*a0234 + m22*a0134
b234134 := m12*m33 - m13*m32
b134134 := m11*m33 - m13*m31
b124134 := m11*m32 - m12*m31
b123134 := m11*a2334 - m12*a1334 + m13*a1234
b234124 := m12*m23 - m13*m22
b134124 := m11*m23 - m13*m21
b124124 := m11*m22 - m12*m21
b123124 := m11*a2324 - m12*a1324 + m13*a1224
b034134 := m10*m33 - m13*m30
b024134 := m10*m32 - m12*m30
b023134 := m10*a2334 - m12*a0334 + m13*a0234
b034124 := m10*m23 - m13*m20
b024124 := m10*m22 - m12*m20
b023124 := m10*a2324 - m12*a0324 + m13*a0224
b014134 := m10*m31 - m11*m30
b013134 := m10*a1334 - m11*a0334 + m13*a0134
b014124 := m10*m21 - m11*m20
b013124 := m10*a1324 - m11*a0324 + m13*a0124
b012134 := m10*a1234 - m11*a0234 + m12*a0134
b012124 := m10*a1224 - m11*a0224 + m12*a0124
m := &colorMImplBodyTranslate{
body: colorMIdentityBody,
}
idet := 1 / det
m.body[0] = idet * (m11*b234234 - m12*b134234 + m13*b124234)
m.body[1] = idet * -(m01*b234234 - m02*b134234 + m03*b124234)
m.body[2] = idet * (m01*b234134 - m02*b134134 + m03*b124134)
m.body[3] = idet * -(m01*b234124 - m02*b134124 + m03*b124124)
m.body[4] = idet * -(m10*b234234 - m12*b034234 + m13*b024234)
m.body[5] = idet * (m00*b234234 - m02*b034234 + m03*b024234)
m.body[6] = idet * -(m00*b234134 - m02*b034134 + m03*b024134)
m.body[7] = idet * (m00*b234124 - m02*b034124 + m03*b024124)
m.body[8] = idet * (m10*b134234 - m11*b034234 + m13*b014234)
m.body[9] = idet * -(m00*b134234 - m01*b034234 + m03*b014234)
m.body[10] = idet * (m00*b134134 - m01*b034134 + m03*b014134)
m.body[11] = idet * -(m00*b134124 - m01*b034124 + m03*b014124)
m.body[12] = idet * -(m10*b124234 - m11*b024234 + m12*b014234)
m.body[13] = idet * (m00*b124234 - m01*b024234 + m02*b014234)
m.body[14] = idet * -(m00*b124134 - m01*b024134 + m02*b014134)
m.body[15] = idet * (m00*b124124 - m01*b024124 + m02*b014124)
m.translate[0] = idet * (m10*b123234 - m11*b023234 + m12*b013234 - m13*b012234)
m.translate[1] = idet * -(m00*b123234 - m01*b023234 + m02*b013234 - m03*b012234)
m.translate[2] = idet * (m00*b123134 - m01*b023134 + m02*b013134 - m03*b012134)
m.translate[3] = idet * -(m00*b123124 - m01*b023124 + m02*b013124 - m03*b012124)
return m
}
func (c ColorMIdentity) At(i, j int) float32 {
if i == j {
return 1
}
return 0
}
func (c colorMImplScale) At(i, j int) float32 {
if i == j {
return c.scale[i]
}
return 0
}
func (c *colorMImplBodyTranslate) At(i, j int) float32 {
if j < ColorMDim-1 {
return c.body[i+j*(ColorMDim-1)]
}
return c.translate[i]
}
// ColorMSetElement sets an element at (i, j).
func ColorMSetElement(c ColorM, i, j int, element float32) ColorM {
newImpl := &colorMImplBodyTranslate{
body: colorMIdentityBody,
}
if !c.IsIdentity() {
c.Elements(newImpl.body[:], newImpl.translate[:])
}
if j < (ColorMDim - 1) {
newImpl.body[i+j*(ColorMDim-1)] = element
} else {
newImpl.translate[i] = element
}
return newImpl
}
func (c ColorMIdentity) Equals(other ColorM) bool {
return other.IsIdentity()
}
func (c colorMImplScale) Equals(other ColorM) bool {
if !other.ScaleOnly() {
return false
}
r, g, b, a := other.scaleElements()
if c.scale[0] != r {
return false
}
if c.scale[1] != g {
return false
}
if c.scale[2] != b {
return false
}
if c.scale[3] != a {
return false
}
return true
}
func (c *colorMImplBodyTranslate) Equals(other ColorM) bool {
var lhsb [16]float32
var lhst [4]float32
// Calling a method of an interface type escapes arguments to heap (#2119).
// Instead, cast `other` to a concrete type and call `Elements` functions of it.
switch other := other.(type) {
case ColorMIdentity:
other.Elements(lhsb[:], lhst[:])
case colorMImplScale:
other.Elements(lhsb[:], lhst[:])
case *colorMImplBodyTranslate:
other.Elements(lhsb[:], lhst[:])
default:
panic("affine: unexpected ColorM implementation")
}
return lhsb == c.body && lhst == c.translate
}
func (c ColorMIdentity) Concat(other ColorM) ColorM {
return other
}
func (c colorMImplScale) Concat(other ColorM) ColorM {
if other.IsIdentity() {
return c
}
if other.ScaleOnly() {
return c.Scale(other.scaleElements())
}
var lhsb [16]float32
var lhst [4]float32
// Calling a method of an interface type escapes arguments to heap (#2119).
// Instead, cast `other` to a concrete type and call `Elements` functions of it.
switch other := other.(type) {
case ColorMIdentity:
other.Elements(lhsb[:], lhst[:])
case colorMImplScale:
other.Elements(lhsb[:], lhst[:])
case *colorMImplBodyTranslate:
other.Elements(lhsb[:], lhst[:])
default:
panic("affine: unexpected ColorM implementation")
}
s := &c.scale
return &colorMImplBodyTranslate{
body: [...]float32{
lhsb[0] * s[0], lhsb[1] * s[0], lhsb[2] * s[0], lhsb[3] * s[0],
lhsb[4] * s[1], lhsb[5] * s[1], lhsb[6] * s[1], lhsb[7] * s[1],
lhsb[8] * s[2], lhsb[9] * s[2], lhsb[10] * s[2], lhsb[11] * s[2],
lhsb[12] * s[3], lhsb[13] * s[3], lhsb[14] * s[3], lhsb[15] * s[3],
},
translate: lhst,
}
}
func (c *colorMImplBodyTranslate) Concat(other ColorM) ColorM {
if other.IsIdentity() {
return c
}
var lhsb [16]float32
var lhst [4]float32
// Calling a method of an interface type escapes arguments to heap (#2119).
// Instead, cast `other` to a concrete type and call `Elements` functions of it.
switch other := other.(type) {
case ColorMIdentity:
other.Elements(lhsb[:], lhst[:])
case colorMImplScale:
other.Elements(lhsb[:], lhst[:])
case *colorMImplBodyTranslate:
other.Elements(lhsb[:], lhst[:])
default:
panic("affine: unexpected ColorM implementation")
}
rhsb := &c.body
rhst := &c.translate
return &colorMImplBodyTranslate{
// TODO: This is a temporary hack to calculate multiply of transposed matrices.
// Fix mulSquare implementation and swap the arguments.
body: mulSquare(rhsb, &lhsb, ColorMDim-1),
translate: [...]float32{
lhsb[0]*rhst[0] + lhsb[4]*rhst[1] + lhsb[8]*rhst[2] + lhsb[12]*rhst[3] + lhst[0],
lhsb[1]*rhst[0] + lhsb[5]*rhst[1] + lhsb[9]*rhst[2] + lhsb[13]*rhst[3] + lhst[1],
lhsb[2]*rhst[0] + lhsb[6]*rhst[1] + lhsb[10]*rhst[2] + lhsb[14]*rhst[3] + lhst[2],
lhsb[3]*rhst[0] + lhsb[7]*rhst[1] + lhsb[11]*rhst[2] + lhsb[15]*rhst[3] + lhst[3],
},
}
}
func (c ColorMIdentity) Scale(r, g, b, a float32) ColorM {
if r == 1 && g == 1 && b == 1 && a == 1 {
return c
}
return colorMImplScale{
scale: [...]float32{r, g, b, a},
}
}
func (c colorMImplScale) Scale(r, g, b, a float32) ColorM {
if r == 1 && g == 1 && b == 1 && a == 1 {
return c
}
return colorMImplScale{
scale: [...]float32{
c.scale[0] * r,
c.scale[1] * g,
c.scale[2] * b,
c.scale[3] * a,
},
}
}
func (c *colorMImplBodyTranslate) Scale(r, g, b, a float32) ColorM {
if r == 1 && g == 1 && b == 1 && a == 1 {
return c
}
if c.ScaleOnly() {
sr, sg, sb, sa := c.scaleElements()
return colorMImplScale{
scale: [...]float32{r * sr, g * sg, b * sb, a * sa},
}
}
eb := c.body
for i := 0; i < ColorMDim-1; i++ {
eb[i*(ColorMDim-1)] *= r
eb[i*(ColorMDim-1)+1] *= g
eb[i*(ColorMDim-1)+2] *= b
eb[i*(ColorMDim-1)+3] *= a
}
et := [...]float32{
c.translate[0] * r,
c.translate[1] * g,
c.translate[2] * b,
c.translate[3] * a,
}
return &colorMImplBodyTranslate{
body: eb,
translate: et,
}
}
func (c ColorMIdentity) Translate(r, g, b, a float32) ColorM {
if r == 0 && g == 0 && b == 0 && a == 0 {
return c
}
return &colorMImplBodyTranslate{
body: colorMIdentityBody,
translate: [...]float32{r, g, b, a},
}
}
func (c colorMImplScale) Translate(r, g, b, a float32) ColorM {
if r == 0 && g == 0 && b == 0 && a == 0 {
return c
}
return &colorMImplBodyTranslate{
body: [...]float32{
c.scale[0], 0, 0, 0,
0, c.scale[1], 0, 0,
0, 0, c.scale[2], 0,
0, 0, 0, c.scale[3],
},
translate: [...]float32{r, g, b, a},
}
}
func (c *colorMImplBodyTranslate) Translate(r, g, b, a float32) ColorM {
if r == 0 && g == 0 && b == 0 && a == 0 {
return c
}
es := c.translate
es[0] += r
es[1] += g
es[2] += b
es[3] += a
return &colorMImplBodyTranslate{
body: c.body,
translate: es,
}
}
var (
// The YCbCr value ranges are:
// Y: [ 0 - 1 ]
// Cb: [-0.5 - 0.5]
// Cr: [-0.5 - 0.5]
rgbToYCbCr = &colorMImplBodyTranslate{
body: [...]float32{
0.2990, -0.1687, 0.5000, 0,
0.5870, -0.3313, -0.4187, 0,
0.1140, 0.5000, -0.0813, 0,
0, 0, 0, 1,
},
}
yCbCrToRgb = &colorMImplBodyTranslate{
body: [...]float32{
1, 1, 1, 0,
0, -0.34414, 1.77200, 0,
1.40200, -0.71414, 0, 0,
0, 0, 0, 1,
},
}
)
// ChangeHSV changes HSV (Hue-Saturation-Value) elements.
// hueTheta is a radian value to rotate hue.
// saturationScale is a value to scale saturation.
// valueScale is a value to scale value (a.k.a. brightness).
//
// This conversion uses RGB to/from YCrCb conversion.
func ChangeHSV(c ColorM, hueTheta float64, saturationScale float32, valueScale float32) ColorM {
if hueTheta == 0 && saturationScale == 1 {
v := valueScale
return c.Scale(v, v, v, 1)
}
sin, cos := math.Sincos(hueTheta)
s32, c32 := float32(sin), float32(cos)
c = c.Concat(rgbToYCbCr)
c = c.Concat(&colorMImplBodyTranslate{
body: [...]float32{
1, 0, 0, 0,
0, c32, s32, 0,
0, -s32, c32, 0,
0, 0, 0, 1,
},
})
s := saturationScale
v := valueScale
c = c.Scale(v, s*v, s*v, 1)
c = c.Concat(yCbCrToRgb)
return c
}
type cachedScalingColorMKey struct {
r, g, b, a float32
}
type cachedScalingColorMValue struct {
c *colorMImplScale
atime uint64
}
+962
View File
@@ -0,0 +1,962 @@
// Copyright 2018 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.
package atlas
import (
"fmt"
"image"
"math"
"math/bits"
"runtime"
"sync"
"github.com/hajimehoshi/ebiten/v2/internal/debug"
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/packing"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
)
var (
minSourceSize = 0
minDestinationSize = 0
maxSize = 0
)
func max(a, b int) int {
if a > b {
return a
}
return b
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
// quadVertices returns vertices to render a quad. These values are passed to graphicscommand.Image.
func quadVertices(dx0, dy0, dx1, dy1, sx0, sy0, sx1, sy1, cr, cg, cb, ca float32) []float32 {
return []float32{
dx0, dy0, sx0, sy0, cr, cg, cb, ca,
dx1, dy0, sx1, sy0, cr, cg, cb, ca,
dx0, dy1, sx0, sy1, cr, cg, cb, ca,
dx1, dy1, sx1, sy1, cr, cg, cb, ca,
}
}
func appendDeferred(f func()) {
deferredM.Lock()
defer deferredM.Unlock()
deferred = append(deferred, f)
}
func flushDeferred() {
deferredM.Lock()
fs := deferred
deferred = nil
deferredM.Unlock()
for _, f := range fs {
f()
}
}
// baseCountToPutOnSourceBackend represents the base time duration when the image can be put onto an atlas.
// Actual time duration is increased in an exponential way for each usage as a rendering target.
const baseCountToPutOnSourceBackend = 10
func putImagesOnSourceBackend(graphicsDriver graphicsdriver.Graphics) {
// The counter usedAsDestinationCount is updated at most once per frame (#2676).
imagesUsedAsDestination.forEach(func(i *Image) {
// This counter is not updated when the backend is created in this frame.
if !i.backendCreatedInThisFrame && i.usedAsDestinationCount < math.MaxInt {
i.usedAsDestinationCount++
}
i.backendCreatedInThisFrame = false
})
imagesUsedAsDestination.clear()
imagesToPutOnSourceBackend.forEach(func(i *Image) {
if i.usedAsSourceCount < math.MaxInt {
i.usedAsSourceCount++
}
if int64(i.usedAsSourceCount) >= int64(baseCountToPutOnSourceBackend*(1<<uint(min(i.usedAsDestinationCount, 31)))) {
i.putOnSourceBackend(graphicsDriver)
i.usedAsSourceCount = 0
}
})
imagesToPutOnSourceBackend.clear()
}
type backend struct {
// image is an atlas on which there might be multiple images.
image *graphicscommand.Image
width int
height int
// page is an atlas map. Each part is called a node.
// If page is nil, the backend's image is isolated and not on an atlas.
page *packing.Page
// source reports whether this backend is mainly used a rendering source, but this is not 100%.
// If a non-source (destination) image is used as a source many times,
// the image's backend might be turned into a source backend to optimize draw calls.
source bool
// sourceInThisFrame reports whether this backend is used as a source in this frame.
// sourceInThisFrame is reset every frame.
sourceInThisFrame bool
}
func (b *backend) tryAlloc(width, height int) (*packing.Node, bool) {
if b.page == nil {
return nil, false
}
n := b.page.Alloc(width, height)
if n == nil {
// The page can't be extended anymore. Return as failure.
return nil, false
}
b.extendIfNeeded(b.page.Size())
return n, true
}
// extendIfNeeded extends the image by the given size if necessary.
// extendIfNeeded creates a new image with the given size and copies the pixels of the given source image.
// extendIfNeeded disposes an old image after its call when a new image is created.
func (b *backend) extendIfNeeded(width, height int) {
if b.width >= width && b.height >= height {
return
}
// Assume that the screen image is never extended.
newImg := newClearedImage(width, height, false)
// Use DrawTriangles instead of WritePixels because the image i might be stale and not have its pixels
// information.
srcs := [graphics.ShaderImageCount]*graphicscommand.Image{b.image}
sw, sh := b.image.InternalSize()
vs := quadVertices(0, 0, float32(sw), float32(sh), 0, 0, float32(sw), float32(sh), 1, 1, 1, 1)
is := graphics.QuadIndices()
dr := image.Rect(0, 0, sw, sh)
newImg.DrawTriangles(srcs, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, NearestFilterShader.ensureShader(), nil, graphicsdriver.FillAll)
b.image.Dispose()
b.image = newImg
b.width = width
b.height = height
}
// newClearedImage creates an emtpy image with the given size.
//
// Note that Dispose is not called automatically.
func newClearedImage(width, height int, screen bool) *graphicscommand.Image {
i := graphicscommand.NewImage(width, height, screen)
// This needs to use 'InternalSize' to render the whole region, or edges are unexpectedly cleared on some
// devices.
iw, ih := i.InternalSize()
clearImage(i, image.Rect(0, 0, iw, ih))
return i
}
func clearImage(i *graphicscommand.Image, region image.Rectangle) {
vs := quadVertices(float32(region.Min.X), float32(region.Min.Y), float32(region.Max.X), float32(region.Max.Y), 0, 0, 0, 0, 0, 0, 0, 0)
is := graphics.QuadIndices()
i.DrawTriangles([graphics.ShaderImageCount]*graphicscommand.Image{}, vs, is, graphicsdriver.BlendClear, region, [graphics.ShaderImageCount]image.Rectangle{}, clearShader.ensureShader(), nil, graphicsdriver.FillAll)
}
func (b *backend) clearPixels(region image.Rectangle) {
if region.Dx() <= 0 || region.Dy() <= 0 {
panic("atlas: width/height must be positive")
}
clearImage(b.image, region.Intersect(image.Rect(0, 0, b.width, b.height)))
}
func (b *backend) writePixels(pixels *graphics.ManagedBytes, region image.Rectangle) {
if region.Dx() <= 0 || region.Dy() <= 0 {
panic("atlas: width/height must be positive")
}
if !region.In(image.Rect(0, 0, b.width, b.height)) {
panic(fmt.Sprintf("atlas: out of range %v", region))
}
b.image.WritePixels(pixels, region)
}
var (
// backendsM is a mutex for critical sections of the backend and packing.Node objects.
backendsM sync.Mutex
// inFrame indicates whether the current state is in between BeginFrame and EndFrame or not.
// If inFrame is false, function calls on an image should be deferred until the next BeginFrame.
inFrame bool
initOnce sync.Once
// theBackends is a set of atlases.
theBackends []*backend
imagesToPutOnSourceBackend smallImageSet
imagesUsedAsDestination smallImageSet
graphicsDriverInitialized bool
deferred []func()
// deferredM is a mutex for the slice operations. This must not be used for other usages.
deferredM sync.Mutex
)
type ImageType int
const (
ImageTypeRegular ImageType = iota
ImageTypeScreen
ImageTypeVolatile
ImageTypeUnmanaged
)
// Image is a rectangle pixel set that might be on an atlas.
type Image struct {
width int
height int
imageType ImageType
backend *backend
backendCreatedInThisFrame bool
node *packing.Node
// usedAsSourceCount represents how long the image is used as a rendering source and kept not modified with
// DrawTriangles.
// In the current implementation, if an image is being modified by DrawTriangles, the image is separated from
// a graphicscommand.Image on an atlas by ensureIsolatedFromSource.
//
// usedAsSourceCount is increased if the image is used as a rendering source, or set to 0 if the image is
// modified.
//
// WritePixels doesn't affect this value since WritePixels can be done on images on an atlas.
usedAsSourceCount int
// usedAsDestinationCount represents how many times an image is used as a rendering destination at DrawTriangles.
// usedAsDestinationCount affects the calculation when to put the image onto a texture atlas again.
//
// usedAsDestinationCount is never reset.
usedAsDestinationCount int
}
// moveTo moves its content to the given image dst.
// After moveTo is called, the image i is no longer available.
//
// moveTo is similar to C++'s move semantics.
func (i *Image) moveTo(dst *Image) {
dst.deallocate()
*dst = *i
// i is no longer available but the finalizer must not be called
// since i and dst share the same backend and the same node.
runtime.SetFinalizer(i, nil)
}
func (i *Image) isOnAtlas() bool {
return i.node != nil
}
func (i *Image) isOnSourceBackend() bool {
if i.backend == nil {
return false
}
return i.backend.source
}
func (i *Image) resetUsedAsSourceCount() {
i.usedAsSourceCount = 0
imagesToPutOnSourceBackend.remove(i)
}
func (i *Image) paddingSize() int {
if i.imageType == ImageTypeRegular {
return 1
}
return 0
}
func (i *Image) ensureIsolatedFromSource(backends []*backend) {
i.resetUsedAsSourceCount()
// imagesUsedAsDestination affects the counter usedAsDestination.
// The larger this counter is, the harder it is for the image to be transferred to the source backend.
imagesUsedAsDestination.add(i)
if i.backend == nil {
// `sourceInThisFrame` of `backends` should be true, so `backends` should be in `bs`.
var bs []*backend
for _, b := range theBackends {
if b.sourceInThisFrame {
bs = append(bs, b)
}
}
i.allocate(bs, false)
i.backendCreatedInThisFrame = true
return
}
if !i.isOnAtlas() {
return
}
// Check if i has the same backend as the given backends.
var needsIsolation bool
for _, b := range backends {
if i.backend == b {
needsIsolation = true
break
}
}
if !needsIsolation {
return
}
newI := NewImage(i.width, i.height, i.imageType)
// Call allocate explicitly in order to have an isolated backend from the specified backends.
// `sourceInThisFrame` of `backends` should be true, so `backends` should be in `bs`.
bs := []*backend{i.backend}
for _, b := range theBackends {
if b.sourceInThisFrame {
bs = append(bs, b)
}
}
newI.allocate(bs, false)
w, h := float32(i.width), float32(i.height)
vs := make([]float32, 4*graphics.VertexFloatCount)
graphics.QuadVertices(vs, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
is := graphics.QuadIndices()
dr := image.Rect(0, 0, i.width, i.height)
newI.drawTriangles([graphics.ShaderImageCount]*Image{i}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillAll)
newI.moveTo(i)
}
func (i *Image) putOnSourceBackend(graphicsDriver graphicsdriver.Graphics) {
if i.backend == nil {
i.allocate(nil, true)
return
}
if i.isOnSourceBackend() {
return
}
if !i.canBePutOnAtlas() {
panic("atlas: putOnSourceBackend cannot be called on a image that cannot be on an atlas")
}
if i.imageType != ImageTypeRegular {
panic(fmt.Sprintf("atlas: the image type must be ImageTypeRegular but %d", i.imageType))
}
newI := NewImage(i.width, i.height, ImageTypeRegular)
newI.allocate(nil, true)
w, h := float32(i.width), float32(i.height)
vs := make([]float32, 4*graphics.VertexFloatCount)
graphics.QuadVertices(vs, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
is := graphics.QuadIndices()
dr := image.Rect(0, 0, i.width, i.height)
newI.drawTriangles([graphics.ShaderImageCount]*Image{i}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillAll)
newI.moveTo(i)
i.usedAsSourceCount = 0
if !i.isOnSourceBackend() {
panic("atlas: i must be on a source backend but not")
}
}
func (i *Image) regionWithPadding() image.Rectangle {
if i.backend == nil {
panic("atlas: backend must not be nil: not allocated yet?")
}
if !i.isOnAtlas() {
return image.Rect(0, 0, i.width+i.paddingSize(), i.height+i.paddingSize())
}
return i.node.Region()
}
// DrawTriangles draws triangles with the given image.
//
// The vertex floats are:
//
// 0: Destination X in pixels
// 1: Destination Y in pixels
// 2: Source X in pixels (the upper-left is (0, 0))
// 3: Source Y in pixels
// 4: Color R [0.0-1.0]
// 5: Color G
// 6: Color B
// 7: Color Y
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) {
backendsM.Lock()
defer backendsM.Unlock()
if !inFrame {
vs := make([]float32, len(vertices))
copy(vs, vertices)
is := make([]uint32, len(indices))
copy(is, indices)
us := make([]uint32, len(uniforms))
copy(us, uniforms)
appendDeferred(func() {
i.drawTriangles(srcs, vs, is, blend, dstRegion, srcRegions, shader, us, fillRule)
})
return
}
i.drawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule)
}
func (i *Image) drawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) {
if len(vertices) == 0 {
return
}
backends := make([]*backend, 0, len(srcs))
for _, src := range srcs {
if src == nil {
continue
}
if src.backend == nil {
// It is possible to spcify i.backend as a forbidden backend, but this might prevent a good allocation for a source image.
// If the backend becomes the same as i's, i's backend will be changed at ensureIsolatedFromSource.
src.allocate(nil, true)
}
backends = append(backends, src.backend)
src.backend.sourceInThisFrame = true
}
i.ensureIsolatedFromSource(backends)
for _, src := range srcs {
// Compare i and source images after ensuring i is not on an atlas, or
// i and a source image might share the same atlas even though i != src.
if src != nil && i.backend.image == src.backend.image {
panic("atlas: Image.DrawTriangles: source must be different from the receiver")
}
}
r := i.regionWithPadding()
// TODO: Check if dstRegion does not to violate the region.
dstRegion = dstRegion.Add(r.Min)
dx, dy := float32(r.Min.X), float32(r.Min.Y)
var oxf, oyf float32
if srcs[0] != nil {
r := srcs[0].regionWithPadding()
oxf, oyf = float32(r.Min.X), float32(r.Min.Y)
n := len(vertices)
for i := 0; i < n; i += graphics.VertexFloatCount {
vertices[i] += dx
vertices[i+1] += dy
vertices[i+2] += oxf
vertices[i+3] += oyf
}
if shader.ir.Unit == shaderir.Texels {
sw, sh := srcs[0].backend.image.InternalSize()
swf, shf := float32(sw), float32(sh)
for i := 0; i < n; i += graphics.VertexFloatCount {
vertices[i+2] /= swf
vertices[i+3] /= shf
}
}
} else {
n := len(vertices)
for i := 0; i < n; i += graphics.VertexFloatCount {
vertices[i] += dx
vertices[i+1] += dy
}
}
for i, src := range srcs {
if src == nil {
continue
}
// A source region can be deliberately empty when this is not needed in order to avoid unexpected
// performance issue (#1293).
if srcRegions[i].Empty() {
continue
}
r := src.regionWithPadding()
srcRegions[i] = srcRegions[i].Add(r.Min)
}
var imgs [graphics.ShaderImageCount]*graphicscommand.Image
for i, src := range srcs {
if src == nil {
continue
}
imgs[i] = src.backend.image
}
i.backend.image.DrawTriangles(imgs, vertices, indices, blend, dstRegion, srcRegions, shader.ensureShader(), uniforms, fillRule)
for _, src := range srcs {
if src == nil {
continue
}
if !src.isOnSourceBackend() && src.canBePutOnAtlas() {
// src might already registered, but assigning it again is not harmful.
imagesToPutOnSourceBackend.add(src)
}
}
}
// WritePixels replaces the pixels on the image.
func (i *Image) WritePixels(pix []byte, region image.Rectangle) {
backendsM.Lock()
defer backendsM.Unlock()
if !inFrame {
copied := make([]byte, len(pix))
copy(copied, pix)
appendDeferred(func() {
i.writePixels(copied, region)
})
return
}
i.writePixels(pix, region)
}
func (i *Image) writePixels(pix []byte, region image.Rectangle) {
if l := 4 * region.Dx() * region.Dy(); len(pix) != l {
panic(fmt.Sprintf("atlas: len(p) must be %d but %d", l, len(pix)))
}
i.resetUsedAsSourceCount()
if i.backend == nil {
if pix == nil {
return
}
// Allocate as a source as this image will likely be used as a source.
i.allocate(nil, true)
}
r := i.regionWithPadding()
if !region.Eq(image.Rect(0, 0, i.width, i.height)) || i.paddingSize() == 0 {
region = region.Add(r.Min)
if pix == nil {
i.backend.clearPixels(region)
return
}
// Copy pixels in the case when pix is modified before the graphics command is executed.
pix2 := graphics.NewManagedBytes(len(pix), func(bs []byte) {
copy(bs, pix)
})
i.backend.writePixels(pix2, region)
return
}
// TODO: These loops assume that paddingSize is 1.
// TODO: Is clearing edges explicitly really needed?
const paddingSize = 1
if paddingSize != i.paddingSize() {
panic(fmt.Sprintf("atlas: writePixels assumes the padding is always 1 but the actual padding was %d", i.paddingSize()))
}
pixb := graphics.NewManagedBytes(4*r.Dx()*r.Dy(), func(bs []byte) {
// Clear the edges. bs might not be zero-cleared.
rowPixels := 4 * r.Dx()
for i := 0; i < rowPixels; i++ {
bs[rowPixels*(r.Dy()-1)+i] = 0
}
for j := 1; j < r.Dy(); j++ {
bs[rowPixels*j-4] = 0
bs[rowPixels*j-3] = 0
bs[rowPixels*j-2] = 0
bs[rowPixels*j-1] = 0
}
// Copy the content.
for j := 0; j < region.Dy(); j++ {
copy(bs[4*j*r.Dx():], pix[4*j*region.Dx():4*(j+1)*region.Dx()])
}
})
i.backend.writePixels(pixb, r)
}
func (i *Image) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte, region image.Rectangle) (ok bool, err error) {
backendsM.Lock()
defer backendsM.Unlock()
if !inFrame {
// Not ready to read pixels. Try this later.
return false, nil
}
// In the tests, BeginFrame might not be called often and then images might not be disposed (#2292).
// To prevent memory leaks, flush the deferred functions here.
flushDeferred()
if err := i.readPixels(graphicsDriver, pixels, region); err != nil {
return false, err
}
return true, nil
}
func (i *Image) readPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte, region image.Rectangle) error {
if i.backend == nil || i.backend.image == nil {
for i := range pixels {
pixels[i] = 0
}
return nil
}
if err := i.backend.image.ReadPixels(graphicsDriver, []graphicsdriver.PixelsArgs{
{
Pixels: pixels,
Region: region.Add(i.regionWithPadding().Min),
},
}); err != nil {
return err
}
return nil
}
// Deallocate deallocates the internal state.
// Even after this call, the image is still available as a new cleared image.
func (i *Image) Deallocate() {
backendsM.Lock()
defer backendsM.Unlock()
if !inFrame {
appendDeferred(func() {
i.deallocate()
runtime.SetFinalizer(i, nil)
})
return
}
i.deallocate()
runtime.SetFinalizer(i, nil)
}
func (i *Image) deallocate() {
defer func() {
i.backend = nil
i.node = nil
}()
i.resetUsedAsSourceCount()
i.usedAsDestinationCount = 0
imagesUsedAsDestination.remove(i)
if i.backend == nil {
// Not allocated yet.
return
}
if i.isOnAtlas() {
i.backend.page.Free(i.node)
if !i.backend.page.IsEmpty() {
// As this part can be reused, this should be cleared explicitly.
r := i.regionWithPadding()
i.backend.clearPixels(r)
return
}
}
i.backend.image.Dispose()
i.backend.image = nil
for idx, sh := range theBackends {
if sh == i.backend {
copy(theBackends[idx:], theBackends[idx+1:])
theBackends[len(theBackends)-1] = nil
theBackends = theBackends[:len(theBackends)-1]
return
}
}
panic("atlas: backend not found at an image being deallocated")
}
func NewImage(width, height int, imageType ImageType) *Image {
// Actual allocation is done lazily, and the lock is not needed.
return &Image{
width: width,
height: height,
imageType: imageType,
}
}
func (i *Image) canBePutOnAtlas() bool {
if minSourceSize == 0 || minDestinationSize == 0 || maxSize == 0 {
panic("atlas: min*Size or maxSize must be initialized")
}
if i.imageType != ImageTypeRegular {
return false
}
return i.width+i.paddingSize() <= maxSize && i.height+i.paddingSize() <= maxSize
}
func (i *Image) finalize() {
// A function from finalizer must not be blocked, but disposing operation can be blocked.
// Defer this operation until it becomes safe. (#913)
appendDeferred(func() {
i.deallocate()
runtime.SetFinalizer(i, nil)
})
}
func (i *Image) allocate(forbiddenBackends []*backend, asSource bool) {
if !graphicsDriverInitialized {
panic("atlas: graphics driver must be ready at allocate but not")
}
if i.backend != nil {
panic("atlas: the image is already allocated")
}
runtime.SetFinalizer(i, (*Image).finalize)
if i.imageType == ImageTypeScreen {
if asSource {
panic("atlas: a screen image cannot be created as a source")
}
// A screen image doesn't have a padding.
i.backend = &backend{
image: newClearedImage(i.width, i.height, true),
width: i.width,
height: i.height,
}
theBackends = append(theBackends, i.backend)
return
}
wp := i.width + i.paddingSize()
hp := i.height + i.paddingSize()
if !i.canBePutOnAtlas() {
if wp > maxSize || hp > maxSize {
panic(fmt.Sprintf("atlas: the image being put on an atlas is too big: width: %d, height: %d", i.width, i.height))
}
i.backend = &backend{
image: newClearedImage(wp, hp, false),
width: wp,
height: hp,
source: asSource && i.imageType == ImageTypeRegular,
}
theBackends = append(theBackends, i.backend)
return
}
// Check if an existing backend is available.
loop:
for _, b := range theBackends {
if b.source != asSource {
continue
}
for _, bb := range forbiddenBackends {
if b == bb {
continue loop
}
}
if n, ok := b.tryAlloc(wp, hp); ok {
i.backend = b
i.node = n
return
}
}
var width, height int
if asSource {
width, height = minSourceSize, minSourceSize
} else {
width, height = minDestinationSize, minDestinationSize
}
for wp > width {
if width == maxSize {
panic(fmt.Sprintf("atlas: the image being put on an atlas is too big: width: %d, height: %d", i.width, i.height))
}
width *= 2
}
for hp > height {
if height == maxSize {
panic(fmt.Sprintf("atlas: the image being put on an atlas is too big: width: %d, height: %d", i.width, i.height))
}
height *= 2
}
b := &backend{
image: newClearedImage(width, height, false),
width: width,
height: height,
page: packing.NewPage(width, height, maxSize),
source: asSource,
}
theBackends = append(theBackends, b)
n := b.page.Alloc(wp, hp)
if n == nil {
panic("atlas: Alloc result must not be nil at allocate")
}
i.backend = b
i.node = n
}
func (i *Image) DumpScreenshot(graphicsDriver graphicsdriver.Graphics, path string, blackbg bool) (string, error) {
backendsM.Lock()
defer backendsM.Unlock()
if !inFrame {
panic("atlas: DumpScreenshots must be called in between BeginFrame and EndFrame")
}
return i.backend.image.Dump(graphicsDriver, path, blackbg, image.Rect(0, 0, i.width, i.height))
}
func EndFrame() error {
backendsM.Lock()
defer backendsM.Unlock()
defer func() {
inFrame = false
}()
if !inFrame {
panic("atlas: inFrame must be true in EndFrame")
}
for _, b := range theBackends {
b.sourceInThisFrame = false
}
return nil
}
func SwapBuffers(graphicsDriver graphicsdriver.Graphics) error {
func() {
backendsM.Lock()
defer backendsM.Unlock()
if inFrame {
panic("atlas: inFrame must be false in SwapBuffer")
}
}()
if debug.IsDebug {
debug.Logf("Internal image sizes:\n")
imgs := make([]*graphicscommand.Image, 0, len(theBackends))
for _, backend := range theBackends {
if backend.image == nil {
continue
}
imgs = append(imgs, backend.image)
}
graphicscommand.LogImagesInfo(imgs)
}
if err := graphicscommand.FlushCommands(graphicsDriver, true); err != nil {
return err
}
return nil
}
func floorPowerOf2(x int) int {
if x <= 0 {
return 0
}
return 1 << (bits.Len(uint(x)) - 1)
}
func BeginFrame(graphicsDriver graphicsdriver.Graphics) error {
backendsM.Lock()
defer backendsM.Unlock()
if inFrame {
panic("atlas: inFrame must be false in BeginFrame")
}
inFrame = true
var err error
initOnce.Do(func() {
err = graphicscommand.InitializeGraphicsDriverState(graphicsDriver)
if err != nil {
return
}
if len(theBackends) != 0 {
panic("atlas: all the images must be not on an atlas before the game starts")
}
// min*Size and maxSize can already be set for testings.
if minSourceSize == 0 {
minSourceSize = 1024
}
if minDestinationSize == 0 {
minDestinationSize = 16
}
if maxSize == 0 {
maxSize = floorPowerOf2(graphicscommand.MaxImageSize(graphicsDriver))
}
graphicsDriverInitialized = true
})
if err != nil {
return err
}
flushDeferred()
putImagesOnSourceBackend(graphicsDriver)
return nil
}
func DumpImages(graphicsDriver graphicsdriver.Graphics, dir string) (string, error) {
backendsM.Lock()
defer backendsM.Unlock()
if !inFrame {
panic("atlas: DumpImages must be called in between BeginFrame and EndFrame")
}
images := make([]*graphicscommand.Image, 0, len(theBackends))
for _, backend := range theBackends {
if backend.image == nil {
continue
}
images = append(images, backend.image)
}
return graphicscommand.DumpImages(images, graphicsDriver, dir)
}
+69
View File
@@ -0,0 +1,69 @@
// Copyright 2023 The Ebitengine 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.
package atlas
// smallImageSet is a set for images.
// smallImageSet uses a slice assuming the number of items is not so big (smaller than 100 or so).
// If the number of items is big, a map might be better than a slice.
type smallImageSet struct {
s []*Image
tmp []*Image
}
func (s *smallImageSet) add(image *Image) {
if image == nil {
panic("atlas: nil image cannot be added")
}
for _, img := range s.s {
if img == image {
return
}
}
s.s = append(s.s, image)
}
func (s *smallImageSet) remove(image *Image) {
for i, img := range s.s {
if img == image {
copy(s.s[i:], s.s[i+1:])
s.s[len(s.s)-1] = nil
s.s = s.s[:len(s.s)-1]
return
}
}
}
func (s *smallImageSet) forEach(f func(*Image)) {
// Copy images to a temporary buffer since f might modify the original slice s.s (#2729).
s.tmp = append(s.tmp, s.s...)
for _, img := range s.tmp {
f(img)
}
// Clear the temporary buffer.
for i := range s.tmp {
s.tmp[i] = nil
}
s.tmp = s.tmp[:0]
}
func (s *smallImageSet) clear() {
for i := range s.s {
s.s[i] = nil
}
s.s = s.s[:0]
}
+123
View File
@@ -0,0 +1,123 @@
// Copyright 2020 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.
package atlas
import (
"fmt"
"runtime"
"golang.org/x/sync/errgroup"
"github.com/hajimehoshi/ebiten/v2/internal/builtinshader"
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
)
type Shader struct {
ir *shaderir.Program
shader *graphicscommand.Shader
}
func NewShader(ir *shaderir.Program) *Shader {
// A shader is initialized lazily, and the lock is not needed.
return &Shader{
ir: ir,
}
}
func (s *Shader) finalize() {
// A function from finalizer must not be blocked, but disposing operation can be blocked.
// Defer this operation until it becomes safe. (#913)
appendDeferred(func() {
s.deallocate()
})
}
func (s *Shader) ensureShader() *graphicscommand.Shader {
if s.shader != nil {
return s.shader
}
s.shader = graphicscommand.NewShader(s.ir)
runtime.SetFinalizer(s, (*Shader).finalize)
return s.shader
}
// Deallocate deallocates the internal state.
func (s *Shader) Deallocate() {
backendsM.Lock()
defer backendsM.Unlock()
if !inFrame {
appendDeferred(func() {
s.deallocate()
})
return
}
s.deallocate()
}
func (s *Shader) deallocate() {
runtime.SetFinalizer(s, nil)
if s.shader == nil {
return
}
s.shader.Dispose()
s.shader = nil
}
var (
NearestFilterShader *Shader
LinearFilterShader *Shader
clearShader *Shader
)
func init() {
var wg errgroup.Group
wg.Go(func() error {
ir, err := graphics.CompileShader([]byte(builtinshader.Shader(builtinshader.FilterNearest, builtinshader.AddressUnsafe, false)))
if err != nil {
return fmt.Errorf("atlas: compiling the nearest shader failed: %w", err)
}
NearestFilterShader = NewShader(ir)
return nil
})
wg.Go(func() error {
ir, err := graphics.CompileShader([]byte(builtinshader.Shader(builtinshader.FilterLinear, builtinshader.AddressUnsafe, false)))
if err != nil {
return fmt.Errorf("atlas: compiling the linear shader failed: %w", err)
}
LinearFilterShader = NewShader(ir)
return nil
})
wg.Go(func() error {
ir, err := graphics.CompileShader([]byte(`//kage:unit pixels
package main
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
return vec4(0)
}`))
if err != nil {
return fmt.Errorf("atlas: compiling the clear shader failed: %w", err)
}
clearShader = NewShader(ir)
return nil
})
if err := wg.Wait(); err != nil {
panic(err)
}
}
+301
View File
@@ -0,0 +1,301 @@
// 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.
package buffered
import (
"fmt"
"image"
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
)
var whiteImage *Image
func init() {
whiteImage = NewImage(3, 3, atlas.ImageTypeRegular)
pix := make([]byte, 4*3*3)
for i := range pix {
pix[i] = 0xff
}
whiteImage.WritePixels(pix, image.Rect(0, 0, 3, 3))
}
type Image struct {
img *atlas.Image
width int
height int
// dotsBuffer is a buffer for drawing a lot of dots.
// An entry in this map is the primary data of pixels for ReadPixels.
dotsBuffer map[image.Point][4]byte
// pixels is cached pixels for ReadPixels.
// pixels might be out of sync with GPU.
// The data of pixels is the secondary data of pixels for ReadPixels.
pixels []byte
// pixelsUnsynced represents whether the pixels in CPU and GPU are not synced.
pixelsUnsynced bool
}
func NewImage(width, height int, imageType atlas.ImageType) *Image {
return &Image{
img: atlas.NewImage(width, height, imageType),
width: width,
height: height,
}
}
func (i *Image) Deallocate() {
i.img.Deallocate()
i.dotsBuffer = nil
i.pixels = nil
i.pixelsUnsynced = false
}
func (i *Image) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte, region image.Rectangle) (bool, error) {
// Do not call flushDotsBufferIfNeeded here. This would slow (image/draw).Draw.
// See ebiten.TestImageDrawOver.
if region.Dx() == 1 && region.Dy() == 1 {
if c, ok := i.dotsBuffer[region.Min]; ok {
copy(pixels, c[:])
return true, nil
}
}
if i.pixels == nil {
pix := make([]byte, 4*i.width*i.height)
ok, err := i.img.ReadPixels(graphicsDriver, pix, image.Rect(0, 0, i.width, i.height))
if err != nil {
return false, err
}
if !ok {
return false, nil
}
i.pixels = pix
}
if len(i.dotsBuffer) > 0 {
for pos, clr := range i.dotsBuffer {
idx := 4 * (pos.Y*i.width + pos.X)
i.pixels[idx] = clr[0]
i.pixels[idx+1] = clr[1]
i.pixels[idx+2] = clr[2]
i.pixels[idx+3] = clr[3]
delete(i.dotsBuffer, pos)
}
i.pixelsUnsynced = true
}
lineWidth := 4 * region.Dx()
for j := 0; j < region.Dy(); j++ {
dstX := 4 * j * region.Dx()
srcX := 4 * ((region.Min.Y+j)*i.width + region.Min.X)
copy(pixels[dstX:dstX+lineWidth], i.pixels[srcX:srcX+lineWidth])
}
return true, nil
}
func (i *Image) DumpScreenshot(graphicsDriver graphicsdriver.Graphics, name string, blackbg bool) (string, error) {
i.syncPixelsIfNeeded()
return i.img.DumpScreenshot(graphicsDriver, name, blackbg)
}
// WritePixels replaces the pixels at the specified region.
func (i *Image) WritePixels(pix []byte, region image.Rectangle) {
if l := 4 * region.Dx() * region.Dy(); len(pix) != l {
panic(fmt.Sprintf("buffered: len(pix) was %d but must be %d", len(pix), l))
}
// Writing one pixel is a special case.
// Do not write pixels in GPU, as (image/draw).Image's functions might call WritePixels with pixels one by one.
if region.Dx() == 1 && region.Dy() == 1 {
// If i.pixels exists, update this instead of adding an entry to dotsBuffer.
if i.pixels != nil {
idx := 4 * (region.Min.Y*i.width + region.Min.X)
i.pixels[idx] = pix[0]
i.pixels[idx+1] = pix[1]
i.pixels[idx+2] = pix[2]
i.pixels[idx+3] = pix[3]
i.pixelsUnsynced = true
delete(i.dotsBuffer, region.Min)
return
}
if i.dotsBuffer == nil {
i.dotsBuffer = map[image.Point][4]byte{}
}
var clr [4]byte
copy(clr[:], pix)
i.dotsBuffer[region.Min] = clr
if len(i.dotsBuffer) >= 10000 {
i.syncPixelsIfNeeded()
}
return
}
// If i.pixels is not nil, this indicates ReadPixels is called and might be called again later.
// Keep and update the pixels data in this case.
if i.pixels != nil {
lineWidth := 4 * region.Dx()
for j := 0; j < region.Dy(); j++ {
dstX := 4 * ((region.Min.Y+j)*i.width + region.Min.X)
srcX := 4 * j * region.Dx()
copy(i.pixels[dstX:dstX+lineWidth], pix[srcX:srcX+lineWidth])
}
// pixelsUnsynced can NOT be set false as the outside pixels of the region is not written by WritePixels here.
// See the test TestUnsyncedPixels.
}
// Even if i.pixels is nil, do not create a pixel cache.
// It is in theroy possible to copy the argument pixels, but this tends to consume a lot of memory.
// Avoid this unless ReadPixels is called.
// Remove entries in the dots buffer that are overwritten by this WritePixels call.
for pos := range i.dotsBuffer {
if !pos.In(region) {
continue
}
delete(i.dotsBuffer, pos)
}
i.img.WritePixels(pix, region)
}
// DrawTriangles draws the src image with the given vertices.
//
// Copying vertices and indices is the caller's responsibility.
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderImageCount]image.Rectangle, shader *atlas.Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) {
for _, src := range srcs {
if i == src {
panic("buffered: Image.DrawTriangles: source images must be different from the receiver")
}
if src != nil {
// src's pixels have to be synced between CPU and GPU,
// but doesn't have to be cleared since src is not modified in this function.
src.syncPixelsIfNeeded()
}
}
i.syncPixelsIfNeeded()
var imgs [graphics.ShaderImageCount]*atlas.Image
for i, img := range srcs {
if img == nil {
continue
}
imgs[i] = img.img
}
i.img.DrawTriangles(imgs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule)
// After rendering, the pixel cache is no longer valid.
i.pixels = nil
}
// syncPixelsIfNeeded syncs the pixels between CPU and GPU.
// After syncPixelsIfNeeded, dotsBuffer is cleared, but pixels might remain.
func (i *Image) syncPixelsIfNeeded() {
if i.pixelsUnsynced {
// If this image already has pixels, use WritePixels instead of DrawTriangles for efficiency.
for pos, clr := range i.dotsBuffer {
idx := 4 * (pos.Y*i.width + pos.X)
i.pixels[idx] = clr[0]
i.pixels[idx+1] = clr[1]
i.pixels[idx+2] = clr[2]
i.pixels[idx+3] = clr[3]
delete(i.dotsBuffer, pos)
}
i.img.WritePixels(i.pixels, image.Rect(0, 0, i.width, i.height))
i.pixelsUnsynced = false
return
}
if len(i.dotsBuffer) == 0 {
return
}
l := len(i.dotsBuffer)
vs := make([]float32, l*4*graphics.VertexFloatCount)
is := make([]uint32, l*6)
sx, sy := float32(1), float32(1)
var idx int
for p, c := range i.dotsBuffer {
dx := float32(p.X)
dy := float32(p.Y)
crf := float32(c[0]) / 0xff
cgf := float32(c[1]) / 0xff
cbf := float32(c[2]) / 0xff
caf := float32(c[3]) / 0xff
vs[graphics.VertexFloatCount*4*idx] = dx
vs[graphics.VertexFloatCount*4*idx+1] = dy
vs[graphics.VertexFloatCount*4*idx+2] = sx
vs[graphics.VertexFloatCount*4*idx+3] = sy
vs[graphics.VertexFloatCount*4*idx+4] = crf
vs[graphics.VertexFloatCount*4*idx+5] = cgf
vs[graphics.VertexFloatCount*4*idx+6] = cbf
vs[graphics.VertexFloatCount*4*idx+7] = caf
vs[graphics.VertexFloatCount*4*idx+8] = dx + 1
vs[graphics.VertexFloatCount*4*idx+9] = dy
vs[graphics.VertexFloatCount*4*idx+10] = sx + 1
vs[graphics.VertexFloatCount*4*idx+11] = sy
vs[graphics.VertexFloatCount*4*idx+12] = crf
vs[graphics.VertexFloatCount*4*idx+13] = cgf
vs[graphics.VertexFloatCount*4*idx+14] = cbf
vs[graphics.VertexFloatCount*4*idx+15] = caf
vs[graphics.VertexFloatCount*4*idx+16] = dx
vs[graphics.VertexFloatCount*4*idx+17] = dy + 1
vs[graphics.VertexFloatCount*4*idx+18] = sx
vs[graphics.VertexFloatCount*4*idx+19] = sy + 1
vs[graphics.VertexFloatCount*4*idx+20] = crf
vs[graphics.VertexFloatCount*4*idx+21] = cgf
vs[graphics.VertexFloatCount*4*idx+22] = cbf
vs[graphics.VertexFloatCount*4*idx+23] = caf
vs[graphics.VertexFloatCount*4*idx+24] = dx + 1
vs[graphics.VertexFloatCount*4*idx+25] = dy + 1
vs[graphics.VertexFloatCount*4*idx+26] = sx + 1
vs[graphics.VertexFloatCount*4*idx+27] = sy + 1
vs[graphics.VertexFloatCount*4*idx+28] = crf
vs[graphics.VertexFloatCount*4*idx+29] = cgf
vs[graphics.VertexFloatCount*4*idx+30] = cbf
vs[graphics.VertexFloatCount*4*idx+31] = caf
is[6*idx] = uint32(4 * idx)
is[6*idx+1] = uint32(4*idx + 1)
is[6*idx+2] = uint32(4*idx + 2)
is[6*idx+3] = uint32(4*idx + 1)
is[6*idx+4] = uint32(4*idx + 2)
is[6*idx+5] = uint32(4*idx + 3)
idx++
}
srcs := [graphics.ShaderImageCount]*atlas.Image{whiteImage.img}
dr := image.Rect(0, 0, i.width, i.height)
blend := graphicsdriver.BlendCopy
i.img.DrawTriangles(srcs, vs, is, blend, dr, [graphics.ShaderImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
// TODO: Use clear if Go 1.21 is available.
for pos := range i.dotsBuffer {
delete(i.dotsBuffer, pos)
}
}
@@ -0,0 +1,167 @@
// Copyright 2022 The Ebitengine 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.
package builtinshader
import (
"bytes"
"fmt"
"sync"
"text/template"
)
type Filter int
const (
FilterNearest Filter = iota
FilterLinear
)
const FilterCount = 2
type Address int
const (
AddressUnsafe Address = iota
AddressClampToZero
AddressRepeat
)
const AddressCount = 3
const (
UniformColorMBody = "ColorMBody"
UniformColorMTranslation = "ColorMTranslation"
)
var (
shaders [FilterCount][AddressCount][2][]byte
shadersM sync.Mutex
)
var tmpl = template.Must(template.New("tmpl").Parse(`//kage:unit pixels
package main
{{if .UseColorM}}
var ColorMBody mat4
var ColorMTranslation vec4
{{end}}
{{if eq .Address .AddressRepeat}}
func adjustTexelForAddressRepeat(p vec2) vec2 {
origin := imageSrc0Origin()
size := imageSrc0Size()
return mod(p - origin, size) + origin
}
{{end}}
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
{{if eq .Filter .FilterNearest}}
{{if eq .Address .AddressUnsafe}}
clr := imageSrc0UnsafeAt(srcPos)
{{else if eq .Address .AddressClampToZero}}
clr := imageSrc0At(srcPos)
{{else if eq .Address .AddressRepeat}}
clr := imageSrc0At(adjustTexelForAddressRepeat(srcPos))
{{end}}
{{else if eq .Filter .FilterLinear}}
p0 := srcPos - 1/2.0
p1 := srcPos + 1/2.0
{{if eq .Address .AddressRepeat}}
p0 = adjustTexelForAddressRepeat(p0)
p1 = adjustTexelForAddressRepeat(p1)
{{end}}
{{if eq .Address .AddressUnsafe}}
c0 := imageSrc0UnsafeAt(p0)
c1 := imageSrc0UnsafeAt(vec2(p1.x, p0.y))
c2 := imageSrc0UnsafeAt(vec2(p0.x, p1.y))
c3 := imageSrc0UnsafeAt(p1)
{{else}}
c0 := imageSrc0At(p0)
c1 := imageSrc0At(vec2(p1.x, p0.y))
c2 := imageSrc0At(vec2(p0.x, p1.y))
c3 := imageSrc0At(p1)
{{end}}
rate := fract(p1)
clr := mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y)
{{end}}
{{if .UseColorM}}
// Un-premultiply alpha.
// When the alpha is 0, 1-sign(alpha) is 1.0, which means division does nothing.
clr.rgb /= clr.a + (1-sign(clr.a))
// Apply the clr matrix.
clr = (ColorMBody * clr) + ColorMTranslation
// Premultiply alpha
clr.rgb *= clr.a
// Apply the color scale.
clr *= color
// Clamp the output.
clr.rgb = min(clr.rgb, clr.a)
{{else}}
// Apply the color scale.
clr *= color
{{end}}
return clr
}
`))
// Shader returns the built-in shader based on the given parameters.
//
// The returned shader always uses a color matrix so far.
func Shader(filter Filter, address Address, useColorM bool) []byte {
shadersM.Lock()
defer shadersM.Unlock()
var c int
if useColorM {
c = 1
}
if s := shaders[filter][address][c]; s != nil {
return s
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, struct {
Filter Filter
FilterNearest Filter
FilterLinear Filter
Address Address
AddressUnsafe Address
AddressClampToZero Address
AddressRepeat Address
UseColorM bool
}{
Filter: filter,
FilterNearest: FilterNearest,
FilterLinear: FilterLinear,
Address: address,
AddressUnsafe: AddressUnsafe,
AddressClampToZero: AddressClampToZero,
AddressRepeat: AddressRepeat,
UseColorM: useColorM,
}); err != nil {
panic(fmt.Sprintf("builtinshader: tmpl.Execute failed: %v", err))
}
b := buf.Bytes()
shaders[filter][address][c] = b
return b
}
+175
View File
@@ -0,0 +1,175 @@
// Copyright 2017 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.
// Package clock manages game timers.
package clock
import (
"sync"
"time"
)
const (
DefaultTPS = 60
SyncWithFPS = -1
)
var (
// tps represents TPS (ticks per second).
tps = DefaultTPS
lastNow int64
// lastSystemTime is the last system time in the previous UpdateFrame.
// lastSystemTime indicates the logical time in the game, so this can be bigger than the current time.
lastSystemTime int64
actualFPS float64
actualTPS float64
prevTPS int64
lastUpdated int64
fpsCount = 0
tpsCount = 0
m sync.Mutex
)
func init() {
n := now()
lastNow = n
lastSystemTime = n
lastUpdated = n
}
func ActualFPS() float64 {
m.Lock()
defer m.Unlock()
return actualFPS
}
func ActualTPS() float64 {
m.Lock()
defer m.Unlock()
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
}
if tps < 0 {
panic("clock: tps must >= 0")
}
diff := now - lastSystemTime
if diff < 0 {
return 0
}
count := 0
syncWithSystemClock := false
// Detect whether the previous time is too old.
// Use either 5 ticks or 5/60 sec in the case when TPS is too big like 300 (#1444).
if diff > max(int64(time.Second)*5/tps, int64(time.Second)*5/60) || prevTPS != tps {
// The previous time is too old.
// Let's force to sync the game time with the system clock.
syncWithSystemClock = true
} else {
count = int(diff * tps / int64(time.Second))
}
prevTPS = tps
// Stabilize the count.
// Without this adjustment, count can be unstable like 0, 2, 0, 2, ...
// TODO: Brush up this logic so that this will work with any FPS. Now this works only when FPS = TPS.
if count == 0 && (int64(time.Second)/tps/2) < diff {
count = 1
}
if count == 2 && (int64(time.Second)/tps*3/2) > diff {
count = 1
}
if syncWithSystemClock {
lastSystemTime = now
} else {
lastSystemTime += int64(count) * int64(time.Second) / tps
}
return count
}
func updateFPSAndTPS(now int64, count int) {
fpsCount++
tpsCount += count
if now < lastUpdated {
panic("clock: lastUpdated must be older than now")
}
if time.Second > time.Duration(now-lastUpdated) {
return
}
actualFPS = float64(fpsCount) * float64(time.Second) / float64(now-lastUpdated)
actualTPS = float64(tpsCount) * float64(time.Second) / float64(now-lastUpdated)
lastUpdated = now
fpsCount = 0
tpsCount = 0
}
// UpdateFrame updates the inner clock state and returns an integer value
// indicating how many times the game should update based on the current tps.
//
// If tps is SyncWithFPS, UpdateFrame always returns 1.
// If tps <= 0 and not SyncWithFPS, UpdateFrame always returns 0.
//
// UpdateFrame is expected to be called once per frame.
func UpdateFrame() int {
m.Lock()
defer m.Unlock()
n := now()
if lastNow > n {
// This ensures that now() must be monotonic (#875).
panic("clock: lastNow must be older than n")
}
lastNow = n
c := 0
if tps == SyncWithFPS {
c = 1
} else if tps > 0 {
c = calcCountFromTPS(int64(tps), n)
}
updateFPSAndTPS(n, c)
return c
}
func SetTPS(newTPS int) {
m.Lock()
defer m.Unlock()
tps = newTPS
}
func TPS() int {
m.Lock()
defer m.Unlock()
return tps
}
+27
View File
@@ -0,0 +1,27 @@
// Copyright 2016 Hajime Hoshi
//
// 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.
package clock
import (
"time"
)
var initTime = time.Now()
func now() int64 {
// time.Since() returns monotonic timer difference (#875):
// https://pkg.go.dev/time#hdr-Monotonic_Clocks
return int64(time.Since(initTime))
}
@@ -0,0 +1,306 @@
// Copyright 2022 The Ebitengine 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.
package cocoa
import (
"unsafe"
"github.com/ebitengine/purego/objc"
)
var (
class_NSInvocation = objc.GetClass("NSInvocation")
class_NSMethodSignature = objc.GetClass("NSMethodSignature")
class_NSAutoreleasePool = objc.GetClass("NSAutoreleasePool")
class_NSString = objc.GetClass("NSString")
class_NSProcessInfo = objc.GetClass("NSProcessInfo")
class_NSColor = objc.GetClass("NSColor")
class_NSWindow = objc.GetClass("NSWindow")
class_NSView = objc.GetClass("NSView")
class_NSScreen = objc.GetClass("NSScreen")
)
var (
sel_alloc = objc.RegisterName("alloc")
sel_new = objc.RegisterName("new")
sel_release = objc.RegisterName("release")
sel_invocationWithMethodSignature = objc.RegisterName("invocationWithMethodSignature:")
sel_setSelector = objc.RegisterName("setSelector:")
sel_setTarget = objc.RegisterName("setTarget:")
sel_setArgumentAtIndex = objc.RegisterName("setArgument:atIndex:")
sel_getReturnValue = objc.RegisterName("getReturnValue:")
sel_invoke = objc.RegisterName("invoke")
sel_invokeWithTarget = objc.RegisterName("invokeWithTarget:")
sel_instanceMethodSignatureForSelector = objc.RegisterName("instanceMethodSignatureForSelector:")
sel_signatureWithObjCTypes = objc.RegisterName("signatureWithObjCTypes:")
sel_initWithUTF8String = objc.RegisterName("initWithUTF8String:")
sel_UTF8String = objc.RegisterName("UTF8String")
sel_length = objc.RegisterName("length")
sel_processInfo = objc.RegisterName("processInfo")
sel_frame = objc.RegisterName("frame")
sel_contentView = objc.RegisterName("contentView")
sel_setBackgroundColor = objc.RegisterName("setBackgroundColor:")
sel_colorWithSRGBRedGreenBlueAlpha = objc.RegisterName("colorWithSRGBRed:green:blue:alpha:")
sel_setFrameSize = objc.RegisterName("setFrameSize:")
sel_object = objc.RegisterName("object")
sel_styleMask = objc.RegisterName("styleMask")
sel_setStyleMask = objc.RegisterName("setStyleMask:")
sel_mainScreen = objc.RegisterName("mainScreen")
sel_screen = objc.RegisterName("screen")
sel_isVisible = objc.RegisterName("isVisible")
sel_deviceDescription = objc.RegisterName("deviceDescription")
sel_objectForKey = objc.RegisterName("objectForKey:")
sel_unsignedIntValue = objc.RegisterName("unsignedIntValue")
)
const (
NSWindowCollectionBehaviorManaged = 1 << 2
NSWindowCollectionBehaviorFullScreenPrimary = 1 << 7
NSWindowCollectionBehaviorFullScreenNone = 1 << 9
)
const (
NSWindowStyleMaskResizable = 1 << 3
NSWindowStyleMaskFullScreen = 1 << 14
)
type CGFloat = float64
type CGSize struct {
Width, Height CGFloat
}
type CGPoint struct {
X, Y float64
}
type CGRect struct {
Origin CGPoint
Size CGSize
}
type NSUInteger = uint
type NSInteger = int
type NSPoint = CGPoint
type NSRect = CGRect
type NSSize = CGSize
type NSError struct {
objc.ID
}
type NSColor struct {
objc.ID
}
func NSColor_colorWithSRGBRedGreenBlueAlpha(red, green, blue, alpha CGFloat) (color NSColor) {
return NSColor{objc.ID(class_NSColor).Send(sel_colorWithSRGBRedGreenBlueAlpha, red, green, blue, alpha)}
}
type NSOperatingSystemVersion struct {
Major, Minor, Patch NSInteger
}
type NSProcessInfo struct {
objc.ID
}
func NSProcessInfo_processInfo() NSProcessInfo {
return NSProcessInfo{objc.ID(class_NSProcessInfo).Send(sel_processInfo)}
}
type NSWindow struct {
objc.ID
}
func (w NSWindow) StyleMask() NSUInteger {
return NSUInteger(w.Send(sel_styleMask))
}
func (w NSWindow) SetStyleMask(styleMask NSUInteger) {
w.Send(sel_setStyleMask, styleMask)
}
func (w NSWindow) SetBackgroundColor(color NSColor) {
w.Send(sel_setBackgroundColor, color.ID)
}
func (w NSWindow) IsVisible() bool {
return w.Send(sel_isVisible) != 0
}
func (w NSWindow) Screen() NSScreen {
return NSScreen{w.Send(sel_screen)}
}
func (w NSWindow) Frame() NSRect {
sig := NSMethodSignature_instanceMethodSignatureForSelector(objc.ID(class_NSWindow), sel_frame)
inv := NSInvocation_invocationWithMethodSignature(sig)
inv.SetTarget(w.ID)
inv.SetSelector(sel_frame)
inv.Invoke()
var rect NSRect
inv.GetReturnValue(unsafe.Pointer(&rect))
return rect
}
func (w NSWindow) ContentView() NSView {
return NSView{w.Send(sel_contentView)}
}
type NSView struct {
objc.ID
}
func (v NSView) SetFrameSize(size CGSize) {
sig := NSMethodSignature_instanceMethodSignatureForSelector(objc.ID(class_NSView), sel_setFrameSize)
inv := NSInvocation_invocationWithMethodSignature(sig)
inv.SetSelector(sel_setFrameSize)
inv.SetArgumentAtIndex(unsafe.Pointer(&size), 2)
inv.InvokeWithTarget(v.ID)
}
func (v NSView) Frame() NSRect {
sig := NSMethodSignature_instanceMethodSignatureForSelector(objc.ID(class_NSView), sel_frame)
inv := NSInvocation_invocationWithMethodSignature(sig)
inv.SetSelector(sel_frame)
inv.InvokeWithTarget(v.ID)
var rect NSRect
inv.GetReturnValue(unsafe.Pointer(&rect))
return rect
}
func (v NSView) SetLayer(layer uintptr) {
v.Send(objc.RegisterName("setLayer:"), layer)
}
func (v NSView) SetWantsLayer(wantsLayer bool) {
v.Send(objc.RegisterName("setWantsLayer:"), wantsLayer)
}
// NSInvocation is being used to call functions that can't be called directly with purego.SyscallN.
// See the downsides of that function for what it cannot do.
type NSInvocation struct {
objc.ID
}
func NSInvocation_invocationWithMethodSignature(sig NSMethodSignature) NSInvocation {
return NSInvocation{objc.ID(class_NSInvocation).Send(sel_invocationWithMethodSignature, sig.ID)}
}
func (i NSInvocation) SetSelector(cmd objc.SEL) {
i.Send(sel_setSelector, cmd)
}
func (i NSInvocation) SetTarget(target objc.ID) {
i.Send(sel_setTarget, target)
}
func (i NSInvocation) SetArgumentAtIndex(arg unsafe.Pointer, idx int) {
i.Send(sel_setArgumentAtIndex, arg, idx)
}
func (i NSInvocation) GetReturnValue(ret unsafe.Pointer) {
i.Send(sel_getReturnValue, ret)
}
func (i NSInvocation) Invoke() {
i.Send(sel_invoke)
}
func (i NSInvocation) InvokeWithTarget(target objc.ID) {
i.Send(sel_invokeWithTarget, target)
}
type NSMethodSignature struct {
objc.ID
}
func NSMethodSignature_instanceMethodSignatureForSelector(self objc.ID, cmd objc.SEL) NSMethodSignature {
return NSMethodSignature{self.Send(sel_instanceMethodSignatureForSelector, cmd)}
}
// NSMethodSignature_signatureWithObjCTypes takes a string that represents the type signature of a method.
// It follows the encoding specified in the Apple Docs.
//
// [Apple Docs]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100
func NSMethodSignature_signatureWithObjCTypes(types string) NSMethodSignature {
return NSMethodSignature{objc.ID(class_NSMethodSignature).Send(sel_signatureWithObjCTypes, types)}
}
type NSAutoreleasePool struct {
objc.ID
}
func NSAutoreleasePool_new() NSAutoreleasePool {
return NSAutoreleasePool{objc.ID(class_NSAutoreleasePool).Send(sel_new)}
}
func (p NSAutoreleasePool) Release() {
p.Send(sel_release)
}
type NSString struct {
objc.ID
}
func NSString_alloc() NSString {
return NSString{objc.ID(class_NSString).Send(sel_alloc)}
}
func (s NSString) InitWithUTF8String(utf8 string) NSString {
return NSString{s.Send(sel_initWithUTF8String, utf8)}
}
func (s NSString) String() string {
return string(unsafe.Slice((*byte)(unsafe.Pointer(s.Send(sel_UTF8String))), s.Send(sel_length)))
}
type NSNotification struct {
objc.ID
}
func (n NSNotification) Object() objc.ID {
return n.Send(sel_object)
}
type NSScreen struct {
objc.ID
}
func NSScreen_mainScreen() NSScreen {
return NSScreen{objc.ID(class_NSScreen).Send(sel_mainScreen)}
}
func (s NSScreen) DeviceDescription() NSDictionary {
return NSDictionary{s.Send(sel_deviceDescription)}
}
type NSDictionary struct {
objc.ID
}
func (d NSDictionary) ObjectForKey(object objc.ID) objc.ID {
return d.Send(sel_objectForKey, object)
}
type NSNumber struct {
objc.ID
}
func (n NSNumber) UnsignedIntValue() uint {
return uint(n.Send(sel_unsignedIntValue))
}
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2023 The Ebitengine 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.
package debug
type Logger interface {
Logf(format string, args ...any)
Flush()
}
@@ -0,0 +1,75 @@
// Copyright 2020 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 ebitenginedebug || ebitendebug
package debug
import (
"fmt"
"sync"
)
const IsDebug = true
var theLogger = &logger{}
var flushM sync.Mutex
// Logf calls the current global logger's Logf.
// Logf buffers the arguments and doesn't dump the log immediately.
// You can dump logs by calling SwitchLogger and Flush.
//
// Logf is not concurrent safe.
func Logf(format string, args ...any) {
theLogger.Logf(format, args...)
}
// SwitchLogger sets a new logger as the current logger and returns the original global logger.
// The new global logger and the returned logger have separate statuses, so you can use them for different goroutines.
//
// SwitchLogger and a returned Logger are not concurrent safe.
func SwitchLogger() Logger {
current := theLogger
theLogger = &logger{}
return current
}
type logger struct {
items []logItem
}
type logItem struct {
format string
args []any
}
func (l *logger) Logf(format string, args ...any) {
l.items = append(l.items, logItem{
format: format,
args: args,
})
}
func (l *logger) Flush() {
// Flushing is protected by a mutex not to mix another logger's logs.
flushM.Lock()
defer flushM.Unlock()
for i, item := range l.items {
fmt.Printf(item.format, item.args...)
l.items[i] = logItem{}
}
l.items = l.items[:0]
}
@@ -0,0 +1,34 @@
// Copyright 2020 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 !ebitenginedebug && !ebitendebug
package debug
const IsDebug = false
func Logf(format string, args ...any) {
}
func SwitchLogger() Logger {
return dummyLogger{}
}
type dummyLogger struct{}
func (dummyLogger) Logf(format string, args ...any) {
}
func (dummyLogger) Flush() {
}
+293
View File
@@ -0,0 +1,293 @@
// Copyright 2023 The Ebitengine 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.
package file
import (
"errors"
"fmt"
"io"
"io/fs"
"syscall/js"
"time"
)
type FileEntryFS struct {
rootEntry js.Value
}
func NewFileEntryFS(root js.Value) *FileEntryFS {
return &FileEntryFS{
rootEntry: root,
}
}
func (f *FileEntryFS) Open(name string) (fs.File, error) {
if !fs.ValidPath(name) {
return nil, &fs.PathError{
Op: "open",
Path: name,
Err: fs.ErrNotExist,
}
}
if name == "." {
return &dir{entry: f.rootEntry}, nil
}
var chEntry chan js.Value
cbSuccess := js.FuncOf(func(this js.Value, args []js.Value) any {
chEntry <- args[0]
close(chEntry)
return nil
})
defer cbSuccess.Release()
cbFailure := js.FuncOf(func(this js.Value, args []js.Value) any {
close(chEntry)
return nil
})
defer cbFailure.Release()
chEntry = make(chan js.Value)
f.rootEntry.Call("getFile", name, nil, cbSuccess, cbFailure)
if entry := <-chEntry; entry.Truthy() {
return &file{entry: entry}, nil
}
chEntry = make(chan js.Value)
f.rootEntry.Call("getDirectory", name, nil, cbSuccess, cbFailure)
if entry := <-chEntry; entry.Truthy() {
return &dir{entry: entry}, nil
}
return nil, &fs.PathError{
Op: "open",
Path: name,
Err: fs.ErrNotExist,
}
}
type file struct {
entry js.Value
file js.Value
offset int64
uint8Array js.Value
}
func getFile(entry js.Value) js.Value {
ch := make(chan js.Value, 1)
cb := js.FuncOf(func(this js.Value, args []js.Value) any {
ch <- args[0]
return nil
})
defer cb.Release()
entry.Call("file", cb)
return <-ch
}
func (f *file) ensureFile() js.Value {
if f.file.Truthy() {
return f.file
}
f.file = getFile(f.entry)
return f.file
}
func (f *file) Stat() (fs.FileInfo, error) {
return &fileInfo{
entry: f.entry,
file: f.ensureFile(),
}, nil
}
func (f *file) Read(buf []byte) (int, error) {
if !f.uint8Array.Truthy() {
chArrayBuffer := make(chan js.Value, 1)
cbThen := js.FuncOf(func(this js.Value, args []js.Value) any {
chArrayBuffer <- args[0]
return nil
})
defer cbThen.Release()
chError := make(chan js.Value, 1)
cbCatch := js.FuncOf(func(this js.Value, args []js.Value) any {
chError <- args[0]
return nil
})
defer cbCatch.Release()
f.ensureFile().Call("arrayBuffer").Call("then", cbThen).Call("catch", cbCatch)
select {
case ab := <-chArrayBuffer:
f.uint8Array = js.Global().Get("Uint8Array").New(ab)
case err := <-chError:
return 0, fmt.Errorf("%s", err.Call("toString").String())
}
}
size := int64(f.uint8Array.Get("byteLength").Float())
if f.offset >= size {
return 0, io.EOF
}
if len(buf) == 0 {
return 0, nil
}
slice := f.uint8Array.Call("subarray", f.offset, f.offset+int64(len(buf)))
n := slice.Get("byteLength").Int()
js.CopyBytesToGo(buf[:n], slice)
f.offset += int64(n)
if f.offset >= size {
return n, io.EOF
}
return n, nil
}
func (f *file) Close() error {
return nil
}
type dir struct {
entry js.Value
entries []js.Value
offset int
}
func (d *dir) Stat() (fs.FileInfo, error) {
return &fileInfo{
entry: d.entry,
}, nil
}
func (d *dir) Read(buf []byte) (int, error) {
return 0, &fs.PathError{
Op: "read",
Path: d.entry.Get("name").String(),
Err: errors.New("is a directory"),
}
}
func (d *dir) Close() error {
return nil
}
func (d *dir) ReadDir(count int) ([]fs.DirEntry, error) {
if d.entries == nil {
ch := make(chan struct{})
var rec js.Func
cb := js.FuncOf(func(this js.Value, args []js.Value) any {
entries := args[0]
if entries.Length() == 0 {
close(ch)
return nil
}
for i := 0; i < entries.Length(); i++ {
ent := entries.Index(i)
// A name can be empty when this directory is a root directory.
if ent.Get("name").String() == "" {
continue
}
if !ent.Get("isFile").Bool() && !ent.Get("isDirectory").Bool() {
continue
}
d.entries = append(d.entries, ent)
}
rec.Value.Call("call")
return nil
})
defer cb.Release()
reader := d.entry.Call("createReader")
rec = js.FuncOf(func(this js.Value, args []js.Value) any {
reader.Call("readEntries", cb)
return nil
})
defer rec.Release()
rec.Value.Call("call")
<-ch
}
n := len(d.entries) - d.offset
if n == 0 {
if count <= 0 {
return nil, nil
}
return nil, io.EOF
}
if count > 0 && n > count {
n = count
}
ents := make([]fs.DirEntry, n)
for i := range ents {
fi := &fileInfo{
entry: d.entries[d.offset+i],
}
if fi.entry.Get("isFile").Bool() {
fi.file = getFile(fi.entry)
}
ents[i] = fs.FileInfoToDirEntry(fi)
}
d.offset += n
return ents, nil
}
type fileInfo struct {
entry js.Value
file js.Value
}
func (f *fileInfo) Name() string {
return f.entry.Get("name").String()
}
func (f *fileInfo) Size() int64 {
if !f.file.Truthy() {
return 0
}
return int64(f.file.Get("size").Float())
}
func (f *fileInfo) Mode() fs.FileMode {
if !f.file.Truthy() {
return 0555 | fs.ModeDir
}
return 0444
}
func (f *fileInfo) ModTime() time.Time {
if !f.file.Truthy() {
return time.Time{}
}
return time.UnixMilli(int64(f.file.Get("lastModified").Float()))
}
func (f *fileInfo) IsDir() bool {
if !f.file.Truthy() {
return true
}
return false
}
func (f *fileInfo) Sys() any {
return nil
}
+174
View File
@@ -0,0 +1,174 @@
// Copyright 2023 The Ebitengine 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 !js
package file
import (
"errors"
"io"
"io/fs"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"time"
)
type VirtualFS struct {
paths []string
}
func NewVirtualFS(paths []string) *VirtualFS {
fs := &VirtualFS{}
fs.paths = make([]string, len(paths))
copy(fs.paths, paths)
return fs
}
func (v *VirtualFS) newRootFS() *virtualFSRoot {
var root virtualFSRoot
root.addRealPaths(v.paths)
return &root
}
func (v *VirtualFS) Open(name string) (fs.File, error) {
if !fs.ValidPath(name) {
return nil, &fs.PathError{
Op: "open",
Path: name,
Err: fs.ErrNotExist,
}
}
if name == "." {
return v.newRootFS(), nil
}
// A valid path must not include a token "." or "..", except for "." itself.
es := strings.Split(name, "/")
for _, realPath := range v.paths {
if filepath.Base(realPath) != es[0] {
continue
}
// TODO: Does this work on Windows?
return os.Open(filepath.Join(append([]string{realPath}, es[1:]...)...))
}
return nil, &fs.PathError{
Op: "open",
Path: name,
Err: fs.ErrNotExist,
}
}
type virtualFSRoot struct {
realPaths []string
offset int
m sync.Mutex
}
func (v *virtualFSRoot) addRealPaths(paths []string) {
v.m.Lock()
defer v.m.Unlock()
for _, path := range paths {
// If the path consists entirely of separators, filepath.Base returns a single separator.
// On Windows, filepath.Base(`C:\`) == `\`.
// Skip root directory paths on purpose. This is almost the same behavior as the Chrome browser.
if filepath.Base(path) == string(filepath.Separator) {
continue
}
v.realPaths = append(v.realPaths, path)
}
sort.Strings(v.realPaths)
}
func (v *virtualFSRoot) Stat() (fs.FileInfo, error) {
return &virtualFSRootFileInfo{}, nil
}
func (v *virtualFSRoot) Read([]byte) (int, error) {
return 0, &fs.PathError{
Op: "read",
Path: "/",
Err: errors.New("is a directory"),
}
}
func (v *virtualFSRoot) Close() error {
return nil
}
func (v *virtualFSRoot) ReadDir(count int) ([]fs.DirEntry, error) {
v.m.Lock()
defer v.m.Unlock()
n := len(v.realPaths) - v.offset
if n == 0 {
if count <= 0 {
return nil, nil
}
return nil, io.EOF
}
if count > 0 && n > count {
n = count
}
ents := make([]fs.DirEntry, n)
for i := range ents {
fi, err := os.Stat(v.realPaths[v.offset+i])
if err != nil {
if count <= 0 {
return ents, err
}
return nil, err
}
ents[i] = fs.FileInfoToDirEntry(fi)
}
v.offset += n
return ents, nil
}
type virtualFSRootFileInfo struct {
}
func (v *virtualFSRootFileInfo) Name() string {
return "."
}
func (v *virtualFSRootFileInfo) Size() int64 {
return 0
}
func (v *virtualFSRootFileInfo) Mode() fs.FileMode {
return 0555 | fs.ModeDir
}
func (v *virtualFSRootFileInfo) ModTime() time.Time {
return time.Time{}
}
func (v *virtualFSRootFileInfo) IsDir() bool {
return true
}
func (v *virtualFSRootFileInfo) Sys() any {
return nil
}
@@ -0,0 +1,113 @@
// Copyright 2022 The Ebitengine 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.
package gamepad
import (
"unsafe"
"github.com/ebitengine/purego"
)
type (
_CFIndex int64
_CFAllocatorRef uintptr
_CFArrayRef uintptr
_CFDictionaryRef uintptr
_CFNumberRef uintptr
_CFTypeRef uintptr
_CFRunLoopRef uintptr
_CFNumberType uintptr
_CFStringRef uintptr
_CFArrayCallBacks struct{}
_CFDictionaryKeyCallBacks struct{}
_CFDictionaryValueCallBacks struct{}
_CFRunLoopRunResult int32
_CFRunLoopMode = _CFStringRef
_CFTimeInterval float64
_CFTypeID uint64
_CFStringEncoding uint32
)
var kCFAllocatorDefault _CFAllocatorRef = 0
const (
kCFStringEncodingUTF8 _CFStringEncoding = 0x08000100
)
const (
kCFNumberSInt32Type _CFNumberType = 3
kCFNumberIntType _CFNumberType = 9
)
var (
kCFTypeDictionaryKeyCallBacks uintptr
kCFTypeDictionaryValueCallBacks uintptr
kCFTypeArrayCallBacks uintptr
kCFRunLoopDefaultMode uintptr
)
func initializeCF() error {
corefoundation, err := purego.Dlopen("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", purego.RTLD_LAZY|purego.RTLD_GLOBAL)
if err != nil {
return err
}
kCFTypeDictionaryKeyCallBacks, err = purego.Dlsym(corefoundation, "kCFTypeDictionaryKeyCallBacks")
if err != nil {
return err
}
kCFTypeDictionaryValueCallBacks, err = purego.Dlsym(corefoundation, "kCFTypeDictionaryValueCallBacks")
if err != nil {
return err
}
kCFTypeArrayCallBacks, err = purego.Dlsym(corefoundation, "kCFTypeArrayCallBacks")
if err != nil {
return err
}
kCFRunLoopDefaultMode, err = purego.Dlsym(corefoundation, "kCFRunLoopDefaultMode")
if err != nil {
return err
}
purego.RegisterLibFunc(&_CFNumberCreate, corefoundation, "CFNumberCreate")
purego.RegisterLibFunc(&_CFNumberGetValue, corefoundation, "CFNumberGetValue")
purego.RegisterLibFunc(&_CFArrayCreate, corefoundation, "CFArrayCreate")
purego.RegisterLibFunc(&_CFArrayGetValueAtIndex, corefoundation, "CFArrayGetValueAtIndex")
purego.RegisterLibFunc(&_CFArrayGetCount, corefoundation, "CFArrayGetCount")
purego.RegisterLibFunc(&_CFDictionaryCreate, corefoundation, "CFDictionaryCreate")
purego.RegisterLibFunc(&_CFRelease, corefoundation, "CFRelease")
purego.RegisterLibFunc(&_CFRunLoopGetMain, corefoundation, "CFRunLoopGetMain")
purego.RegisterLibFunc(&_CFRunLoopRunInMode, corefoundation, "CFRunLoopRunInMode")
purego.RegisterLibFunc(&_CFGetTypeID, corefoundation, "CFGetTypeID")
purego.RegisterLibFunc(&_CFStringGetCString, corefoundation, "CFStringGetCString")
purego.RegisterLibFunc(&_CFStringCreateWithCString, corefoundation, "CFStringCreateWithCString")
return nil
}
var (
_CFNumberCreate func(allocator _CFAllocatorRef, theType _CFNumberType, valuePtr unsafe.Pointer) _CFNumberRef
_CFNumberGetValue func(number _CFNumberRef, theType _CFNumberType, valuePtr unsafe.Pointer) bool
_CFArrayCreate func(allocator _CFAllocatorRef, values *unsafe.Pointer, numValues _CFIndex, callbacks *_CFArrayCallBacks) _CFArrayRef
_CFArrayGetValueAtIndex func(array _CFArrayRef, index _CFIndex) uintptr
_CFArrayGetCount func(array _CFArrayRef) _CFIndex
_CFDictionaryCreate func(allocator _CFAllocatorRef, keys *unsafe.Pointer, values *unsafe.Pointer, numValues _CFIndex, keyCallBacks *_CFDictionaryKeyCallBacks, valueCallBacks *_CFDictionaryValueCallBacks) _CFDictionaryRef
_CFRelease func(cf _CFTypeRef)
_CFRunLoopGetMain func() _CFRunLoopRef
_CFRunLoopRunInMode func(mode _CFRunLoopMode, seconds _CFTimeInterval, returnAfterSourceHandled bool) _CFRunLoopRunResult
_CFGetTypeID func(cf _CFTypeRef) _CFTypeID
_CFStringGetCString func(theString _CFStringRef, buffer []byte, encoding _CFStringEncoding) bool
_CFStringCreateWithCString func(alloc _CFAllocatorRef, cstr []byte, encoding _CFStringEncoding) _CFStringRef
)
@@ -0,0 +1,503 @@
// Copyright 2022 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.
package gamepad
import (
"fmt"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
const (
_DI_OK = 0
_DI_NOEFFECT = _SI_FALSE
_DI_PROPNOEFFECT = _SI_FALSE
_DI_DEGREES = 100
_DI8DEVCLASS_GAMECTRL = 4
_DIDFT_ABSAXIS = 0x00000002
_DIDFT_AXIS = 0x00000003
_DIDFT_BUTTON = 0x0000000C
_DIDFT_POV = 0x00000010
_DIDFT_OPTIONAL = 0x80000000
_DIDFT_ANYINSTANCE = 0x00FFFF00
_DIDOI_ASPECTPOSITION = 0x00000100
_DIEDFL_ALLDEVICES = 0x00000000
_DIENUM_STOP = 0
_DIENUM_CONTINUE = 1
_DIERR_INPUTLOST = windows.SEVERITY_ERROR<<31 | windows.FACILITY_WIN32<<16 | windows.ERROR_READ_FAULT
_DIERR_NOTACQUIRED = windows.SEVERITY_ERROR<<31 | windows.FACILITY_WIN32<<16 | windows.ERROR_INVALID_ACCESS
_DIJOFS_X = uint32(unsafe.Offsetof(_DIJOYSTATE{}.lX))
_DIJOFS_Y = uint32(unsafe.Offsetof(_DIJOYSTATE{}.lY))
_DIJOFS_Z = uint32(unsafe.Offsetof(_DIJOYSTATE{}.lZ))
_DIJOFS_RX = uint32(unsafe.Offsetof(_DIJOYSTATE{}.lRx))
_DIJOFS_RY = uint32(unsafe.Offsetof(_DIJOYSTATE{}.lRy))
_DIJOFS_RZ = uint32(unsafe.Offsetof(_DIJOYSTATE{}.lRz))
_DIPH_DEVICE = 0
_DIPH_BYID = 2
_DIPROP_AXISMODE = 2
_DIPROP_GUIDANDPATH = 12
_DIPROP_RANGE = 4
_DIPROPAXISMODE_ABS = 0
_DIRECTINPUT_VERSION = 0x0800
_GWL_WNDPROC = -4
_MAX_PATH = 260
_RIDI_DEVICEINFO = 0x2000000b
_RIDI_DEVICENAME = 0x20000007
_RIM_TYPEHID = 2
_SI_FALSE = 1
_WM_DEVICECHANGE = 0x0219
_XINPUT_CAPS_WIRELESS = 0x0002
_XINPUT_DEVSUBTYPE_GAMEPAD = 0x01
_XINPUT_DEVSUBTYPE_WHEEL = 0x02
_XINPUT_DEVSUBTYPE_ARCADE_STICK = 0x03
_XINPUT_DEVSUBTYPE_FLIGHT_STICK = 0x04
_XINPUT_DEVSUBTYPE_DANCE_PAD = 0x05
_XINPUT_DEVSUBTYPE_GUITAR = 0x06
_XINPUT_DEVSUBTYPE_DRUM_KIT = 0x08
_XINPUT_GAMEPAD_DPAD_UP = 0x0001
_XINPUT_GAMEPAD_DPAD_DOWN = 0x0002
_XINPUT_GAMEPAD_DPAD_LEFT = 0x0004
_XINPUT_GAMEPAD_DPAD_RIGHT = 0x0008
_XINPUT_GAMEPAD_START = 0x0010
_XINPUT_GAMEPAD_BACK = 0x0020
_XINPUT_GAMEPAD_LEFT_THUMB = 0x0040
_XINPUT_GAMEPAD_RIGHT_THUMB = 0x0080
_XINPUT_GAMEPAD_LEFT_SHOULDER = 0x0100
_XINPUT_GAMEPAD_RIGHT_SHOULDER = 0x0200
_XINPUT_GAMEPAD_A = 0x1000
_XINPUT_GAMEPAD_B = 0x2000
_XINPUT_GAMEPAD_X = 0x4000
_XINPUT_GAMEPAD_Y = 0x8000
)
func _DIDFT_GETTYPE(n uint32) byte {
return byte(n)
}
func _DIJOFS_SLIDER(n int) uint32 {
return uint32(unsafe.Offsetof(_DIJOYSTATE{}.rglSlider) + uintptr(n)*unsafe.Sizeof(int32(0)))
}
func _DIJOFS_POV(n int) uint32 {
return uint32(unsafe.Offsetof(_DIJOYSTATE{}.rgdwPOV) + uintptr(n)*unsafe.Sizeof(uint32(0)))
}
func _DIJOFS_BUTTON(n int) uint32 {
return uint32(unsafe.Offsetof(_DIJOYSTATE{}.rgbButtons) + uintptr(n))
}
var (
_IID_IDirectInput8W = windows.GUID{Data1: 0xbf798031, Data2: 0x483a, Data3: 0x4da2, Data4: [...]byte{0xaa, 0x99, 0x5d, 0x64, 0xed, 0x36, 0x97, 0x00}}
_GUID_XAxis = windows.GUID{Data1: 0xa36d02e0, Data2: 0xc9f3, Data3: 0x11cf, Data4: [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
_GUID_YAxis = windows.GUID{Data1: 0xa36d02e1, Data2: 0xc9f3, Data3: 0x11cf, Data4: [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
_GUID_ZAxis = windows.GUID{Data1: 0xa36d02e2, Data2: 0xc9f3, Data3: 0x11cf, Data4: [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
_GUID_RxAxis = windows.GUID{Data1: 0xa36d02f4, Data2: 0xc9f3, Data3: 0x11cf, Data4: [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
_GUID_RyAxis = windows.GUID{Data1: 0xa36d02f5, Data2: 0xc9f3, Data3: 0x11cf, Data4: [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
_GUID_RzAxis = windows.GUID{Data1: 0xa36d02e3, Data2: 0xc9f3, Data3: 0x11cf, Data4: [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
_GUID_Slider = windows.GUID{Data1: 0xa36d02e4, Data2: 0xc9f3, Data3: 0x11cf, Data4: [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
_GUID_POV = windows.GUID{Data1: 0xa36d02f2, Data2: 0xc9f3, Data3: 0x11cf, Data4: [...]byte{0xbf, 0xc7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}}
)
var (
kernel32 = windows.NewLazySystemDLL("kernel32.dll")
user32 = windows.NewLazySystemDLL("user32.dll")
procGetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
procCallWindowProcW = user32.NewProc("CallWindowProcW")
procGetRawInputDeviceInfoW = user32.NewProc("GetRawInputDeviceInfoW")
procGetRawInputDeviceList = user32.NewProc("GetRawInputDeviceList")
procSetWindowLongW = user32.NewProc("SetWindowLongW") // 32-Bit Windows version.
procSetWindowLongPtrW = user32.NewProc("SetWindowLongPtrW") // 64-Bit Windows version.
)
func _GetModuleHandleW() (uintptr, error) {
m, _, e := procGetModuleHandleW.Call(0)
if m == 0 {
if e != nil && e != windows.ERROR_SUCCESS {
return 0, fmt.Errorf("gamepad: GetModuleHandleW failed: %w", e)
}
return 0, fmt.Errorf("gamepad: GetModuleHandleW returned 0")
}
return m, nil
}
func _CallWindowProcW(lpPrevWndFunc uintptr, hWnd uintptr, msg uint32, wParam, lParam uintptr) uintptr {
r, _, _ := procCallWindowProcW.Call(lpPrevWndFunc, hWnd, uintptr(msg), wParam, lParam)
return r
}
func _GetRawInputDeviceInfoW(hDevice windows.Handle, uiCommand uint32, pData unsafe.Pointer, pcb *uint32) (uint32, error) {
r, _, e := procGetRawInputDeviceInfoW.Call(uintptr(hDevice), uintptr(uiCommand), uintptr(pData), uintptr(unsafe.Pointer(pcb)))
if uint32(r) == ^uint32(0) {
if e != nil && e != windows.ERROR_SUCCESS {
return 0, fmt.Errorf("gamepad: GetRawInputDeviceInfoW failed: %w", e)
}
return 0, fmt.Errorf("gamepad: GetRawInputDeviceInfoW returned -1")
}
return uint32(r), nil
}
func _GetRawInputDeviceList(pRawInputDeviceList *_RAWINPUTDEVICELIST, puiNumDevices *uint32) (uint32, error) {
r, _, e := procGetRawInputDeviceList.Call(uintptr(unsafe.Pointer(pRawInputDeviceList)), uintptr(unsafe.Pointer(puiNumDevices)), unsafe.Sizeof(_RAWINPUTDEVICELIST{}))
if uint32(r) == ^uint32(0) {
if e != nil && e != windows.ERROR_SUCCESS {
return 0, fmt.Errorf("gamepad: GetRawInputDeviceList failed: %w", e)
}
return 0, fmt.Errorf("gamepad: GetRawInputDeviceList returned -1")
}
return uint32(r), nil
}
func _SetWindowLongPtrW(hWnd windows.HWND, nIndex int32, dwNewLong uintptr) (uintptr, error) {
var p *windows.LazyProc
if procSetWindowLongPtrW.Find() == nil {
// 64-Bit Windows.
p = procSetWindowLongPtrW
} else {
// 32-Bit Windows.
p = procSetWindowLongW
}
h, _, e := p.Call(uintptr(hWnd), uintptr(nIndex), dwNewLong)
if h == 0 {
if e != nil && e != windows.ERROR_SUCCESS {
return 0, fmt.Errorf("gamepad: SetWindowLongPtrW failed: %w", e)
}
return 0, fmt.Errorf("gamepad: SetWindowLongPtrW returned 0")
}
return h, nil
}
type _DIDATAFORMAT struct {
dwSize uint32
dwObjSize uint32
dwFlags uint32
dwDataSize uint32
dwNumObjs uint32
rgodf *_DIOBJECTDATAFORMAT
}
type _DIDEVCAPS struct {
dwSize uint32
dwFlags uint32
dwDevType uint32
dwAxes uint32
dwButtons uint32
dwPOVs uint32
dwFFSamplePeriod uint32
dwFFMinTimeResolution uint32
dwFirmwareRevision uint32
dwHardwareRevision uint32
dwFFDriverVersion uint32
}
type _DIDEVICEINSTANCEW struct {
dwSize uint32
guidInstance windows.GUID
guidProduct windows.GUID
dwDevType uint32
tszInstanceName [_MAX_PATH]uint16
tszProductName [_MAX_PATH]uint16
guidFFDriver windows.GUID
wUsagePage uint16
wUsage uint16
}
type _DIDEVICEOBJECTINSTANCEW struct {
dwSize uint32
guidType windows.GUID
dwOfs uint32
dwType uint32
dwFlags uint32
tszName [_MAX_PATH]uint16
dwFFMaxForce uint32
dwFFForceResolution uint32
wCollectionNumber uint16
wDesignatorIndex uint16
wUsagePage uint16
wUsage uint16
dwDimension uint32
wExponent uint16
wReserved uint16
}
type _DIJOYSTATE struct {
lX int32
lY int32
lZ int32
lRx int32
lRy int32
lRz int32
rglSlider [2]int32
rgdwPOV [4]uint32
rgbButtons [32]byte
}
type _DIOBJECTDATAFORMAT struct {
pguid *windows.GUID
dwOfs uint32
dwType uint32
dwFlags uint32
}
type _DIPROPDWORD struct {
diph _DIPROPHEADER
dwData uint32
}
type _DIPROPGUIDANDPATH struct {
diph _DIPROPHEADER
guidClass windows.GUID
wszPath [_MAX_PATH]uint16
}
type _DIPROPHEADER struct {
dwSize uint32
dwHeaderSize uint32
dwObj uint32
dwHow uint32
}
type _DIPROPRANGE struct {
diph _DIPROPHEADER
lMin int32
lMax int32
}
type _IDirectInput8W struct {
vtbl *_IDirectInput8W_Vtbl
}
type _IDirectInput8W_Vtbl struct {
QueryInterface uintptr
AddRef uintptr
Release uintptr
CreateDevice uintptr
EnumDevices uintptr
GetDeviceStatus uintptr
RunControlPanel uintptr
Initialize uintptr
FindDevice uintptr
EnumDevicesBySemantics uintptr
ConfigureDevices uintptr
}
func (d *_IDirectInput8W) CreateDevice(rguid *windows.GUID, lplpDirectInputDevice **_IDirectInputDevice8W, pUnkOuter unsafe.Pointer) error {
r, _, _ := syscall.Syscall6(d.vtbl.CreateDevice, 4,
uintptr(unsafe.Pointer(d)),
uintptr(unsafe.Pointer(rguid)), uintptr(unsafe.Pointer(lplpDirectInputDevice)), uintptr(pUnkOuter),
0, 0)
if uint32(r) != _DI_OK {
return fmt.Errorf("gamepad: IDirectInput8::CreateDevice failed: %w", handleError(windows.Handle(uint32(r))))
}
return nil
}
func (d *_IDirectInput8W) EnumDevices(dwDevType uint32, lpCallback uintptr, pvRef unsafe.Pointer, dwFlags uint32) error {
r, _, _ := syscall.Syscall6(d.vtbl.EnumDevices, 5,
uintptr(unsafe.Pointer(d)),
uintptr(dwDevType), lpCallback, uintptr(pvRef), uintptr(dwFlags),
0)
if uint32(r) != _DI_OK {
return fmt.Errorf("gamepad: IDirectInput8::EnumDevices failed: %w", handleError(windows.Handle(uint32(r))))
}
return nil
}
type _IDirectInputDevice8W struct {
vtbl *_IDirectInputDevice8W_Vtbl
}
type _IDirectInputDevice8W_Vtbl struct {
QueryInterface uintptr
AddRef uintptr
Release uintptr
GetCapabilities uintptr
EnumObjects uintptr
GetProperty uintptr
SetProperty uintptr
Acquire uintptr
Unacquire uintptr
GetDeviceState uintptr
GetDeviceData uintptr
SetDataFormat uintptr
SetEventNotification uintptr
SetCooperativeLevel uintptr
GetObjectInfo uintptr
GetDeviceInfo uintptr
RunControlPanel uintptr
Initialize uintptr
CreateEffect uintptr
EnumEffects uintptr
GetEffectInfo uintptr
GetForceFeedbackState uintptr
SendForceFeedbackCommand uintptr
EnumCreatedEffectObjects uintptr
Escape uintptr
Poll uintptr
SendDeviceData uintptr
EnumEffectsInFile uintptr
WriteEffectToFile uintptr
BuildActionMap uintptr
SetActionMap uintptr
GetImageInfo uintptr
}
func (d *_IDirectInputDevice8W) Acquire() error {
r, _, _ := syscall.Syscall(d.vtbl.Acquire, 1, uintptr(unsafe.Pointer(d)), 0, 0)
if uint32(r) != _DI_OK && uint32(r) != _SI_FALSE {
return fmt.Errorf("gamepad: IDirectInputDevice8::Acquire failed: %w", handleError(windows.Handle(uint32(r))))
}
return nil
}
func (d *_IDirectInputDevice8W) EnumObjects(lpCallback uintptr, pvRef unsafe.Pointer, dwFlags uint32) error {
r, _, _ := syscall.Syscall6(d.vtbl.EnumObjects, 4,
uintptr(unsafe.Pointer(d)),
lpCallback, uintptr(pvRef), uintptr(dwFlags),
0, 0)
if uint32(r) != _DI_OK {
return fmt.Errorf("gamepad: IDirectInputDevice8::EnumObjects failed: %w", handleError(windows.Handle(uint32(r))))
}
return nil
}
func (d *_IDirectInputDevice8W) GetCapabilities(lpDIDevCaps *_DIDEVCAPS) error {
r, _, _ := syscall.Syscall(d.vtbl.GetCapabilities, 2, uintptr(unsafe.Pointer(d)), uintptr(unsafe.Pointer(lpDIDevCaps)), 0)
if uint32(r) != _DI_OK {
return fmt.Errorf("gamepad: IDirectInputDevice8::GetCapabilities failed: %w", handleError(windows.Handle(uint32(r))))
}
return nil
}
func (d *_IDirectInputDevice8W) GetDeviceState(cbData uint32, lpvData unsafe.Pointer) error {
r, _, _ := syscall.Syscall(d.vtbl.GetDeviceState, 3, uintptr(unsafe.Pointer(d)), uintptr(cbData), uintptr(lpvData))
if uint32(r) != _DI_OK {
return fmt.Errorf("gamepad: IDirectInputDevice8::GetDeviceState failed: %w", handleError(windows.Handle(uint32(r))))
}
return nil
}
func (d *_IDirectInputDevice8W) GetProperty(rguidProp uintptr, pdiph *_DIPROPHEADER) error {
r, _, _ := syscall.Syscall(d.vtbl.GetProperty, 3, uintptr(unsafe.Pointer(d)), rguidProp, uintptr(unsafe.Pointer(pdiph)))
if uint32(r) != _DI_OK {
return fmt.Errorf("gamepad: IDirectInputDevice8::GetProperty failed: %w", handleError(windows.Handle(uint32(r))))
}
return nil
}
func (d *_IDirectInputDevice8W) Poll() error {
r, _, _ := syscall.Syscall(d.vtbl.Poll, 1, uintptr(unsafe.Pointer(d)), 0, 0)
if uint32(r) != _DI_OK && uint32(r) != _DI_NOEFFECT {
return fmt.Errorf("gamepad: IDirectInputDevice8::Poll failed: %w", handleError(windows.Handle(uint32(r))))
}
return nil
}
func (d *_IDirectInputDevice8W) Release() uint32 {
r, _, _ := syscall.Syscall(d.vtbl.Release, 1, uintptr(unsafe.Pointer(d)), 0, 0)
return uint32(r)
}
func (d *_IDirectInputDevice8W) SetDataFormat(lpdf *_DIDATAFORMAT) error {
r, _, _ := syscall.Syscall(d.vtbl.SetDataFormat, 2, uintptr(unsafe.Pointer(d)), uintptr(unsafe.Pointer(lpdf)), 0)
if uint32(r) != _DI_OK {
return fmt.Errorf("gamepad: IDirectInputDevice8::SetDataFormat failed: %w", handleError(windows.Handle(uint32(r))))
}
return nil
}
func (d *_IDirectInputDevice8W) SetProperty(rguidProp uintptr, pdiph *_DIPROPHEADER) error {
r, _, _ := syscall.Syscall(d.vtbl.SetProperty, 3, uintptr(unsafe.Pointer(d)), rguidProp, uintptr(unsafe.Pointer(pdiph)))
if uint32(r) != _DI_OK && uint32(r) != _DI_PROPNOEFFECT {
return fmt.Errorf("gamepad: IDirectInputDevice8::SetProperty failed: %w", handleError(windows.Handle(uint32(r))))
}
return nil
}
type _RID_DEVICE_INFO struct {
cbSize uint32
dwType uint32
hid _RID_DEVICE_INFO_HID // Originally, this member is a union.
}
type _RID_DEVICE_INFO_HID struct {
dwVendorId uint32
dwProductId uint32
dwVersionNumber uint32
usUsagePage uint16
usUsage uint16
_ uint32 // A padding adjusting with the size of RID_DEVICE_INFO_KEYBOARD
_ uint32 // A padding adjusting with the size of RID_DEVICE_INFO_KEYBOARD
}
type _RAWINPUTDEVICELIST struct {
hDevice windows.Handle
dwType uint32
}
type _XINPUT_CAPABILITIES struct {
typ byte
subType byte
flags uint16
gamepad _XINPUT_GAMEPAD
vibration _XINPUT_VIBRATION
}
type _XINPUT_GAMEPAD struct {
wButtons uint16
bLeftTrigger byte
bRightTrigger byte
sThumbLX int16
sThumbLY int16
sThumbRX int16
sThumbRY int16
}
type _XINPUT_STATE struct {
dwPacketNumber uint32
Gamepad _XINPUT_GAMEPAD
}
type _XINPUT_VIBRATION struct {
wLeftMotorSpeed uint16
wRightMotorSpeed uint16
}
@@ -0,0 +1,132 @@
// Copyright 2022 The Ebitengine 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.
package gamepad
import (
"unsafe"
"github.com/ebitengine/purego"
)
const kIOReturnSuccess = 0
const kIOHIDOptionsTypeNone _IOOptionBits = 0
const (
kIOHIDElementTypeInput_Misc = 1
kIOHIDElementTypeInput_Button = 2
kIOHIDElementTypeInput_Axis = 3
)
const (
kHIDPage_GenericDesktop = 0x1
kHIDPage_Simulation = 0x2
kHIDPage_Button = 0x9
kHIDPage_Consumer = 0x0C
)
const (
kHIDUsage_GD_Joystick = 0x4
kHIDUsage_GD_GamePad = 0x5
kHIDUsage_GD_MultiAxisController = 0x8
kHIDUsage_GD_X = 0x30
kHIDUsage_GD_Y = 0x31
kHIDUsage_GD_Z = 0x32
kHIDUsage_GD_Rx = 0x33
kHIDUsage_GD_Ry = 0x34
kHIDUsage_GD_Rz = 0x35
kHIDUsage_GD_Slider = 0x36
kHIDUsage_GD_Dial = 0x37
kHIDUsage_GD_Wheel = 0x38
kHIDUsage_GD_Hatswitch = 0x39
kHIDUsage_GD_Start = 0x3D
kHIDUsage_GD_Select = 0x3E
kHIDUsage_GD_SystemMainMenu = 0x85
kHIDUsage_GD_DPadUp = 0x90
kHIDUsage_GD_DPadDown = 0x91
kHIDUsage_GD_DPadRight = 0x92
kHIDUsage_GD_DPadLeft = 0x93
kHIDUsage_Sim_Rudder = 0xBA
kHIDUsage_Sim_Throttle = 0xBB
kHIDUsage_Sim_Accelerator = 0xC4
kHIDUsage_Sim_Brake = 0xC5
kHIDUsage_Sim_Steering = 0xC8
)
var (
kIOHIDVendorIDKey = []byte("VendorID\x00")
kIOHIDProductIDKey = []byte("ProductID\x00")
kIOHIDVersionNumberKey = []byte("VersionNumber\x00")
kIOHIDProductKey = []byte("Product\x00")
kIOHIDDeviceUsagePageKey = []byte("DeviceUsagePage\x00")
kIOHIDDeviceUsageKey = []byte("DeviceUsage\x00")
)
type (
_IOOptionBits uint32
_IOHIDManagerRef uintptr
_IOHIDDeviceRef uintptr
_IOHIDElementRef uintptr
_IOHIDValueRef uintptr
_IOReturn int32
_IOHIDElementType uint32
)
type _IOHIDDeviceCallback func(context unsafe.Pointer, result _IOReturn, sender unsafe.Pointer, device _IOHIDDeviceRef)
func initializeIOKit() error {
iokit, err := purego.Dlopen("/System/Library/Frameworks/IOKit.framework/IOKit", purego.RTLD_LAZY|purego.RTLD_GLOBAL)
if err != nil {
return err
}
purego.RegisterLibFunc(&_IOHIDElementGetTypeID, iokit, "IOHIDElementGetTypeID")
purego.RegisterLibFunc(&_IOHIDManagerCreate, iokit, "IOHIDManagerCreate")
purego.RegisterLibFunc(&_IOHIDDeviceGetProperty, iokit, "IOHIDDeviceGetProperty")
purego.RegisterLibFunc(&_IOHIDManagerOpen, iokit, "IOHIDManagerOpen")
purego.RegisterLibFunc(&_IOHIDManagerSetDeviceMatchingMultiple, iokit, "IOHIDManagerSetDeviceMatchingMultiple")
purego.RegisterLibFunc(&_IOHIDManagerRegisterDeviceMatchingCallback, iokit, "IOHIDManagerRegisterDeviceMatchingCallback")
purego.RegisterLibFunc(&_IOHIDManagerRegisterDeviceRemovalCallback, iokit, "IOHIDManagerRegisterDeviceRemovalCallback")
purego.RegisterLibFunc(&_IOHIDManagerScheduleWithRunLoop, iokit, "IOHIDManagerScheduleWithRunLoop")
purego.RegisterLibFunc(&_IOHIDElementGetType, iokit, "IOHIDElementGetType")
purego.RegisterLibFunc(&_IOHIDElementGetUsage, iokit, "IOHIDElementGetUsage")
purego.RegisterLibFunc(&_IOHIDElementGetUsagePage, iokit, "IOHIDElementGetUsagePage")
purego.RegisterLibFunc(&_IOHIDElementGetLogicalMin, iokit, "IOHIDElementGetLogicalMin")
purego.RegisterLibFunc(&_IOHIDElementGetLogicalMax, iokit, "IOHIDElementGetLogicalMax")
purego.RegisterLibFunc(&_IOHIDDeviceGetValue, iokit, "IOHIDDeviceGetValue")
purego.RegisterLibFunc(&_IOHIDValueGetIntegerValue, iokit, "IOHIDValueGetIntegerValue")
purego.RegisterLibFunc(&_IOHIDDeviceCopyMatchingElements, iokit, "IOHIDDeviceCopyMatchingElements")
return nil
}
var (
_IOHIDElementGetTypeID func() _CFTypeID
_IOHIDManagerCreate func(allocator _CFAllocatorRef, options _IOOptionBits) _IOHIDManagerRef
_IOHIDDeviceGetProperty func(device _IOHIDDeviceRef, key _CFStringRef) _CFTypeRef
_IOHIDManagerOpen func(manager _IOHIDManagerRef, options _IOOptionBits) _IOReturn
_IOHIDManagerSetDeviceMatchingMultiple func(manager _IOHIDManagerRef, multiple _CFArrayRef)
_IOHIDManagerRegisterDeviceMatchingCallback func(manager _IOHIDManagerRef, callback _IOHIDDeviceCallback, context unsafe.Pointer)
_IOHIDManagerRegisterDeviceRemovalCallback func(manager _IOHIDManagerRef, callback _IOHIDDeviceCallback, context unsafe.Pointer)
_IOHIDManagerScheduleWithRunLoop func(manager _IOHIDManagerRef, runLoop _CFRunLoopRef, runLoopMode _CFStringRef)
_IOHIDElementGetType func(element _IOHIDElementRef) _IOHIDElementType
_IOHIDElementGetUsage func(element _IOHIDElementRef) uint32
_IOHIDElementGetUsagePage func(element _IOHIDElementRef) uint32
_IOHIDElementGetLogicalMin func(element _IOHIDElementRef) _CFIndex
_IOHIDElementGetLogicalMax func(element _IOHIDElementRef) _CFIndex
_IOHIDDeviceGetValue func(device _IOHIDDeviceRef, element _IOHIDElementRef, pValue *_IOHIDValueRef) _IOReturn
_IOHIDValueGetIntegerValue func(value _IOHIDValueRef) _CFIndex
_IOHIDDeviceCopyMatchingElements func(device _IOHIDDeviceRef, matching _CFDictionaryRef, options _IOOptionBits) _CFArrayRef
)
+464
View File
@@ -0,0 +1,464 @@
// 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.
package gamepad
// #cgo CFLAGS: -x objective-c
// #cgo LDFLAGS: -framework Foundation -framework GameController
//
// #import <GameController/GameController.h>
//
// static NSString* GCInputXboxShareButton = @"Button Share";
//
// enum ControllerButton {
// kControllerButtonInvalid = -1,
// kControllerButtonA,
// kControllerButtonB,
// kControllerButtonX,
// kControllerButtonY,
// kControllerButtonBack,
// kControllerButtonGuide,
// kControllerButtonStart,
// kControllerButtonLeftStick,
// kControllerButtonRightStick,
// kControllerButtonLeftShoulder,
// kControllerButtonRightShoulder,
// kControllerButtonDpadUp,
// kControllerButtonDpadDown,
// kControllerButtonDpadLeft,
// kControllerButtonDpadRight,
// kControllerButtonMisc1,
// kControllerButtonPaddle1,
// kControllerButtonPaddle2,
// kControllerButtonPaddle3,
// kControllerButtonPaddle4,
// kControllerButtonTouchpad,
// kControllerButtonMax,
// };
//
// enum HatState : uint8_t {
// kHatCentered = 0x00,
// kHatUp = 0x01,
// kHatRight = 0x02,
// kHatDown = 0x04,
// kHatLeft = 0x08,
// kHatRightUp = kHatRight | kHatUp,
// kHatRightDown = kHatRight | kHatDown,
// kHatLeftUp = kHatLeft | kHatUp,
// kHatLeftDown = kHatLeft | kHatDown,
// };
//
// enum USBVendor {
// kUSBVendorApple = 0x05ac,
// kUSBVendorMicrosoft = 0x045e,
// kUSBVendorSony = 0x054c,
// };
//
// enum USBProduct {
// kUSBProductSonyDS4Slim = 0x09cc,
// kUSBProductSonyDS5 = 0x0ce6,
// kUSBProductXboxOneEliteSeries2Bluetooth = 0x0b05,
// kUSBProductXboxOneSRev1Bluetooth = 0x02e0,
// kUSBProductXboxSeriesXBluetooth = 0x0b13,
// };
//
// struct ControllerProperty {
// uint8_t nAxes;
// uint8_t nButtons;
// uint8_t nHats;
// uint16_t buttonMask;
// char guid[16];
// char name[256];
// bool hasDualshockTouchpad;
// bool hasXboxPaddles;
// bool hasXboxShareButton;
// };
//
// void ebitenAddGamepad(uintptr_t controller, struct ControllerProperty* prop);
// void ebitenRemoveGamepad(uintptr_t controller);
//
// static size_t min(size_t a, size_t b) {
// return a < b ? a : b;
// }
//
// static void getControllerPropertyFromController(GCController* controller, struct ControllerProperty* property) {
// @autoreleasepool {
// uint16_t vendor = 0;
// uint16_t product = 0;
// uint16_t subtype = 0;
//
// const char* name = nil;
// if (controller.vendorName) {
// name = controller.vendorName.UTF8String;
// }
// if (!name) {
// name = "MFi Gamepad";
// }
// memcpy(property->name, name, min(sizeof(property->name), strlen(name)));
//
// if (controller.extendedGamepad) {
// GCExtendedGamepad* gamepad = controller.extendedGamepad;
//
// bool isXbox = false;
// bool isPS4 = false;
// bool isPS5 = false;
// if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
// NSString* productCategory = [controller productCategory];
// if ([productCategory isEqualToString:@"DualShock 4"]) {
// isPS4 = 1;
// } else if ([productCategory isEqualToString:@"DualSense"]) {
// isPS5 = 1;
// } else if ([productCategory isEqualToString:@"Xbox One"]) {
// isXbox = 1;
// }
// } else {
// NSString* vendorName = [controller vendorName];
// if ([vendorName isEqualToString:@"DUALSHOCK"]) {
// isPS4 = 1;
// } else if ([vendorName isEqualToString:@"DualSense"]) {
// isPS5 = 1;
// } else if ([vendorName isEqualToString:@"Xbox"]) {
// isXbox = 1;
// }
// }
//
// property->buttonMask |= (1 << kControllerButtonA);
// property->buttonMask |= (1 << kControllerButtonB);
// property->buttonMask |= (1 << kControllerButtonX);
// property->buttonMask |= (1 << kControllerButtonY);
// property->buttonMask |= (1 << kControllerButtonLeftShoulder);
// property->buttonMask |= (1 << kControllerButtonRightShoulder);
// property->nButtons += 6;
//
// #pragma clang diagnostic push
// #pragma clang diagnostic ignored "-Wunguarded-availability-new"
//
// if ([gamepad respondsToSelector:@selector(leftThumbstickButton)] && gamepad.leftThumbstickButton) {
// property->buttonMask |= (1 << kControllerButtonLeftStick);
// property->nButtons++;
// }
// if ([gamepad respondsToSelector:@selector(rightThumbstickButton)] && gamepad.rightThumbstickButton) {
// property->buttonMask |= (1 << kControllerButtonRightStick);
// property->nButtons++;
// }
// if ([gamepad respondsToSelector:@selector(buttonOptions)] && gamepad.buttonOptions) {
// property->buttonMask |= (1 << kControllerButtonBack);
// property->nButtons++;
// }
// if ([gamepad respondsToSelector:@selector(buttonHome)] && gamepad.buttonHome) {
// property->buttonMask |= (1 << kControllerButtonGuide);
// property->nButtons++;
// }
//
// property->buttonMask |= (1 << kControllerButtonStart);
// property->nButtons++;
//
// if ([controller respondsToSelector:@selector(physicalInputProfile)]) {
// if (controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton] != nil) {
// property->hasDualshockTouchpad = true;
// property->buttonMask |= (1 << kControllerButtonMisc1);
// property->nButtons++;
// }
// if (controller.physicalInputProfile.buttons[GCInputXboxPaddleOne] != nil) {
// property->hasXboxPaddles = true;
// property->buttonMask |= (1 << kControllerButtonPaddle1);
// property->nButtons++;
// }
// if (controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo] != nil) {
// property->hasXboxPaddles = true;
// property->buttonMask |= (1 << kControllerButtonPaddle2);
// property->nButtons++;
// }
// if (controller.physicalInputProfile.buttons[GCInputXboxPaddleThree] != nil) {
// property->hasXboxPaddles = true;
// property->buttonMask |= (1 << kControllerButtonPaddle3);
// property->nButtons++;
// }
// if (controller.physicalInputProfile.buttons[GCInputXboxPaddleFour] != nil) {
// property->hasXboxPaddles = true;
// property->buttonMask |= (1 << kControllerButtonPaddle4);
// property->nButtons++;
// }
// if (controller.physicalInputProfile.buttons[GCInputXboxShareButton] != nil) {
// property->hasXboxShareButton = true;
// property->buttonMask |= (1 << kControllerButtonMisc1);
// property->nButtons++;
// }
// }
//
// #pragma clang diagnostic pop
//
// if (isXbox) {
// vendor = kUSBVendorMicrosoft;
// if (property->hasXboxPaddles) {
// product = kUSBProductXboxOneEliteSeries2Bluetooth;
// subtype = 1;
// } else if (property->hasXboxShareButton) {
// product = kUSBProductXboxSeriesXBluetooth;
// subtype = 1;
// } else {
// product = kUSBProductXboxOneSRev1Bluetooth;
// subtype = 0;
// }
// } else if (isPS4) {
// vendor = kUSBVendorSony;
// product = kUSBProductSonyDS4Slim;
// if (property->hasDualshockTouchpad) {
// subtype = 1;
// } else {
// subtype = 0;
// }
// } else if (isPS5) {
// vendor = kUSBVendorSony;
// product = kUSBProductSonyDS5;
// subtype = 0;
// } else {
// vendor = kUSBVendorApple;
// product = 1;
// subtype = 1;
// }
//
// property->nAxes = 6;
// property->nHats = 1;
// }
//
// const int kSDLHardwareBusBluetooth = 0x05;
// property->guid[0] = (uint8_t)(kSDLHardwareBusBluetooth);
// property->guid[1] = (uint8_t)(kSDLHardwareBusBluetooth >> 8);
// property->guid[2] = 0;
// property->guid[3] = 0;
// property->guid[4] = (uint8_t)(vendor);
// property->guid[5] = (uint8_t)(vendor >> 8);
// property->guid[6] = 0;
// property->guid[7] = 0;
// property->guid[8] = (uint8_t)(product);
// property->guid[9] = (uint8_t)(product >> 8);
// property->guid[10] = 0;
// property->guid[11] = 0;
// property->guid[12] = (uint8_t)(property->buttonMask);
// property->guid[13] = (uint8_t)(property->buttonMask >> 8);
// if (vendor == kUSBVendorApple) {
// property->guid[14] = 'm';
// } else {
// property->guid[14] = 0;
// }
// property->guid[15] = subtype;
// }
// }
//
// static void addController(GCController* controller) {
// // Ignore if the controller is not an actual controller.
// if (!controller.extendedGamepad && controller.microGamepad) {
// return;
// }
//
// struct ControllerProperty property = {};
// getControllerPropertyFromController(controller, &property);
// ebitenAddGamepad((uintptr_t)(controller), &property);
// }
//
// static void removeController(GCController* controller) {
// ebitenRemoveGamepad((uintptr_t)(controller));
// }
//
// struct ControllerState {
// uint8_t buttons[32];
// float axes[32];
// enum HatState hat;
// };
//
// static enum HatState getHatState(GCControllerDirectionPad* dpad) {
// enum HatState hat = 0;
// if (dpad.up.isPressed) {
// hat |= kHatUp;
// } else if (dpad.down.isPressed) {
// hat |= kHatDown;
// }
// if (dpad.left.isPressed) {
// hat |= kHatLeft;
// } else if (dpad.right.isPressed) {
// hat |= kHatRight;
// }
// return hat;
// }
//
// static void getControllerState(uintptr_t controller_ptr, struct ControllerState* controllerState,
// uint16_t buttonMask, uint8_t nHats,
// bool hasDualshockTouchpad, bool hasXboxPaddles, bool hasXboxShareButton) {
// GCController* controller = (GCController*)(controller_ptr);
// @autoreleasepool {
// if (controller.extendedGamepad) {
// GCExtendedGamepad* gamepad = controller.extendedGamepad;
//
// controllerState->axes[0] = gamepad.leftThumbstick.xAxis.value;
// controllerState->axes[1] = -gamepad.leftThumbstick.yAxis.value;
// controllerState->axes[2] = gamepad.leftTrigger.value * 2 - 1;
// controllerState->axes[3] = gamepad.rightThumbstick.xAxis.value;
// controllerState->axes[4] = -gamepad.rightThumbstick.yAxis.value;
// controllerState->axes[5] = gamepad.rightTrigger.value * 2 - 1;
//
// int buttonCount = 0;
// controllerState->buttons[buttonCount++] = gamepad.buttonA.isPressed;
// controllerState->buttons[buttonCount++] = gamepad.buttonB.isPressed;
// controllerState->buttons[buttonCount++] = gamepad.buttonX.isPressed;
// controllerState->buttons[buttonCount++] = gamepad.buttonY.isPressed;
// controllerState->buttons[buttonCount++] = gamepad.leftShoulder.isPressed;
// controllerState->buttons[buttonCount++] = gamepad.rightShoulder.isPressed;
//
// #pragma clang diagnostic push
// #pragma clang diagnostic ignored "-Wunguarded-availability-new"
//
// if (buttonMask & (1 << kControllerButtonLeftStick)) {
// controllerState->buttons[buttonCount++] = gamepad.leftThumbstickButton.isPressed;
// }
// if (buttonMask & (1 << kControllerButtonRightStick)) {
// controllerState->buttons[buttonCount++] = gamepad.rightThumbstickButton.isPressed;
// }
// if (buttonMask & (1 << kControllerButtonBack)) {
// controllerState->buttons[buttonCount++] = gamepad.buttonOptions.isPressed;
// }
// if (buttonMask & (1 << kControllerButtonGuide)) {
// controllerState->buttons[buttonCount++] = gamepad.buttonHome.isPressed;
// }
// if (buttonMask & (1 << kControllerButtonStart)) {
// controllerState->buttons[buttonCount++] = gamepad.buttonMenu.isPressed;
// }
//
// if (hasDualshockTouchpad) {
// controllerState->buttons[buttonCount++] = controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton].isPressed;
// }
// if (hasXboxPaddles) {
// if (buttonMask & (1 << kControllerButtonPaddle1)) {
// controllerState->buttons[buttonCount++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleOne].isPressed;
// }
// if (buttonMask & (1 << kControllerButtonPaddle2)) {
// controllerState->buttons[buttonCount++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo].isPressed;
// }
// if (buttonMask & (1 << kControllerButtonPaddle3)) {
// controllerState->buttons[buttonCount++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleThree].isPressed;
// }
// if (buttonMask & (1 << kControllerButtonPaddle4)) {
// controllerState->buttons[buttonCount++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleFour].isPressed;
// }
// }
// if (hasXboxShareButton) {
// controllerState->buttons[buttonCount++] = controller.physicalInputProfile.buttons[GCInputXboxShareButton].isPressed;
// }
//
// #pragma clang diagnostic pop
//
// if (nHats) {
// controllerState->hat = getHatState(gamepad.dpad);
// }
// }
// }
// }
//
// static void initializeGamepads(void) {
// @autoreleasepool {
// for (GCController* controller in [GCController controllers]) {
// addController(controller);
// }
// NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
// [center addObserverForName:GCControllerDidConnectNotification
// object:nil
// queue:nil
// usingBlock:^(NSNotification* notification) {
// addController(notification.object);
// }];
// [center addObserverForName:GCControllerDidDisconnectNotification
// object:nil
// queue:nil
// usingBlock:^(NSNotification* notification) {
// removeController(notification.object);
// }];
// }
// }
import "C"
import (
"encoding/hex"
"unsafe"
)
//export ebitenAddGamepad
func ebitenAddGamepad(controller C.uintptr_t, prop *C.struct_ControllerProperty) {
theGamepads.addIOSGamepad(controller, prop)
}
//export ebitenRemoveGamepad
func ebitenRemoveGamepad(controller C.uintptr_t) {
theGamepads.removeIOSGamepad(controller)
}
func (g *gamepads) addIOSGamepad(controller C.uintptr_t, prop *C.struct_ControllerProperty) {
g.m.Lock()
defer g.m.Unlock()
name := C.GoString(&prop.name[0])
sdlID := hex.EncodeToString(C.GoBytes(unsafe.Pointer(&prop.guid[0]), 16))
gp := g.add(name, sdlID)
gp.native = &nativeGamepadImpl{
controller: uintptr(controller),
axes: make([]float64, prop.nAxes),
buttons: make([]bool, prop.nButtons+prop.nHats*4),
hats: make([]int, prop.nHats),
buttonMask: uint16(prop.buttonMask),
hasDualshockTouchpad: bool(prop.hasDualshockTouchpad),
hasXboxPaddles: bool(prop.hasXboxPaddles),
hasXboxShareButton: bool(prop.hasXboxShareButton),
}
}
func (g *gamepads) removeIOSGamepad(controller C.uintptr_t) {
g.m.Lock()
defer g.m.Unlock()
g.remove(func(gamepad *Gamepad) bool {
return gamepad.native.(*nativeGamepadImpl).controller == uintptr(controller)
})
}
func initializeIOSGamepads() {
C.initializeGamepads()
}
func (g *nativeGamepadImpl) updateIOSGamepad() {
var state C.struct_ControllerState
C.getControllerState(C.uintptr_t(g.controller), &state, C.uint16_t(g.buttonMask), C.uint8_t(len(g.hats)),
C.bool(g.hasDualshockTouchpad), C.bool(g.hasXboxPaddles), C.bool(g.hasXboxShareButton))
nButtons := len(g.buttons) - len(g.hats)*4
for i := 0; i < nButtons; i++ {
g.buttons[i] = state.buttons[i] != 0
}
// Follow the GLFW way to process hats.
// See _glfwInputJoystickHat.
if len(g.hats) > 0 {
base := len(g.buttons) - len(g.hats)*4
g.buttons[base] = state.hat&0x01 != 0
g.buttons[base+1] = state.hat&0x02 != 0
g.buttons[base+2] = state.hat&0x04 != 0
g.buttons[base+3] = state.hat&0x08 != 0
}
for i := range g.axes {
g.axes[i] = float64(state.axes[i])
}
if len(g.hats) > 0 {
g.hats[0] = int(state.hat)
}
}
+138
View File
@@ -0,0 +1,138 @@
// Copyright 2022 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 !android && !nintendosdk && !playstation5
package gamepad
import (
"unsafe"
"golang.org/x/sys/unix"
)
const (
_ABS_X = 0x00
_ABS_Y = 0x01
_ABS_Z = 0x02
_ABS_RX = 0x03
_ABS_RY = 0x04
_ABS_RZ = 0x05
_ABS_HAT0X = 0x10
_ABS_HAT0Y = 0x11
_ABS_HAT1X = 0x12
_ABS_HAT1Y = 0x13
_ABS_HAT2X = 0x14
_ABS_HAT2Y = 0x15
_ABS_HAT3Y = 0x17
_ABS_MAX = 0x3f
_ABS_CNT = _ABS_MAX + 1
_BTN_MISC = 0x100
_BTN_GAMEPAD = 0x130
_BTN_A = 0x130
_BTN_B = 0x131
_BTN_NORTH = 0x133
_BTN_X = 0x133
_BTN_WEST = 0x134
_BTN_Y = 0x134
_BTN_TL = 0x136
_BTN_TR = 0x137
_BTN_TL2 = 0x138
_BTN_TR2 = 0x139
_BTN_SELECT = 0x13a
_BTN_START = 0x13b
_BTN_MODE = 0x13c
_BTN_THUMBL = 0x13d
_BTN_THUMBR = 0x13e
_BTN_DPAD_UP = 0x220
_BTN_DPAD_DOWN = 0x221
_BTN_DPAD_LEFT = 0x222
_BTN_DPAD_RIGHT = 0x223
_IOC_NONE = 0
_IOC_WRITE = 1
_IOC_READ = 2
_IOC_NRBITS = 8
_IOC_TYPEBITS = 8
_IOC_SIZEBITS = 14
_IOC_DIRBITS = 2
_IOC_NRSHIFT = 0
_IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS
_IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS
_IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS
_KEY_MAX = 0x2ff
_KEY_CNT = _KEY_MAX + 1
_SYN_REPORT = 0
_SYN_DROPPED = 3
)
func _IOC(dir, typ, nr, size uint) uint {
return dir<<_IOC_DIRSHIFT | typ<<_IOC_TYPESHIFT | nr<<_IOC_NRSHIFT | size<<_IOC_SIZESHIFT
}
func _IOR(typ, nr, size uint) uint {
return _IOC(_IOC_READ, typ, nr, size)
}
func _EVIOCGABS(abs uint) uint {
return _IOR('E', 0x40+abs, uint(unsafe.Sizeof(input_absinfo{})))
}
func _EVIOCGBIT(ev, len uint) uint {
return _IOC(_IOC_READ, 'E', 0x20+ev, len)
}
func _EVIOCGID() uint {
return _IOR('E', 0x02, uint(unsafe.Sizeof(input_id{})))
}
func _EVIOCGNAME(len uint) uint {
return _IOC(_IOC_READ, 'E', 0x06, len)
}
type input_absinfo struct {
value int32
minimum int32
maximum int32
fuzz int32
flat int32
resolution int32
}
type input_event struct {
time unix.Timeval
typ uint16
code uint16
value int32
}
type input_id struct {
bustype uint16
vendor uint16
product uint16
version uint16
}
func ioctl(fd int, request uint, ptr unsafe.Pointer) error {
r, _, e := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(request), uintptr(ptr))
if r < 0 {
return unix.Errno(e)
}
return nil
}
@@ -0,0 +1,268 @@
// Copyright 2022 The Ebitengine 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.
package gamepad
import (
"fmt"
"runtime"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
type handleError windows.Handle
func (h handleError) Error() string {
return fmt.Sprintf("HANDLE(%d)", h)
}
var (
gameInput = windows.NewLazySystemDLL("GameInput.dll")
procGameInputCreate = gameInput.NewProc("GameInputCreate")
)
type _GameInputCallbackToken uint64
type _GameInputDeviceStatus int32
const (
_GameInputDeviceNoStatus _GameInputDeviceStatus = 0x00000000
_GameInputDeviceConnected _GameInputDeviceStatus = 0x00000001
_GameInputDeviceInputEnabled _GameInputDeviceStatus = 0x00000002
_GameInputDeviceOutputEnabled _GameInputDeviceStatus = 0x00000004
_GameInputDeviceRawIoEnabled _GameInputDeviceStatus = 0x00000008
_GameInputDeviceAudioCapture _GameInputDeviceStatus = 0x00000010
_GameInputDeviceAudioRender _GameInputDeviceStatus = 0x00000020
_GameInputDeviceSynchronized _GameInputDeviceStatus = 0x00000040
_GameInputDeviceWireless _GameInputDeviceStatus = 0x00000080
_GameInputDeviceUserIdle _GameInputDeviceStatus = 0x00100000
_GameInputDeviceAnyStatus _GameInputDeviceStatus = 0x00FFFFFF
)
type _GameInputEnumerationKind int32
const (
_GameInputNoEnumeration _GameInputEnumerationKind = 0
_GameInputAsyncEnumeration _GameInputEnumerationKind = 1
_GameInputBlockingEnumeration _GameInputEnumerationKind = 2
)
type _GameInputGamepadButtons int32
const (
_GameInputGamepadNone _GameInputGamepadButtons = 0x00000000
_GameInputGamepadMenu _GameInputGamepadButtons = 0x00000001
_GameInputGamepadView _GameInputGamepadButtons = 0x00000002
_GameInputGamepadA _GameInputGamepadButtons = 0x00000004
_GameInputGamepadB _GameInputGamepadButtons = 0x00000008
_GameInputGamepadX _GameInputGamepadButtons = 0x00000010
_GameInputGamepadY _GameInputGamepadButtons = 0x00000020
_GameInputGamepadDPadUp _GameInputGamepadButtons = 0x00000040
_GameInputGamepadDPadDown _GameInputGamepadButtons = 0x00000080
_GameInputGamepadDPadLeft _GameInputGamepadButtons = 0x00000100
_GameInputGamepadDPadRight _GameInputGamepadButtons = 0x00000200
_GameInputGamepadLeftShoulder _GameInputGamepadButtons = 0x00000400
_GameInputGamepadRightShoulder _GameInputGamepadButtons = 0x00000800
_GameInputGamepadLeftThumbstick _GameInputGamepadButtons = 0x00001000
_GameInputGamepadRightThumbstick _GameInputGamepadButtons = 0x00002000
)
type _GameInputKind int32
const (
_GameInputKindUnknown _GameInputKind = 0x00000000
_GameInputKindRawDeviceReport _GameInputKind = 0x00000001
_GameInputKindControllerAxis _GameInputKind = 0x00000002
_GameInputKindControllerButton _GameInputKind = 0x00000004
_GameInputKindControllerSwitch _GameInputKind = 0x00000008
_GameInputKindController _GameInputKind = 0x0000000E
_GameInputKindKeyboard _GameInputKind = 0x00000010
_GameInputKindMouse _GameInputKind = 0x00000020
_GameInputKindTouch _GameInputKind = 0x00000100
_GameInputKindMotion _GameInputKind = 0x00001000
_GameInputKindArcadeStick _GameInputKind = 0x00010000
_GameInputKindFlightStick _GameInputKind = 0x00020000
_GameInputKindGamepad _GameInputKind = 0x00040000
_GameInputKindRacingWheel _GameInputKind = 0x00080000
_GameInputKindUiNavigation _GameInputKind = 0x01000000
_GameInputKindAny _GameInputKind = 0x0FFFFFFF
)
type _GameInputGamepadState struct {
buttons _GameInputGamepadButtons
leftTrigger float32
rightTrigger float32
leftThumbstickX float32
leftThumbstickY float32
rightThumbstickX float32
rightThumbstickY float32
}
type _GameInputRumbleParams struct {
lowFrequency float32
highFrequency float32
leftTrigger float32
rightTrigger float32
}
func _GameInputCreate() (*_IGameInput, error) {
var gameInput *_IGameInput
r, _, _ := procGameInputCreate.Call(uintptr(unsafe.Pointer(&gameInput)))
if uint32(r) != uint32(windows.S_OK) {
return nil, fmt.Errorf("gamepad: GameInputCreate failed: %w", handleError(windows.Handle(uint32(r))))
}
return gameInput, nil
}
type _IGameInput struct {
vtbl *_IGameInput_Vtbl
}
type _IGameInput_Vtbl struct {
QueryInterface uintptr
AddRef uintptr
Release uintptr
GetCurrentTimestamp uintptr
GetCurrentReading uintptr
GetNextReading uintptr
GetPreviousReading uintptr
GetTemporalReading uintptr
RegisterReadingCallback uintptr
RegisterDeviceCallback uintptr
RegisterGuideButtonCallback uintptr
RegisterKeyboardLayoutCallback uintptr
StopCallback uintptr
UnregisterCallback uintptr
CreateDispatcher uintptr
CreateAggregateDevice uintptr
FindDeviceFromId uintptr
FindDeviceFromObject uintptr
FindDeviceFromPlatformHandle uintptr
FindDeviceFromPlatformString uintptr
EnableOemDeviceSupport uintptr
SetFocusPolicy uintptr
}
func (i *_IGameInput) GetCurrentReading(inputKind _GameInputKind, device *_IGameInputDevice) (*_IGameInputReading, error) {
var reading *_IGameInputReading
r, _, _ := syscall.Syscall6(i.vtbl.GetCurrentReading, 4, uintptr(unsafe.Pointer(i)),
uintptr(inputKind), uintptr(unsafe.Pointer(device)), uintptr(unsafe.Pointer(&reading)),
0, 0)
runtime.KeepAlive(device)
if uint32(r) != uint32(windows.S_OK) {
return nil, fmt.Errorf("gamepad: IGameInput::GetCurrentReading failed: %w", handleError(windows.Handle(uint32(r))))
}
return reading, nil
}
func (i *_IGameInput) RegisterDeviceCallback(device *_IGameInputDevice,
inputKind _GameInputKind,
statusFilter _GameInputDeviceStatus,
enumerationKind _GameInputEnumerationKind,
context unsafe.Pointer,
callbackFunc uintptr,
callbackToken *_GameInputCallbackToken) error {
r, _, _ := syscall.Syscall9(i.vtbl.RegisterDeviceCallback, 8, uintptr(unsafe.Pointer(i)),
uintptr(unsafe.Pointer(device)), uintptr(inputKind), uintptr(statusFilter),
uintptr(enumerationKind), uintptr(context), callbackFunc,
uintptr(unsafe.Pointer(callbackToken)), 0)
runtime.KeepAlive(device)
runtime.KeepAlive(callbackToken)
if uint32(r) != uint32(windows.S_OK) {
return fmt.Errorf("gamepad: IGameInput::RegisterDeviceCallback failed: %w", handleError(windows.Handle(uint32(r))))
}
return nil
}
type _IGameInputDevice struct {
vtbl *_IGameInputDevice_Vtbl
}
type _IGameInputDevice_Vtbl struct {
QueryInterface uintptr
AddRef uintptr
Release uintptr
GetDeviceInfo uintptr
GetDeviceStatus uintptr
GetBatteryState uintptr
CreateForceFeedbackEffect uintptr
IsForceFeedbackMotorPoweredOn uintptr
SetForceFeedbackMotorGain uintptr
SetHapticMotorState uintptr
SetRumbleState uintptr
SetInputSynchronizationState uintptr
SendInputSynchronizationHint uintptr
PowerOff uintptr
CreateRawDeviceReport uintptr
GetRawDeviceFeature uintptr
SetRawDeviceFeature uintptr
SendRawDeviceOutput uintptr
ExecuteRawDeviceIoControl uintptr
AcquireExclusiveRawDeviceAccess uintptr
ReleaseExclusiveRawDeviceAccess uintptr
}
func (i *_IGameInputDevice) SetRumbleState(params *_GameInputRumbleParams, timestamp uint64) {
_, _, _ = syscall.Syscall(i.vtbl.SetRumbleState, 3, uintptr(unsafe.Pointer(i)), uintptr(unsafe.Pointer(params)), uintptr(timestamp))
runtime.KeepAlive(params)
}
type _IGameInputReading struct {
vtbl *_IGameInputReading_Vtbl
}
type _IGameInputReading_Vtbl struct {
QueryInterface uintptr
AddRef uintptr
Release uintptr
GetInputKind uintptr
GetSequenceNumber uintptr
GetTimestamp uintptr
GetDevice uintptr
GetRawReport uintptr
GetControllerAxisCount uintptr
GetControllerAxisState uintptr
GetControllerButtonCount uintptr
GetControllerButtonState uintptr
GetControllerSwitchCount uintptr
GetControllerSwitchState uintptr
GetKeyCount uintptr
GetKeyState uintptr
GetMouseState uintptr
GetTouchCount uintptr
GetTouchState uintptr
GetMotionState uintptr
GetArcadeStickState uintptr
GetFlightStickState uintptr
GetGamepadState uintptr
GetRacingWheelState uintptr
GetUiNavigationState uintptr
}
func (i *_IGameInputReading) GetGamepadState() (_GameInputGamepadState, bool) {
var state _GameInputGamepadState
r, _, _ := syscall.Syscall(i.vtbl.GetGamepadState, 2, uintptr(unsafe.Pointer(i)), uintptr(unsafe.Pointer(&state)), 0)
return state, int32(r) != 0
}
func (i *_IGameInputReading) Release() uint32 {
r, _, _ := syscall.Syscall(i.vtbl.Release, 1, uintptr(unsafe.Pointer(i)), 0, 0)
return uint32(r)
}
+54
View File
@@ -0,0 +1,54 @@
// Copyright 2022 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.
package gamepad
type Button int
const (
Button0 Button = iota
Button1
Button2
Button3
Button4
Button5
Button6
Button7
Button8
Button9
Button10
Button11
Button12
Button13
Button14
Button15
Button16
Button17
Button18
Button19
Button20
Button21
Button22
Button23
Button24
Button25
Button26
Button27
Button28
Button29
Button30
Button31
)
const ButtonCount = 32
@@ -0,0 +1,161 @@
// Copyright 2022 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.
package gamepad
import (
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
)
func AddAndroidGamepad(androidDeviceID int, name, sdlID string, axisCount, hatCount int) {
theGamepads.addAndroidGamepad(androidDeviceID, name, sdlID, axisCount, hatCount)
}
func RemoveAndroidGamepad(androidDeviceID int) {
theGamepads.removeAndroidGamepad(androidDeviceID)
}
func UpdateAndroidGamepadAxis(androidDeviceID int, axis int, value float64) {
theGamepads.updateAndroidGamepadAxis(androidDeviceID, axis, value)
}
func UpdateAndroidGamepadButton(androidDeviceID int, button Button, pressed bool) {
theGamepads.updateAndroidGamepadButton(androidDeviceID, button, pressed)
}
func UpdateAndroidGamepadHat(androidDeviceID int, hat int, xValue, yValue int) {
theGamepads.updateAndroidGamepadHat(androidDeviceID, hat, xValue, yValue)
}
func (g *gamepads) addAndroidGamepad(androidDeviceID int, name, sdlID string, axisCount, hatCount int) {
g.m.Lock()
defer g.m.Unlock()
gp := g.add(name, sdlID)
gp.native = &nativeGamepadImpl{
androidDeviceID: androidDeviceID,
axesReady: make([]bool, axisCount),
axes: make([]float64, axisCount),
buttons: make([]bool, gamepaddb.SDLControllerButtonMax+1),
hats: make([]int, hatCount),
}
}
func (g *gamepads) removeAndroidGamepad(androidDeviceID int) {
g.m.Lock()
defer g.m.Unlock()
g.remove(func(gamepad *Gamepad) bool {
return gamepad.native.(*nativeGamepadImpl).androidDeviceID == androidDeviceID
})
}
func (g *gamepads) updateAndroidGamepadAxis(androidDeviceID int, axis int, value float64) {
g.m.Lock()
defer g.m.Unlock()
gp := g.find(func(gamepad *Gamepad) bool {
return gamepad.native.(*nativeGamepadImpl).androidDeviceID == androidDeviceID
})
if gp == nil {
return
}
gp.updateAndroidGamepadAxis(axis, value)
}
func (g *gamepads) updateAndroidGamepadButton(androidDeviceID int, button Button, pressed bool) {
g.m.Lock()
defer g.m.Unlock()
gp := g.find(func(gamepad *Gamepad) bool {
return gamepad.native.(*nativeGamepadImpl).androidDeviceID == androidDeviceID
})
if gp == nil {
return
}
gp.updateAndroidGamepadButton(button, pressed)
}
func (g *gamepads) updateAndroidGamepadHat(androidDeviceID int, hat int, xValue, yValue int) {
g.m.Lock()
defer g.m.Unlock()
gp := g.find(func(gamepad *Gamepad) bool {
return gamepad.native.(*nativeGamepadImpl).androidDeviceID == androidDeviceID
})
if gp == nil {
return
}
gp.updateAndroidGamepadHat(hat, xValue, yValue)
}
func (g *Gamepad) updateAndroidGamepadAxis(axis int, value float64) {
g.m.Lock()
defer g.m.Unlock()
n := g.native.(*nativeGamepadImpl)
if axis < 0 || axis >= len(n.axes) {
return
}
n.axes[axis] = value
// MotionEvent with 0 value can be sent when a gamepad is connected even though an axis is not touched (#2598).
// This is problematic when an axis is a trigger button where -1 should be the default value.
// When MotionEvent with non-0 value is sent, it seems fine to assume that the axis is actually touched and ready.
if value != 0 {
n.axesReady[axis] = true
}
}
func (g *Gamepad) updateAndroidGamepadButton(button Button, pressed bool) {
g.m.Lock()
defer g.m.Unlock()
n := g.native.(*nativeGamepadImpl)
if button < 0 || int(button) >= len(n.buttons) {
return
}
n.buttons[button] = pressed
}
func (g *Gamepad) updateAndroidGamepadHat(hat int, xValue, yValue int) {
g.m.Lock()
defer g.m.Unlock()
n := g.native.(*nativeGamepadImpl)
if hat < 0 || hat >= len(n.hats) {
return
}
var v int
switch {
case xValue < 0:
v |= hatLeft
case xValue > 0:
v |= hatRight
}
switch {
case yValue < 0:
v |= hatUp
case yValue > 0:
v |= hatDown
}
n.hats[hat] = v
// Update the gamepad buttons in addition to hats.
// See https://github.com/libsdl-org/SDL/blob/47f2373dc13b66c48bf4024fcdab53cd0bdd59bb/src/joystick/android/SDL_sysjoystick.c#L290-L301
n.buttons[gamepaddb.SDLControllerButtonDpadLeft] = v&hatLeft != 0
n.buttons[gamepaddb.SDLControllerButtonDpadRight] = v&hatRight != 0
n.buttons[gamepaddb.SDLControllerButtonDpadUp] = v&hatUp != 0
n.buttons[gamepaddb.SDLControllerButtonDpadDown] = v&hatDown != 0
}
+419
View File
@@ -0,0 +1,419 @@
// Copyright 2022 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.
package gamepad
import (
"sync"
"time"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
)
type ID int
const (
hatCentered = 0
hatUp = 1
hatRight = 2
hatDown = 4
hatLeft = 8
hatRightUp = hatRight | hatUp
hatRightDown = hatRight | hatDown
hatLeftUp = hatLeft | hatUp
hatLeftDown = hatLeft | hatDown
)
type gamepads struct {
inited bool
gamepads []*Gamepad
m sync.Mutex
native nativeGamepads
}
type nativeGamepads interface {
init(gamepads *gamepads) error
update(gamepads *gamepads) error
}
var theGamepads = gamepads{
native: newNativeGamepadsImpl(),
}
// AppendGamepadIDs is concurrent-safe.
func AppendGamepadIDs(ids []ID) []ID {
return theGamepads.appendGamepadIDs(ids)
}
// Update is concurrent-safe.
func Update() error {
return theGamepads.update()
}
// Get is concurrent-safe.
func Get(id ID) *Gamepad {
return theGamepads.get(id)
}
func SetNativeWindow(nativeWindow uintptr) {
theGamepads.setNativeWindow(nativeWindow)
}
func (g *gamepads) appendGamepadIDs(ids []ID) []ID {
g.m.Lock()
defer g.m.Unlock()
for i, gp := range g.gamepads {
if gp != nil {
ids = append(ids, ID(i))
}
}
return ids
}
func (g *gamepads) update() error {
g.m.Lock()
defer g.m.Unlock()
if !g.inited {
if err := g.native.init(g); err != nil {
return err
}
g.inited = true
}
if err := g.native.update(g); err != nil {
return err
}
// A gamepad can be detected even though there are not. Apparently, some special devices are
// recognized as gamepads by OSes. In this case, the number of the 'buttons' can exceed the
// maximum. Skip such devices as a tentative solution (#1173, #2039).
g.remove(func(gamepad *Gamepad) bool {
return gamepad.ButtonCount() > ButtonCount
})
for _, gp := range g.gamepads {
if gp == nil {
continue
}
if err := gp.update(g); err != nil {
return err
}
}
return nil
}
func (g *gamepads) get(id ID) *Gamepad {
g.m.Lock()
defer g.m.Unlock()
if id < 0 || int(id) >= len(g.gamepads) {
return nil
}
return g.gamepads[id]
}
func (g *gamepads) find(cond func(*Gamepad) bool) *Gamepad {
for _, gp := range g.gamepads {
if gp == nil {
continue
}
if cond(gp) {
return gp
}
}
return nil
}
func (g *gamepads) add(name, sdlID string) *Gamepad {
for i, gp := range g.gamepads {
if gp == nil {
gp := &Gamepad{
name: name,
sdlID: sdlID,
}
g.gamepads[i] = gp
return gp
}
}
gp := &Gamepad{
name: name,
sdlID: sdlID,
}
g.gamepads = append(g.gamepads, gp)
return gp
}
func (g *gamepads) remove(cond func(*Gamepad) bool) {
for i, gp := range g.gamepads {
if gp == nil {
continue
}
if cond(gp) {
g.gamepads[i] = nil
}
}
}
func (g *gamepads) setNativeWindow(nativeWindow uintptr) {
g.m.Lock()
defer g.m.Unlock()
var n any = g.native
if n, ok := n.(interface{ setNativeWindow(uintptr) }); ok {
n.setNativeWindow(nativeWindow)
}
}
type Gamepad struct {
name string
sdlID string
m sync.Mutex
native nativeGamepad
}
type mappingInput interface {
Pressed() bool
Value() float64 // Normalized to range: 0..1.
}
type axisMappingInput struct {
g nativeGamepad
axis int
}
func (a axisMappingInput) Pressed() bool {
return a.g.axisValue(a.axis) > gamepaddb.ButtonPressedThreshold
}
func (a axisMappingInput) Value() float64 {
return a.g.axisValue(a.axis)*0.5 + 0.5
}
type buttonMappingInput struct {
g nativeGamepad
button int
}
func (b buttonMappingInput) Pressed() bool {
return b.g.isButtonPressed(b.button)
}
func (b buttonMappingInput) Value() float64 {
return b.g.buttonValue(b.button)
}
type hatMappingInput struct {
g nativeGamepad
hat int
direction int
}
func (h hatMappingInput) Pressed() bool {
return h.g.hatState(h.hat)&h.direction != 0
}
func (h hatMappingInput) Value() float64 {
if h.Pressed() {
return 1
}
return 0
}
type nativeGamepad interface {
update(gamepads *gamepads) error
hasOwnStandardLayoutMapping() bool
standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput
standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput
axisCount() int
buttonCount() int
hatCount() int
isAxisReady(axis int) bool
axisValue(axis int) float64
buttonValue(button int) float64
isButtonPressed(button int) bool
hatState(hat int) int
vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64)
}
func (g *Gamepad) update(gamepads *gamepads) error {
g.m.Lock()
defer g.m.Unlock()
return g.native.update(gamepads)
}
// Name is concurrent-safe.
func (g *Gamepad) Name() string {
// This is immutable and doesn't have to be protected by a mutex.
if name := gamepaddb.Name(g.sdlID); name != "" {
return name
}
return g.name
}
// SDLID is concurrent-safe.
func (g *Gamepad) SDLID() string {
// This is immutable and doesn't have to be protected by a mutex.
return g.sdlID
}
// AxisCount is concurrent-safe.
func (g *Gamepad) AxisCount() int {
g.m.Lock()
defer g.m.Unlock()
return g.native.axisCount()
}
// ButtonCount is concurrent-safe.
func (g *Gamepad) ButtonCount() int {
g.m.Lock()
defer g.m.Unlock()
return g.native.buttonCount()
}
// HatCount is concurrent-safe.
func (g *Gamepad) HatCount() int {
g.m.Lock()
defer g.m.Unlock()
return g.native.hatCount()
}
// IsAxisReady is concurrent-safe.
func (g *Gamepad) IsAxisReady(axis int) bool {
g.m.Lock()
defer g.m.Unlock()
return g.native.isAxisReady(axis)
}
// Axis is concurrent-safe.
func (g *Gamepad) Axis(axis int) float64 {
g.m.Lock()
defer g.m.Unlock()
return g.native.axisValue(axis)
}
// Button is concurrent-safe.
func (g *Gamepad) Button(button int) bool {
g.m.Lock()
defer g.m.Unlock()
return g.native.isButtonPressed(button)
}
// Hat is concurrent-safe.
func (g *Gamepad) Hat(hat int) int {
g.m.Lock()
defer g.m.Unlock()
return g.native.hatState(hat)
}
// IsStandardLayoutAvailable is concurrent-safe.
func (g *Gamepad) IsStandardLayoutAvailable() bool {
g.m.Lock()
defer g.m.Unlock()
if gamepaddb.HasStandardLayoutMapping(g.sdlID) {
return true
}
return g.native.hasOwnStandardLayoutMapping()
}
// IsStandardAxisAvailable is concurrent safe.
func (g *Gamepad) IsStandardAxisAvailable(axis gamepaddb.StandardAxis) bool {
g.m.Lock()
defer g.m.Unlock()
if gamepaddb.HasStandardLayoutMapping(g.sdlID) {
return gamepaddb.HasStandardAxis(g.sdlID, axis)
}
return g.native.standardAxisInOwnMapping(axis) != nil
}
// IsStandardButtonAvailable is concurrent safe.
func (g *Gamepad) IsStandardButtonAvailable(button gamepaddb.StandardButton) bool {
g.m.Lock()
defer g.m.Unlock()
if gamepaddb.HasStandardLayoutMapping(g.sdlID) {
return gamepaddb.HasStandardButton(g.sdlID, button)
}
return g.native.standardButtonInOwnMapping(button) != nil
}
// StandardAxisValue is concurrent-safe.
func (g *Gamepad) StandardAxisValue(axis gamepaddb.StandardAxis) float64 {
if gamepaddb.HasStandardLayoutMapping(g.sdlID) {
// StandardAxisValue invokes g.Axis, g.Button, or g.Hat so this cannot be locked.
return gamepaddb.StandardAxisValue(g.sdlID, axis, g)
}
g.m.Lock()
defer g.m.Unlock()
if m := g.native.standardAxisInOwnMapping(axis); m != nil {
return m.Value()*2 - 1
}
return 0
}
// StandardButtonValue is concurrent-safe.
func (g *Gamepad) StandardButtonValue(button gamepaddb.StandardButton) float64 {
if gamepaddb.HasStandardLayoutMapping(g.sdlID) {
// StandardButtonValue invokes g.Axis, g.Button, or g.Hat so this cannot be locked.
return gamepaddb.StandardButtonValue(g.sdlID, button, g)
}
g.m.Lock()
defer g.m.Unlock()
if m := g.native.standardButtonInOwnMapping(button); m != nil {
return m.Value()
}
return 0
}
// IsStandardButtonPressed is concurrent-safe.
func (g *Gamepad) IsStandardButtonPressed(button gamepaddb.StandardButton) bool {
if gamepaddb.HasStandardLayoutMapping(g.sdlID) {
// IsStandardButtonPressed invokes g.Axis, g.Button, or g.Hat so this cannot be locked.
return gamepaddb.IsStandardButtonPressed(g.sdlID, button, g)
}
g.m.Lock()
defer g.m.Unlock()
if m := g.native.standardButtonInOwnMapping(button); m != nil {
return m.Pressed()
}
return false
}
// Vibrate is concurrent-safe.
func (g *Gamepad) Vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
g.m.Lock()
defer g.m.Unlock()
g.native.vibrate(duration, strongMagnitude, weakMagnitude)
}
@@ -0,0 +1,112 @@
// Copyright 2022 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.
package gamepad
import (
"time"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
)
type nativeGamepadsImpl struct{}
func newNativeGamepadsImpl() nativeGamepads {
return &nativeGamepadsImpl{}
}
func (*nativeGamepadsImpl) init(gamepads *gamepads) error {
return nil
}
func (*nativeGamepadsImpl) update(gamepads *gamepads) error {
return nil
}
type nativeGamepadImpl struct {
androidDeviceID int
axesReady []bool
axes []float64
buttons []bool
hats []int
}
func (*nativeGamepadImpl) update(gamepad *gamepads) error {
// Do nothing. The state of gamepads are given via APIs in extern_android.go.
return nil
}
func (*nativeGamepadImpl) hasOwnStandardLayoutMapping() bool {
return false
}
func (*nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput {
return nil
}
func (*nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput {
return nil
}
func (g *nativeGamepadImpl) axisCount() int {
return len(g.axes)
}
func (g *nativeGamepadImpl) buttonCount() int {
return len(g.buttons)
}
func (g *nativeGamepadImpl) hatCount() int {
return len(g.hats)
}
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
if axis < 0 || axis >= len(g.axesReady) {
return false
}
return g.axesReady[axis]
}
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
if axis < 0 || axis >= len(g.axes) {
return 0
}
return g.axes[axis]
}
func (g *nativeGamepadImpl) isButtonPressed(button int) bool {
if button < 0 || button >= len(g.buttons) {
return false
}
return g.buttons[button]
}
func (g *nativeGamepadImpl) buttonValue(button int) float64 {
if g.isButtonPressed(button) {
return 1
}
return 0
}
func (g *nativeGamepadImpl) hatState(hat int) int {
if hat < 0 || hat >= len(g.hats) {
return 0
}
return g.hats[hat]
}
func (g *nativeGamepadImpl) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
// TODO: Implement this (#1452)
}
@@ -0,0 +1,436 @@
// Copyright 2022 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 !ios
package gamepad
import (
"errors"
"fmt"
"sort"
"strings"
"sync"
"time"
"unsafe"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
)
type nativeGamepadsImpl struct {
hidManager _IOHIDManagerRef
devicesToAdd []_IOHIDDeviceRef
devicesToRemove []_IOHIDDeviceRef
devicesM sync.Mutex
}
func newNativeGamepadsImpl() nativeGamepads {
return &nativeGamepadsImpl{}
}
func (g *nativeGamepadsImpl) init(gamepads *gamepads) error {
if err := initializeCF(); err != nil {
return err
}
if err := initializeIOKit(); err != nil {
return err
}
var dicts []_CFDictionaryRef
page := kHIDPage_GenericDesktop
for _, usage := range []uint{
kHIDUsage_GD_Joystick,
kHIDUsage_GD_GamePad,
kHIDUsage_GD_MultiAxisController,
} {
pageRef := _CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, unsafe.Pointer(&page))
if pageRef == 0 {
return errors.New("gamepad: CFNumberCreate returned nil")
}
defer _CFRelease(_CFTypeRef(pageRef))
usageRef := _CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, unsafe.Pointer(&usage))
if usageRef == 0 {
return errors.New("gamepad: CFNumberCreate returned nil")
}
defer _CFRelease(_CFTypeRef(usageRef))
keys := []_CFStringRef{
_CFStringCreateWithCString(kCFAllocatorDefault, kIOHIDDeviceUsagePageKey, kCFStringEncodingUTF8),
_CFStringCreateWithCString(kCFAllocatorDefault, kIOHIDDeviceUsageKey, kCFStringEncodingUTF8),
}
values := []_CFNumberRef{
pageRef,
usageRef,
}
dict := _CFDictionaryCreate(kCFAllocatorDefault,
(*unsafe.Pointer)(unsafe.Pointer(&keys[0])),
(*unsafe.Pointer)(unsafe.Pointer(&values[0])),
_CFIndex(len(keys)), *(**_CFDictionaryKeyCallBacks)(unsafe.Pointer(&kCFTypeDictionaryKeyCallBacks)), *(**_CFDictionaryValueCallBacks)(unsafe.Pointer(&kCFTypeDictionaryValueCallBacks)))
if dict == 0 {
return errors.New("gamepad: CFDictionaryCreate returned nil")
}
defer _CFRelease(_CFTypeRef(dict))
dicts = append(dicts, dict)
}
matching := _CFArrayCreate(kCFAllocatorDefault,
(*unsafe.Pointer)(unsafe.Pointer(&dicts[0])),
_CFIndex(len(dicts)), *(**_CFArrayCallBacks)(unsafe.Pointer(&kCFTypeArrayCallBacks)))
if matching == 0 {
return errors.New("gamepad: CFArrayCreateMutable returned nil")
}
defer _CFRelease(_CFTypeRef(matching))
g.hidManager = _IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone)
if _IOHIDManagerOpen(g.hidManager, kIOHIDOptionsTypeNone) != kIOReturnSuccess {
return errors.New("gamepad: IOHIDManagerOpen failed")
}
_IOHIDManagerSetDeviceMatchingMultiple(g.hidManager, matching)
_IOHIDManagerRegisterDeviceMatchingCallback(g.hidManager, ebitenGamepadMatchingCallback, nil)
_IOHIDManagerRegisterDeviceRemovalCallback(g.hidManager, ebitenGamepadRemovalCallback, nil)
_IOHIDManagerScheduleWithRunLoop(g.hidManager, _CFRunLoopGetMain(), **(**_CFStringRef)(unsafe.Pointer(&kCFRunLoopDefaultMode)))
// Execute the run loop once in order to register any initially-attached gamepads.
_CFRunLoopRunInMode(**(**_CFStringRef)(unsafe.Pointer(&kCFRunLoopDefaultMode)), 0, false)
return nil
}
func ebitenGamepadMatchingCallback(ctx unsafe.Pointer, res _IOReturn, sender unsafe.Pointer, device _IOHIDDeviceRef) {
n := theGamepads.native.(*nativeGamepadsImpl)
n.devicesM.Lock()
defer n.devicesM.Unlock()
n.devicesToAdd = append(n.devicesToAdd, device)
}
func ebitenGamepadRemovalCallback(ctx unsafe.Pointer, res _IOReturn, sender unsafe.Pointer, device _IOHIDDeviceRef) {
n := theGamepads.native.(*nativeGamepadsImpl)
n.devicesM.Lock()
defer n.devicesM.Unlock()
n.devicesToRemove = append(n.devicesToRemove, device)
}
func (g *nativeGamepadsImpl) update(gamepads *gamepads) error {
n := theGamepads.native.(*nativeGamepadsImpl)
n.devicesM.Lock()
defer n.devicesM.Unlock()
for _, device := range g.devicesToAdd {
g.addDevice(device, gamepads)
}
for _, device := range g.devicesToRemove {
gamepads.remove(func(g *Gamepad) bool {
return g.native.(*nativeGamepadImpl).device == device
})
}
g.devicesToAdd = g.devicesToAdd[:0]
g.devicesToRemove = g.devicesToRemove[:0]
return nil
}
func (g *nativeGamepadsImpl) addDevice(device _IOHIDDeviceRef, gamepads *gamepads) {
if gamepads.find(func(g *Gamepad) bool {
return g.native.(*nativeGamepadImpl).device == device
}) != nil {
return
}
name := "Unknown"
if prop := _IOHIDDeviceGetProperty(device, _CFStringCreateWithCString(kCFAllocatorDefault, kIOHIDProductKey, kCFStringEncodingUTF8)); prop != 0 {
var cstr [256]byte
_CFStringGetCString(_CFStringRef(prop), cstr[:], kCFStringEncodingUTF8)
name = strings.TrimRight(string(cstr[:]), "\x00")
}
var vendor uint32
if prop := _IOHIDDeviceGetProperty(device, _CFStringCreateWithCString(kCFAllocatorDefault, kIOHIDVendorIDKey, kCFStringEncodingUTF8)); prop != 0 {
_CFNumberGetValue(_CFNumberRef(prop), kCFNumberSInt32Type, unsafe.Pointer(&vendor))
}
var product uint32
if prop := _IOHIDDeviceGetProperty(device, _CFStringCreateWithCString(kCFAllocatorDefault, kIOHIDProductIDKey, kCFStringEncodingUTF8)); prop != 0 {
_CFNumberGetValue(_CFNumberRef(prop), kCFNumberSInt32Type, unsafe.Pointer(&product))
}
var version uint32
if prop := _IOHIDDeviceGetProperty(device, _CFStringCreateWithCString(kCFAllocatorDefault, kIOHIDVersionNumberKey, kCFStringEncodingUTF8)); prop != 0 {
_CFNumberGetValue(_CFNumberRef(prop), kCFNumberSInt32Type, unsafe.Pointer(&version))
}
var sdlID string
if vendor != 0 && product != 0 {
sdlID = fmt.Sprintf("03000000%02x%02x0000%02x%02x0000%02x%02x0000",
byte(vendor), byte(vendor>>8),
byte(product), byte(product>>8),
byte(version), byte(version>>8))
} else {
bs := []byte(name)
if len(bs) < 12 {
bs = append(bs, make([]byte, 12-len(bs))...)
}
sdlID = fmt.Sprintf("05000000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
bs[0], bs[1], bs[2], bs[3], bs[4], bs[5], bs[6], bs[7], bs[8], bs[9], bs[10], bs[11])
}
elements := _IOHIDDeviceCopyMatchingElements(device, 0, kIOHIDOptionsTypeNone)
defer _CFRelease(_CFTypeRef(elements))
n := &nativeGamepadImpl{
device: device,
}
gp := gamepads.add(name, sdlID)
gp.native = n
for i := _CFIndex(0); i < _CFArrayGetCount(elements); i++ {
native := (_IOHIDElementRef)(_CFArrayGetValueAtIndex(elements, i))
if _CFGetTypeID(_CFTypeRef(native)) != _IOHIDElementGetTypeID() {
continue
}
typ := _IOHIDElementGetType(native)
if typ != kIOHIDElementTypeInput_Axis &&
typ != kIOHIDElementTypeInput_Button &&
typ != kIOHIDElementTypeInput_Misc {
continue
}
usage := _IOHIDElementGetUsage(native)
page := _IOHIDElementGetUsagePage(native)
switch page {
case kHIDPage_GenericDesktop:
switch usage {
case kHIDUsage_GD_X, kHIDUsage_GD_Y, kHIDUsage_GD_Z,
kHIDUsage_GD_Rx, kHIDUsage_GD_Ry, kHIDUsage_GD_Rz,
kHIDUsage_GD_Slider, kHIDUsage_GD_Dial, kHIDUsage_GD_Wheel:
n.axes = append(n.axes, element{
native: native,
usage: int(usage),
index: len(n.axes),
minimum: int(_IOHIDElementGetLogicalMin(native)),
maximum: int(_IOHIDElementGetLogicalMax(native)),
})
case kHIDUsage_GD_Hatswitch:
n.hats = append(n.hats, element{
native: native,
usage: int(usage),
index: len(n.hats),
minimum: int(_IOHIDElementGetLogicalMin(native)),
maximum: int(_IOHIDElementGetLogicalMax(native)),
})
case kHIDUsage_GD_DPadUp, kHIDUsage_GD_DPadRight, kHIDUsage_GD_DPadDown, kHIDUsage_GD_DPadLeft,
kHIDUsage_GD_SystemMainMenu, kHIDUsage_GD_Select, kHIDUsage_GD_Start:
n.buttons = append(n.buttons, element{
native: native,
usage: int(usage),
index: len(n.buttons),
minimum: int(_IOHIDElementGetLogicalMin(native)),
maximum: int(_IOHIDElementGetLogicalMax(native)),
})
}
case kHIDPage_Simulation:
switch usage {
case kHIDUsage_Sim_Accelerator, kHIDUsage_Sim_Brake, kHIDUsage_Sim_Throttle, kHIDUsage_Sim_Rudder, kHIDUsage_Sim_Steering:
n.axes = append(n.axes, element{
native: native,
usage: int(usage),
index: len(n.axes),
minimum: int(_IOHIDElementGetLogicalMin(native)),
maximum: int(_IOHIDElementGetLogicalMax(native)),
})
}
case kHIDPage_Button, kHIDPage_Consumer:
n.buttons = append(n.buttons, element{
native: native,
usage: int(usage),
index: len(n.buttons),
minimum: int(_IOHIDElementGetLogicalMin(native)),
maximum: int(_IOHIDElementGetLogicalMax(native)),
})
}
}
sort.Stable(n.axes)
sort.Stable(n.buttons)
sort.Stable(n.hats)
}
type element struct {
native _IOHIDElementRef
usage int
index int
minimum int
maximum int
}
type elements []element
func (e elements) Len() int {
return len(e)
}
func (e elements) Less(i, j int) bool {
if e[i].usage != e[j].usage {
return e[i].usage < e[j].usage
}
if e[i].index != e[j].index {
return e[i].index < e[j].index
}
return false
}
func (e elements) Swap(i, j int) {
e[i], e[j] = e[j], e[i]
}
type nativeGamepadImpl struct {
device _IOHIDDeviceRef
axes elements
buttons elements
hats elements
axisValues []float64
buttonValues []bool
hatValues []int
}
func (g *nativeGamepadImpl) elementValue(e *element) int {
var valueRef _IOHIDValueRef
if _IOHIDDeviceGetValue(g.device, e.native, &valueRef) == kIOReturnSuccess {
return int(_IOHIDValueGetIntegerValue(valueRef))
}
return 0
}
func (g *nativeGamepadImpl) update(gamepads *gamepads) error {
if cap(g.axisValues) < len(g.axes) {
g.axisValues = make([]float64, len(g.axes))
}
g.axisValues = g.axisValues[:len(g.axes)]
if cap(g.buttonValues) < len(g.buttons) {
g.buttonValues = make([]bool, len(g.buttons))
}
g.buttonValues = g.buttonValues[:len(g.buttons)]
if cap(g.hatValues) < len(g.hats) {
g.hatValues = make([]int, len(g.hats))
}
g.hatValues = g.hatValues[:len(g.hats)]
for i, a := range g.axes {
raw := g.elementValue(&a)
if raw < a.minimum {
a.minimum = raw
}
if raw > a.maximum {
a.maximum = raw
}
var value float64
if size := a.maximum - a.minimum; size != 0 {
value = 2*float64(raw-a.minimum)/float64(size) - 1
}
g.axisValues[i] = value
}
for i, b := range g.buttons {
g.buttonValues[i] = (g.elementValue(&b) - b.minimum) > 0
}
hatStates := []int{
hatUp,
hatRightUp,
hatRight,
hatRightDown,
hatDown,
hatLeftDown,
hatLeft,
hatLeftUp,
}
for i, h := range g.hats {
if state := g.elementValue(&h) - h.minimum; state < 0 || state >= len(hatStates) {
g.hatValues[i] = hatCentered
} else {
g.hatValues[i] = hatStates[state]
}
}
return nil
}
func (g *nativeGamepadImpl) hasOwnStandardLayoutMapping() bool {
return false
}
func (*nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput {
return nil
}
func (*nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput {
return nil
}
func (g *nativeGamepadImpl) axisCount() int {
return len(g.axisValues)
}
func (g *nativeGamepadImpl) buttonCount() int {
return len(g.buttonValues)
}
func (g *nativeGamepadImpl) hatCount() int {
return len(g.hatValues)
}
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
return axis >= 0 && axis < g.axisCount()
}
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
if axis < 0 || axis >= len(g.axisValues) {
return 0
}
return g.axisValues[axis]
}
func (g *nativeGamepadImpl) buttonValue(button int) float64 {
if g.isButtonPressed(button) {
return 1
}
return 0
}
func (g *nativeGamepadImpl) isButtonPressed(button int) bool {
if button < 0 || button >= len(g.buttonValues) {
return false
}
return g.buttonValues[button]
}
func (g *nativeGamepadImpl) hatState(hat int) int {
if hat < 0 || hat >= len(g.hatValues) {
return hatCentered
}
return g.hatValues[hat]
}
func (g *nativeGamepadImpl) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
// TODO: Implement this (#1452)
}
@@ -0,0 +1,837 @@
// Copyright 2022 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.
package gamepad
import (
"errors"
"fmt"
"sort"
"strings"
"sync/atomic"
"syscall"
"time"
"unsafe"
"golang.org/x/sys/windows"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
)
type dinputObjectType int
const (
dinputObjectTypeAxis dinputObjectType = iota
dinputObjectTypeSlider
dinputObjectTypeButton
dinputObjectTypePOV
)
var dinputObjectDataFormats = []_DIOBJECTDATAFORMAT{
{&_GUID_XAxis, _DIJOFS_X, _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
{&_GUID_YAxis, _DIJOFS_Y, _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
{&_GUID_ZAxis, _DIJOFS_Z, _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
{&_GUID_RxAxis, _DIJOFS_RX, _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
{&_GUID_RyAxis, _DIJOFS_RY, _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
{&_GUID_RzAxis, _DIJOFS_RZ, _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
{&_GUID_Slider, _DIJOFS_SLIDER(0), _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
{&_GUID_Slider, _DIJOFS_SLIDER(1), _DIDFT_AXIS | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, _DIDOI_ASPECTPOSITION},
{&_GUID_POV, _DIJOFS_POV(0), _DIDFT_POV | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{&_GUID_POV, _DIJOFS_POV(1), _DIDFT_POV | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{&_GUID_POV, _DIJOFS_POV(2), _DIDFT_POV | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{&_GUID_POV, _DIJOFS_POV(3), _DIDFT_POV | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(0), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(1), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(2), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(3), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(4), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(5), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(6), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(7), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(8), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(9), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(10), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(11), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(12), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(13), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(14), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(15), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(16), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(17), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(18), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(19), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(20), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(21), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(22), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(23), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(24), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(25), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(26), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(27), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(28), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(29), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(30), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
{nil, _DIJOFS_BUTTON(31), _DIDFT_BUTTON | _DIDFT_OPTIONAL | _DIDFT_ANYINSTANCE, 0},
}
var xinputButtons = []uint16{
_XINPUT_GAMEPAD_A,
_XINPUT_GAMEPAD_B,
_XINPUT_GAMEPAD_X,
_XINPUT_GAMEPAD_Y,
_XINPUT_GAMEPAD_LEFT_SHOULDER,
_XINPUT_GAMEPAD_RIGHT_SHOULDER,
_XINPUT_GAMEPAD_BACK,
_XINPUT_GAMEPAD_START,
_XINPUT_GAMEPAD_LEFT_THUMB,
_XINPUT_GAMEPAD_RIGHT_THUMB,
}
type nativeGamepadsDesktop struct {
dinput8 windows.Handle
dinput8API *_IDirectInput8W
xinput windows.Handle
procDirectInput8Create uintptr
procXInputGetCapabilities uintptr
procXInputGetState uintptr
origWndProc uintptr
wndProcCallback uintptr
enumDevicesCallback uintptr
enumObjectsCallback uintptr
nativeWindow windows.HWND
deviceChanged int32
err error
}
type dinputObject struct {
objectType dinputObjectType
index int
}
type enumObjectsContext struct {
device *_IDirectInputDevice8W
objects []dinputObject
axisCount int
sliderCount int
buttonCount int
povCount int
}
func (g *nativeGamepadsDesktop) init(gamepads *gamepads) error {
// As there is no guarantee that the DLL exists, NewLazySystemDLL is not available.
// TODO: Is there a 'system' version of LoadLibrary?
if h, err := windows.LoadLibrary("dinput8.dll"); err == nil {
g.dinput8 = h
p, err := windows.GetProcAddress(h, "DirectInput8Create")
if err != nil {
return err
}
g.procDirectInput8Create = p
}
// TODO: Loading xinput1_4.dll or xinput9_1_0.dll should be enough.
// See https://source.chromium.org/chromium/chromium/src/+/main:device/gamepad/xinput_data_fetcher_win.cc;l=75-84;drc=643cdf61903e99f27c3d80daee67e217e9d280e0
for _, dll := range []string{
"xinput1_4.dll",
"xinput1_3.dll",
"xinput9_1_0.dll",
"xinput1_2.dll",
"xinput1_1.dll",
} {
if h, err := windows.LoadLibrary(dll); err == nil {
g.xinput = h
{
p, err := windows.GetProcAddress(h, "XInputGetCapabilities")
if err != nil {
return err
}
g.procXInputGetCapabilities = p
}
{
p, err := windows.GetProcAddress(h, "XInputGetState")
if err != nil {
return err
}
g.procXInputGetState = p
}
break
}
}
if g.dinput8 != 0 {
// TODO: Use _GetModuleHandleExW to align with GLFW v3.3.8.
m, err := _GetModuleHandleW()
if err != nil {
return err
}
var api *_IDirectInput8W
if err := g.directInput8Create(m, _DIRECTINPUT_VERSION, &_IID_IDirectInput8W, &api, nil); err != nil {
return err
}
g.dinput8API = api
if err := g.detectConnection(gamepads); err != nil {
return err
}
}
return nil
}
func (g *nativeGamepadsDesktop) directInput8Create(hinst uintptr, dwVersion uint32, riidltf *windows.GUID, ppvOut **_IDirectInput8W, punkOuter unsafe.Pointer) error {
r, _, _ := syscall.Syscall6(g.procDirectInput8Create, 5,
hinst, uintptr(dwVersion), uintptr(unsafe.Pointer(riidltf)), uintptr(unsafe.Pointer(ppvOut)), uintptr(punkOuter),
0)
if uint32(r) != _DI_OK {
return fmt.Errorf("gamepad: DirectInput8Create failed: %w", handleError(windows.Handle(uint32(r))))
}
return nil
}
func (g *nativeGamepadsDesktop) xinputGetCapabilities(dwUserIndex uint32, dwFlags uint32, pCapabilities *_XINPUT_CAPABILITIES) error {
// XInputGetCapabilities doesn't call SetLastError and returns an error code directly.
r, _, _ := syscall.Syscall(g.procXInputGetCapabilities, 3,
uintptr(dwUserIndex), uintptr(dwFlags), uintptr(unsafe.Pointer(pCapabilities)))
if e := syscall.Errno(uint32(r)); e != windows.ERROR_SUCCESS {
return fmt.Errorf("gamepad: XInputGetCapabilities failed: %w", e)
}
return nil
}
func (g *nativeGamepadsDesktop) xinputGetState(dwUserIndex uint32, pState *_XINPUT_STATE) error {
// XInputGetState doesn't call SetLastError and returns an error code directly.
r, _, _ := syscall.Syscall(g.procXInputGetState, 2,
uintptr(dwUserIndex), uintptr(unsafe.Pointer(pState)), 0)
if e := syscall.Errno(uint32(r)); e != windows.ERROR_SUCCESS {
return fmt.Errorf("gamepad: XInputGetCapabilities failed: %w", e)
}
return nil
}
func (g *nativeGamepadsDesktop) detectConnection(gamepads *gamepads) error {
if g.dinput8 != 0 {
if g.enumDevicesCallback == 0 {
g.enumDevicesCallback = windows.NewCallback(g.dinput8EnumDevicesCallback)
}
if err := g.dinput8API.EnumDevices(_DI8DEVCLASS_GAMECTRL, g.enumDevicesCallback, unsafe.Pointer(gamepads), _DIEDFL_ALLDEVICES); err != nil {
return err
}
if g.err != nil {
return g.err
}
}
if g.xinput != 0 {
const xuserMaxCount = 4
for i := 0; i < xuserMaxCount; i++ {
if gamepads.find(func(g *Gamepad) bool {
n := g.native.(*nativeGamepadDesktop)
return n.dinputDevice == nil && n.xinputIndex == i
}) != nil {
continue
}
var xic _XINPUT_CAPABILITIES
if err := g.xinputGetCapabilities(uint32(i), 0, &xic); err != nil {
if !errors.Is(err, windows.ERROR_DEVICE_NOT_CONNECTED) {
return err
}
continue
}
sdlID := fmt.Sprintf("78696e707574%02x000000000000000000", xic.subType&0xff)
name := "Unknown XInput Device"
switch xic.subType {
case _XINPUT_DEVSUBTYPE_GAMEPAD:
if xic.flags&_XINPUT_CAPS_WIRELESS != 0 {
name = "Wireless Xbox Controller"
} else {
name = "Xbox Controller"
}
case _XINPUT_DEVSUBTYPE_WHEEL:
name = "XInput Wheel"
case _XINPUT_DEVSUBTYPE_ARCADE_STICK:
name = "XInput Arcade Stick"
case _XINPUT_DEVSUBTYPE_FLIGHT_STICK:
name = "XInput Flight Stick"
case _XINPUT_DEVSUBTYPE_DANCE_PAD:
name = "XInput Dance Pad"
case _XINPUT_DEVSUBTYPE_GUITAR:
name = "XInput Guitar"
case _XINPUT_DEVSUBTYPE_DRUM_KIT:
name = "XInput Drum Kit"
}
gp := gamepads.add(name, sdlID)
gp.native = &nativeGamepadDesktop{
xinputIndex: i,
}
}
}
return nil
}
func (g *nativeGamepadsDesktop) dinput8EnumDevicesCallback(lpddi *_DIDEVICEINSTANCEW, pvRef unsafe.Pointer) uintptr {
gamepads := (*gamepads)(pvRef)
if g.err != nil {
return _DIENUM_STOP
}
s, err := supportsXInput(lpddi.guidProduct)
if err != nil {
g.err = err
return _DIENUM_STOP
}
if s {
return _DIENUM_CONTINUE
}
var device *_IDirectInputDevice8W
if err := g.dinput8API.CreateDevice(&lpddi.guidInstance, &device, nil); err != nil {
g.err = err
return _DIENUM_STOP
}
// lpddi.guidInstance is not relialable as a unique identity when the same multiple devices are connected (#3046).
// Use HID Path instead.
getDInputPath := func(device *_IDirectInputDevice8W) (string, error) {
var prop _DIPROPGUIDANDPATH
prop.diph.dwHeaderSize = uint32(unsafe.Sizeof(_DIPROPHEADER{}))
prop.diph.dwSize = uint32(unsafe.Sizeof(_DIPROPGUIDANDPATH{}))
if err := device.GetProperty(_DIPROP_GUIDANDPATH, &prop.diph); err != nil {
return "", err
}
return windows.UTF16ToString(prop.wszPath[:]), nil
}
dinputPath, err := getDInputPath(device)
if err != nil {
g.err = err
device.Release()
return _DIENUM_STOP
}
var findErr error
if gamepads.find(func(g *Gamepad) bool {
// A DInput device can be nil when the device is an XInput device (#3047).
d := g.native.(*nativeGamepadDesktop).dinputDevice
if d == nil {
return false
}
path, err := getDInputPath(d)
if err != nil {
findErr = err
return true
}
return path == dinputPath
}) != nil {
if findErr != nil {
g.err = findErr
device.Release()
return _DIENUM_STOP
}
device.Release()
return _DIENUM_CONTINUE
}
dataFormat := _DIDATAFORMAT{
dwSize: uint32(unsafe.Sizeof(_DIDATAFORMAT{})),
dwObjSize: uint32(unsafe.Sizeof(_DIOBJECTDATAFORMAT{})),
dwFlags: _DIDFT_ABSAXIS,
dwDataSize: uint32(unsafe.Sizeof(_DIJOYSTATE{})),
dwNumObjs: uint32(len(dinputObjectDataFormats)),
rgodf: &dinputObjectDataFormats[0],
}
if err := device.SetDataFormat(&dataFormat); err != nil {
g.err = err
device.Release()
return _DIENUM_STOP
}
dc := _DIDEVCAPS{
dwSize: uint32(unsafe.Sizeof(_DIDEVCAPS{})),
}
if err := device.GetCapabilities(&dc); err != nil {
g.err = err
device.Release()
return _DIENUM_STOP
}
dipd := _DIPROPDWORD{
diph: _DIPROPHEADER{
dwSize: uint32(unsafe.Sizeof(_DIPROPDWORD{})),
dwHeaderSize: uint32(unsafe.Sizeof(_DIPROPHEADER{})),
dwHow: _DIPH_DEVICE,
},
dwData: _DIPROPAXISMODE_ABS,
}
if err := device.SetProperty(_DIPROP_AXISMODE, &dipd.diph); err != nil {
g.err = err
device.Release()
return _DIENUM_STOP
}
ctx := enumObjectsContext{
device: device,
}
if g.enumObjectsCallback == 0 {
g.enumObjectsCallback = windows.NewCallback(g.dinputDevice8EnumObjectsCallback)
}
if err := device.EnumObjects(g.enumObjectsCallback, unsafe.Pointer(&ctx), _DIDFT_AXIS|_DIDFT_BUTTON|_DIDFT_POV); err != nil {
g.err = err
device.Release()
return _DIENUM_STOP
}
sort.Slice(ctx.objects, func(i, j int) bool {
if ctx.objects[i].objectType != ctx.objects[j].objectType {
return ctx.objects[i].objectType < ctx.objects[j].objectType
}
return ctx.objects[i].index < ctx.objects[j].index
})
name := windows.UTF16ToString(lpddi.tszInstanceName[:])
var sdlID string
if string(lpddi.guidProduct.Data4[2:8]) == "PIDVID" {
// This seems different from the current SDL implementation.
// Probably guidProduct includes the vendor and the product information, but this works.
// From the game controller database, the 'version' part seems always 0.
sdlID = fmt.Sprintf("03000000%02x%02x0000%02x%02x000000000000",
byte(lpddi.guidProduct.Data1),
byte(lpddi.guidProduct.Data1>>8),
byte(lpddi.guidProduct.Data1>>16),
byte(lpddi.guidProduct.Data1>>24))
} else {
bs := []byte(name)
if len(bs) < 12 {
bs = append(bs, make([]byte, 12-len(bs))...)
}
sdlID = fmt.Sprintf("05000000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
bs[0], bs[1], bs[2], bs[3], bs[4], bs[5], bs[6], bs[7], bs[8], bs[9], bs[10], bs[11])
}
gp := gamepads.add(name, sdlID)
gp.native = &nativeGamepadDesktop{
dinputDevice: device,
dinputObjects: ctx.objects,
dinputPath: dinputPath,
dinputAxes: make([]float64, ctx.axisCount+ctx.sliderCount),
dinputButtons: make([]bool, ctx.buttonCount),
dinputHats: make([]int, ctx.povCount),
}
return _DIENUM_CONTINUE
}
func supportsXInput(guid windows.GUID) (bool, error) {
var count uint32
if r, err := _GetRawInputDeviceList(nil, &count); err != nil {
return false, err
} else if r != 0 {
return false, nil
}
if count == 0 {
return false, nil
}
ridl := make([]_RAWINPUTDEVICELIST, count)
if _, err := _GetRawInputDeviceList(&ridl[0], &count); err != nil {
return false, err
}
for i := 0; i < int(count); i++ {
if ridl[i].dwType != _RIM_TYPEHID {
continue
}
rdi := _RID_DEVICE_INFO{
cbSize: uint32(unsafe.Sizeof(_RID_DEVICE_INFO{})),
}
size := uint32(unsafe.Sizeof(rdi))
if _, err := _GetRawInputDeviceInfoW(ridl[i].hDevice, _RIDI_DEVICEINFO, unsafe.Pointer(&rdi), &size); err != nil {
// GetRawInputDeviceInfoW can return an error (#2603).
continue
}
if uint32(rdi.hid.dwVendorId)|(uint32(rdi.hid.dwProductId)<<16) != guid.Data1 {
continue
}
var name [256]uint16
size = uint32(unsafe.Sizeof(name))
if _, err := _GetRawInputDeviceInfoW(ridl[i].hDevice, _RIDI_DEVICENAME, unsafe.Pointer(&name[0]), &size); err != nil {
return false, err
}
if strings.Contains(windows.UTF16ToString(name[:]), "IG_") {
return true, nil
}
}
return false, nil
}
func (g *nativeGamepadsDesktop) dinputDevice8EnumObjectsCallback(lpddoi *_DIDEVICEOBJECTINSTANCEW, pvRef unsafe.Pointer) uintptr {
ctx := (*enumObjectsContext)(pvRef)
switch {
case _DIDFT_GETTYPE(lpddoi.dwType)&_DIDFT_AXIS != 0:
var index int
switch lpddoi.guidType {
case _GUID_Slider:
index = ctx.sliderCount
case _GUID_XAxis:
index = 0
case _GUID_YAxis:
index = 1
case _GUID_ZAxis:
index = 2
case _GUID_RxAxis:
index = 3
case _GUID_RyAxis:
index = 4
case _GUID_RzAxis:
index = 5
default:
return _DIENUM_CONTINUE
}
dipr := _DIPROPRANGE{
diph: _DIPROPHEADER{
dwSize: uint32(unsafe.Sizeof(_DIPROPRANGE{})),
dwHeaderSize: uint32(unsafe.Sizeof(_DIPROPHEADER{})),
dwObj: lpddoi.dwType,
dwHow: _DIPH_BYID,
},
lMin: -32768,
lMax: 32767,
}
if err := ctx.device.SetProperty(_DIPROP_RANGE, &dipr.diph); err != nil {
return _DIENUM_CONTINUE
}
var objectType dinputObjectType
if lpddoi.guidType == _GUID_Slider {
objectType = dinputObjectTypeSlider
ctx.sliderCount++
} else {
objectType = dinputObjectTypeAxis
ctx.axisCount++
}
ctx.objects = append(ctx.objects, dinputObject{
objectType: objectType,
index: index,
})
case _DIDFT_GETTYPE(lpddoi.dwType)&_DIDFT_BUTTON != 0:
ctx.objects = append(ctx.objects, dinputObject{
objectType: dinputObjectTypeButton,
index: ctx.buttonCount,
})
ctx.buttonCount++
case _DIDFT_GETTYPE(lpddoi.dwType)&_DIDFT_POV != 0:
ctx.objects = append(ctx.objects, dinputObject{
objectType: dinputObjectTypePOV,
index: ctx.povCount,
})
ctx.povCount++
}
return _DIENUM_CONTINUE
}
func (g *nativeGamepadsDesktop) update(gamepads *gamepads) error {
if g.err != nil {
return g.err
}
if g.origWndProc == 0 {
if g.wndProcCallback == 0 {
g.wndProcCallback = windows.NewCallback(g.wndProc)
}
// Note that a Win32API GetActiveWindow doesn't work on Xbox.
h, err := _SetWindowLongPtrW(g.nativeWindow, _GWL_WNDPROC, g.wndProcCallback)
if err != nil {
return err
}
g.origWndProc = h
}
if atomic.LoadInt32(&g.deviceChanged) != 0 {
if err := g.detectConnection(gamepads); err != nil {
g.err = err
}
atomic.StoreInt32(&g.deviceChanged, 0)
}
return nil
}
func (g *nativeGamepadsDesktop) wndProc(hWnd uintptr, uMsg uint32, wParam, lParam uintptr) uintptr {
switch uMsg {
case _WM_DEVICECHANGE:
atomic.StoreInt32(&g.deviceChanged, 1)
}
return _CallWindowProcW(g.origWndProc, hWnd, uMsg, wParam, lParam)
}
func (g *nativeGamepadsDesktop) setNativeWindow(nativeWindow uintptr) {
g.nativeWindow = windows.HWND(nativeWindow)
}
type nativeGamepadDesktop struct {
dinputDevice *_IDirectInputDevice8W
dinputObjects []dinputObject
dinputPath string
dinputAxes []float64
dinputButtons []bool
dinputHats []int
xinputIndex int
xinputState _XINPUT_STATE
}
func (*nativeGamepadDesktop) hasOwnStandardLayoutMapping() bool {
return false
}
func (*nativeGamepadDesktop) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput {
return nil
}
func (*nativeGamepadDesktop) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput {
return nil
}
func (g *nativeGamepadDesktop) usesDInput() bool {
return g.dinputDevice != nil
}
func (g *nativeGamepadDesktop) update(gamepads *gamepads) (err error) {
var disconnected bool
defer func() {
if !disconnected && err == nil {
return
}
gamepads.remove(func(gamepad *Gamepad) bool {
return gamepad.native == g
})
if g.dinputDevice != nil {
g.dinputDevice.Release()
}
}()
if g.usesDInput() {
if err := g.dinputDevice.Poll(); err != nil {
if !errors.Is(err, handleError(_DIERR_NOTACQUIRED)) && !errors.Is(err, handleError(_DIERR_INPUTLOST)) {
return err
}
}
var state _DIJOYSTATE
if err := g.dinputDevice.GetDeviceState(uint32(unsafe.Sizeof(state)), unsafe.Pointer(&state)); err != nil {
if !errors.Is(err, handleError(_DIERR_NOTACQUIRED)) && !errors.Is(err, handleError(_DIERR_INPUTLOST)) {
return err
}
// Acquire can return an error just after a gamepad is disconnected. Ignore the error.
_ = g.dinputDevice.Acquire()
if err := g.dinputDevice.Poll(); err != nil {
if !errors.Is(err, handleError(_DIERR_NOTACQUIRED)) && !errors.Is(err, handleError(_DIERR_INPUTLOST)) {
return err
}
}
if err := g.dinputDevice.GetDeviceState(uint32(unsafe.Sizeof(state)), unsafe.Pointer(&state)); err != nil {
if !errors.Is(err, handleError(_DIERR_NOTACQUIRED)) && !errors.Is(err, handleError(_DIERR_INPUTLOST)) {
return err
}
disconnected = true
return nil
}
}
var ai, bi, hi int
for _, obj := range g.dinputObjects {
switch obj.objectType {
case dinputObjectTypeAxis:
var v int32
switch obj.index {
case 0:
v = state.lX
case 1:
v = state.lY
case 2:
v = state.lZ
case 3:
v = state.lRx
case 4:
v = state.lRy
case 5:
v = state.lRz
}
g.dinputAxes[ai] = (float64(v) + 0.5) / 32767.5
ai++
case dinputObjectTypeSlider:
v := state.rglSlider[obj.index]
g.dinputAxes[ai] = (float64(v) + 0.5) / 32767.5
ai++
case dinputObjectTypeButton:
v := (state.rgbButtons[obj.index] & 0x80) != 0
g.dinputButtons[bi] = v
bi++
case dinputObjectTypePOV:
stateIndex := state.rgdwPOV[obj.index] / (45 * _DI_DEGREES)
v := hatCentered
switch stateIndex {
case 0:
v = hatUp
case 1:
v = hatRightUp
case 2:
v = hatRight
case 3:
v = hatRightDown
case 4:
v = hatDown
case 5:
v = hatLeftDown
case 6:
v = hatLeft
case 7:
v = hatLeftUp
}
g.dinputHats[hi] = v
hi++
}
}
return nil
}
var state _XINPUT_STATE
if err := gamepads.native.(*nativeGamepadsDesktop).xinputGetState(uint32(g.xinputIndex), &state); err != nil {
if !errors.Is(err, windows.ERROR_DEVICE_NOT_CONNECTED) {
return err
}
disconnected = true
return nil
}
g.xinputState = state
return nil
}
func (g *nativeGamepadDesktop) axisCount() int {
if g.usesDInput() {
return len(g.dinputAxes)
}
return 6
}
func (g *nativeGamepadDesktop) buttonCount() int {
if g.usesDInput() {
return len(g.dinputButtons)
}
return len(xinputButtons)
}
func (g *nativeGamepadDesktop) hatCount() int {
if g.usesDInput() {
return len(g.dinputHats)
}
return 1
}
func (g *nativeGamepadDesktop) isAxisReady(axis int) bool {
return axis >= 0 && axis < g.axisCount()
}
func (g *nativeGamepadDesktop) axisValue(axis int) float64 {
if g.usesDInput() {
if axis < 0 || axis >= len(g.dinputAxes) {
return 0
}
return g.dinputAxes[axis]
}
var v float64
switch axis {
case 0:
v = (float64(g.xinputState.Gamepad.sThumbLX) + 0.5) / 32767.5
case 1:
v = -(float64(g.xinputState.Gamepad.sThumbLY) + 0.5) / 32767.5
case 2:
v = (float64(g.xinputState.Gamepad.sThumbRX) + 0.5) / 32767.5
case 3:
v = -(float64(g.xinputState.Gamepad.sThumbRY) + 0.5) / 32767.5
case 4:
v = float64(g.xinputState.Gamepad.bLeftTrigger)/127.5 - 1.0
case 5:
v = float64(g.xinputState.Gamepad.bRightTrigger)/127.5 - 1.0
}
return v
}
func (g *nativeGamepadDesktop) isButtonPressed(button int) bool {
if g.usesDInput() {
if button < 0 || button >= len(g.dinputButtons) {
return false
}
return g.dinputButtons[button]
}
if button < 0 || button >= len(xinputButtons) {
return false
}
return g.xinputState.Gamepad.wButtons&xinputButtons[button] != 0
}
func (g *nativeGamepadDesktop) buttonValue(button int) float64 {
if g.isButtonPressed(button) {
return 1
}
return 0
}
func (g *nativeGamepadDesktop) hatState(hat int) int {
if g.usesDInput() {
if hat < 0 || hat >= len(g.dinputHats) {
return 0
}
return g.dinputHats[hat]
}
if hat != 0 {
return 0
}
var v int
if g.xinputState.Gamepad.wButtons&_XINPUT_GAMEPAD_DPAD_UP != 0 {
v |= hatUp
}
if g.xinputState.Gamepad.wButtons&_XINPUT_GAMEPAD_DPAD_RIGHT != 0 {
v |= hatRight
}
if g.xinputState.Gamepad.wButtons&_XINPUT_GAMEPAD_DPAD_DOWN != 0 {
v |= hatDown
}
if g.xinputState.Gamepad.wButtons&_XINPUT_GAMEPAD_DPAD_LEFT != 0 {
v |= hatLeft
}
return v
}
func (g *nativeGamepadDesktop) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
// TODO: Implement this (#1452)
}
+113
View File
@@ -0,0 +1,113 @@
// Copyright 2022 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.
package gamepad
import (
"time"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
)
type nativeGamepadsImpl struct{}
func newNativeGamepadsImpl() nativeGamepads {
return &nativeGamepadsImpl{}
}
func (*nativeGamepadsImpl) init(gamepads *gamepads) error {
initializeIOSGamepads()
return nil
}
func (*nativeGamepadsImpl) update(gamepads *gamepads) error {
return nil
}
type nativeGamepadImpl struct {
controller uintptr
buttonMask uint16
hasDualshockTouchpad bool
hasXboxPaddles bool
hasXboxShareButton bool
axes []float64
buttons []bool
hats []int
}
func (g *nativeGamepadImpl) update(gamepad *gamepads) error {
g.updateIOSGamepad()
return nil
}
func (*nativeGamepadImpl) hasOwnStandardLayoutMapping() bool {
return false
}
func (*nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput {
return nil
}
func (*nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput {
return nil
}
func (g *nativeGamepadImpl) axisCount() int {
return len(g.axes)
}
func (g *nativeGamepadImpl) buttonCount() int {
return len(g.buttons)
}
func (g *nativeGamepadImpl) hatCount() int {
return len(g.hats)
}
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
return axis >= 0 && axis < g.axisCount()
}
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
if axis < 0 || axis >= len(g.axes) {
return 0
}
return g.axes[axis]
}
func (g *nativeGamepadImpl) isButtonPressed(button int) bool {
if button < 0 || button >= len(g.buttons) {
return false
}
return g.buttons[button]
}
func (g *nativeGamepadImpl) buttonValue(button int) float64 {
if g.isButtonPressed(button) {
return 1
}
return 0
}
func (g *nativeGamepadImpl) hatState(hat int) int {
if hat < 0 || hat >= len(g.hats) {
return 0
}
return g.hats[hat]
}
func (g *nativeGamepadImpl) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
// TODO: Implement this (#1452)
}
+214
View File
@@ -0,0 +1,214 @@
// Copyright 2022 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.
package gamepad
import (
"encoding/hex"
"syscall/js"
"time"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
)
var (
object = js.Global().Get("Object")
)
type nativeGamepadsImpl struct {
indices map[int]struct{}
}
func newNativeGamepadsImpl() nativeGamepads {
return &nativeGamepadsImpl{}
}
func (g *nativeGamepadsImpl) init(gamepads *gamepads) error {
return nil
}
func (g *nativeGamepadsImpl) update(gamepads *gamepads) error {
// TODO: Use the gamepad events instead of navigator.getGamepads.
defer func() {
for k := range g.indices {
delete(g.indices, k)
}
}()
nav := js.Global().Get("navigator")
if !nav.Truthy() {
return nil
}
// getGamepads might not exist under a non-secure context (#2100).
if !nav.Get("getGamepads").Truthy() {
js.Global().Get("console").Call("warn", "navigator.getGamepads is not available. This might require a secure (HTTPS) context.")
return nil
}
gps := nav.Call("getGamepads")
if !gps.Truthy() {
return nil
}
l := gps.Length()
for idx := 0; idx < l; idx++ {
gp := gps.Index(idx)
if !gp.Truthy() {
continue
}
index := gp.Get("index").Int()
if g.indices == nil {
g.indices = map[int]struct{}{}
}
g.indices[index] = struct{}{}
// The gamepad is not registered yet, register this.
gamepad := gamepads.find(func(gamepad *Gamepad) bool {
return index == gamepad.native.(*nativeGamepadImpl).index
})
if gamepad == nil {
name := gp.Get("id").String()
// This emulates the implementation of EMSCRIPTEN_JoystickGetDeviceGUID.
// https://github.com/libsdl-org/SDL/blob/0e9560aea22818884921e5e5064953257bfe7fa7/src/joystick/emscripten/SDL_sysjoystick.c#L385
var sdlID [16]byte
copy(sdlID[:], []byte(name))
gamepad = gamepads.add(name, hex.EncodeToString(sdlID[:]))
gamepad.native = &nativeGamepadImpl{
index: index,
mapping: gp.Get("mapping").String(),
}
}
gamepad.native.(*nativeGamepadImpl).value = gp
}
// Remove an unused gamepads.
gamepads.remove(func(gamepad *Gamepad) bool {
_, ok := g.indices[gamepad.native.(*nativeGamepadImpl).index]
return !ok
})
return nil
}
type nativeGamepadImpl struct {
value js.Value
index int
mapping string
}
func (g *nativeGamepadImpl) hasOwnStandardLayoutMapping() bool {
return g.mapping == "standard"
}
func (g *nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput {
if !g.hasOwnStandardLayoutMapping() {
return nil
}
if axis < 0 || int(axis) >= g.axisCount() {
return nil
}
return axisMappingInput{g: g, axis: int(axis)}
}
func (g *nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput {
if !g.hasOwnStandardLayoutMapping() {
return nil
}
if button < 0 || int(button) >= g.buttonCount() {
return nil
}
return buttonMappingInput{g: g, button: int(button)}
}
func (g *nativeGamepadImpl) update(gamepads *gamepads) error {
return nil
}
func (g *nativeGamepadImpl) axisCount() int {
return g.value.Get("axes").Length()
}
func (g *nativeGamepadImpl) buttonCount() int {
return g.value.Get("buttons").Length()
}
func (g *nativeGamepadImpl) hatCount() int {
return 0
}
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
return axis >= 0 && axis < g.axisCount()
}
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
axes := g.value.Get("axes")
if axis < 0 || axis >= axes.Length() {
return 0
}
return axes.Index(axis).Float()
}
func (g *nativeGamepadImpl) buttonValue(button int) float64 {
buttons := g.value.Get("buttons")
if button < 0 || button >= buttons.Length() {
return 0
}
return buttons.Index(button).Get("value").Float()
}
func (g *nativeGamepadImpl) isButtonPressed(button int) bool {
buttons := g.value.Get("buttons")
if button < 0 || button >= buttons.Length() {
return false
}
return buttons.Index(button).Get("pressed").Bool()
}
func (g *nativeGamepadImpl) hatState(hat int) int {
return hatCentered
}
func (g *nativeGamepadImpl) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
// vibrationActuator is available on Chrome.
if va := g.value.Get("vibrationActuator"); va.Truthy() {
if !va.Get("playEffect").Truthy() {
return
}
prop := object.New()
prop.Set("startDelay", 0)
prop.Set("duration", float64(duration/time.Millisecond))
prop.Set("strongMagnitude", strongMagnitude)
prop.Set("weakMagnitude", weakMagnitude)
va.Call("playEffect", "dual-rumble", prop)
return
}
// hapticActuators is available on Firefox.
if ha := g.value.Get("hapticActuators"); ha.Truthy() {
// TODO: Is this order correct?
if ha.Length() > 0 {
ha.Index(0).Call("pulse", strongMagnitude, float64(duration/time.Millisecond))
}
if ha.Length() > 1 {
ha.Index(1).Call("pulse", weakMagnitude, float64(duration/time.Millisecond))
}
return
}
}
@@ -0,0 +1,629 @@
// Copyright 2022 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 !android && !nintendosdk && !playstation5
package gamepad
import (
"fmt"
"os"
"path/filepath"
"regexp"
"runtime"
"time"
"unsafe"
"golang.org/x/sys/unix"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
)
const dirName = "/dev/input"
var reEvent = regexp.MustCompile(`^event[0-9]+$`)
func isBitSet(s []byte, bit int) bool {
return s[bit/8]&(1<<(bit%8)) != 0
}
type nativeGamepadsImpl struct {
inotify int
watch int
}
func newNativeGamepadsImpl() nativeGamepads {
return &nativeGamepadsImpl{}
}
func (g *nativeGamepadsImpl) init(gamepads *gamepads) error {
// Check the existence of the directory `dirName`.
var stat unix.Stat_t
if err := unix.Stat(dirName, &stat); err != nil {
if err == unix.ENOENT {
return nil
}
return fmt.Errorf("gamepad: Stat failed: %w", err)
}
if stat.Mode&unix.S_IFDIR == 0 {
return nil
}
inotify, err := unix.InotifyInit1(unix.IN_NONBLOCK | unix.IN_CLOEXEC)
if err != nil {
return fmt.Errorf("gamepad: InotifyInit1 failed: %w", err)
}
g.inotify = inotify
if g.inotify > 0 {
// Register for IN_ATTRIB to get notified when udev is done.
// This works well in practice but the true way is libudev.
watch, err := unix.InotifyAddWatch(g.inotify, dirName, unix.IN_CREATE|unix.IN_ATTRIB|unix.IN_DELETE)
if err != nil {
return fmt.Errorf("gamepad: InotifyAddWatch failed: %w", err)
}
g.watch = watch
}
ents, err := os.ReadDir(dirName)
if err != nil {
return fmt.Errorf("gamepad: ReadDir(%s) failed: %w", dirName, err)
}
for _, ent := range ents {
if ent.IsDir() {
continue
}
if !reEvent.MatchString(ent.Name()) {
continue
}
if err := g.openGamepad(gamepads, filepath.Join(dirName, ent.Name())); err != nil {
return err
}
}
return nil
}
func (*nativeGamepadsImpl) openGamepad(gamepads *gamepads, path string) (err error) {
if gamepads.find(func(gamepad *Gamepad) bool {
return gamepad.native.(*nativeGamepadImpl).path == path
}) != nil {
return nil
}
fd, err := unix.Open(path, unix.O_RDONLY|unix.O_NONBLOCK, 0)
if err != nil {
if err == unix.EACCES {
return nil
}
// This happens with the Snap sandbox.
if err == unix.EPERM {
return nil
}
// This happens just after a disconnection.
if err == unix.ENOENT {
return nil
}
return fmt.Errorf("gamepad: Open failed: %w", err)
}
defer func() {
if err != nil {
_ = unix.Close(fd)
}
}()
evBits := make([]byte, (unix.EV_CNT+7)/8)
keyBits := make([]byte, (_KEY_CNT+7)/8)
absBits := make([]byte, (_ABS_CNT+7)/8)
var id input_id
if err := ioctl(fd, _EVIOCGBIT(0, uint(len(evBits))), unsafe.Pointer(&evBits[0])); err != nil {
return fmt.Errorf("gamepad: ioctl for evBits failed: %w", err)
}
if err := ioctl(fd, _EVIOCGBIT(unix.EV_KEY, uint(len(keyBits))), unsafe.Pointer(&keyBits[0])); err != nil {
return fmt.Errorf("gamepad: ioctl for keyBits failed: %w", err)
}
if err := ioctl(fd, _EVIOCGBIT(unix.EV_ABS, uint(len(absBits))), unsafe.Pointer(&absBits[0])); err != nil {
return fmt.Errorf("gamepad: ioctl for absBits failed: %w", err)
}
if err := ioctl(fd, _EVIOCGID(), unsafe.Pointer(&id)); err != nil {
return fmt.Errorf("gamepad: ioctl for an ID failed: %w", err)
}
if !isBitSet(evBits, unix.EV_KEY) {
if err := unix.Close(fd); err != nil {
return err
}
return nil
}
if !isBitSet(evBits, unix.EV_ABS) {
if err := unix.Close(fd); err != nil {
return err
}
return nil
}
cname := make([]byte, 256)
name := "Unknown"
// TODO: Is it OK to ignore the error here?
if err := ioctl(fd, uint(_EVIOCGNAME(uint(len(cname)))), unsafe.Pointer(&cname[0])); err == nil {
name = unix.ByteSliceToString(cname)
}
var sdlID string
if id.vendor != 0 && id.product != 0 && id.version != 0 {
sdlID = fmt.Sprintf("%02x%02x0000%02x%02x0000%02x%02x0000%02x%02x0000",
byte(id.bustype), byte(id.bustype>>8),
byte(id.vendor), byte(id.vendor>>8),
byte(id.product), byte(id.product>>8),
byte(id.version), byte(id.version>>8))
} else {
bs := []byte(name)
if len(bs) < 12 {
bs = append(bs, make([]byte, 12-len(bs))...)
}
sdlID = fmt.Sprintf("%02x%02x0000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
byte(id.bustype), byte(id.bustype>>8),
bs[0], bs[1], bs[2], bs[3], bs[4], bs[5], bs[6], bs[7], bs[8], bs[9], bs[10], bs[11])
}
n := &nativeGamepadImpl{
path: path,
fd: fd,
}
gp := gamepads.add(name, sdlID)
gp.native = n
runtime.SetFinalizer(gp, func(gp *Gamepad) {
n.close()
})
var axisCount int
var buttonCount int
var hatCount int
for i := range n.keyMap {
n.keyMap[i] = -1
}
for i := range n.absMap {
n.absMap[i] = -1
}
for code := _BTN_MISC; code < _KEY_CNT; code++ {
if !isBitSet(keyBits, code) {
continue
}
n.keyMap[code-_BTN_MISC] = buttonCount
buttonCount++
}
for code := 0; code < _ABS_CNT; code++ {
if !isBitSet(absBits, code) {
continue
}
if code >= _ABS_HAT0X && code <= _ABS_HAT3Y {
// Write the hat index both for the X and the Y hat axis.
// That way, the hat can be referenced using either axis, which is used by the code building hatMappingInput.
n.absMap[code] = hatCount
code++
n.absMap[code] = hatCount
hatCount++
continue
}
if err := ioctl(n.fd, uint(_EVIOCGABS(uint(code))), unsafe.Pointer(&n.absInfo[code])); err != nil {
return fmt.Errorf("gamepad: ioctl for an abs at openGamepad failed: %w", err)
}
n.absMap[code] = axisCount
axisCount++
}
n.axisCount_ = axisCount
n.buttonCount_ = buttonCount
n.hatCount_ = hatCount
n.computeStandardLayout(id.vendor)
if err := n.pollAbsState(); err != nil {
return err
}
return nil
}
func (g *nativeGamepadsImpl) update(gamepads *gamepads) error {
if g.inotify <= 0 {
return nil
}
buf := make([]byte, 16384)
n, err := unix.Read(g.inotify, buf[:])
if err != nil {
if err == unix.EAGAIN {
return nil
}
return fmt.Errorf("gamepad: Read failed: %w", err)
}
buf = buf[:n]
for len(buf) > 0 {
e := unix.InotifyEvent{
Wd: int32(buf[0]) | int32(buf[1])<<8 | int32(buf[2])<<16 | int32(buf[3])<<24,
Mask: uint32(buf[4]) | uint32(buf[5])<<8 | uint32(buf[6])<<16 | uint32(buf[7])<<24,
Cookie: uint32(buf[8]) | uint32(buf[9])<<8 | uint32(buf[10])<<16 | uint32(buf[11])<<24,
Len: uint32(buf[12]) | uint32(buf[13])<<8 | uint32(buf[14])<<16 | uint32(buf[15])<<24,
}
name := unix.ByteSliceToString(buf[16 : 16+e.Len-1]) // len includes the null terminate.
buf = buf[16+e.Len:]
if !reEvent.MatchString(name) {
continue
}
path := filepath.Join(dirName, name)
if e.Mask&(unix.IN_CREATE|unix.IN_ATTRIB) != 0 {
if err := g.openGamepad(gamepads, path); err != nil {
return err
}
continue
}
if e.Mask&unix.IN_DELETE != 0 {
if gp := gamepads.find(func(gamepad *Gamepad) bool {
return gamepad.native.(*nativeGamepadImpl).path == path
}); gp != nil {
gp.native.(*nativeGamepadImpl).close()
gamepads.remove(func(gamepad *Gamepad) bool {
return gamepad == gp
})
}
continue
}
}
return nil
}
type nativeGamepadImpl struct {
fd int
path string
keyMap [_KEY_CNT - _BTN_MISC]int
absMap [_ABS_CNT]int
absInfo [_ABS_CNT]input_absinfo
dropped bool
axes [_ABS_CNT]float64
buttons [_KEY_CNT - _BTN_MISC]bool
hats [4]int
axisCount_ int
buttonCount_ int
hatCount_ int
stdAxisMap map[gamepaddb.StandardAxis]mappingInput
stdButtonMap map[gamepaddb.StandardButton]mappingInput
}
func (g *nativeGamepadImpl) close() {
if g.fd != 0 {
_ = unix.Close(g.fd)
}
g.fd = 0
}
func (g *nativeGamepadImpl) update(gamepad *gamepads) error {
if g.fd == 0 {
return nil
}
for {
buf := make([]byte, unsafe.Sizeof(input_event{}))
// TODO: Should the returned byte count be cared?
if _, err := unix.Read(g.fd, buf); err != nil {
if err == unix.EAGAIN {
break
}
// Disconnected
if err == unix.ENODEV {
g.close()
return nil
}
return fmt.Errorf("gamepad: Read failed: %w", err)
}
const (
offsetTyp = unsafe.Offsetof(input_event{}.typ)
offsetCode = unsafe.Offsetof(input_event{}.code)
offsetValue = unsafe.Offsetof(input_event{}.value)
)
// time is not used.
e := input_event{
typ: uint16(buf[offsetTyp]) | uint16(buf[offsetTyp+1])<<8,
code: uint16(buf[offsetCode]) | uint16(buf[offsetCode+1])<<8,
value: int32(buf[offsetValue]) | int32(buf[offsetValue+1])<<8 | int32(buf[offsetValue+2])<<16 | int32(buf[offsetValue+3])<<24,
}
if e.typ == unix.EV_SYN {
switch e.code {
case _SYN_DROPPED:
g.dropped = true
case _SYN_REPORT:
g.dropped = false
if err := g.pollAbsState(); err != nil {
return fmt.Errorf("gamepad: poll absolute state: %w", err)
}
}
}
if g.dropped {
continue
}
switch e.typ {
case unix.EV_KEY:
if int(e.code-_BTN_MISC) < len(g.keyMap) {
idx := g.keyMap[e.code-_BTN_MISC]
if idx < 0 {
continue
}
g.buttons[idx] = e.value != 0
}
case unix.EV_ABS:
g.handleAbsEvent(int(e.code), e.value)
}
}
return nil
}
func (g *nativeGamepadImpl) pollAbsState() error {
for code := 0; code < _ABS_CNT; code++ {
if g.absMap[code] < 0 {
continue
}
if err := ioctl(g.fd, uint(_EVIOCGABS(uint(code))), unsafe.Pointer(&g.absInfo[code])); err != nil {
return fmt.Errorf("gamepad: ioctl for an abs at pollAbsState failed: %w", err)
}
g.handleAbsEvent(code, g.absInfo[code].value)
}
return nil
}
func (g *nativeGamepadImpl) handleAbsEvent(code int, value int32) {
index := g.absMap[code]
if index < 0 {
return
}
if code >= _ABS_HAT0X && code <= _ABS_HAT3Y {
axis := (code - _ABS_HAT0X) % 2
switch axis {
case 0:
switch {
case value < 0:
g.hats[index] |= hatLeft
g.hats[index] &^= hatRight
case value > 0:
g.hats[index] &^= hatLeft
g.hats[index] |= hatRight
default:
g.hats[index] &^= hatLeft | hatRight
}
case 1:
switch {
case value < 0:
g.hats[index] |= hatUp
g.hats[index] &^= hatDown
case value > 0:
g.hats[index] &^= hatUp
g.hats[index] |= hatDown
default:
g.hats[index] &^= hatUp | hatDown
}
}
return
}
info := g.absInfo[code]
v := float64(value)
if r := float64(info.maximum) - float64(info.minimum); r != 0 {
v = (v - float64(info.minimum)) / r
v = v*2 - 1
}
g.axes[index] = v
}
func (g *nativeGamepadImpl) computeStandardLayout(vendor uint16) {
g.stdAxisMap = map[gamepaddb.StandardAxis]mappingInput{}
g.stdButtonMap = map[gamepaddb.StandardButton]mappingInput{}
// NOTE: assignments to the same value are in exact reverse order as SDL2,
// so we can just overwrite rather than checking.
// BTN_GAMEPAD implies that the kernel module implements standard mapping.
if b := g.keyMap[_BTN_GAMEPAD-_BTN_MISC]; b < 0 {
return
}
// A and B buttons go by name.
if b := g.keyMap[_BTN_A-_BTN_MISC]; b >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonRightBottom] = buttonMappingInput{g: g, button: b}
}
if b := g.keyMap[_BTN_B-_BTN_MISC]; b >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonRightRight] = buttonMappingInput{g: g, button: b}
}
if vendor == 0x054c /* USB_VENDOR_SONY */ {
// Sony uses WEST/NORTH buttons.
if b := g.keyMap[_BTN_WEST-_BTN_MISC]; b >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonRightLeft] = buttonMappingInput{g: g, button: b}
}
if b := g.keyMap[_BTN_NORTH-_BTN_MISC]; b >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonRightTop] = buttonMappingInput{g: g, button: b}
}
} else {
// Xbox uses X/Y buttons.
// Note that this is the opposite assignment following the WEST/NORTH mappings,
// and contradicts Linux kernel documentation which states
// that buttons are always assigned by physical location.
// However, it matches actual Xbox gamepads, and SDL2 has the same logic.
if b := g.keyMap[_BTN_X-_BTN_MISC]; b >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonRightLeft] = buttonMappingInput{g: g, button: b}
}
if b := g.keyMap[_BTN_Y-_BTN_MISC]; b >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonRightTop] = buttonMappingInput{g: g, button: b}
}
}
// Center and thumb buttons.
if b := g.keyMap[_BTN_SELECT-_BTN_MISC]; b >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonCenterLeft] = buttonMappingInput{g: g, button: b}
}
if b := g.keyMap[_BTN_START-_BTN_MISC]; b >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonCenterRight] = buttonMappingInput{g: g, button: b}
}
if b := g.keyMap[_BTN_THUMBL-_BTN_MISC]; b >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonLeftStick] = buttonMappingInput{g: g, button: b}
}
if b := g.keyMap[_BTN_THUMBR-_BTN_MISC]; b >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonRightStick] = buttonMappingInput{g: g, button: b}
}
if b := g.keyMap[_BTN_MODE-_BTN_MISC]; b >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonCenterCenter] = buttonMappingInput{g: g, button: b}
}
// Shoulder buttons can be analog or digital. Prefer digital ones.
if h := g.absMap[_ABS_HAT1Y]; h >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonFrontTopLeft] = hatMappingInput{g: g, hat: h, direction: hatDown}
}
if h := g.absMap[_ABS_HAT1X]; h >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonFrontTopRight] = hatMappingInput{g: g, hat: h, direction: hatRight}
}
if b := g.keyMap[_BTN_TL-_BTN_MISC]; b >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonFrontTopLeft] = buttonMappingInput{g: g, button: b}
}
if b := g.keyMap[_BTN_TR-_BTN_MISC]; b >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonFrontTopRight] = buttonMappingInput{g: g, button: b}
}
// Triggers can be analog or digital. Prefer analog ones.
if b := g.keyMap[_BTN_TL2-_BTN_MISC]; b >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonFrontBottomLeft] = buttonMappingInput{g: g, button: b}
}
if b := g.keyMap[_BTN_TR2-_BTN_MISC]; b >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonFrontBottomRight] = buttonMappingInput{g: g, button: b}
}
if a := g.absMap[_ABS_Z]; a >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonFrontBottomLeft] = axisMappingInput{g: g, axis: a}
}
if a := g.absMap[_ABS_RZ]; a >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonFrontBottomRight] = axisMappingInput{g: g, axis: a}
}
if h := g.absMap[_ABS_HAT2Y]; h >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonFrontBottomLeft] = hatMappingInput{g: g, hat: h, direction: hatDown}
}
if h := g.absMap[_ABS_HAT2X]; h >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonFrontBottomRight] = hatMappingInput{g: g, hat: h, direction: hatRight}
}
// D-pad can be analog or digital. Prefer digital one.
if h := g.absMap[_ABS_HAT0X]; h >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonLeftLeft] = hatMappingInput{g: g, hat: h, direction: hatLeft}
g.stdButtonMap[gamepaddb.StandardButtonLeftRight] = hatMappingInput{g: g, hat: h, direction: hatRight}
}
if h := g.absMap[_ABS_HAT0Y]; h >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonLeftTop] = hatMappingInput{g: g, hat: h, direction: hatUp}
g.stdButtonMap[gamepaddb.StandardButtonLeftBottom] = hatMappingInput{g: g, hat: h, direction: hatDown}
}
if b := g.keyMap[_BTN_DPAD_UP-_BTN_MISC]; b >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonLeftTop] = buttonMappingInput{g: g, button: b}
}
if b := g.keyMap[_BTN_DPAD_DOWN-_BTN_MISC]; b >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonLeftBottom] = buttonMappingInput{g: g, button: b}
}
if b := g.keyMap[_BTN_DPAD_LEFT-_BTN_MISC]; b >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonLeftLeft] = buttonMappingInput{g: g, button: b}
}
if b := g.keyMap[_BTN_DPAD_RIGHT-_BTN_MISC]; b >= 0 {
g.stdButtonMap[gamepaddb.StandardButtonLeftRight] = buttonMappingInput{g: g, button: b}
}
// Left stick.
if a := g.absMap[_ABS_X]; a >= 0 {
g.stdAxisMap[gamepaddb.StandardAxisLeftStickHorizontal] = axisMappingInput{g: g, axis: a}
}
if a := g.absMap[_ABS_Y]; a >= 0 {
g.stdAxisMap[gamepaddb.StandardAxisLeftStickVertical] = axisMappingInput{g: g, axis: a}
}
// Right stick.
if a := g.absMap[_ABS_RX]; a >= 0 {
g.stdAxisMap[gamepaddb.StandardAxisRightStickHorizontal] = axisMappingInput{g: g, axis: a}
}
if a := g.absMap[_ABS_RY]; a >= 0 {
g.stdAxisMap[gamepaddb.StandardAxisRightStickVertical] = axisMappingInput{g: g, axis: a}
}
}
func (g *nativeGamepadImpl) hasOwnStandardLayoutMapping() bool {
return len(g.stdAxisMap) != 0 || len(g.stdButtonMap) != 0
}
func (g *nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput {
return g.stdAxisMap[axis]
}
func (g *nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput {
return g.stdButtonMap[button]
}
func (g *nativeGamepadImpl) axisCount() int {
return g.axisCount_
}
func (g *nativeGamepadImpl) buttonCount() int {
return g.buttonCount_
}
func (g *nativeGamepadImpl) hatCount() int {
return g.hatCount_
}
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
return axis >= 0 && axis < g.axisCount()
}
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
if axis < 0 || axis >= g.axisCount_ {
return 0
}
return g.axes[axis]
}
func (g *nativeGamepadImpl) isButtonPressed(button int) bool {
if button < 0 || button >= g.buttonCount_ {
return false
}
return g.buttons[button]
}
func (g *nativeGamepadImpl) buttonValue(button int) float64 {
if g.isButtonPressed(button) {
return 1
}
return 0
}
func (g *nativeGamepadImpl) hatState(hat int) int {
if hat < 0 || hat >= g.hatCount_ {
return hatCentered
}
return g.hats[hat]
}
func (g *nativeGamepadImpl) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
// TODO: Implement this (#1452)
}
@@ -0,0 +1,29 @@
// Copyright 2022 The Ebitengine 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 nintendosdk
// The actual implementation will be provided by -overlay.
#include "gamepad_nintendosdk.h"
extern "C" void ebitengine_UpdateGamepads() {}
extern "C" int ebitengine_GetGamepadCount() { return 0; }
extern "C" void ebitengine_GetGamepads(struct Gamepad *gamepads) {}
extern "C" void ebitengine_VibrateGamepad(int id, double durationInSeconds,
double strongMagnitude,
double weakMagnitude) {}
@@ -0,0 +1,180 @@
// Copyright 2022 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 nintendosdk
package gamepad
// #cgo !darwin LDFLAGS: -Wl,-unresolved-symbols=ignore-all
// #cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup
//
// #include "gamepad_nintendosdk.h"
import "C"
import (
"time"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
)
type nativeGamepadsImpl struct {
gamepads []C.struct_Gamepad
ids map[int]struct{}
}
func newNativeGamepadsImpl() nativeGamepads {
return &nativeGamepadsImpl{}
}
func (*nativeGamepadsImpl) init(gamepads *gamepads) error {
return nil
}
func (g *nativeGamepadsImpl) update(gamepads *gamepads) error {
C.ebitengine_UpdateGamepads()
g.gamepads = g.gamepads[:0]
if n := int(C.ebitengine_GetGamepadCount()); n > 0 {
if cap(g.gamepads) < n {
g.gamepads = make([]C.struct_Gamepad, n)
} else {
g.gamepads = g.gamepads[:n]
}
C.ebitengine_GetGamepads(&g.gamepads[0])
}
for id := range g.ids {
delete(g.ids, id)
}
for _, gp := range g.gamepads {
if g.ids == nil {
g.ids = map[int]struct{}{}
}
g.ids[int(gp.id)] = struct{}{}
gamepad := gamepads.find(func(gamepad *Gamepad) bool {
return gamepad.native.(*nativeGamepadImpl).id == int(gp.id)
})
if gamepad == nil {
gamepad = gamepads.add("", "")
gamepad.native = &nativeGamepadImpl{
id: int(gp.id),
standard: bool(gp.standard != 0),
axisValues: make([]float64, gp.axis_count),
buttonPressed: make([]bool, gp.button_count),
buttonValues: make([]float64, gp.button_count),
}
}
gamepad.m.Lock()
n := gamepad.native.(*nativeGamepadImpl)
for i := range n.axisValues {
n.axisValues[i] = float64(gp.axis_values[i])
}
for i := range n.buttonValues {
n.buttonValues[i] = float64(gp.button_values[i])
}
for i := range n.buttonPressed {
n.buttonPressed[i] = gp.button_pressed[i] != 0
}
gamepad.m.Unlock()
}
// Remove an unused gamepads.
gamepads.remove(func(gamepad *Gamepad) bool {
_, ok := g.ids[gamepad.native.(*nativeGamepadImpl).id]
return !ok
})
return nil
}
type nativeGamepadImpl struct {
id int
standard bool
axisValues []float64
buttonPressed []bool
buttonValues []float64
}
func (*nativeGamepadImpl) update(gamepad *gamepads) error {
return nil
}
func (g *nativeGamepadImpl) hasOwnStandardLayoutMapping() bool {
return g.standard
}
func (g *nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput {
// TODO: Implement this on the C side.
if axis < 0 || int(axis) >= len(g.axisValues) {
return nil
}
return axisMappingInput{g: g, axis: int(axis)}
}
func (g *nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput {
// TODO: Implement this on the C side.
if button < 0 || int(button) >= len(g.buttonValues) {
return nil
}
return buttonMappingInput{g: g, button: int(button)}
}
func (g *nativeGamepadImpl) axisCount() int {
return len(g.axisValues)
}
func (g *nativeGamepadImpl) buttonCount() int {
return len(g.buttonValues)
}
func (g *nativeGamepadImpl) hatCount() int {
return 0
}
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
return axis >= 0 && axis < g.axisCount()
}
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
if axis < 0 || axis >= len(g.axisValues) {
return 0
}
return g.axisValues[axis]
}
func (g *nativeGamepadImpl) isButtonPressed(button int) bool {
if button < 0 || button >= len(g.buttonPressed) {
return false
}
return g.buttonPressed[button]
}
func (g *nativeGamepadImpl) buttonValue(button int) float64 {
if button < 0 || button >= len(g.buttonValues) {
return 0
}
return g.buttonValues[button]
}
func (*nativeGamepadImpl) hatState(hat int) int {
return hatCentered
}
func (g *nativeGamepadImpl) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
C.ebitengine_VibrateGamepad(C.int(g.id), C.double(float64(duration)/float64(time.Second)), C.double(strongMagnitude), C.double(weakMagnitude))
}
@@ -0,0 +1,39 @@
// Copyright 2022 The Ebitengine 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 nintendosdk
struct Gamepad {
int id;
char standard;
int button_count;
int axis_count;
char button_pressed[32];
float button_values[32];
float axis_values[16];
};
#ifdef __cplusplus
extern "C" {
#endif
void ebitengine_UpdateGamepads();
int ebitengine_GetGamepadCount();
void ebitengine_GetGamepads(struct Gamepad *gamepads);
void ebitengine_VibrateGamepad(int id, double durationInSeconds,
double strongMagnitude, double weakMagnitude);
#ifdef __cplusplus
} // extern "C"
#endif
@@ -0,0 +1,90 @@
// Copyright 2022 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 !darwin && !js && !linux && !windows
package gamepad
import (
"time"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
)
type nativeGamepadsImpl struct{}
func newNativeGamepadsImpl() nativeGamepads {
return &nativeGamepadsImpl{}
}
func (*nativeGamepadsImpl) init(gamepads *gamepads) error {
return nil
}
func (*nativeGamepadsImpl) update(gamepads *gamepads) error {
return nil
}
type nativeGamepadImpl struct{}
func (*nativeGamepadImpl) update(gamepad *gamepads) error {
return nil
}
func (*nativeGamepadImpl) hasOwnStandardLayoutMapping() bool {
return false
}
func (*nativeGamepadImpl) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput {
return nil
}
func (*nativeGamepadImpl) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput {
return nil
}
func (*nativeGamepadImpl) axisCount() int {
return 0
}
func (*nativeGamepadImpl) buttonCount() int {
return 0
}
func (*nativeGamepadImpl) hatCount() int {
return 0
}
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
return false
}
func (*nativeGamepadImpl) axisValue(axis int) float64 {
return 0
}
func (*nativeGamepadImpl) isButtonPressed(button int) bool {
return false
}
func (*nativeGamepadImpl) buttonValue(button int) float64 {
return 0
}
func (*nativeGamepadImpl) hatState(hat int) int {
return hatCentered
}
func (g *nativeGamepadImpl) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
}
@@ -0,0 +1,32 @@
// Copyright 2023 The Ebitengine 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 playstation5
package gamepad
type nativeGamepadsImpl struct {
}
func newNativeGamepadsImpl() nativeGamepads {
return &nativeGamepadsImpl{}
}
func (g *nativeGamepadsImpl) init(gamepads *gamepads) error {
return nil
}
func (g *nativeGamepadsImpl) update(gamepads *gamepads) error {
return nil
}
@@ -0,0 +1,26 @@
// Copyright 2022 The Ebitengine 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.
package gamepad
import (
"github.com/hajimehoshi/ebiten/v2/internal/microsoftgdk"
)
func newNativeGamepadsImpl() nativeGamepads {
if microsoftgdk.IsXbox() {
return &nativeGamepadsXbox{}
}
return &nativeGamepadsDesktop{}
}
@@ -0,0 +1,265 @@
// Copyright 2022 The Ebitengine 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.
package gamepad
import (
"time"
"unsafe"
"golang.org/x/sys/windows"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
)
func standardButtonToGamepadInputGamepadButton(b gamepaddb.StandardButton) (_GameInputGamepadButtons, bool) {
switch b {
case gamepaddb.StandardButtonRightBottom:
return _GameInputGamepadA, true
case gamepaddb.StandardButtonRightRight:
return _GameInputGamepadB, true
case gamepaddb.StandardButtonRightLeft:
return _GameInputGamepadX, true
case gamepaddb.StandardButtonRightTop:
return _GameInputGamepadY, true
case gamepaddb.StandardButtonFrontTopLeft:
return _GameInputGamepadLeftShoulder, true
case gamepaddb.StandardButtonFrontTopRight:
return _GameInputGamepadRightShoulder, true
case gamepaddb.StandardButtonFrontBottomLeft:
return 0, false // Use leftTrigger instead.
case gamepaddb.StandardButtonFrontBottomRight:
return 0, false // Use rightTrigger instead.
case gamepaddb.StandardButtonCenterLeft:
return _GameInputGamepadView, true
case gamepaddb.StandardButtonCenterRight:
return _GameInputGamepadMenu, true
case gamepaddb.StandardButtonLeftStick:
return _GameInputGamepadLeftThumbstick, true
case gamepaddb.StandardButtonRightStick:
return _GameInputGamepadRightThumbstick, true
case gamepaddb.StandardButtonLeftTop:
return _GameInputGamepadDPadUp, true
case gamepaddb.StandardButtonLeftBottom:
return _GameInputGamepadDPadDown, true
case gamepaddb.StandardButtonLeftLeft:
return _GameInputGamepadDPadLeft, true
case gamepaddb.StandardButtonLeftRight:
return _GameInputGamepadDPadRight, true
case gamepaddb.StandardButtonCenterCenter:
return 0, false
}
return 0, false
}
type nativeGamepadsXbox struct {
gameInput *_IGameInput
deviceCallbackPtr uintptr
token _GameInputCallbackToken
}
func (n *nativeGamepadsXbox) init(gamepads *gamepads) error {
g, err := _GameInputCreate()
if err != nil {
return err
}
n.gameInput = g
n.deviceCallbackPtr = windows.NewCallbackCDecl(n.deviceCallback)
if err := n.gameInput.RegisterDeviceCallback(
nil,
_GameInputKindGamepad,
_GameInputDeviceConnected,
_GameInputBlockingEnumeration,
unsafe.Pointer(gamepads),
n.deviceCallbackPtr,
&n.token,
); err != nil {
return err
}
return nil
}
func (n *nativeGamepadsXbox) update(gamepads *gamepads) error {
return nil
}
func (n *nativeGamepadsXbox) deviceCallback(callbackToken _GameInputCallbackToken, context unsafe.Pointer, device *_IGameInputDevice, timestamp uint64, currentStatus _GameInputDeviceStatus, previousStatus _GameInputDeviceStatus) uintptr {
gps := (*gamepads)(context)
// Connected.
if currentStatus&_GameInputDeviceConnected != 0 {
// TODO: Give a good name and a SDL ID.
gp := gps.add("", "00000000000000000000000000000000")
gp.native = &nativeGamepadXbox{
gameInputDevice: device,
}
return 0
}
// Disconnected.
gps.remove(func(gamepad *Gamepad) bool {
return gamepad.native.(*nativeGamepadXbox).gameInputDevice == device
})
return 0
}
type nativeGamepadXbox struct {
gameInputDevice *_IGameInputDevice
state _GameInputGamepadState
vib bool
vibEnd time.Time
}
func (n *nativeGamepadXbox) update(gamepads *gamepads) error {
gameInput := gamepads.native.(*nativeGamepadsXbox).gameInput
r, err := gameInput.GetCurrentReading(_GameInputKindGamepad, n.gameInputDevice)
if err != nil {
return err
}
defer r.Release()
state, ok := r.GetGamepadState()
if !ok {
n.state = _GameInputGamepadState{}
return nil
}
n.state = state
if n.vib && time.Now().Sub(n.vibEnd) >= 0 {
n.gameInputDevice.SetRumbleState(&_GameInputRumbleParams{
lowFrequency: 0,
highFrequency: 0,
}, 0)
n.vib = false
}
return nil
}
func (n *nativeGamepadXbox) hasOwnStandardLayoutMapping() bool {
return true
}
func (n *nativeGamepadXbox) standardAxisInOwnMapping(axis gamepaddb.StandardAxis) mappingInput {
switch axis {
case gamepaddb.StandardAxisLeftStickHorizontal,
gamepaddb.StandardAxisLeftStickVertical,
gamepaddb.StandardAxisRightStickHorizontal,
gamepaddb.StandardAxisRightStickVertical:
return axisMappingInput{g: n, axis: int(axis)}
}
return nil
}
func (n *nativeGamepadXbox) standardButtonInOwnMapping(button gamepaddb.StandardButton) mappingInput {
switch button {
case gamepaddb.StandardButtonFrontBottomLeft,
gamepaddb.StandardButtonFrontBottomRight:
return buttonMappingInput{g: n, button: int(button)}
}
if _, ok := standardButtonToGamepadInputGamepadButton(button); !ok {
return nil
}
return buttonMappingInput{g: n, button: int(button)}
}
func (n *nativeGamepadXbox) axisCount() int {
return int(gamepaddb.StandardAxisMax) + 1
}
func (n *nativeGamepadXbox) buttonCount() int {
return int(gamepaddb.StandardButtonMax) + 1
}
func (n *nativeGamepadXbox) hatCount() int {
return 0
}
func (g *nativeGamepadXbox) isAxisReady(axis int) bool {
return axis >= 0 && axis < g.axisCount()
}
func (n *nativeGamepadXbox) axisValue(axis int) float64 {
switch gamepaddb.StandardAxis(axis) {
case gamepaddb.StandardAxisLeftStickHorizontal:
return float64(n.state.leftThumbstickX)
case gamepaddb.StandardAxisLeftStickVertical:
return -float64(n.state.leftThumbstickY)
case gamepaddb.StandardAxisRightStickHorizontal:
return float64(n.state.rightThumbstickX)
case gamepaddb.StandardAxisRightStickVertical:
return -float64(n.state.rightThumbstickY)
}
return 0
}
func (n *nativeGamepadXbox) buttonValue(button int) float64 {
switch gamepaddb.StandardButton(button) {
case gamepaddb.StandardButtonFrontBottomLeft:
return float64(n.state.leftTrigger)
case gamepaddb.StandardButtonFrontBottomRight:
return float64(n.state.rightTrigger)
}
b, ok := standardButtonToGamepadInputGamepadButton(gamepaddb.StandardButton(button))
if !ok {
return 0
}
if n.state.buttons&b != 0 {
return 1
}
return 0
}
func (n *nativeGamepadXbox) isButtonPressed(button int) bool {
switch gamepaddb.StandardButton(button) {
case gamepaddb.StandardButtonFrontBottomLeft:
return n.state.leftTrigger > gamepaddb.ButtonPressedThreshold
case gamepaddb.StandardButtonFrontBottomRight:
return n.state.rightTrigger > gamepaddb.ButtonPressedThreshold
}
b, ok := standardButtonToGamepadInputGamepadButton(gamepaddb.StandardButton(button))
if !ok {
return false
}
if n.state.buttons&b != 0 {
return true
}
return false
}
func (n *nativeGamepadXbox) hatState(hat int) int {
return 0
}
func (n *nativeGamepadXbox) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
if strongMagnitude <= 0 && weakMagnitude <= 0 {
n.vib = false
n.gameInputDevice.SetRumbleState(&_GameInputRumbleParams{
lowFrequency: 0,
highFrequency: 0,
}, 0)
return
}
n.vib = true
n.vibEnd = time.Now().Add(duration)
n.gameInputDevice.SetRumbleState(&_GameInputRumbleParams{
lowFrequency: float32(strongMagnitude),
highFrequency: float32(weakMagnitude),
}, 0)
}
+52
View File
@@ -0,0 +1,52 @@
// Copyright 2015 Hajime Hoshi
//
// 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.
package gamepaddb
type StandardButton int
// https://www.w3.org/TR/gamepad/#remapping
const (
StandardButtonRightBottom StandardButton = iota
StandardButtonRightRight
StandardButtonRightLeft
StandardButtonRightTop
StandardButtonFrontTopLeft
StandardButtonFrontTopRight
StandardButtonFrontBottomLeft
StandardButtonFrontBottomRight
StandardButtonCenterLeft
StandardButtonCenterRight
StandardButtonLeftStick
StandardButtonRightStick
StandardButtonLeftTop
StandardButtonLeftBottom
StandardButtonLeftLeft
StandardButtonLeftRight
StandardButtonCenterCenter
StandardButtonMax = StandardButtonCenterCenter
)
type StandardAxis int
// https://www.w3.org/TR/gamepad/#remapping
const (
StandardAxisLeftStickHorizontal StandardAxis = iota
StandardAxisLeftStickVertical
StandardAxisRightStickHorizontal
StandardAxisRightStickVertical
StandardAxisMax = StandardAxisRightStickVertical
)
+49
View File
@@ -0,0 +1,49 @@
// Copyright 2024 The Ebitengine 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 !js && !microsoftgdk && !nintendosdk && !playstation5
package gamepaddb
import (
_ "embed"
)
// gamecontrollerdb.txt is downloaded at https://github.com/mdqinc/SDL_GameControllerDB.
// To update the database file, run:
//
// curl --location --remote-name https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/master/gamecontrollerdb.txt
//go:embed gamecontrollerdb.txt
var gamecontrollerdb_txt []byte
var additionalGLFWGamepads = []byte(`
78696e70757401000000000000000000,XInput Gamepad (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
78696e70757402000000000000000000,XInput Wheel (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
78696e70757403000000000000000000,XInput Arcade Stick (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
78696e70757404000000000000000000,XInput Flight Stick (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
78696e70757405000000000000000000,XInput Dance Pad (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
78696e70757406000000000000000000,XInput Guitar (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
78696e70757408000000000000000000,XInput Drum Kit (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,
`)
func init() {
if err := Update(gamecontrollerdb_txt); err != nil {
panic(err)
}
if err := Update(additionalGLFWGamepads); err != nil {
panic(err)
}
}
File diff suppressed because it is too large Load Diff
+743
View File
@@ -0,0 +1,743 @@
// 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.
package gamepaddb
import (
"bufio"
"bytes"
"encoding/hex"
"fmt"
"runtime"
"strconv"
"strings"
"sync"
)
type platform int
const (
platformUnknown platform = iota
platformWindows
platformMacOS
platformUnix
platformAndroid
platformIOS
)
func currentPlatform() platform {
if runtime.GOOS == "windows" {
return platformWindows
}
if runtime.GOOS == "aix" ||
runtime.GOOS == "dragonfly" ||
runtime.GOOS == "freebsd" ||
runtime.GOOS == "hurd" ||
runtime.GOOS == "illumos" ||
runtime.GOOS == "linux" ||
runtime.GOOS == "netbsd" ||
runtime.GOOS == "openbsd" ||
runtime.GOOS == "solaris" {
return platformUnix
}
if runtime.GOOS == "android" {
return platformAndroid
}
if runtime.GOOS == "ios" {
return platformIOS
}
if runtime.GOOS == "darwin" {
return platformMacOS
}
return platformUnknown
}
type mappingType int
const (
mappingTypeButton mappingType = iota
mappingTypeAxis
mappingTypeHat
)
const (
HatUp = 1
HatRight = 2
HatDown = 4
HatLeft = 8
)
type mapping struct {
Type mappingType
Index int
AxisScale float64
AxisOffset float64
HatState int
}
var (
gamepadNames = map[string]string{}
gamepadButtonMappings = map[string]map[StandardButton]mapping{}
gamepadAxisMappings = map[string]map[StandardAxis]mapping{}
mappingsM sync.RWMutex
)
func parseLine(line string, platform platform) (id string, name string, buttons map[StandardButton]mapping, axes map[StandardAxis]mapping, err error) {
line = strings.TrimSpace(line)
if len(line) == 0 {
return "", "", nil, nil, nil
}
if line[0] == '#' {
return "", "", nil, nil, nil
}
tokens := strings.Split(line, ",")
if len(tokens) < 2 {
return "", "", nil, nil, fmt.Errorf("gamepaddb: syntax error")
}
for _, token := range tokens[2:] {
if len(token) == 0 {
continue
}
tks := strings.Split(token, ":")
if len(tks) < 2 {
return "", "", nil, nil, fmt.Errorf("gamepaddb: syntax error")
}
// Note that the platform part is listed in the definition of SDL_GetPlatform.
if tks[0] == "platform" {
switch tks[1] {
case "Windows":
if platform != platformWindows {
return "", "", nil, nil, nil
}
case "Mac OS X":
if platform != platformMacOS {
return "", "", nil, nil, nil
}
case "Linux":
if platform != platformUnix {
return "", "", nil, nil, nil
}
case "Android":
if platform != platformAndroid {
return "", "", nil, nil, nil
}
case "iOS":
if platform != platformIOS {
return "", "", nil, nil, nil
}
case "":
// Allow any platforms
default:
return "", "", nil, nil, fmt.Errorf("gamepaddb: unexpected platform: %s", tks[1])
}
continue
}
gb, err := parseMappingElement(tks[1])
if err != nil {
return "", "", nil, nil, err
}
if b, ok := toStandardGamepadButton(tks[0]); ok {
if buttons == nil {
buttons = map[StandardButton]mapping{}
}
buttons[b] = gb
continue
}
if a, ok := toStandardGamepadAxis(tks[0]); ok {
if axes == nil {
axes = map[StandardAxis]mapping{}
}
axes[a] = gb
continue
}
// The buttons like "misc1" are ignored so far.
// There is no corresponding button in the Web standard gamepad layout.
}
return tokens[0], tokens[1], buttons, axes, nil
}
func parseMappingElement(str string) (mapping, error) {
switch {
case str[0] == 'a' || strings.HasPrefix(str, "+a") || strings.HasPrefix(str, "-a"):
var tilda bool
if str[len(str)-1] == '~' {
str = str[:len(str)-1]
tilda = true
}
min := -1.0
max := 1.0
numstr := str[1:]
if str[0] == '+' {
numstr = str[2:]
// Only use the positive half, i.e. 0..1.
min = 0
} else if str[0] == '-' {
numstr = str[2:]
// Only use the negative half, i.e. -1..0,
// but invert the sense so 0 does not "press" buttons.
//
// In other words, this is the same as '+' but with the input axis
// value reversed.
//
// See SDL's source:
// https://github.com/libsdl-org/SDL/blob/f398d8a42422c049d77c744658f1cd2bb011ed4a/src/joystick/SDL_gamecontroller.c#L960
min, max = 0, min
}
// Map min..max to -1..+1.
//
// See SDL's source:
// https://github.com/libsdl-org/SDL/blob/f398d8a42422c049d77c744658f1cd2bb011ed4a/src/joystick/SDL_gamecontroller.c#L276
// then simplify assuming output range -1..+1.
//
// Yields:
scale := 2 / (max - min)
offset := -(max + min) / (max - min)
if tilda {
scale = -scale
offset = -offset
}
index, err := strconv.Atoi(numstr)
if err != nil {
return mapping{}, err
}
return mapping{
Type: mappingTypeAxis,
Index: index,
AxisScale: scale,
AxisOffset: offset,
}, nil
case str[0] == 'b':
index, err := strconv.Atoi(str[1:])
if err != nil {
return mapping{}, err
}
return mapping{
Type: mappingTypeButton,
Index: index,
}, nil
case str[0] == 'h':
tokens := strings.Split(str[1:], ".")
if len(tokens) < 2 {
return mapping{}, fmt.Errorf("gamepaddb: unexpected hat: %s", str)
}
index, err := strconv.Atoi(tokens[0])
if err != nil {
return mapping{}, err
}
hat, err := strconv.Atoi(tokens[1])
if err != nil {
return mapping{}, err
}
return mapping{
Type: mappingTypeHat,
Index: index,
HatState: hat,
}, nil
}
return mapping{}, fmt.Errorf("gamepaddb: unepxected mapping: %s", str)
}
func toStandardGamepadButton(str string) (StandardButton, bool) {
switch str {
case "a":
return StandardButtonRightBottom, true
case "b":
return StandardButtonRightRight, true
case "x":
return StandardButtonRightLeft, true
case "y":
return StandardButtonRightTop, true
case "back":
return StandardButtonCenterLeft, true
case "start":
return StandardButtonCenterRight, true
case "guide":
return StandardButtonCenterCenter, true
case "leftshoulder":
return StandardButtonFrontTopLeft, true
case "rightshoulder":
return StandardButtonFrontTopRight, true
case "leftstick":
return StandardButtonLeftStick, true
case "rightstick":
return StandardButtonRightStick, true
case "dpup":
return StandardButtonLeftTop, true
case "dpright":
return StandardButtonLeftRight, true
case "dpdown":
return StandardButtonLeftBottom, true
case "dpleft":
return StandardButtonLeftLeft, true
case "lefttrigger":
return StandardButtonFrontBottomLeft, true
case "righttrigger":
return StandardButtonFrontBottomRight, true
default:
return 0, false
}
}
func toStandardGamepadAxis(str string) (StandardAxis, bool) {
switch str {
case "leftx":
return StandardAxisLeftStickHorizontal, true
case "lefty":
return StandardAxisLeftStickVertical, true
case "rightx":
return StandardAxisRightStickHorizontal, true
case "righty":
return StandardAxisRightStickVertical, true
default:
return 0, false
}
}
func buttonMappings(id string) map[StandardButton]mapping {
if m, ok := gamepadButtonMappings[id]; ok {
return m
}
if currentPlatform() == platformAndroid {
if addAndroidDefaultMappings(id) {
return gamepadButtonMappings[id]
}
}
return nil
}
func axisMappings(id string) map[StandardAxis]mapping {
if m, ok := gamepadAxisMappings[id]; ok {
return m
}
if currentPlatform() == platformAndroid {
if addAndroidDefaultMappings(id) {
return gamepadAxisMappings[id]
}
}
return nil
}
func HasStandardLayoutMapping(id string) bool {
mappingsM.RLock()
defer mappingsM.RUnlock()
return buttonMappings(id) != nil || axisMappings(id) != nil
}
type GamepadState interface {
IsAxisReady(index int) bool
Axis(index int) float64
Button(index int) bool
Hat(index int) int
}
func Name(id string) string {
mappingsM.RLock()
defer mappingsM.RUnlock()
return gamepadNames[id]
}
func HasStandardAxis(id string, axis StandardAxis) bool {
mappingsM.RLock()
defer mappingsM.RUnlock()
mappings := axisMappings(id)
if mappings == nil {
return false
}
_, ok := mappings[axis]
return ok
}
func StandardAxisValue(id string, axis StandardAxis, state GamepadState) float64 {
mappingsM.RLock()
defer mappingsM.RUnlock()
mappings := axisMappings(id)
if mappings == nil {
return 0
}
mapping, ok := mappings[axis]
if !ok {
return 0
}
switch mapping.Type {
case mappingTypeAxis:
if !state.IsAxisReady(mapping.Index) {
return 0
}
v := state.Axis(mapping.Index)*mapping.AxisScale + mapping.AxisOffset
if v > 1 {
return 1
} else if v < -1 {
return -1
}
return v
case mappingTypeButton:
if state.Button(mapping.Index) {
return 1
} else {
return -1
}
case mappingTypeHat:
if state.Hat(mapping.Index)&mapping.HatState != 0 {
return 1
} else {
return -1
}
}
return 0
}
func HasStandardButton(id string, button StandardButton) bool {
mappingsM.RLock()
defer mappingsM.RUnlock()
mappings := buttonMappings(id)
if mappings == nil {
return false
}
_, ok := mappings[button]
return ok
}
func StandardButtonValue(id string, button StandardButton, state GamepadState) float64 {
mappingsM.RLock()
defer mappingsM.RUnlock()
return standardButtonValue(id, button, state)
}
func standardButtonValue(id string, button StandardButton, state GamepadState) float64 {
mappings := buttonMappings(id)
if mappings == nil {
return 0
}
mapping, ok := mappings[button]
if !ok {
return 0
}
switch mapping.Type {
case mappingTypeAxis:
if !state.IsAxisReady(mapping.Index) {
return 0
}
v := state.Axis(mapping.Index)*mapping.AxisScale + mapping.AxisOffset
if v > 1 {
v = 1
} else if v < -1 {
v = -1
}
// Adjust [-1, 1] to [0, 1]
return (v + 1) / 2
case mappingTypeButton:
if state.Button(mapping.Index) {
return 1
}
return 0
case mappingTypeHat:
if state.Hat(mapping.Index)&mapping.HatState != 0 {
return 1
}
return 0
}
return 0
}
// ButtonPressedThreshold represents the value up to which a button counts as not yet pressed.
// This has been set to match XInput's trigger dead zone.
// See https://source.chromium.org/chromium/chromium/src/+/main:device/gamepad/public/cpp/gamepad.h;l=22-23;drc=6997f8a177359bb99598988ed5e900841984d242
// Note: should be used with >, not >=, comparisons.
const ButtonPressedThreshold = 30.0 / 255.0
func IsStandardButtonPressed(id string, button StandardButton, state GamepadState) bool {
mappingsM.RLock()
defer mappingsM.RUnlock()
mappings, ok := gamepadButtonMappings[id]
if !ok {
return false
}
mapping, ok := mappings[button]
if !ok {
return false
}
switch mapping.Type {
case mappingTypeAxis:
v := standardButtonValue(id, button, state)
return v > ButtonPressedThreshold
case mappingTypeButton:
return state.Button(mapping.Index)
case mappingTypeHat:
return state.Hat(mapping.Index)&mapping.HatState != 0
}
return false
}
// Update adds new gamepad mappings.
// The string must be in the format of SDL_GameControllerDB.
//
// Update works atomically. If an error happens, nothing is updated.
func Update(mappingData []byte) error {
mappingsM.Lock()
defer mappingsM.Unlock()
buf := bytes.NewBuffer(mappingData)
s := bufio.NewScanner(buf)
type parsedLine struct {
id string
name string
buttons map[StandardButton]mapping
axes map[StandardAxis]mapping
}
var lines []parsedLine
for s.Scan() {
line := s.Text()
id, name, buttons, axes, err := parseLine(line, currentPlatform())
if err != nil {
return err
}
if id != "" {
lines = append(lines, parsedLine{
id: id,
name: name,
buttons: buttons,
axes: axes,
})
}
}
if err := s.Err(); err != nil {
return err
}
for _, l := range lines {
gamepadNames[l.id] = l.name
gamepadButtonMappings[l.id] = l.buttons
gamepadAxisMappings[l.id] = l.axes
}
return nil
}
func addAndroidDefaultMappings(id string) bool {
// See https://github.com/libsdl-org/SDL/blob/120c76c84bbce4c1bfed4e9eb74e10678bd83120/src/joystick/SDL_gamecontroller.c#L468-L568
const faceButtonMask = ((1 << SDLControllerButtonA) |
(1 << SDLControllerButtonB) |
(1 << SDLControllerButtonX) |
(1 << SDLControllerButtonY))
idBytes, err := hex.DecodeString(id)
if err != nil {
return false
}
buttonMask := uint16(idBytes[12]) | (uint16(idBytes[13]) << 8)
axisMask := uint16(idBytes[14]) | (uint16(idBytes[15]) << 8)
if buttonMask == 0 && axisMask == 0 {
return false
}
if buttonMask&faceButtonMask == 0 {
return false
}
gamepadButtonMappings[id] = map[StandardButton]mapping{}
gamepadAxisMappings[id] = map[StandardAxis]mapping{}
// For mappings, see mobile/ebitenmobileview/input_android.go.
if buttonMask&(1<<SDLControllerButtonA) != 0 {
gamepadButtonMappings[id][StandardButtonRightBottom] = mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonA,
}
}
if buttonMask&(1<<SDLControllerButtonB) != 0 {
gamepadButtonMappings[id][StandardButtonRightRight] = mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonB,
}
} else {
// Use the back button as "B" for easy UI navigation with TV remotes.
gamepadButtonMappings[id][StandardButtonRightRight] = mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonBack,
}
buttonMask &^= uint16(1) << SDLControllerButtonBack
}
if buttonMask&(1<<SDLControllerButtonX) != 0 {
gamepadButtonMappings[id][StandardButtonRightLeft] = mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonX,
}
}
if buttonMask&(1<<SDLControllerButtonY) != 0 {
gamepadButtonMappings[id][StandardButtonRightTop] = mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonY,
}
}
if buttonMask&(1<<SDLControllerButtonBack) != 0 {
gamepadButtonMappings[id][StandardButtonCenterLeft] = mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonBack,
}
}
if buttonMask&(1<<SDLControllerButtonGuide) != 0 {
// TODO: If SDKVersion >= 30, add this code:
//
// gamepadButtonMappings[id][StandardButtonCenterCenter] = mapping{
// Type: mappingTypeButton,
// Index: SDLControllerButtonGuide,
// }
}
if buttonMask&(1<<SDLControllerButtonStart) != 0 {
gamepadButtonMappings[id][StandardButtonCenterRight] = mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonStart,
}
}
if buttonMask&(1<<SDLControllerButtonLeftStick) != 0 {
gamepadButtonMappings[id][StandardButtonLeftStick] = mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonLeftStick,
}
}
if buttonMask&(1<<SDLControllerButtonRightStick) != 0 {
gamepadButtonMappings[id][StandardButtonRightStick] = mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonRightStick,
}
}
if buttonMask&(1<<SDLControllerButtonLeftShoulder) != 0 {
gamepadButtonMappings[id][StandardButtonFrontTopLeft] = mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonLeftShoulder,
}
}
if buttonMask&(1<<SDLControllerButtonRightShoulder) != 0 {
gamepadButtonMappings[id][StandardButtonFrontTopRight] = mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonRightShoulder,
}
}
if buttonMask&(1<<SDLControllerButtonDpadUp) != 0 {
gamepadButtonMappings[id][StandardButtonLeftTop] = mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonDpadUp,
}
}
if buttonMask&(1<<SDLControllerButtonDpadDown) != 0 {
gamepadButtonMappings[id][StandardButtonLeftBottom] = mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonDpadDown,
}
}
if buttonMask&(1<<SDLControllerButtonDpadLeft) != 0 {
gamepadButtonMappings[id][StandardButtonLeftLeft] = mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonDpadLeft,
}
}
if buttonMask&(1<<SDLControllerButtonDpadRight) != 0 {
gamepadButtonMappings[id][StandardButtonLeftRight] = mapping{
Type: mappingTypeButton,
Index: SDLControllerButtonDpadRight,
}
}
if axisMask&(1<<SDLControllerAxisLeftX) != 0 {
gamepadAxisMappings[id][StandardAxisLeftStickHorizontal] = mapping{
Type: mappingTypeAxis,
Index: SDLControllerAxisLeftX,
AxisScale: 1,
AxisOffset: 0,
}
}
if axisMask&(1<<SDLControllerAxisLeftY) != 0 {
gamepadAxisMappings[id][StandardAxisLeftStickVertical] = mapping{
Type: mappingTypeAxis,
Index: SDLControllerAxisLeftY,
AxisScale: 1,
AxisOffset: 0,
}
}
if axisMask&(1<<SDLControllerAxisRightX) != 0 {
gamepadAxisMappings[id][StandardAxisRightStickHorizontal] = mapping{
Type: mappingTypeAxis,
Index: SDLControllerAxisRightX,
AxisScale: 1,
AxisOffset: 0,
}
}
if axisMask&(1<<SDLControllerAxisRightY) != 0 {
gamepadAxisMappings[id][StandardAxisRightStickVertical] = mapping{
Type: mappingTypeAxis,
Index: SDLControllerAxisRightY,
AxisScale: 1,
AxisOffset: 0,
}
}
if axisMask&(1<<SDLControllerAxisTriggerLeft) != 0 {
gamepadButtonMappings[id][StandardButtonFrontBottomLeft] = mapping{
Type: mappingTypeAxis,
Index: SDLControllerAxisTriggerLeft,
AxisScale: 1,
AxisOffset: 0,
}
}
if axisMask&(1<<SDLControllerAxisTriggerRight) != 0 {
gamepadButtonMappings[id][StandardButtonFrontBottomRight] = mapping{
Type: mappingTypeAxis,
Index: SDLControllerAxisTriggerRight,
AxisScale: 1,
AxisOffset: 0,
}
}
return true
}
+51
View File
@@ -0,0 +1,51 @@
// Copyright 2022 The Ebitengine 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.
package gamepaddb
// See https://github.com/libsdl-org/SDL/blob/120c76c84bbce4c1bfed4e9eb74e10678bd83120/include/SDL_gamecontroller.h#L655-L680
const (
SDLControllerButtonA = 0
SDLControllerButtonB = 1
SDLControllerButtonX = 2
SDLControllerButtonY = 3
SDLControllerButtonBack = 4
SDLControllerButtonGuide = 5
SDLControllerButtonStart = 6
SDLControllerButtonLeftStick = 7
SDLControllerButtonRightStick = 8
SDLControllerButtonLeftShoulder = 9
SDLControllerButtonRightShoulder = 10
SDLControllerButtonDpadUp = 11
SDLControllerButtonDpadDown = 12
SDLControllerButtonDpadLeft = 13
SDLControllerButtonDpadRight = 14
SDLControllerButtonMisc1 = 15
SDLControllerButtonPaddle1 = 16
SDLControllerButtonPaddle2 = 17
SDLControllerButtonPaddle3 = 18
SDLControllerButtonPaddle4 = 19
SDLControllerButtonTouchpad = 20
SDLControllerButtonMax = SDLControllerButtonTouchpad // This is different from the original SDL_CONTROLLER_BUTTON_MAX.
)
// See https://github.com/libsdl-org/SDL/blob/120c76c84bbce4c1bfed4e9eb74e10678bd83120/include/SDL_gamecontroller.h#L550-L560
const (
SDLControllerAxisLeftX = 0
SDLControllerAxisLeftY = 1
SDLControllerAxisRightX = 2
SDLControllerAxisRightY = 3
SDLControllerAxisTriggerLeft = 4
SDLControllerAxisTriggerRight = 5
)
+52
View File
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2009-2016 Camilla Löwy <elmindreda@glfw.org>
// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
package glfw
// #include "internal_unix.h"
import "C"
import (
"fmt"
"github.com/ebitengine/purego"
)
type mach_timebase_info_data_t struct {
numer uint32
denom uint32
}
var mach_absolute_time func() uint64
var mach_timebase_info func(*mach_timebase_info_data_t)
var pthread_key_create func(key *C.pthread_key_t, destructor uintptr) int32
var pthread_key_delete func(key C.pthread_key_t) int32
var pthread_getspecific func(key C.pthread_key_t) uintptr
var pthread_setspecific func(key C.pthread_key_t, value uintptr) int32
var pthread_mutex_init func(mutex *C.pthread_mutex_t, attr *C.pthread_mutexattr_t) int32
var pthread_mutex_destroy func(mutex *C.pthread_mutex_t) int32
var pthread_mutex_lock func(mutex *C.pthread_mutex_t) int32
var pthread_mutex_unlock func(mutex *C.pthread_mutex_t) int32
// TODO: replace with Go error handling
var _glfwInputError func(code int32, format *C.char)
func init() {
purego.RegisterLibFunc(&_glfwInputError, purego.RTLD_DEFAULT, "_glfwInputError")
libSystem, err := purego.Dlopen("/usr/lib/libSystem.B.dylib", purego.RTLD_LAZY|purego.RTLD_GLOBAL)
if err != nil {
panic(fmt.Errorf("glfw: failed to dlopen: %w", err))
}
purego.RegisterLibFunc(&mach_absolute_time, libSystem, "mach_absolute_time")
purego.RegisterLibFunc(&mach_timebase_info, libSystem, "mach_timebase_info")
purego.RegisterLibFunc(&pthread_key_create, libSystem, "pthread_key_create")
purego.RegisterLibFunc(&pthread_key_delete, libSystem, "pthread_key_delete")
purego.RegisterLibFunc(&pthread_getspecific, libSystem, "pthread_getspecific")
purego.RegisterLibFunc(&pthread_setspecific, libSystem, "pthread_setspecific")
purego.RegisterLibFunc(&pthread_mutex_init, libSystem, "pthread_mutex_init")
purego.RegisterLibFunc(&pthread_mutex_destroy, libSystem, "pthread_mutex_destroy")
purego.RegisterLibFunc(&pthread_mutex_lock, libSystem, "pthread_mutex_lock")
purego.RegisterLibFunc(&pthread_mutex_unlock, libSystem, "pthread_mutex_unlock")
}
File diff suppressed because it is too large Load Diff
+14
View File
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2012 The glfw3-go Authors
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
//go:build freebsd || netbsd || openbsd
package glfw
import "C"
// #cgo pkg-config: x11 xau xcb xdmcp
// #cgo CFLAGS: -D_GLFW_HAS_DLOPEN -D_GLFW_X11 -D_GLFW_HAS_GLXGETPROCADDRESSARB
// #cgo LDFLAGS: -lm
import "C"
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2012 The glfw3-go Authors
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
package glfw
// #cgo CFLAGS: -D_GLFW_COCOA -Wno-deprecated-declarations
// #cgo LDFLAGS: -framework Cocoa -framework IOKit -framework CoreVideo
import "C"
+9
View File
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2012 The glfw3-go Authors
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
package glfw
// #cgo CFLAGS: -D_GLFW_X11
// #cgo LDFLAGS: -lX11 -lXrandr -lXxf86vm -lXi -lXcursor -lm -lXinerama -ldl -lrt
import "C"
@@ -0,0 +1,573 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2009-2019 Camilla Löwy <elmindreda@glfw.org>
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
#include "internal_unix.h"
#include <sys/param.h> // For MAXPATHLEN
// Needed for _NSGetProgname
#include <crt_externs.h>
// Change to our application bundle's resources directory, if present
//
static void changeToResourcesDirectory(void)
{
char resourcesPath[MAXPATHLEN];
CFBundleRef bundle = CFBundleGetMainBundle();
if (!bundle)
return;
CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(bundle);
CFStringRef last = CFURLCopyLastPathComponent(resourcesURL);
if (CFStringCompare(CFSTR("Resources"), last, 0) != kCFCompareEqualTo)
{
CFRelease(last);
CFRelease(resourcesURL);
return;
}
CFRelease(last);
if (!CFURLGetFileSystemRepresentation(resourcesURL,
true,
(UInt8*) resourcesPath,
MAXPATHLEN))
{
CFRelease(resourcesURL);
return;
}
CFRelease(resourcesURL);
chdir(resourcesPath);
}
// Set up the menu bar (manually)
// This is nasty, nasty stuff -- calls to undocumented semi-private APIs that
// could go away at any moment, lots of stuff that really should be
// localize(d|able), etc. Add a nib to save us this horror.
//
static void createMenuBar(void)
{
size_t i;
NSString* appName = nil;
NSDictionary* bundleInfo = [[NSBundle mainBundle] infoDictionary];
NSString* nameKeys[] =
{
@"CFBundleDisplayName",
@"CFBundleName",
@"CFBundleExecutable",
};
// Try to figure out what the calling application is called
for (i = 0; i < sizeof(nameKeys) / sizeof(nameKeys[0]); i++)
{
id name = bundleInfo[nameKeys[i]];
if (name &&
[name isKindOfClass:[NSString class]] &&
![name isEqualToString:@""])
{
appName = name;
break;
}
}
if (!appName)
{
char** progname = _NSGetProgname();
if (progname && *progname)
appName = @(*progname);
else
appName = @"GLFW Application";
}
NSMenu* bar = [[NSMenu alloc] init];
[NSApp setMainMenu:bar];
NSMenuItem* appMenuItem =
[bar addItemWithTitle:@"" action:NULL keyEquivalent:@""];
NSMenu* appMenu = [[NSMenu alloc] init];
[appMenuItem setSubmenu:appMenu];
[appMenu addItemWithTitle:[NSString stringWithFormat:@"About %@", appName]
action:@selector(orderFrontStandardAboutPanel:)
keyEquivalent:@""];
[appMenu addItem:[NSMenuItem separatorItem]];
NSMenu* servicesMenu = [[NSMenu alloc] init];
[NSApp setServicesMenu:servicesMenu];
[[appMenu addItemWithTitle:@"Services"
action:NULL
keyEquivalent:@""] setSubmenu:servicesMenu];
[servicesMenu release];
[appMenu addItem:[NSMenuItem separatorItem]];
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", appName]
action:@selector(hide:)
keyEquivalent:@"h"];
[[appMenu addItemWithTitle:@"Hide Others"
action:@selector(hideOtherApplications:)
keyEquivalent:@"h"]
setKeyEquivalentModifierMask:NSEventModifierFlagOption | NSEventModifierFlagCommand];
[appMenu addItemWithTitle:@"Show All"
action:@selector(unhideAllApplications:)
keyEquivalent:@""];
[appMenu addItem:[NSMenuItem separatorItem]];
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", appName]
action:@selector(terminate:)
keyEquivalent:@"q"];
NSMenuItem* windowMenuItem =
[bar addItemWithTitle:@"" action:NULL keyEquivalent:@""];
[bar release];
NSMenu* windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
[NSApp setWindowsMenu:windowMenu];
[windowMenuItem setSubmenu:windowMenu];
[windowMenu addItemWithTitle:@"Minimize"
action:@selector(performMiniaturize:)
keyEquivalent:@"m"];
[windowMenu addItemWithTitle:@"Zoom"
action:@selector(performZoom:)
keyEquivalent:@""];
[windowMenu addItem:[NSMenuItem separatorItem]];
[windowMenu addItemWithTitle:@"Bring All to Front"
action:@selector(arrangeInFront:)
keyEquivalent:@""];
// TODO: Make this appear at the bottom of the menu (for consistency)
[windowMenu addItem:[NSMenuItem separatorItem]];
[[windowMenu addItemWithTitle:@"Enter Full Screen"
action:@selector(toggleFullScreen:)
keyEquivalent:@"f"]
setKeyEquivalentModifierMask:NSEventModifierFlagControl | NSEventModifierFlagCommand];
// Prior to Snow Leopard, we need to use this oddly-named semi-private API
// to get the application menu working properly.
SEL setAppleMenuSelector = NSSelectorFromString(@"setAppleMenu:");
[NSApp performSelector:setAppleMenuSelector withObject:appMenu];
}
// Create key code translation tables
//
static void createKeyTables(void)
{
int scancode;
memset(_glfw.ns.keycodes, -1, sizeof(_glfw.ns.keycodes));
memset(_glfw.ns.scancodes, -1, sizeof(_glfw.ns.scancodes));
_glfw.ns.keycodes[0x1D] = GLFW_KEY_0;
_glfw.ns.keycodes[0x12] = GLFW_KEY_1;
_glfw.ns.keycodes[0x13] = GLFW_KEY_2;
_glfw.ns.keycodes[0x14] = GLFW_KEY_3;
_glfw.ns.keycodes[0x15] = GLFW_KEY_4;
_glfw.ns.keycodes[0x17] = GLFW_KEY_5;
_glfw.ns.keycodes[0x16] = GLFW_KEY_6;
_glfw.ns.keycodes[0x1A] = GLFW_KEY_7;
_glfw.ns.keycodes[0x1C] = GLFW_KEY_8;
_glfw.ns.keycodes[0x19] = GLFW_KEY_9;
_glfw.ns.keycodes[0x00] = GLFW_KEY_A;
_glfw.ns.keycodes[0x0B] = GLFW_KEY_B;
_glfw.ns.keycodes[0x08] = GLFW_KEY_C;
_glfw.ns.keycodes[0x02] = GLFW_KEY_D;
_glfw.ns.keycodes[0x0E] = GLFW_KEY_E;
_glfw.ns.keycodes[0x03] = GLFW_KEY_F;
_glfw.ns.keycodes[0x05] = GLFW_KEY_G;
_glfw.ns.keycodes[0x04] = GLFW_KEY_H;
_glfw.ns.keycodes[0x22] = GLFW_KEY_I;
_glfw.ns.keycodes[0x26] = GLFW_KEY_J;
_glfw.ns.keycodes[0x28] = GLFW_KEY_K;
_glfw.ns.keycodes[0x25] = GLFW_KEY_L;
_glfw.ns.keycodes[0x2E] = GLFW_KEY_M;
_glfw.ns.keycodes[0x2D] = GLFW_KEY_N;
_glfw.ns.keycodes[0x1F] = GLFW_KEY_O;
_glfw.ns.keycodes[0x23] = GLFW_KEY_P;
_glfw.ns.keycodes[0x0C] = GLFW_KEY_Q;
_glfw.ns.keycodes[0x0F] = GLFW_KEY_R;
_glfw.ns.keycodes[0x01] = GLFW_KEY_S;
_glfw.ns.keycodes[0x11] = GLFW_KEY_T;
_glfw.ns.keycodes[0x20] = GLFW_KEY_U;
_glfw.ns.keycodes[0x09] = GLFW_KEY_V;
_glfw.ns.keycodes[0x0D] = GLFW_KEY_W;
_glfw.ns.keycodes[0x07] = GLFW_KEY_X;
_glfw.ns.keycodes[0x10] = GLFW_KEY_Y;
_glfw.ns.keycodes[0x06] = GLFW_KEY_Z;
_glfw.ns.keycodes[0x27] = GLFW_KEY_APOSTROPHE;
_glfw.ns.keycodes[0x2A] = GLFW_KEY_BACKSLASH;
_glfw.ns.keycodes[0x2B] = GLFW_KEY_COMMA;
_glfw.ns.keycodes[0x18] = GLFW_KEY_EQUAL;
_glfw.ns.keycodes[0x32] = GLFW_KEY_GRAVE_ACCENT;
_glfw.ns.keycodes[0x21] = GLFW_KEY_LEFT_BRACKET;
_glfw.ns.keycodes[0x1B] = GLFW_KEY_MINUS;
_glfw.ns.keycodes[0x2F] = GLFW_KEY_PERIOD;
_glfw.ns.keycodes[0x1E] = GLFW_KEY_RIGHT_BRACKET;
_glfw.ns.keycodes[0x29] = GLFW_KEY_SEMICOLON;
_glfw.ns.keycodes[0x2C] = GLFW_KEY_SLASH;
_glfw.ns.keycodes[0x0A] = GLFW_KEY_WORLD_1;
_glfw.ns.keycodes[0x33] = GLFW_KEY_BACKSPACE;
_glfw.ns.keycodes[0x39] = GLFW_KEY_CAPS_LOCK;
_glfw.ns.keycodes[0x75] = GLFW_KEY_DELETE;
_glfw.ns.keycodes[0x7D] = GLFW_KEY_DOWN;
_glfw.ns.keycodes[0x77] = GLFW_KEY_END;
_glfw.ns.keycodes[0x24] = GLFW_KEY_ENTER;
_glfw.ns.keycodes[0x35] = GLFW_KEY_ESCAPE;
_glfw.ns.keycodes[0x7A] = GLFW_KEY_F1;
_glfw.ns.keycodes[0x78] = GLFW_KEY_F2;
_glfw.ns.keycodes[0x63] = GLFW_KEY_F3;
_glfw.ns.keycodes[0x76] = GLFW_KEY_F4;
_glfw.ns.keycodes[0x60] = GLFW_KEY_F5;
_glfw.ns.keycodes[0x61] = GLFW_KEY_F6;
_glfw.ns.keycodes[0x62] = GLFW_KEY_F7;
_glfw.ns.keycodes[0x64] = GLFW_KEY_F8;
_glfw.ns.keycodes[0x65] = GLFW_KEY_F9;
_glfw.ns.keycodes[0x6D] = GLFW_KEY_F10;
_glfw.ns.keycodes[0x67] = GLFW_KEY_F11;
_glfw.ns.keycodes[0x6F] = GLFW_KEY_F12;
_glfw.ns.keycodes[0x69] = GLFW_KEY_F13;
_glfw.ns.keycodes[0x6B] = GLFW_KEY_F14;
_glfw.ns.keycodes[0x71] = GLFW_KEY_F15;
_glfw.ns.keycodes[0x6A] = GLFW_KEY_F16;
_glfw.ns.keycodes[0x40] = GLFW_KEY_F17;
_glfw.ns.keycodes[0x4F] = GLFW_KEY_F18;
_glfw.ns.keycodes[0x50] = GLFW_KEY_F19;
_glfw.ns.keycodes[0x5A] = GLFW_KEY_F20;
_glfw.ns.keycodes[0x73] = GLFW_KEY_HOME;
_glfw.ns.keycodes[0x72] = GLFW_KEY_INSERT;
_glfw.ns.keycodes[0x7B] = GLFW_KEY_LEFT;
_glfw.ns.keycodes[0x3A] = GLFW_KEY_LEFT_ALT;
_glfw.ns.keycodes[0x3B] = GLFW_KEY_LEFT_CONTROL;
_glfw.ns.keycodes[0x38] = GLFW_KEY_LEFT_SHIFT;
_glfw.ns.keycodes[0x37] = GLFW_KEY_LEFT_SUPER;
_glfw.ns.keycodes[0x6E] = GLFW_KEY_MENU;
_glfw.ns.keycodes[0x47] = GLFW_KEY_NUM_LOCK;
_glfw.ns.keycodes[0x79] = GLFW_KEY_PAGE_DOWN;
_glfw.ns.keycodes[0x74] = GLFW_KEY_PAGE_UP;
_glfw.ns.keycodes[0x7C] = GLFW_KEY_RIGHT;
_glfw.ns.keycodes[0x3D] = GLFW_KEY_RIGHT_ALT;
_glfw.ns.keycodes[0x3E] = GLFW_KEY_RIGHT_CONTROL;
_glfw.ns.keycodes[0x3C] = GLFW_KEY_RIGHT_SHIFT;
_glfw.ns.keycodes[0x36] = GLFW_KEY_RIGHT_SUPER;
_glfw.ns.keycodes[0x31] = GLFW_KEY_SPACE;
_glfw.ns.keycodes[0x30] = GLFW_KEY_TAB;
_glfw.ns.keycodes[0x7E] = GLFW_KEY_UP;
_glfw.ns.keycodes[0x52] = GLFW_KEY_KP_0;
_glfw.ns.keycodes[0x53] = GLFW_KEY_KP_1;
_glfw.ns.keycodes[0x54] = GLFW_KEY_KP_2;
_glfw.ns.keycodes[0x55] = GLFW_KEY_KP_3;
_glfw.ns.keycodes[0x56] = GLFW_KEY_KP_4;
_glfw.ns.keycodes[0x57] = GLFW_KEY_KP_5;
_glfw.ns.keycodes[0x58] = GLFW_KEY_KP_6;
_glfw.ns.keycodes[0x59] = GLFW_KEY_KP_7;
_glfw.ns.keycodes[0x5B] = GLFW_KEY_KP_8;
_glfw.ns.keycodes[0x5C] = GLFW_KEY_KP_9;
_glfw.ns.keycodes[0x45] = GLFW_KEY_KP_ADD;
_glfw.ns.keycodes[0x41] = GLFW_KEY_KP_DECIMAL;
_glfw.ns.keycodes[0x4B] = GLFW_KEY_KP_DIVIDE;
_glfw.ns.keycodes[0x4C] = GLFW_KEY_KP_ENTER;
_glfw.ns.keycodes[0x51] = GLFW_KEY_KP_EQUAL;
_glfw.ns.keycodes[0x43] = GLFW_KEY_KP_MULTIPLY;
_glfw.ns.keycodes[0x4E] = GLFW_KEY_KP_SUBTRACT;
for (scancode = 0; scancode < 256; scancode++)
{
// Store the reverse translation for faster key name lookup
if (_glfw.ns.keycodes[scancode] >= 0)
_glfw.ns.scancodes[_glfw.ns.keycodes[scancode]] = scancode;
}
}
// Retrieve Unicode data for the current keyboard layout
//
static GLFWbool updateUnicodeDataNS(void)
{
if (_glfw.ns.inputSource)
{
CFRelease(_glfw.ns.inputSource);
_glfw.ns.inputSource = NULL;
_glfw.ns.unicodeData = nil;
}
_glfw.ns.inputSource = TISCopyCurrentKeyboardLayoutInputSource();
if (!_glfw.ns.inputSource)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Cocoa: Failed to retrieve keyboard layout input source");
return GLFW_FALSE;
}
_glfw.ns.unicodeData =
TISGetInputSourceProperty(_glfw.ns.inputSource,
kTISPropertyUnicodeKeyLayoutData);
if (!_glfw.ns.unicodeData)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Cocoa: Failed to retrieve keyboard layout Unicode data");
return GLFW_FALSE;
}
return GLFW_TRUE;
}
// Load HIToolbox.framework and the TIS symbols we need from it
//
static GLFWbool initializeTIS(void)
{
// This works only because Cocoa has already loaded it properly
_glfw.ns.tis.bundle =
CFBundleGetBundleWithIdentifier(CFSTR("com.apple.HIToolbox"));
if (!_glfw.ns.tis.bundle)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Cocoa: Failed to load HIToolbox.framework");
return GLFW_FALSE;
}
CFStringRef* kPropertyUnicodeKeyLayoutData =
CFBundleGetDataPointerForName(_glfw.ns.tis.bundle,
CFSTR("kTISPropertyUnicodeKeyLayoutData"));
_glfw.ns.tis.CopyCurrentKeyboardLayoutInputSource =
CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle,
CFSTR("TISCopyCurrentKeyboardLayoutInputSource"));
_glfw.ns.tis.GetInputSourceProperty =
CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle,
CFSTR("TISGetInputSourceProperty"));
_glfw.ns.tis.GetKbdType =
CFBundleGetFunctionPointerForName(_glfw.ns.tis.bundle,
CFSTR("LMGetKbdType"));
if (!kPropertyUnicodeKeyLayoutData ||
!TISCopyCurrentKeyboardLayoutInputSource ||
!TISGetInputSourceProperty ||
!LMGetKbdType)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Cocoa: Failed to load TIS API symbols");
return GLFW_FALSE;
}
_glfw.ns.tis.kPropertyUnicodeKeyLayoutData =
*kPropertyUnicodeKeyLayoutData;
return updateUnicodeDataNS();
}
@interface GLFWHelper : NSObject
@end
@implementation GLFWHelper
- (void)selectedKeyboardInputSourceChanged:(NSObject* )object
{
updateUnicodeDataNS();
}
- (void)doNothing:(id)object
{
}
@end // GLFWHelper
@interface GLFWApplicationDelegate : NSObject <NSApplicationDelegate>
@end
@implementation GLFWApplicationDelegate
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
_GLFWwindow* window;
for (window = _glfw.windowListHead; window; window = window->next)
_glfwInputWindowCloseRequest(window);
return NSTerminateCancel;
}
- (void)applicationDidChangeScreenParameters:(NSNotification *) notification
{
_GLFWwindow* window;
for (window = _glfw.windowListHead; window; window = window->next)
{
if (window->context.client != GLFW_NO_API)
[window->context.nsgl.object update];
}
_glfwPollMonitorsNS();
}
- (void)applicationWillFinishLaunching:(NSNotification *)notification
{
if (_glfw.hints.init.ns.menubar)
{
// Menu bar setup must go between sharedApplication and finishLaunching
// in order to properly emulate the behavior of NSApplicationMain
if ([[NSBundle mainBundle] pathForResource:@"MainMenu" ofType:@"nib"])
{
[[NSBundle mainBundle] loadNibNamed:@"MainMenu"
owner:NSApp
topLevelObjects:&_glfw.ns.nibObjects];
}
else
createMenuBar();
}
}
- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
_glfw.ns.finishedLaunching = GLFW_TRUE;
_glfwPlatformPostEmptyEvent();
// In case we are unbundled, make us a proper UI application
if (_glfw.hints.init.ns.menubar)
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp stop:nil];
}
- (void)applicationDidHide:(NSNotification *)notification
{
int i;
for (i = 0; i < _glfw.monitorCount; i++)
_glfwRestoreVideoModeNS(_glfw.monitors[i]);
}
@end // GLFWApplicationDelegate
//////////////////////////////////////////////////////////////////////////
////// GLFW platform API //////
//////////////////////////////////////////////////////////////////////////
int _glfwPlatformInit(void)
{
@autoreleasepool {
_glfw.ns.helper = [[GLFWHelper alloc] init];
[NSThread detachNewThreadSelector:@selector(doNothing:)
toTarget:_glfw.ns.helper
withObject:nil];
if (NSApp)
_glfw.ns.finishedLaunching = GLFW_TRUE;
[NSApplication sharedApplication];
_glfw.ns.delegate = [[GLFWApplicationDelegate alloc] init];
if (_glfw.ns.delegate == nil)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Cocoa: Failed to create application delegate");
return GLFW_FALSE;
}
[NSApp setDelegate:_glfw.ns.delegate];
NSEvent* (^block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
{
if ([event modifierFlags] & NSEventModifierFlagCommand)
[[NSApp keyWindow] sendEvent:event];
return event;
};
_glfw.ns.keyUpMonitor =
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp
handler:block];
if (_glfw.hints.init.ns.chdir)
changeToResourcesDirectory();
// Press and Hold prevents some keys from emitting repeated characters
NSDictionary* defaults = @{@"ApplePressAndHoldEnabled":@NO};
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
[[NSNotificationCenter defaultCenter]
addObserver:_glfw.ns.helper
selector:@selector(selectedKeyboardInputSourceChanged:)
name:NSTextInputContextKeyboardSelectionDidChangeNotification
object:nil];
createKeyTables();
_glfw.ns.eventSource = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
if (!_glfw.ns.eventSource)
return GLFW_FALSE;
CGEventSourceSetLocalEventsSuppressionInterval(_glfw.ns.eventSource, 0.0);
if (!initializeTIS())
return GLFW_FALSE;
_glfwInitTimerNS();
_glfwPollMonitorsNS();
return GLFW_TRUE;
} // autoreleasepool
}
void _glfwPlatformTerminate(void)
{
@autoreleasepool {
if (_glfw.ns.inputSource)
{
CFRelease(_glfw.ns.inputSource);
_glfw.ns.inputSource = NULL;
_glfw.ns.unicodeData = nil;
}
if (_glfw.ns.eventSource)
{
CFRelease(_glfw.ns.eventSource);
_glfw.ns.eventSource = NULL;
}
if (_glfw.ns.delegate)
{
[NSApp setDelegate:nil];
[_glfw.ns.delegate release];
_glfw.ns.delegate = nil;
}
if (_glfw.ns.helper)
{
[[NSNotificationCenter defaultCenter]
removeObserver:_glfw.ns.helper
name:NSTextInputContextKeyboardSelectionDidChangeNotification
object:nil];
[[NSNotificationCenter defaultCenter]
removeObserver:_glfw.ns.helper];
[_glfw.ns.helper release];
_glfw.ns.helper = nil;
}
if (_glfw.ns.keyUpMonitor)
[NSEvent removeMonitor:_glfw.ns.keyUpMonitor];
free(_glfw.ns.clipboardString);
_glfwTerminateNSGL();
_glfwTerminateEGL();
_glfwTerminateOSMesa();
} // autoreleasepool
}
const char* _glfwPlatformGetVersionString(void)
{
return _GLFW_VERSION_NUMBER " Cocoa NSGL EGL OSMesa"
#if defined(_GLFW_BUILD_DLL)
" dynamic"
#endif
;
}
@@ -0,0 +1,603 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard
// SPDX-FileCopyrightText: 2006-2019 Camilla Löwy <elmindreda@glfw.org>
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
#include "internal_unix.h"
#include <stdlib.h>
#include <limits.h>
#include <math.h>
#include <IOKit/graphics/IOGraphicsLib.h>
#include <ApplicationServices/ApplicationServices.h>
// Get the name of the specified display, or NULL
//
static char* getMonitorName(CGDirectDisplayID displayID, NSScreen* screen)
{
// IOKit doesn't work on Apple Silicon anymore
// Luckily, 10.15 introduced -[NSScreen localizedName].
// Use it if available, and fall back to IOKit otherwise.
if (screen)
{
if ([screen respondsToSelector:@selector(localizedName)])
{
NSString* name = [screen valueForKey:@"localizedName"];
if (name)
return _glfw_strdup([name UTF8String]);
}
}
io_iterator_t it;
io_service_t service;
CFDictionaryRef info;
if (IOServiceGetMatchingServices(MACH_PORT_NULL,
IOServiceMatching("IODisplayConnect"),
&it) != 0)
{
// This may happen if a desktop Mac is running headless
return _glfw_strdup("Display");
}
while ((service = IOIteratorNext(it)) != 0)
{
info = IODisplayCreateInfoDictionary(service,
kIODisplayOnlyPreferredName);
CFNumberRef vendorIDRef =
CFDictionaryGetValue(info, CFSTR(kDisplayVendorID));
CFNumberRef productIDRef =
CFDictionaryGetValue(info, CFSTR(kDisplayProductID));
if (!vendorIDRef || !productIDRef)
{
CFRelease(info);
continue;
}
unsigned int vendorID, productID;
CFNumberGetValue(vendorIDRef, kCFNumberIntType, &vendorID);
CFNumberGetValue(productIDRef, kCFNumberIntType, &productID);
if (CGDisplayVendorNumber(displayID) == vendorID &&
CGDisplayModelNumber(displayID) == productID)
{
// Info dictionary is used and freed below
break;
}
CFRelease(info);
}
IOObjectRelease(it);
if (!service)
return _glfw_strdup("Display");
CFDictionaryRef names =
CFDictionaryGetValue(info, CFSTR(kDisplayProductName));
CFStringRef nameRef;
if (!names || !CFDictionaryGetValueIfPresent(names, CFSTR("en_US"),
(const void**) &nameRef))
{
// This may happen if a desktop Mac is running headless
CFRelease(info);
return _glfw_strdup("Display");
}
const CFIndex size =
CFStringGetMaximumSizeForEncoding(CFStringGetLength(nameRef),
kCFStringEncodingUTF8);
char* name = calloc(size + 1, 1);
CFStringGetCString(nameRef, name, size, kCFStringEncodingUTF8);
CFRelease(info);
return name;
}
// Check whether the display mode should be included in enumeration
//
static GLFWbool modeIsGood(CGDisplayModeRef mode)
{
uint32_t flags = CGDisplayModeGetIOFlags(mode);
if (!(flags & kDisplayModeValidFlag) || !(flags & kDisplayModeSafeFlag))
return GLFW_FALSE;
if (flags & kDisplayModeInterlacedFlag)
return GLFW_FALSE;
if (flags & kDisplayModeStretchedFlag)
return GLFW_FALSE;
#if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100
CFStringRef format = CGDisplayModeCopyPixelEncoding(mode);
if (CFStringCompare(format, CFSTR(IO16BitDirectPixels), 0) &&
CFStringCompare(format, CFSTR(IO32BitDirectPixels), 0))
{
CFRelease(format);
return GLFW_FALSE;
}
CFRelease(format);
#endif /* MAC_OS_X_VERSION_MAX_ALLOWED */
return GLFW_TRUE;
}
// Convert Core Graphics display mode to GLFW video mode
//
static GLFWvidmode vidmodeFromCGDisplayMode(CGDisplayModeRef mode,
double fallbackRefreshRate)
{
GLFWvidmode result;
result.width = (int) CGDisplayModeGetWidth(mode);
result.height = (int) CGDisplayModeGetHeight(mode);
result.refreshRate = (int) round(CGDisplayModeGetRefreshRate(mode));
if (result.refreshRate == 0)
result.refreshRate = (int) round(fallbackRefreshRate);
#if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100
CFStringRef format = CGDisplayModeCopyPixelEncoding(mode);
if (CFStringCompare(format, CFSTR(IO16BitDirectPixels), 0) == 0)
{
result.redBits = 5;
result.greenBits = 5;
result.blueBits = 5;
}
else
#endif /* MAC_OS_X_VERSION_MAX_ALLOWED */
{
result.redBits = 8;
result.greenBits = 8;
result.blueBits = 8;
}
#if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100
CFRelease(format);
#endif /* MAC_OS_X_VERSION_MAX_ALLOWED */
return result;
}
// Starts reservation for display fading
//
static CGDisplayFadeReservationToken beginFadeReservation(void)
{
CGDisplayFadeReservationToken token = kCGDisplayFadeReservationInvalidToken;
if (CGAcquireDisplayFadeReservation(5, &token) == kCGErrorSuccess)
{
CGDisplayFade(token, 0.3,
kCGDisplayBlendNormal,
kCGDisplayBlendSolidColor,
0.0, 0.0, 0.0,
TRUE);
}
return token;
}
// Ends reservation for display fading
//
static void endFadeReservation(CGDisplayFadeReservationToken token)
{
if (token != kCGDisplayFadeReservationInvalidToken)
{
CGDisplayFade(token, 0.5,
kCGDisplayBlendSolidColor,
kCGDisplayBlendNormal,
0.0, 0.0, 0.0,
FALSE);
CGReleaseDisplayFadeReservation(token);
}
}
// Returns the display refresh rate queried from the I/O registry
//
static double getFallbackRefreshRate(CGDirectDisplayID displayID)
{
double refreshRate = 60.0;
io_iterator_t it;
io_service_t service;
if (IOServiceGetMatchingServices(MACH_PORT_NULL,
IOServiceMatching("IOFramebuffer"),
&it) != 0)
{
return refreshRate;
}
while ((service = IOIteratorNext(it)) != 0)
{
const CFNumberRef indexRef =
IORegistryEntryCreateCFProperty(service,
CFSTR("IOFramebufferOpenGLIndex"),
kCFAllocatorDefault,
kNilOptions);
if (!indexRef)
continue;
uint32_t index = 0;
CFNumberGetValue(indexRef, kCFNumberIntType, &index);
CFRelease(indexRef);
if (CGOpenGLDisplayMaskToDisplayID(1 << index) != displayID)
continue;
const CFNumberRef clockRef =
IORegistryEntryCreateCFProperty(service,
CFSTR("IOFBCurrentPixelClock"),
kCFAllocatorDefault,
kNilOptions);
const CFNumberRef countRef =
IORegistryEntryCreateCFProperty(service,
CFSTR("IOFBCurrentPixelCount"),
kCFAllocatorDefault,
kNilOptions);
uint32_t clock = 0, count = 0;
if (clockRef)
{
CFNumberGetValue(clockRef, kCFNumberIntType, &clock);
CFRelease(clockRef);
}
if (countRef)
{
CFNumberGetValue(countRef, kCFNumberIntType, &count);
CFRelease(countRef);
}
if (clock > 0 && count > 0)
refreshRate = clock / (double) count;
break;
}
IOObjectRelease(it);
return refreshRate;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
// Poll for changes in the set of connected monitors
//
void _glfwPollMonitorsNS(void)
{
uint32_t displayCount;
CGGetOnlineDisplayList(0, NULL, &displayCount);
CGDirectDisplayID* displays = calloc(displayCount, sizeof(CGDirectDisplayID));
CGGetOnlineDisplayList(displayCount, displays, &displayCount);
for (int i = 0; i < _glfw.monitorCount; i++)
_glfw.monitors[i]->ns.screen = nil;
_GLFWmonitor** disconnected = NULL;
uint32_t disconnectedCount = _glfw.monitorCount;
if (disconnectedCount)
{
disconnected = calloc(_glfw.monitorCount, sizeof(_GLFWmonitor*));
memcpy(disconnected,
_glfw.monitors,
_glfw.monitorCount * sizeof(_GLFWmonitor*));
}
for (uint32_t i = 0; i < displayCount; i++)
{
if (CGDisplayIsAsleep(displays[i]))
continue;
const uint32_t unitNumber = CGDisplayUnitNumber(displays[i]);
NSScreen* screen = nil;
for (screen in [NSScreen screens])
{
NSNumber* screenNumber = [screen deviceDescription][@"NSScreenNumber"];
// HACK: Compare unit numbers instead of display IDs to work around
// display replacement on machines with automatic graphics
// switching
if (CGDisplayUnitNumber([screenNumber unsignedIntValue]) == unitNumber)
break;
}
// HACK: Compare unit numbers instead of display IDs to work around
// display replacement on machines with automatic graphics
// switching
uint32_t j;
for (j = 0; j < disconnectedCount; j++)
{
if (disconnected[j] && disconnected[j]->ns.unitNumber == unitNumber)
{
disconnected[j]->ns.screen = screen;
disconnected[j] = NULL;
break;
}
}
if (j < disconnectedCount)
continue;
const CGSize size = CGDisplayScreenSize(displays[i]);
char* name = getMonitorName(displays[i], screen);
if (!name)
continue;
_GLFWmonitor* monitor = _glfwAllocMonitor(name, size.width, size.height);
monitor->ns.displayID = displays[i];
monitor->ns.unitNumber = unitNumber;
monitor->ns.screen = screen;
free(name);
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displays[i]);
if (CGDisplayModeGetRefreshRate(mode) == 0.0)
monitor->ns.fallbackRefreshRate = getFallbackRefreshRate(displays[i]);
CGDisplayModeRelease(mode);
_glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_LAST);
}
for (uint32_t i = 0; i < disconnectedCount; i++)
{
if (disconnected[i])
_glfwInputMonitor(disconnected[i], GLFW_DISCONNECTED, 0);
}
free(disconnected);
free(displays);
}
// Change the current video mode
//
void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired)
{
GLFWvidmode current;
_glfwPlatformGetVideoMode(monitor, &current);
const GLFWvidmode* best = _glfwChooseVideoMode(monitor, desired);
if (_glfwCompareVideoModes(&current, best) == 0)
return;
CFArrayRef modes = CGDisplayCopyAllDisplayModes(monitor->ns.displayID, NULL);
const CFIndex count = CFArrayGetCount(modes);
CGDisplayModeRef native = NULL;
for (CFIndex i = 0; i < count; i++)
{
CGDisplayModeRef dm = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
if (!modeIsGood(dm))
continue;
const GLFWvidmode mode =
vidmodeFromCGDisplayMode(dm, monitor->ns.fallbackRefreshRate);
if (_glfwCompareVideoModes(best, &mode) == 0)
{
native = dm;
break;
}
}
if (native)
{
if (monitor->ns.previousMode == NULL)
monitor->ns.previousMode = CGDisplayCopyDisplayMode(monitor->ns.displayID);
CGDisplayFadeReservationToken token = beginFadeReservation();
CGDisplaySetDisplayMode(monitor->ns.displayID, native, NULL);
endFadeReservation(token);
}
CFRelease(modes);
}
// Restore the previously saved (original) video mode
//
void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor)
{
if (monitor->ns.previousMode)
{
CGDisplayFadeReservationToken token = beginFadeReservation();
CGDisplaySetDisplayMode(monitor->ns.displayID,
monitor->ns.previousMode, NULL);
endFadeReservation(token);
CGDisplayModeRelease(monitor->ns.previousMode);
monitor->ns.previousMode = NULL;
}
}
//////////////////////////////////////////////////////////////////////////
////// GLFW platform API //////
//////////////////////////////////////////////////////////////////////////
void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor)
{
}
void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
{
@autoreleasepool {
const CGRect bounds = CGDisplayBounds(monitor->ns.displayID);
if (xpos)
*xpos = (int) bounds.origin.x;
if (ypos)
*ypos = (int) bounds.origin.y;
} // autoreleasepool
}
void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor,
float* xscale, float* yscale)
{
@autoreleasepool {
if (!monitor->ns.screen)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Cocoa: Cannot query content scale without screen");
}
const NSRect points = [monitor->ns.screen frame];
const NSRect pixels = [monitor->ns.screen convertRectToBacking:points];
if (xscale)
*xscale = (float) (pixels.size.width / points.size.width);
if (yscale)
*yscale = (float) (pixels.size.height / points.size.height);
} // autoreleasepool
}
void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor,
int* xpos, int* ypos,
int* width, int* height)
{
@autoreleasepool {
if (!monitor->ns.screen)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Cocoa: Cannot query workarea without screen");
}
const NSRect frameRect = [monitor->ns.screen visibleFrame];
if (xpos)
*xpos = frameRect.origin.x;
if (ypos)
*ypos = _glfwTransformYNS(frameRect.origin.y + frameRect.size.height - 1);
if (width)
*width = frameRect.size.width;
if (height)
*height = frameRect.size.height;
} // autoreleasepool
}
GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
{
@autoreleasepool {
*count = 0;
CFArrayRef modes = CGDisplayCopyAllDisplayModes(monitor->ns.displayID, NULL);
const CFIndex found = CFArrayGetCount(modes);
GLFWvidmode* result = calloc(found, sizeof(GLFWvidmode));
for (CFIndex i = 0; i < found; i++)
{
CGDisplayModeRef dm = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
if (!modeIsGood(dm))
continue;
const GLFWvidmode mode =
vidmodeFromCGDisplayMode(dm, monitor->ns.fallbackRefreshRate);
CFIndex j;
for (j = 0; j < *count; j++)
{
if (_glfwCompareVideoModes(result + j, &mode) == 0)
break;
}
// Skip duplicate modes
if (j < *count)
continue;
(*count)++;
result[*count - 1] = mode;
}
CFRelease(modes);
return result;
} // autoreleasepool
}
void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode *mode)
{
@autoreleasepool {
CGDisplayModeRef native = CGDisplayCopyDisplayMode(monitor->ns.displayID);
*mode = vidmodeFromCGDisplayMode(native, monitor->ns.fallbackRefreshRate);
CGDisplayModeRelease(native);
} // autoreleasepool
}
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
{
@autoreleasepool {
uint32_t size = CGDisplayGammaTableCapacity(monitor->ns.displayID);
CGGammaValue* values = calloc(size * 3, sizeof(CGGammaValue));
CGGetDisplayTransferByTable(monitor->ns.displayID,
size,
values,
values + size,
values + size * 2,
&size);
_glfwAllocGammaArrays(ramp, size);
for (uint32_t i = 0; i < size; i++)
{
ramp->red[i] = (unsigned short) (values[i] * 65535);
ramp->green[i] = (unsigned short) (values[i + size] * 65535);
ramp->blue[i] = (unsigned short) (values[i + size * 2] * 65535);
}
free(values);
return GLFW_TRUE;
} // autoreleasepool
}
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
{
@autoreleasepool {
CGGammaValue* values = calloc(ramp->size * 3, sizeof(CGGammaValue));
for (unsigned int i = 0; i < ramp->size; i++)
{
values[i] = ramp->red[i] / 65535.f;
values[i + ramp->size] = ramp->green[i] / 65535.f;
values[i + ramp->size * 2] = ramp->blue[i] / 65535.f;
}
CGSetDisplayTransferByTable(monitor->ns.displayID,
ramp->size,
values,
values + ramp->size,
values + ramp->size * 2);
free(values);
} // autoreleasepool
}
//////////////////////////////////////////////////////////////////////////
////// GLFW native API //////
//////////////////////////////////////////////////////////////////////////
GLFWAPI CGDirectDisplayID glfwGetCocoaMonitor(GLFWmonitor* handle)
{
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
_GLFW_REQUIRE_INIT_OR_RETURN(kCGNullDirectDisplay);
return monitor->ns.displayID;
}
@@ -0,0 +1,171 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2009-2019 Camilla Löwy <elmindreda@glfw.org>
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
#include <stdint.h>
#include <dlfcn.h>
#include <Carbon/Carbon.h>
// NOTE: All of NSGL was deprecated in the 10.14 SDK
// This disables the pointless warnings for every symbol we use
#ifndef GL_SILENCE_DEPRECATION
#define GL_SILENCE_DEPRECATION
#endif
#if defined(__OBJC__)
#import <Cocoa/Cocoa.h>
#else
typedef void* id;
#endif
// NOTE: Many Cocoa enum values have been renamed and we need to build across
// SDK versions where one is unavailable or deprecated.
// We use the newer names in code and replace them with the older names if
// the base SDK does not provide the newer names.
#if MAC_OS_X_VERSION_MAX_ALLOWED < 101200
#define NSBitmapFormatAlphaNonpremultiplied NSAlphaNonpremultipliedBitmapFormat
#define NSEventMaskAny NSAnyEventMask
#define NSEventMaskKeyUp NSKeyUpMask
#define NSEventModifierFlagCapsLock NSAlphaShiftKeyMask
#define NSEventModifierFlagCommand NSCommandKeyMask
#define NSEventModifierFlagControl NSControlKeyMask
#define NSEventModifierFlagDeviceIndependentFlagsMask NSDeviceIndependentModifierFlagsMask
#define NSEventModifierFlagOption NSAlternateKeyMask
#define NSEventModifierFlagShift NSShiftKeyMask
#define NSEventTypeApplicationDefined NSApplicationDefined
#define NSWindowStyleMaskBorderless NSBorderlessWindowMask
#define NSWindowStyleMaskClosable NSClosableWindowMask
#define NSWindowStyleMaskMiniaturizable NSMiniaturizableWindowMask
#define NSWindowStyleMaskResizable NSResizableWindowMask
#define NSWindowStyleMaskTitled NSTitledWindowMask
#endif
// NOTE: Many Cocoa dynamically linked constants have been renamed and we need
// to build across SDK versions where one is unavailable or deprecated.
// We use the newer names in code and replace them with the older names if
// the deployment target is older than the newer names.
#if MAC_OS_X_VERSION_MIN_REQUIRED < 101300
#define NSPasteboardTypeURL NSURLPboardType
#endif
#include "posix_thread_unix.h"
#include "nsgl_context_darwin.h"
#include "egl_context_unix.h"
#include "osmesa_context_unix.h"
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
#define _glfw_dlclose(handle) dlclose(handle)
#define _glfw_dlsym(handle, name) dlsym(handle, name)
#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->ns.layer)
#define _GLFW_EGL_NATIVE_DISPLAY EGL_DEFAULT_DISPLAY
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowNS ns
#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryNS ns
#define _GLFW_PLATFORM_LIBRARY_TIMER_STATE _GLFWtimerNS ns
#define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorNS ns
#define _GLFW_PLATFORM_CURSOR_STATE _GLFWcursorNS ns
// HIToolbox.framework pointer typedefs
#define kTISPropertyUnicodeKeyLayoutData _glfw.ns.tis.kPropertyUnicodeKeyLayoutData
typedef TISInputSourceRef (*PFN_TISCopyCurrentKeyboardLayoutInputSource)(void);
#define TISCopyCurrentKeyboardLayoutInputSource _glfw.ns.tis.CopyCurrentKeyboardLayoutInputSource
typedef void* (*PFN_TISGetInputSourceProperty)(TISInputSourceRef,CFStringRef);
#define TISGetInputSourceProperty _glfw.ns.tis.GetInputSourceProperty
typedef UInt8 (*PFN_LMGetKbdType)(void);
#define LMGetKbdType _glfw.ns.tis.GetKbdType
// Cocoa-specific per-window data
//
typedef struct _GLFWwindowNS
{
id object;
id delegate;
id view;
id layer;
GLFWbool maximized;
GLFWbool occluded;
GLFWbool retina;
// Cached window properties to filter out duplicate events
int width, height;
int fbWidth, fbHeight;
float xscale, yscale;
// The total sum of the distances the cursor has been warped
// since the last cursor motion event was processed
// This is kept to counteract Cocoa doing the same internally
double cursorWarpDeltaX, cursorWarpDeltaY;
} _GLFWwindowNS;
// Cocoa-specific global data
//
typedef struct _GLFWlibraryNS
{
CGEventSourceRef eventSource;
id delegate;
GLFWbool finishedLaunching;
GLFWbool cursorHidden;
TISInputSourceRef inputSource;
id unicodeData;
id helper;
id keyUpMonitor;
id nibObjects;
char keynames[GLFW_KEY_LAST + 1][17];
short int keycodes[256];
short int scancodes[GLFW_KEY_LAST + 1];
char* clipboardString;
CGPoint cascadePoint;
// Where to place the cursor when re-enabled
double restoreCursorPosX, restoreCursorPosY;
// The window whose disabled cursor mode is active
_GLFWwindow* disabledCursorWindow;
struct {
CFBundleRef bundle;
PFN_TISCopyCurrentKeyboardLayoutInputSource CopyCurrentKeyboardLayoutInputSource;
PFN_TISGetInputSourceProperty GetInputSourceProperty;
PFN_LMGetKbdType GetKbdType;
CFStringRef kPropertyUnicodeKeyLayoutData;
} tis;
} _GLFWlibraryNS;
// Cocoa-specific per-monitor data
//
typedef struct _GLFWmonitorNS
{
CGDirectDisplayID displayID;
CGDisplayModeRef previousMode;
uint32_t unitNumber;
id screen;
double fallbackRefreshRate;
} _GLFWmonitorNS;
// Cocoa-specific per-cursor data
//
typedef struct _GLFWcursorNS
{
id object;
} _GLFWcursorNS;
// Cocoa-specific global timer data
//
typedef struct _GLFWtimerNS
{
uint64_t frequency;
} _GLFWtimerNS;
void _glfwInitTimerNS(void);
void _glfwPollMonitorsNS(void);
void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired);
void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor);
float _glfwTransformYNS(float y);
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2009-2016 Camilla Löwy <elmindreda@glfw.org>
// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
package glfw
// #include "internal_unix.h"
import "C"
//export _glfwInitTimerNS
func _glfwInitTimerNS() {
var info mach_timebase_info_data_t
mach_timebase_info(&info)
C._glfw.timer.ns.frequency = C.ulonglong(info.denom*1e9) / C.ulonglong(info.numer)
}
//export _glfwPlatformGetTimerValue
func _glfwPlatformGetTimerValue() uint64 {
return mach_absolute_time()
}
//export _glfwPlatformGetTimerFrequency
func _glfwPlatformGetTimerFrequency() uint64 {
return uint64(C._glfw.timer.ns.frequency)
}
File diff suppressed because it is too large Load Diff
+204
View File
@@ -0,0 +1,204 @@
// Copyright 2018 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 darwin || freebsd || linux || netbsd || openbsd || windows
package glfw
import (
"fmt"
)
type (
Action int
ErrorCode int
Hint int
InputMode int
Key int
ModifierKey int
MouseButton int
PeripheralEvent int
StandardCursor int
)
const (
DontCare = -1
False = 0
True = 1
)
const (
Release = Action(0)
Press = Action(1)
Repeat = Action(2)
)
const (
ModAlt = ModifierKey(0x0004)
ModCapsLock = ModifierKey(0x0010)
ModControl = ModifierKey(0x0002)
ModNumLock = ModifierKey(0x0020)
ModShift = ModifierKey(0x0001)
ModSuper = ModifierKey(0x0008)
)
const (
MouseButton1 = MouseButton(0)
MouseButton2 = MouseButton(1)
MouseButton3 = MouseButton(2)
MouseButton4 = MouseButton(3)
MouseButton5 = MouseButton(4)
MouseButton6 = MouseButton(5)
MouseButton7 = MouseButton(6)
MouseButton8 = MouseButton(7)
MouseButtonLast = MouseButton8
MouseButtonLeft = MouseButton1
MouseButtonRight = MouseButton2
MouseButtonMiddle = MouseButton3
)
const (
AccumAlphaBits = Hint(0x0002100A)
AccumBlueBits = Hint(0x00021009)
AccumGreenBits = Hint(0x00021008)
AccumRedBits = Hint(0x00021007)
AlphaBits = Hint(0x00021004)
AutoIconify = Hint(0x00020006)
AuxBuffers = Hint(0x0002100B)
BlueBits = Hint(0x00021003)
CenterCursor = Hint(0x00020009)
ClientAPI = Hint(0x00022001)
ContextCreationAPI = Hint(0x0002200B)
ContextNoError = Hint(0x0002200A)
ContextReleaseBehavior = Hint(0x00022009)
ContextRevision = Hint(0x00022004)
ContextRobustness = Hint(0x00022005)
ContextVersionMajor = Hint(0x00022002)
ContextVersionMinor = Hint(0x00022003)
Decorated = Hint(0x00020005)
DepthBits = Hint(0x00021005)
DoubleBuffer = Hint(0x00021010)
Floating = Hint(0x00020007)
Focused = Hint(0x00020001)
FocusOnShow = Hint(0x0002000C)
GreenBits = Hint(0x00021002)
Hovered = Hint(0x0002000B)
Iconified = Hint(0x00020002)
Maximized = Hint(0x00020008)
MousePassthrough = Hint(0x0002000D)
OpenGLDebugContext = Hint(0x00022007)
OpenGLForwardCompat = Hint(0x00022006)
OpenGLProfile = Hint(0x00022008)
RedBits = Hint(0x00021001)
RefreshRate = Hint(0x0002100F)
Resizable = Hint(0x00020003)
Samples = Hint(0x0002100D)
ScaleToMonitor = Hint(0x0002200C)
SRGBCapable = Hint(0x0002100E)
StencilBits = Hint(0x00021006)
Stereo = Hint(0x0002100C)
TransparentFramebuffer = Hint(0x0002000A)
Visible = Hint(0x00020004)
X11ClassName = Hint(0x00024001)
X11InstanceName = Hint(0x00024002)
)
const (
CursorMode = InputMode(0x00033001)
LockKeyMods = InputMode(0x00033004)
RawMouseMotion = InputMode(0x00033005)
StickyKeysMode = InputMode(0x00033002)
StickyMouseButtonsMode = InputMode(0x00033003)
)
const (
AnyReleaseBehavior = 0
CursorDisabled = 0x00034003
CursorHidden = 0x00034002
CursorNormal = 0x00034001
EGLContextAPI = 0x00036002
LoseContextOnReset = 0x00031002
NativeContextAPI = 0x00036001
NoAPI = 0
NoResetNotification = 0x00031001
NoRobustness = 0
OpenGLAPI = 0x00030001
OpenGLAnyProfile = 0
OpenGLCompatProfile = 0x00032002
OpenGLCoreProfile = 0x00032001
OpenGLESAPI = 0x00030002
OSMesaContextAPI = 0x00036003
ReleaseBehaviorFlush = 0x00035001
ReleaseBehaviorNone = 0x00035002
)
const (
NotInitialized = ErrorCode(0x00010001)
NoCurrentContext = ErrorCode(0x00010002)
InvalidEnum = ErrorCode(0x00010003)
InvalidValue = ErrorCode(0x00010004)
OutOfMemory = ErrorCode(0x00010005)
APIUnavailable = ErrorCode(0x00010006)
VersionUnavailable = ErrorCode(0x00010007)
PlatformError = ErrorCode(0x00010008)
FormatUnavailable = ErrorCode(0x00010009)
NoWindowContext = ErrorCode(0x0001000A)
)
func (e ErrorCode) Error() string {
switch e {
case NotInitialized:
return "the GLFW library is not initialized"
case NoCurrentContext:
return "there is no current context"
case InvalidEnum:
return "invalid argument for enum parameter"
case InvalidValue:
return "invalid value for parameter"
case OutOfMemory:
return "out of memory"
case APIUnavailable:
return "the requested API is unavailable"
case VersionUnavailable:
return "the requested API version is unavailable"
case PlatformError:
return "a platform-specific error occurred"
case FormatUnavailable:
return "the requested format is unavailable"
case NoWindowContext:
return "the specified window has no context"
default:
return fmt.Sprintf("GLFW error (%d)", e)
}
}
const (
Connected = PeripheralEvent(0x00040001)
Disconnected = PeripheralEvent(0x00040002)
)
const (
ArrowCursor = StandardCursor(0x00036001)
IBeamCursor = StandardCursor(0x00036002)
CrosshairCursor = StandardCursor(0x00036003)
HandCursor = StandardCursor(0x00036004)
HResizeCursor = StandardCursor(0x00036005)
VResizeCursor = StandardCursor(0x00036006)
// v3.4
ResizeNWSECursor = StandardCursor(0x00036007)
ResizeNESWCursor = StandardCursor(0x00036008)
ResizeAllCursor = StandardCursor(0x00036009)
NotAllowedCursor = StandardCursor(0x0003600A)
)
+736
View File
@@ -0,0 +1,736 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard
// SPDX-FileCopyrightText: 2006-2016 Camilla Löwy <elmindreda@glfw.org>
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
//go:build darwin || freebsd || linux || netbsd || openbsd
#include "internal_unix.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <stdio.h>
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
// Checks whether the desired context attributes are valid
//
// This function checks things like whether the specified client API version
// exists and whether all relevant options have supported and non-conflicting
// values
//
GLFWbool _glfwIsValidContextConfig(const _GLFWctxconfig* ctxconfig)
{
if (ctxconfig->share)
{
if (ctxconfig->client == GLFW_NO_API ||
ctxconfig->share->context.client == GLFW_NO_API)
{
_glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL);
return GLFW_FALSE;
}
}
if (ctxconfig->source != GLFW_NATIVE_CONTEXT_API &&
ctxconfig->source != GLFW_EGL_CONTEXT_API &&
ctxconfig->source != GLFW_OSMESA_CONTEXT_API)
{
_glfwInputError(GLFW_INVALID_ENUM,
"Invalid context creation API 0x%08X",
ctxconfig->source);
return GLFW_FALSE;
}
if (ctxconfig->client != GLFW_NO_API &&
ctxconfig->client != GLFW_OPENGL_API &&
ctxconfig->client != GLFW_OPENGL_ES_API)
{
_glfwInputError(GLFW_INVALID_ENUM,
"Invalid client API 0x%08X",
ctxconfig->client);
return GLFW_FALSE;
}
if (ctxconfig->client == GLFW_OPENGL_API)
{
if ((ctxconfig->major < 1 || ctxconfig->minor < 0) ||
(ctxconfig->major == 1 && ctxconfig->minor > 5) ||
(ctxconfig->major == 2 && ctxconfig->minor > 1) ||
(ctxconfig->major == 3 && ctxconfig->minor > 3))
{
// OpenGL 1.0 is the smallest valid version
// OpenGL 1.x series ended with version 1.5
// OpenGL 2.x series ended with version 2.1
// OpenGL 3.x series ended with version 3.3
// For now, let everything else through
_glfwInputError(GLFW_INVALID_VALUE,
"Invalid OpenGL version %i.%i",
ctxconfig->major, ctxconfig->minor);
return GLFW_FALSE;
}
if (ctxconfig->profile)
{
if (ctxconfig->profile != GLFW_OPENGL_CORE_PROFILE &&
ctxconfig->profile != GLFW_OPENGL_COMPAT_PROFILE)
{
_glfwInputError(GLFW_INVALID_ENUM,
"Invalid OpenGL profile 0x%08X",
ctxconfig->profile);
return GLFW_FALSE;
}
if (ctxconfig->major <= 2 ||
(ctxconfig->major == 3 && ctxconfig->minor < 2))
{
// Desktop OpenGL context profiles are only defined for version 3.2
// and above
_glfwInputError(GLFW_INVALID_VALUE,
"Context profiles are only defined for OpenGL version 3.2 and above");
return GLFW_FALSE;
}
}
if (ctxconfig->forward && ctxconfig->major <= 2)
{
// Forward-compatible contexts are only defined for OpenGL version 3.0 and above
_glfwInputError(GLFW_INVALID_VALUE,
"Forward-compatibility is only defined for OpenGL version 3.0 and above");
return GLFW_FALSE;
}
}
else if (ctxconfig->client == GLFW_OPENGL_ES_API)
{
if (ctxconfig->major < 1 || ctxconfig->minor < 0 ||
(ctxconfig->major == 1 && ctxconfig->minor > 1) ||
(ctxconfig->major == 2 && ctxconfig->minor > 0))
{
// OpenGL ES 1.0 is the smallest valid version
// OpenGL ES 1.x series ended with version 1.1
// OpenGL ES 2.x series ended with version 2.0
// For now, let everything else through
_glfwInputError(GLFW_INVALID_VALUE,
"Invalid OpenGL ES version %i.%i",
ctxconfig->major, ctxconfig->minor);
return GLFW_FALSE;
}
}
if (ctxconfig->robustness)
{
if (ctxconfig->robustness != GLFW_NO_RESET_NOTIFICATION &&
ctxconfig->robustness != GLFW_LOSE_CONTEXT_ON_RESET)
{
_glfwInputError(GLFW_INVALID_ENUM,
"Invalid context robustness mode 0x%08X",
ctxconfig->robustness);
return GLFW_FALSE;
}
}
if (ctxconfig->release)
{
if (ctxconfig->release != GLFW_RELEASE_BEHAVIOR_NONE &&
ctxconfig->release != GLFW_RELEASE_BEHAVIOR_FLUSH)
{
_glfwInputError(GLFW_INVALID_ENUM,
"Invalid context release behavior 0x%08X",
ctxconfig->release);
return GLFW_FALSE;
}
}
return GLFW_TRUE;
}
// Chooses the framebuffer config that best matches the desired one
//
const _GLFWfbconfig* _glfwChooseFBConfig(const _GLFWfbconfig* desired,
const _GLFWfbconfig* alternatives,
unsigned int count)
{
unsigned int i;
unsigned int missing, leastMissing = UINT_MAX;
unsigned int colorDiff, leastColorDiff = UINT_MAX;
unsigned int extraDiff, leastExtraDiff = UINT_MAX;
const _GLFWfbconfig* current;
const _GLFWfbconfig* closest = NULL;
for (i = 0; i < count; i++)
{
current = alternatives + i;
if (desired->stereo > 0 && current->stereo == 0)
{
// Stereo is a hard constraint
continue;
}
// Count number of missing buffers
{
missing = 0;
if (desired->alphaBits > 0 && current->alphaBits == 0)
missing++;
if (desired->depthBits > 0 && current->depthBits == 0)
missing++;
if (desired->stencilBits > 0 && current->stencilBits == 0)
missing++;
if (desired->auxBuffers > 0 &&
current->auxBuffers < desired->auxBuffers)
{
missing += desired->auxBuffers - current->auxBuffers;
}
if (desired->samples > 0 && current->samples == 0)
{
// Technically, several multisampling buffers could be
// involved, but that's a lower level implementation detail and
// not important to us here, so we count them as one
missing++;
}
if (desired->transparent != current->transparent)
missing++;
}
// These polynomials make many small channel size differences matter
// less than one large channel size difference
// Calculate color channel size difference value
{
colorDiff = 0;
if (desired->redBits != GLFW_DONT_CARE)
{
colorDiff += (desired->redBits - current->redBits) *
(desired->redBits - current->redBits);
}
if (desired->greenBits != GLFW_DONT_CARE)
{
colorDiff += (desired->greenBits - current->greenBits) *
(desired->greenBits - current->greenBits);
}
if (desired->blueBits != GLFW_DONT_CARE)
{
colorDiff += (desired->blueBits - current->blueBits) *
(desired->blueBits - current->blueBits);
}
}
// Calculate non-color channel size difference value
{
extraDiff = 0;
if (desired->alphaBits != GLFW_DONT_CARE)
{
extraDiff += (desired->alphaBits - current->alphaBits) *
(desired->alphaBits - current->alphaBits);
}
if (desired->depthBits != GLFW_DONT_CARE)
{
extraDiff += (desired->depthBits - current->depthBits) *
(desired->depthBits - current->depthBits);
}
if (desired->stencilBits != GLFW_DONT_CARE)
{
extraDiff += (desired->stencilBits - current->stencilBits) *
(desired->stencilBits - current->stencilBits);
}
if (desired->accumRedBits != GLFW_DONT_CARE)
{
extraDiff += (desired->accumRedBits - current->accumRedBits) *
(desired->accumRedBits - current->accumRedBits);
}
if (desired->accumGreenBits != GLFW_DONT_CARE)
{
extraDiff += (desired->accumGreenBits - current->accumGreenBits) *
(desired->accumGreenBits - current->accumGreenBits);
}
if (desired->accumBlueBits != GLFW_DONT_CARE)
{
extraDiff += (desired->accumBlueBits - current->accumBlueBits) *
(desired->accumBlueBits - current->accumBlueBits);
}
if (desired->accumAlphaBits != GLFW_DONT_CARE)
{
extraDiff += (desired->accumAlphaBits - current->accumAlphaBits) *
(desired->accumAlphaBits - current->accumAlphaBits);
}
if (desired->samples != GLFW_DONT_CARE)
{
extraDiff += (desired->samples - current->samples) *
(desired->samples - current->samples);
}
if (desired->sRGB && !current->sRGB)
extraDiff++;
}
// Figure out if the current one is better than the best one found so far
// Least number of missing buffers is the most important heuristic,
// then color buffer size match and lastly size match for other buffers
if (missing < leastMissing)
closest = current;
else if (missing == leastMissing)
{
if ((colorDiff < leastColorDiff) ||
(colorDiff == leastColorDiff && extraDiff < leastExtraDiff))
{
closest = current;
}
}
if (current == closest)
{
leastMissing = missing;
leastColorDiff = colorDiff;
leastExtraDiff = extraDiff;
}
}
return closest;
}
// Retrieves the attributes of the current context
//
GLFWbool _glfwRefreshContextAttribs(_GLFWwindow* window,
const _GLFWctxconfig* ctxconfig)
{
int i;
_GLFWwindow* previous;
const char* version;
const char* prefixes[] =
{
"OpenGL ES-CM ",
"OpenGL ES-CL ",
"OpenGL ES ",
NULL
};
window->context.source = ctxconfig->source;
window->context.client = GLFW_OPENGL_API;
previous = _glfwPlatformGetTls(&_glfw.contextSlot);
glfwMakeContextCurrent((GLFWwindow*) window);
window->context.GetIntegerv = (PFNGLGETINTEGERVPROC)
window->context.getProcAddress("glGetIntegerv");
window->context.GetString = (PFNGLGETSTRINGPROC)
window->context.getProcAddress("glGetString");
if (!window->context.GetIntegerv || !window->context.GetString)
{
_glfwInputError(GLFW_PLATFORM_ERROR, "Entry point retrieval is broken");
glfwMakeContextCurrent((GLFWwindow*) previous);
return GLFW_FALSE;
}
version = (const char*) window->context.GetString(GL_VERSION);
if (!version)
{
if (ctxconfig->client == GLFW_OPENGL_API)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"OpenGL version string retrieval is broken");
}
else
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"OpenGL ES version string retrieval is broken");
}
glfwMakeContextCurrent((GLFWwindow*) previous);
return GLFW_FALSE;
}
for (i = 0; prefixes[i]; i++)
{
const size_t length = strlen(prefixes[i]);
if (strncmp(version, prefixes[i], length) == 0)
{
version += length;
window->context.client = GLFW_OPENGL_ES_API;
break;
}
}
if (!sscanf(version, "%d.%d.%d",
&window->context.major,
&window->context.minor,
&window->context.revision))
{
if (window->context.client == GLFW_OPENGL_API)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"No version found in OpenGL version string");
}
else
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"No version found in OpenGL ES version string");
}
glfwMakeContextCurrent((GLFWwindow*) previous);
return GLFW_FALSE;
}
if (window->context.major < ctxconfig->major ||
(window->context.major == ctxconfig->major &&
window->context.minor < ctxconfig->minor))
{
// The desired OpenGL version is greater than the actual version
// This only happens if the machine lacks {GLX|WGL}_ARB_create_context
// /and/ the user has requested an OpenGL version greater than 1.0
// For API consistency, we emulate the behavior of the
// {GLX|WGL}_ARB_create_context extension and fail here
if (window->context.client == GLFW_OPENGL_API)
{
_glfwInputError(GLFW_VERSION_UNAVAILABLE,
"Requested OpenGL version %i.%i, got version %i.%i",
ctxconfig->major, ctxconfig->minor,
window->context.major, window->context.minor);
}
else
{
_glfwInputError(GLFW_VERSION_UNAVAILABLE,
"Requested OpenGL ES version %i.%i, got version %i.%i",
ctxconfig->major, ctxconfig->minor,
window->context.major, window->context.minor);
}
glfwMakeContextCurrent((GLFWwindow*) previous);
return GLFW_FALSE;
}
if (window->context.major >= 3)
{
// OpenGL 3.0+ uses a different function for extension string retrieval
// We cache it here instead of in glfwExtensionSupported mostly to alert
// users as early as possible that their build may be broken
window->context.GetStringi = (PFNGLGETSTRINGIPROC)
window->context.getProcAddress("glGetStringi");
if (!window->context.GetStringi)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Entry point retrieval is broken");
glfwMakeContextCurrent((GLFWwindow*) previous);
return GLFW_FALSE;
}
}
if (window->context.client == GLFW_OPENGL_API)
{
// Read back context flags (OpenGL 3.0 and above)
if (window->context.major >= 3)
{
GLint flags;
window->context.GetIntegerv(GL_CONTEXT_FLAGS, &flags);
if (flags & GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT)
window->context.forward = GLFW_TRUE;
if (flags & GL_CONTEXT_FLAG_DEBUG_BIT)
window->context.debug = GLFW_TRUE;
else if (glfwExtensionSupported("GL_ARB_debug_output") &&
ctxconfig->debug)
{
// HACK: This is a workaround for older drivers (pre KHR_debug)
// not setting the debug bit in the context flags for
// debug contexts
window->context.debug = GLFW_TRUE;
}
if (flags & GL_CONTEXT_FLAG_NO_ERROR_BIT_KHR)
window->context.noerror = GLFW_TRUE;
}
// Read back OpenGL context profile (OpenGL 3.2 and above)
if (window->context.major >= 4 ||
(window->context.major == 3 && window->context.minor >= 2))
{
GLint mask;
window->context.GetIntegerv(GL_CONTEXT_PROFILE_MASK, &mask);
if (mask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT)
window->context.profile = GLFW_OPENGL_COMPAT_PROFILE;
else if (mask & GL_CONTEXT_CORE_PROFILE_BIT)
window->context.profile = GLFW_OPENGL_CORE_PROFILE;
else if (glfwExtensionSupported("GL_ARB_compatibility"))
{
// HACK: This is a workaround for the compatibility profile bit
// not being set in the context flags if an OpenGL 3.2+
// context was created without having requested a specific
// version
window->context.profile = GLFW_OPENGL_COMPAT_PROFILE;
}
}
// Read back robustness strategy
if (glfwExtensionSupported("GL_ARB_robustness"))
{
// NOTE: We avoid using the context flags for detection, as they are
// only present from 3.0 while the extension applies from 1.1
GLint strategy;
window->context.GetIntegerv(GL_RESET_NOTIFICATION_STRATEGY_ARB,
&strategy);
if (strategy == GL_LOSE_CONTEXT_ON_RESET_ARB)
window->context.robustness = GLFW_LOSE_CONTEXT_ON_RESET;
else if (strategy == GL_NO_RESET_NOTIFICATION_ARB)
window->context.robustness = GLFW_NO_RESET_NOTIFICATION;
}
}
else
{
// Read back robustness strategy
if (glfwExtensionSupported("GL_EXT_robustness"))
{
// NOTE: The values of these constants match those of the OpenGL ARB
// one, so we can reuse them here
GLint strategy;
window->context.GetIntegerv(GL_RESET_NOTIFICATION_STRATEGY_ARB,
&strategy);
if (strategy == GL_LOSE_CONTEXT_ON_RESET_ARB)
window->context.robustness = GLFW_LOSE_CONTEXT_ON_RESET;
else if (strategy == GL_NO_RESET_NOTIFICATION_ARB)
window->context.robustness = GLFW_NO_RESET_NOTIFICATION;
}
}
if (glfwExtensionSupported("GL_KHR_context_flush_control"))
{
GLint behavior;
window->context.GetIntegerv(GL_CONTEXT_RELEASE_BEHAVIOR, &behavior);
if (behavior == GL_NONE)
window->context.release = GLFW_RELEASE_BEHAVIOR_NONE;
else if (behavior == GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH)
window->context.release = GLFW_RELEASE_BEHAVIOR_FLUSH;
}
// Clearing the front buffer to black to avoid garbage pixels left over from
// previous uses of our bit of VRAM
{
PFNGLCLEARPROC glClear = (PFNGLCLEARPROC)
window->context.getProcAddress("glClear");
glClear(GL_COLOR_BUFFER_BIT);
if (window->doublebuffer)
window->context.swapBuffers(window);
}
glfwMakeContextCurrent((GLFWwindow*) previous);
return GLFW_TRUE;
}
// Searches an extension string for the specified extension
//
GLFWbool _glfwStringInExtensionString(const char* string, const char* extensions)
{
const char* start = extensions;
for (;;)
{
const char* where;
const char* terminator;
where = strstr(start, string);
if (!where)
return GLFW_FALSE;
terminator = where + strlen(string);
if (where == start || *(where - 1) == ' ')
{
if (*terminator == ' ' || *terminator == '\0')
break;
}
start = terminator;
}
return GLFW_TRUE;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW public API //////
//////////////////////////////////////////////////////////////////////////
GLFWAPI void glfwMakeContextCurrent(GLFWwindow* handle)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
_GLFWwindow* previous;
_GLFW_REQUIRE_INIT();
previous = _glfwPlatformGetTls(&_glfw.contextSlot);
if (window && window->context.client == GLFW_NO_API)
{
_glfwInputError(GLFW_NO_WINDOW_CONTEXT,
"Cannot make current with a window that has no OpenGL or OpenGL ES context");
return;
}
if (previous)
{
if (!window || window->context.source != previous->context.source)
previous->context.makeCurrent(NULL);
}
if (window)
window->context.makeCurrent(window);
}
GLFWAPI GLFWwindow* glfwGetCurrentContext(void)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return _glfwPlatformGetTls(&_glfw.contextSlot);
}
GLFWAPI void glfwSwapBuffers(GLFWwindow* handle)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT();
if (window->context.client == GLFW_NO_API)
{
_glfwInputError(GLFW_NO_WINDOW_CONTEXT,
"Cannot swap buffers of a window that has no OpenGL or OpenGL ES context");
return;
}
window->context.swapBuffers(window);
}
GLFWAPI void glfwSwapInterval(int interval)
{
_GLFWwindow* window;
_GLFW_REQUIRE_INIT();
window = _glfwPlatformGetTls(&_glfw.contextSlot);
if (!window)
{
_glfwInputError(GLFW_NO_CURRENT_CONTEXT,
"Cannot set swap interval without a current OpenGL or OpenGL ES context");
return;
}
window->context.swapInterval(interval);
}
GLFWAPI int glfwExtensionSupported(const char* extension)
{
_GLFWwindow* window;
assert(extension != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(GLFW_FALSE);
window = _glfwPlatformGetTls(&_glfw.contextSlot);
if (!window)
{
_glfwInputError(GLFW_NO_CURRENT_CONTEXT,
"Cannot query extension without a current OpenGL or OpenGL ES context");
return GLFW_FALSE;
}
if (*extension == '\0')
{
_glfwInputError(GLFW_INVALID_VALUE, "Extension name cannot be an empty string");
return GLFW_FALSE;
}
if (window->context.major >= 3)
{
int i;
GLint count;
// Check if extension is in the modern OpenGL extensions string list
window->context.GetIntegerv(GL_NUM_EXTENSIONS, &count);
for (i = 0; i < count; i++)
{
const char* en = (const char*)
window->context.GetStringi(GL_EXTENSIONS, i);
if (!en)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Extension string retrieval is broken");
return GLFW_FALSE;
}
if (strcmp(en, extension) == 0)
return GLFW_TRUE;
}
}
else
{
// Check if extension is in the old style OpenGL extensions string
const char* extensions = (const char*)
window->context.GetString(GL_EXTENSIONS);
if (!extensions)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Extension string retrieval is broken");
return GLFW_FALSE;
}
if (_glfwStringInExtensionString(extension, extensions))
return GLFW_TRUE;
}
// Check if extension is in the platform-specific string
return window->context.extensionSupported(extension);
}
GLFWAPI GLFWglproc glfwGetProcAddress(const char* procname)
{
_GLFWwindow* window;
assert(procname != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
window = _glfwPlatformGetTls(&_glfw.contextSlot);
if (!window)
{
_glfwInputError(GLFW_NO_CURRENT_CONTEXT,
"Cannot query entry point without a current OpenGL or OpenGL ES context");
return NULL;
}
return window->context.getProcAddress(procname);
}
+118
View File
@@ -0,0 +1,118 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2012 The glfw3-go Authors
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
//go:build darwin || freebsd || linux || netbsd || openbsd
package glfw
//#include <stdlib.h>
//#define GLFW_INCLUDE_NONE
//#include "glfw3_unix.h"
import "C"
import (
"unsafe"
)
// MakeContextCurrent makes the context of the window current.
// Originally GLFW 3 passes a null pointer to detach the context.
// But since we're using receivers, DetachCurrentContext should
// be used instead.
func (w *Window) MakeContextCurrent() error {
C.glfwMakeContextCurrent(w.data)
if err := fetchErrorIgnoringPlatformError(); err != nil {
return err
}
return nil
}
// DetachCurrentContext detaches the current context.
func DetachCurrentContext() error {
C.glfwMakeContextCurrent(nil)
if err := fetchErrorIgnoringPlatformError(); err != nil {
return err
}
return nil
}
// GetCurrentContext returns the window whose context is current.
func GetCurrentContext() (*Window, error) {
w := C.glfwGetCurrentContext()
if err := fetchErrorIgnoringPlatformError(); err != nil {
return nil, err
}
if w == nil {
return nil, nil
}
return windows.get(w), nil
}
// SwapBuffers swaps the front and back buffers of the window. If the
// swap interval is greater than zero, the GPU driver waits the specified number
// of screen updates before swapping the buffers.
func (w *Window) SwapBuffers() error {
C.glfwSwapBuffers(w.data)
if err := fetchErrorIgnoringPlatformError(); err != nil {
return err
}
return nil
}
// SwapInterval sets the swap interval for the current context, i.e. the number
// of screen updates to wait before swapping the buffers of a window and
// returning from SwapBuffers. This is sometimes called
// 'vertical synchronization', 'vertical retrace synchronization' or 'vsync'.
//
// Contexts that support either of the WGL_EXT_swap_control_tear and
// GLX_EXT_swap_control_tear extensions also accept negative swap intervals,
// which allow the driver to swap even if a frame arrives a little bit late.
// You can check for the presence of these extensions using
// ExtensionSupported. For more information about swap tearing,
// see the extension specifications.
//
// Some GPU drivers do not honor the requested swap interval, either because of
// user settings that override the request or due to bugs in the driver.
func SwapInterval(interval int) error {
C.glfwSwapInterval(C.int(interval))
if err := fetchErrorIgnoringPlatformError(); err != nil {
return err
}
return nil
}
// ExtensionSupported reports whether the specified OpenGL or context creation
// API extension is supported by the current context. For example, on Windows
// both the OpenGL and WGL extension strings are checked.
//
// As this functions searches one or more extension strings on each call, it is
// recommended that you cache its results if it's going to be used frequently.
// The extension strings will not change during the lifetime of a context, so
// there is no danger in doing this.
func ExtensionSupported(extension string) (bool, error) {
e := C.CString(extension)
defer C.free(unsafe.Pointer(e))
ret := C.glfwExtensionSupported(e) != 0
if err := fetchErrorIgnoringPlatformError(); err != nil {
return false, err
}
return ret, nil
}
// GetProcAddress returns the address of the specified OpenGL or OpenGL ES core
// or extension function, if it is supported by the current context.
//
// A context must be current on the calling thread. Calling this function
// without a current context will cause a GLFW_NO_CURRENT_CONTEXT error.
//
// This function is used to provide GL proc resolving capabilities to an
// external C library.
func GetProcAddress(procname string) (unsafe.Pointer, error) {
p := C.CString(procname)
defer C.free(unsafe.Pointer(p))
ret := unsafe.Pointer(C.glfwGetProcAddress(p))
if err := fetchErrorIgnoringPlatformError(); err != nil {
return nil, err
}
return ret, nil
}
@@ -0,0 +1,610 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard
// SPDX-FileCopyrightText: 2006-2019 Camilla Löwy <elmindreda@glfw.org>
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
package glfw
import (
"fmt"
"math"
"regexp"
"strconv"
"strings"
"unsafe"
"github.com/ebitengine/purego"
)
func checkValidContextConfig(ctxconfig *ctxconfig) error {
if ctxconfig.share != nil {
if ctxconfig.client == NoAPI || ctxconfig.share.context.client == NoAPI {
return NoWindowContext
}
}
if ctxconfig.source != NativeContextAPI &&
ctxconfig.source != EGLContextAPI &&
ctxconfig.source != OSMesaContextAPI {
return fmt.Errorf("glfw: invalid context creation API 0x%08X: %w", ctxconfig.source, InvalidEnum)
}
if ctxconfig.client != NoAPI &&
ctxconfig.client != OpenGLAPI &&
ctxconfig.client != OpenGLESAPI {
return fmt.Errorf("glfw: invalid client API 0x%08X: %w", ctxconfig.client, InvalidEnum)
}
if ctxconfig.client == OpenGLAPI {
if (ctxconfig.major < 1 || ctxconfig.minor < 0) ||
(ctxconfig.major == 1 && ctxconfig.minor > 5) ||
(ctxconfig.major == 2 && ctxconfig.minor > 1) ||
(ctxconfig.major == 3 && ctxconfig.minor > 3) {
// OpenGL 1.0 is the smallest valid version
// OpenGL 1.x series ended with version 1.5
// OpenGL 2.x series ended with version 2.1
// OpenGL 3.x series ended with version 3.3
// For now, let everything else through
return fmt.Errorf("glfw: invalid OpenGL version %d.%d: %w", ctxconfig.major, ctxconfig.minor, InvalidValue)
}
if ctxconfig.profile != 0 {
if ctxconfig.profile != OpenGLCoreProfile && ctxconfig.profile != OpenGLCompatProfile {
return fmt.Errorf("glfw: invalid OpenGL profile 0x%08X: %w", ctxconfig.profile, InvalidEnum)
}
if ctxconfig.major <= 2 || (ctxconfig.major == 3 && ctxconfig.minor < 2) {
// Desktop OpenGL context profiles are only defined for version 3.2
// and above
return fmt.Errorf("glfw: context profiles are only defined for OpenGL version 3.2 and above: %w", InvalidValue)
}
}
if ctxconfig.forward && ctxconfig.major <= 2 {
// Forward-compatible contexts are only defined for OpenGL version 3.0 and above
return fmt.Errorf("glfw: forward-compatibility is only defined for OpenGL version 3.0 and above: %w", InvalidValue)
}
} else if ctxconfig.client == OpenGLESAPI {
if ctxconfig.major < 1 || ctxconfig.minor < 0 ||
(ctxconfig.major == 1 && ctxconfig.minor > 1) ||
(ctxconfig.major == 2 && ctxconfig.minor > 0) {
// OpenGL ES 1.0 is the smallest valid version
// OpenGL ES 1.x series ended with version 1.1
// OpenGL ES 2.x series ended with version 2.0
// For now, let everything else through
return fmt.Errorf("glfw: invalid OpenGL ES version %d.%d: %w", ctxconfig.major, ctxconfig.minor, InvalidValue)
}
}
if ctxconfig.robustness != 0 {
if ctxconfig.robustness != NoResetNotification && ctxconfig.robustness != LoseContextOnReset {
return fmt.Errorf("glfw: invalid context robustness mode 0x%08X: %w", ctxconfig.robustness, InvalidEnum)
}
}
if ctxconfig.release != 0 {
if ctxconfig.release != ReleaseBehaviorNone && ctxconfig.release != ReleaseBehaviorFlush {
return fmt.Errorf("glfw: invalid context release behavior 0x%08X: %w", ctxconfig.release, InvalidEnum)
}
}
return nil
}
func chooseFBConfig(desired *fbconfig, alternatives []*fbconfig) *fbconfig {
leastMissing := math.MaxInt32
leastColorDiff := math.MaxInt32
leastExtraDiff := math.MaxInt32
var closest *fbconfig
for _, current := range alternatives {
if desired.stereo && !current.stereo {
// Stereo is a hard constraint
continue
}
// Count number of missing buffers
missing := 0
if desired.alphaBits > 0 && current.alphaBits == 0 {
missing++
}
if desired.depthBits > 0 && current.depthBits == 0 {
missing++
}
if desired.stencilBits > 0 && current.stencilBits == 0 {
missing++
}
if desired.auxBuffers > 0 &&
current.auxBuffers < desired.auxBuffers {
missing += desired.auxBuffers - current.auxBuffers
}
if desired.samples > 0 && current.samples == 0 {
// Technically, several multisampling buffers could be
// involved, but that's a lower level implementation detail and
// not important to us here, so we count them as one
missing++
}
if desired.transparent != current.transparent {
missing++
}
// These polynomials make many small channel size differences matter
// less than one large channel size difference
// Calculate color channel size difference value
colorDiff := 0
if desired.redBits != DontCare {
colorDiff += (desired.redBits - current.redBits) *
(desired.redBits - current.redBits)
}
if desired.greenBits != DontCare {
colorDiff += (desired.greenBits - current.greenBits) *
(desired.greenBits - current.greenBits)
}
if desired.blueBits != DontCare {
colorDiff += (desired.blueBits - current.blueBits) *
(desired.blueBits - current.blueBits)
}
// Calculate non-color channel size difference value
extraDiff := 0
if desired.alphaBits != DontCare {
extraDiff += (desired.alphaBits - current.alphaBits) *
(desired.alphaBits - current.alphaBits)
}
if desired.depthBits != DontCare {
extraDiff += (desired.depthBits - current.depthBits) *
(desired.depthBits - current.depthBits)
}
if desired.stencilBits != DontCare {
extraDiff += (desired.stencilBits - current.stencilBits) *
(desired.stencilBits - current.stencilBits)
}
if desired.accumRedBits != DontCare {
extraDiff += (desired.accumRedBits - current.accumRedBits) *
(desired.accumRedBits - current.accumRedBits)
}
if desired.accumGreenBits != DontCare {
extraDiff += (desired.accumGreenBits - current.accumGreenBits) *
(desired.accumGreenBits - current.accumGreenBits)
}
if desired.accumBlueBits != DontCare {
extraDiff += (desired.accumBlueBits - current.accumBlueBits) *
(desired.accumBlueBits - current.accumBlueBits)
}
if desired.accumAlphaBits != DontCare {
extraDiff += (desired.accumAlphaBits - current.accumAlphaBits) *
(desired.accumAlphaBits - current.accumAlphaBits)
}
if desired.samples != DontCare {
extraDiff += (desired.samples - current.samples) *
(desired.samples - current.samples)
}
if desired.sRGB && !current.sRGB {
extraDiff++
}
// Figure out if the current one is better than the best one found so far
// Least number of missing buffers is the most important heuristic,
// then color buffer size match and lastly size match for other buffers
if missing < leastMissing {
closest = current
} else if missing == leastMissing {
if (colorDiff < leastColorDiff) || (colorDiff == leastColorDiff && extraDiff < leastExtraDiff) {
closest = current
}
}
if current == closest {
leastMissing = missing
leastColorDiff = colorDiff
leastExtraDiff = extraDiff
}
}
return closest
}
func (w *Window) refreshContextAttribs(ctxconfig *ctxconfig) (ferr error) {
const (
GL_COLOR_BUFFER_BIT = 0x00004000
GL_CONTEXT_COMPATIBILITY_PROFILE_BIT = 0x00000002
GL_CONTEXT_CORE_PROFILE_BIT = 0x00000001
GL_CONTEXT_FLAG_DEBUG_BIT = 0x00000002
GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT = 0x00000001
GL_CONTEXT_FLAG_NO_ERROR_BIT_KHR = 0x00000008
GL_CONTEXT_FLAGS = 0x821E
GL_CONTEXT_PROFILE_MASK = 0x9126
GL_CONTEXT_RELEASE_BEHAVIOR = 0x82FB
GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH = 0x82FC
GL_LOSE_CONTEXT_ON_RESET_ARB = 0x8252
GL_NO_RESET_NOTIFICATION_ARB = 0x8261
GL_NONE = 0
GL_RESET_NOTIFICATION_STRATEGY_ARB = 0x8256
GL_VERSION = 0x1F02
)
w.context.source = ctxconfig.source
w.context.client = OpenGLAPI
p, err := _glfw.contextSlot.get()
if err != nil {
return err
}
previous := (*Window)(unsafe.Pointer(p))
defer func() {
err := previous.MakeContextCurrent()
if ferr == nil {
ferr = err
}
}()
if err := w.MakeContextCurrent(); err != nil {
return err
}
getIntegerv := w.context.getProcAddress("glGetIntegerv")
getString := w.context.getProcAddress("glGetString")
if getIntegerv == 0 || getString == 0 {
return fmt.Errorf("glfw: entry point retrieval is broken: %w", PlatformError)
}
r, _, _ := purego.SyscallN(getString, GL_VERSION)
version := bytePtrToString((*byte)(unsafe.Pointer(r)))
if version == "" {
if ctxconfig.client == OpenGLAPI {
return fmt.Errorf("glfw: OpenGL version string retrieval is broken: %w", PlatformError)
} else {
return fmt.Errorf("glfw: OpenGL ES version string retrieval is broken: %w", PlatformError)
}
}
for _, prefix := range []string{
"OpenGL ES-CM ",
"OpenGL ES-CL ",
"OpenGL ES "} {
if strings.HasPrefix(version, prefix) {
version = version[len(prefix):]
w.context.client = OpenGLESAPI
break
}
}
m := regexp.MustCompile(`^(\d+)(\.(\d+)(\.(\d+))?)?`).FindStringSubmatch(version)
if m == nil {
if w.context.client == OpenGLAPI {
return fmt.Errorf("glfw: no version found in OpenGL version string: %w", PlatformError)
} else {
return fmt.Errorf("glfw: no version found in OpenGL ES version string: %w", PlatformError)
}
}
w.context.major, _ = strconv.Atoi(m[1])
w.context.minor, _ = strconv.Atoi(m[3])
w.context.revision, _ = strconv.Atoi(m[5])
if w.context.major < ctxconfig.major || (w.context.major == ctxconfig.major && w.context.minor < ctxconfig.minor) {
// The desired OpenGL version is greater than the actual version
// This only happens if the machine lacks {GLX|WGL}_ARB_create_context
// /and/ the user has requested an OpenGL version greater than 1.0
// For API consistency, we emulate the behavior of the
// {GLX|WGL}_ARB_create_context extension and fail here
if w.context.client == OpenGLAPI {
return fmt.Errorf("glfw: requested OpenGL version %d.%d, got version %d.%d: %w", ctxconfig.major, ctxconfig.minor, w.context.major, w.context.minor, VersionUnavailable)
} else {
return fmt.Errorf("glfw: requested OpenGL ES version %d.%d, got version %d.%d: %w", ctxconfig.major, ctxconfig.minor, w.context.major, w.context.minor, VersionUnavailable)
}
}
if w.context.major >= 3 {
// OpenGL 3.0+ uses a different function for extension string retrieval
// We cache it here instead of in glfwExtensionSupported mostly to alert
// users as early as possible that their build may be broken
glGetStringi := w.context.getProcAddress("glGetStringi")
if glGetStringi == 0 {
return fmt.Errorf("glfw: entry point retrieval is broken: %w", PlatformError)
}
}
if w.context.client == OpenGLAPI {
// Read back context flags (OpenGL 3.0 and above)
if w.context.major >= 3 {
var flags int32
_, _, _ = purego.SyscallN(getIntegerv, GL_CONTEXT_FLAGS, uintptr(unsafe.Pointer(&flags)))
if flags&GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT != 0 {
w.context.forward = true
}
if flags&GL_CONTEXT_FLAG_DEBUG_BIT != 0 {
w.context.debug = true
} else {
ok, err := ExtensionSupported("GL_ARB_debug_output")
if err != nil {
return err
}
if ok && ctxconfig.debug {
// HACK: This is a workaround for older drivers (pre KHR_debug)
// not setting the debug bit in the context flags for
// debug contexts
w.context.debug = true
}
}
if flags&GL_CONTEXT_FLAG_NO_ERROR_BIT_KHR != 0 {
w.context.noerror = true
}
}
// Read back OpenGL context profile (OpenGL 3.2 and above)
if w.context.major >= 4 || (w.context.major == 3 && w.context.minor >= 2) {
var mask int32
_, _, _ = purego.SyscallN(getIntegerv, GL_CONTEXT_PROFILE_MASK, uintptr(unsafe.Pointer(&mask)))
if mask&GL_CONTEXT_COMPATIBILITY_PROFILE_BIT != 0 {
w.context.profile = OpenGLCompatProfile
} else if mask&GL_CONTEXT_CORE_PROFILE_BIT != 0 {
w.context.profile = OpenGLCoreProfile
} else {
ok, err := ExtensionSupported("GL_ARB_compatibility")
if err != nil {
return err
}
if ok {
// HACK: This is a workaround for the compatibility profile bit
// not being set in the context flags if an OpenGL 3.2+
// context was created without having requested a specific
// version
w.context.profile = OpenGLCompatProfile
}
}
}
// Read back robustness strategy
ok, err := ExtensionSupported("GL_ARB_robustness")
if err != nil {
return err
}
if ok {
// NOTE: We avoid using the context flags for detection, as they are
// only present from 3.0 while the extension applies from 1.1
var strategy int32
_, _, _ = purego.SyscallN(getIntegerv, GL_RESET_NOTIFICATION_STRATEGY_ARB, uintptr(unsafe.Pointer(&strategy)))
if strategy == GL_LOSE_CONTEXT_ON_RESET_ARB {
w.context.robustness = LoseContextOnReset
} else if strategy == GL_NO_RESET_NOTIFICATION_ARB {
w.context.robustness = NoResetNotification
}
}
} else {
// Read back robustness strategy
ok, err := ExtensionSupported("GL_EXT_robustness")
if err != nil {
return err
}
if ok {
// NOTE: The values of these constants match those of the OpenGL ARB
// one, so we can reuse them here
var strategy int32
_, _, _ = purego.SyscallN(getIntegerv, GL_RESET_NOTIFICATION_STRATEGY_ARB, uintptr(unsafe.Pointer(&strategy)))
if strategy == GL_LOSE_CONTEXT_ON_RESET_ARB {
w.context.robustness = LoseContextOnReset
} else if strategy == GL_NO_RESET_NOTIFICATION_ARB {
w.context.robustness = NoResetNotification
}
}
}
ok, err := ExtensionSupported("GL_KHR_context_flush_control")
if err != nil {
return err
}
if ok {
var behavior int32
_, _, _ = purego.SyscallN(getIntegerv, GL_CONTEXT_RELEASE_BEHAVIOR, uintptr(unsafe.Pointer(&behavior)))
if behavior == GL_NONE {
w.context.release = ReleaseBehaviorNone
} else if behavior == GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH {
w.context.release = ReleaseBehaviorFlush
}
}
// Clearing the front buffer to black to avoid garbage pixels left over from
// previous uses of our bit of VRAM
glClear := w.context.getProcAddress("glClear")
_, _, _ = purego.SyscallN(glClear, GL_COLOR_BUFFER_BIT)
if w.doublebuffer {
if err := w.context.swapBuffers(w); err != nil {
return err
}
}
return nil
}
func (w *Window) MakeContextCurrent() error {
if !_glfw.initialized {
return NotInitialized
}
ptr, err := _glfw.contextSlot.get()
if err != nil {
return err
}
previous := (*Window)(unsafe.Pointer(ptr))
if w != nil && w.context.client == NoAPI {
return fmt.Errorf("glfw: cannot make current with a window that has no OpenGL or OpenGL ES context: %w", NoWindowContext)
}
if previous != nil {
if w == nil || w.context.source != previous.context.source {
if err := previous.context.makeCurrent(nil); err != nil {
return err
}
}
}
if w != nil {
if err := w.context.makeCurrent(w); err != nil {
return err
}
}
return nil
}
func GetCurrentContext() (*Window, error) {
if !_glfw.initialized {
return nil, NotInitialized
}
ptr, err := _glfw.contextSlot.get()
if err != nil {
return nil, err
}
return (*Window)(unsafe.Pointer(ptr)), nil
}
func (w *Window) SwapBuffers() error {
if !_glfw.initialized {
return NotInitialized
}
if w.context.client == NoAPI {
return fmt.Errorf("glfw: cannot swap buffers of a window that has no OpenGL or OpenGL ES context: %w", NoWindowContext)
}
if err := w.context.swapBuffers(w); err != nil {
return err
}
return nil
}
func SwapInterval(interval int) error {
if !_glfw.initialized {
return NotInitialized
}
ptr, err := _glfw.contextSlot.get()
if err != nil {
return err
}
window := (*Window)(unsafe.Pointer(ptr))
if window == nil {
return fmt.Errorf("glfw: cannot set swap interval without a current OpenGL or OpenGL ES context %w", NoCurrentContext)
}
if err := window.context.swapInterval(interval); err != nil {
return err
}
return nil
}
func ExtensionSupported(extension string) (bool, error) {
const (
GL_EXTENSIONS = 0x1F03
GL_NUM_EXTENSIONS = 0x821D
)
if !_glfw.initialized {
return false, NotInitialized
}
ptr, err := _glfw.contextSlot.get()
if err != nil {
return false, err
}
window := (*Window)(unsafe.Pointer(ptr))
if window == nil {
return false, fmt.Errorf("glfw: cannot query extension without a current OpenGL or OpenGL ES context %w", NoCurrentContext)
}
if window.context.major >= 3 {
// Check if extension is in the modern OpenGL extensions string list
glGetIntegerv := window.context.getProcAddress("glGetIntegerv")
var count int32
_, _, _ = purego.SyscallN(glGetIntegerv, GL_NUM_EXTENSIONS, uintptr(unsafe.Pointer(&count)))
glGetStringi := window.context.getProcAddress("glGetStringi")
for i := 0; i < int(count); i++ {
r, _, _ := purego.SyscallN(glGetStringi, GL_EXTENSIONS, uintptr(i))
if r == 0 {
return false, fmt.Errorf("glfw: extension string retrieval is broken: %w", PlatformError)
}
en := bytePtrToString((*byte)(unsafe.Pointer(r)))
if en == extension {
return true, nil
}
}
} else {
// Check if extension is in the old style OpenGL extensions string
glGetString := window.context.getProcAddress("glGetString")
r, _, _ := purego.SyscallN(glGetString, GL_EXTENSIONS)
if r == 0 {
return false, fmt.Errorf("glfw: extension string retrieval is broken: %w", PlatformError)
}
extensions := bytePtrToString((*byte)(unsafe.Pointer(r)))
for _, str := range strings.Split(extensions, " ") {
if str == extension {
return true, nil
}
}
}
// Check if extension is in the platform-specific string
return window.context.extensionSupported(extension), nil
}
// bytePtrToString takes a pointer to a sequence of text and returns the corresponding string.
// If the pointer is nil, it returns the empty string. It assumes that the text sequence is
// terminated at a zero byte; if the zero byte is not present, the program may crash.
// It is copied from golang.org/x/sys/windows/syscall.go for use on macOS, Linux and Windows
func bytePtrToString(p *byte) string {
if p == nil {
return ""
}
if *p == 0 {
return ""
}
// Find NUL terminator.
n := 0
for ptr := unsafe.Pointer(p); *(*byte)(ptr) != 0; n++ {
ptr = unsafe.Add(ptr, 1)
}
// unsafe.String(p, n) is available as of Go 1.20.
return string(unsafe.Slice(p, n))
}
@@ -0,0 +1,771 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard
// SPDX-FileCopyrightText: 2006-2019 Camilla Löwy <elmindreda@glfw.org>
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
//go:build darwin || freebsd || linux || netbsd || openbsd
#include "internal_unix.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
// Return a description of the specified EGL error
//
static const char* getEGLErrorString(EGLint error)
{
switch (error)
{
case EGL_SUCCESS:
return "Success";
case EGL_NOT_INITIALIZED:
return "EGL is not or could not be initialized";
case EGL_BAD_ACCESS:
return "EGL cannot access a requested resource";
case EGL_BAD_ALLOC:
return "EGL failed to allocate resources for the requested operation";
case EGL_BAD_ATTRIBUTE:
return "An unrecognized attribute or attribute value was passed in the attribute list";
case EGL_BAD_CONTEXT:
return "An EGLContext argument does not name a valid EGL rendering context";
case EGL_BAD_CONFIG:
return "An EGLConfig argument does not name a valid EGL frame buffer configuration";
case EGL_BAD_CURRENT_SURFACE:
return "The current surface of the calling thread is a window, pixel buffer or pixmap that is no longer valid";
case EGL_BAD_DISPLAY:
return "An EGLDisplay argument does not name a valid EGL display connection";
case EGL_BAD_SURFACE:
return "An EGLSurface argument does not name a valid surface configured for GL rendering";
case EGL_BAD_MATCH:
return "Arguments are inconsistent";
case EGL_BAD_PARAMETER:
return "One or more argument values are invalid";
case EGL_BAD_NATIVE_PIXMAP:
return "A NativePixmapType argument does not refer to a valid native pixmap";
case EGL_BAD_NATIVE_WINDOW:
return "A NativeWindowType argument does not refer to a valid native window";
case EGL_CONTEXT_LOST:
return "The application must destroy all contexts and reinitialise";
default:
return "ERROR: UNKNOWN EGL ERROR";
}
}
// Returns the specified attribute of the specified EGLConfig
//
static int getEGLConfigAttrib(EGLConfig config, int attrib)
{
int value;
eglGetConfigAttrib(_glfw.egl.display, config, attrib, &value);
return value;
}
// Return the EGLConfig most closely matching the specified hints
//
static GLFWbool chooseEGLConfig(const _GLFWctxconfig* ctxconfig,
const _GLFWfbconfig* desired,
EGLConfig* result)
{
EGLConfig* nativeConfigs;
_GLFWfbconfig* usableConfigs;
const _GLFWfbconfig* closest;
int i, nativeCount, usableCount;
eglGetConfigs(_glfw.egl.display, NULL, 0, &nativeCount);
if (!nativeCount)
{
_glfwInputError(GLFW_API_UNAVAILABLE, "EGL: No EGLConfigs returned");
return GLFW_FALSE;
}
nativeConfigs = calloc(nativeCount, sizeof(EGLConfig));
eglGetConfigs(_glfw.egl.display, nativeConfigs, nativeCount, &nativeCount);
usableConfigs = calloc(nativeCount, sizeof(_GLFWfbconfig));
usableCount = 0;
for (i = 0; i < nativeCount; i++)
{
const EGLConfig n = nativeConfigs[i];
_GLFWfbconfig* u = usableConfigs + usableCount;
// Only consider RGB(A) EGLConfigs
if (getEGLConfigAttrib(n, EGL_COLOR_BUFFER_TYPE) != EGL_RGB_BUFFER)
continue;
// Only consider window EGLConfigs
if (!(getEGLConfigAttrib(n, EGL_SURFACE_TYPE) & EGL_WINDOW_BIT))
continue;
#if defined(_GLFW_X11)
{
XVisualInfo vi = {0};
// Only consider EGLConfigs with associated Visuals
vi.visualid = getEGLConfigAttrib(n, EGL_NATIVE_VISUAL_ID);
if (!vi.visualid)
continue;
if (desired->transparent)
{
int count;
XVisualInfo* vis =
XGetVisualInfo(_glfw.x11.display, VisualIDMask, &vi, &count);
if (vis)
{
u->transparent = _glfwIsVisualTransparentX11(vis[0].visual);
XFree(vis);
}
}
}
#endif // _GLFW_X11
if (ctxconfig->client == GLFW_OPENGL_ES_API)
{
if (ctxconfig->major == 1)
{
if (!(getEGLConfigAttrib(n, EGL_RENDERABLE_TYPE) & EGL_OPENGL_ES_BIT))
continue;
}
else
{
if (!(getEGLConfigAttrib(n, EGL_RENDERABLE_TYPE) & EGL_OPENGL_ES2_BIT))
continue;
}
}
else if (ctxconfig->client == GLFW_OPENGL_API)
{
if (!(getEGLConfigAttrib(n, EGL_RENDERABLE_TYPE) & EGL_OPENGL_BIT))
continue;
}
u->redBits = getEGLConfigAttrib(n, EGL_RED_SIZE);
u->greenBits = getEGLConfigAttrib(n, EGL_GREEN_SIZE);
u->blueBits = getEGLConfigAttrib(n, EGL_BLUE_SIZE);
u->alphaBits = getEGLConfigAttrib(n, EGL_ALPHA_SIZE);
u->depthBits = getEGLConfigAttrib(n, EGL_DEPTH_SIZE);
u->stencilBits = getEGLConfigAttrib(n, EGL_STENCIL_SIZE);
u->samples = getEGLConfigAttrib(n, EGL_SAMPLES);
u->doublebuffer = desired->doublebuffer;
u->handle = (uintptr_t) n;
usableCount++;
}
closest = _glfwChooseFBConfig(desired, usableConfigs, usableCount);
if (closest)
*result = (EGLConfig) closest->handle;
free(nativeConfigs);
free(usableConfigs);
return closest != NULL;
}
static void makeContextCurrentEGL(_GLFWwindow* window)
{
if (window)
{
if (!eglMakeCurrent(_glfw.egl.display,
window->context.egl.surface,
window->context.egl.surface,
window->context.egl.handle))
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"EGL: Failed to make context current: %s",
getEGLErrorString(eglGetError()));
return;
}
}
else
{
if (!eglMakeCurrent(_glfw.egl.display,
EGL_NO_SURFACE,
EGL_NO_SURFACE,
EGL_NO_CONTEXT))
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"EGL: Failed to clear current context: %s",
getEGLErrorString(eglGetError()));
return;
}
}
_glfwPlatformSetTls(&_glfw.contextSlot, window);
}
static void swapBuffersEGL(_GLFWwindow* window)
{
if (window != _glfwPlatformGetTls(&_glfw.contextSlot))
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"EGL: The context must be current on the calling thread when swapping buffers");
return;
}
eglSwapBuffers(_glfw.egl.display, window->context.egl.surface);
}
static void swapIntervalEGL(int interval)
{
eglSwapInterval(_glfw.egl.display, interval);
}
static int extensionSupportedEGL(const char* extension)
{
const char* extensions = eglQueryString(_glfw.egl.display, EGL_EXTENSIONS);
if (extensions)
{
if (_glfwStringInExtensionString(extension, extensions))
return GLFW_TRUE;
}
return GLFW_FALSE;
}
static GLFWglproc getProcAddressEGL(const char* procname)
{
_GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot);
if (window->context.egl.client)
{
GLFWglproc proc = (GLFWglproc) _glfw_dlsym(window->context.egl.client,
procname);
if (proc)
return proc;
}
return eglGetProcAddress(procname);
}
static void destroyContextEGL(_GLFWwindow* window)
{
#if defined(_GLFW_X11)
// NOTE: Do not unload libGL.so.1 while the X11 display is still open,
// as it will make XCloseDisplay segfault
if (window->context.client != GLFW_OPENGL_API)
#endif // _GLFW_X11
{
if (window->context.egl.client)
{
_glfw_dlclose(window->context.egl.client);
window->context.egl.client = NULL;
}
}
if (window->context.egl.surface)
{
eglDestroySurface(_glfw.egl.display, window->context.egl.surface);
window->context.egl.surface = EGL_NO_SURFACE;
}
if (window->context.egl.handle)
{
eglDestroyContext(_glfw.egl.display, window->context.egl.handle);
window->context.egl.handle = EGL_NO_CONTEXT;
}
}
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
// Initialize EGL
//
GLFWbool _glfwInitEGL(void)
{
int i;
const char* sonames[] =
{
#if defined(_GLFW_EGL_LIBRARY)
_GLFW_EGL_LIBRARY,
#elif defined(_GLFW_COCOA)
"libEGL.dylib",
#elif defined(__CYGWIN__)
"libEGL-1.so",
#elif defined(__OpenBSD__) || defined(__NetBSD__)
"libEGL.so",
#else
"libEGL.so.1",
#endif
NULL
};
if (_glfw.egl.handle)
return GLFW_TRUE;
for (i = 0; sonames[i]; i++)
{
_glfw.egl.handle = _glfw_dlopen(sonames[i]);
if (_glfw.egl.handle)
break;
}
if (!_glfw.egl.handle)
{
_glfwInputError(GLFW_API_UNAVAILABLE, "EGL: Library not found");
return GLFW_FALSE;
}
_glfw.egl.prefix = (strncmp(sonames[i], "lib", 3) == 0);
_glfw.egl.GetConfigAttrib = (PFN_eglGetConfigAttrib)
_glfw_dlsym(_glfw.egl.handle, "eglGetConfigAttrib");
_glfw.egl.GetConfigs = (PFN_eglGetConfigs)
_glfw_dlsym(_glfw.egl.handle, "eglGetConfigs");
_glfw.egl.GetDisplay = (PFN_eglGetDisplay)
_glfw_dlsym(_glfw.egl.handle, "eglGetDisplay");
_glfw.egl.GetError = (PFN_eglGetError)
_glfw_dlsym(_glfw.egl.handle, "eglGetError");
_glfw.egl.Initialize = (PFN_eglInitialize)
_glfw_dlsym(_glfw.egl.handle, "eglInitialize");
_glfw.egl.Terminate = (PFN_eglTerminate)
_glfw_dlsym(_glfw.egl.handle, "eglTerminate");
_glfw.egl.BindAPI = (PFN_eglBindAPI)
_glfw_dlsym(_glfw.egl.handle, "eglBindAPI");
_glfw.egl.CreateContext = (PFN_eglCreateContext)
_glfw_dlsym(_glfw.egl.handle, "eglCreateContext");
_glfw.egl.DestroySurface = (PFN_eglDestroySurface)
_glfw_dlsym(_glfw.egl.handle, "eglDestroySurface");
_glfw.egl.DestroyContext = (PFN_eglDestroyContext)
_glfw_dlsym(_glfw.egl.handle, "eglDestroyContext");
_glfw.egl.CreateWindowSurface = (PFN_eglCreateWindowSurface)
_glfw_dlsym(_glfw.egl.handle, "eglCreateWindowSurface");
_glfw.egl.MakeCurrent = (PFN_eglMakeCurrent)
_glfw_dlsym(_glfw.egl.handle, "eglMakeCurrent");
_glfw.egl.SwapBuffers = (PFN_eglSwapBuffers)
_glfw_dlsym(_glfw.egl.handle, "eglSwapBuffers");
_glfw.egl.SwapInterval = (PFN_eglSwapInterval)
_glfw_dlsym(_glfw.egl.handle, "eglSwapInterval");
_glfw.egl.QueryString = (PFN_eglQueryString)
_glfw_dlsym(_glfw.egl.handle, "eglQueryString");
_glfw.egl.GetProcAddress = (PFN_eglGetProcAddress)
_glfw_dlsym(_glfw.egl.handle, "eglGetProcAddress");
if (!_glfw.egl.GetConfigAttrib ||
!_glfw.egl.GetConfigs ||
!_glfw.egl.GetDisplay ||
!_glfw.egl.GetError ||
!_glfw.egl.Initialize ||
!_glfw.egl.Terminate ||
!_glfw.egl.BindAPI ||
!_glfw.egl.CreateContext ||
!_glfw.egl.DestroySurface ||
!_glfw.egl.DestroyContext ||
!_glfw.egl.CreateWindowSurface ||
!_glfw.egl.MakeCurrent ||
!_glfw.egl.SwapBuffers ||
!_glfw.egl.SwapInterval ||
!_glfw.egl.QueryString ||
!_glfw.egl.GetProcAddress)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"EGL: Failed to load required entry points");
_glfwTerminateEGL();
return GLFW_FALSE;
}
_glfw.egl.display = eglGetDisplay(_GLFW_EGL_NATIVE_DISPLAY);
if (_glfw.egl.display == EGL_NO_DISPLAY)
{
_glfwInputError(GLFW_API_UNAVAILABLE,
"EGL: Failed to get EGL display: %s",
getEGLErrorString(eglGetError()));
_glfwTerminateEGL();
return GLFW_FALSE;
}
if (!eglInitialize(_glfw.egl.display, &_glfw.egl.major, &_glfw.egl.minor))
{
_glfwInputError(GLFW_API_UNAVAILABLE,
"EGL: Failed to initialize EGL: %s",
getEGLErrorString(eglGetError()));
_glfwTerminateEGL();
return GLFW_FALSE;
}
_glfw.egl.KHR_create_context =
extensionSupportedEGL("EGL_KHR_create_context");
_glfw.egl.KHR_create_context_no_error =
extensionSupportedEGL("EGL_KHR_create_context_no_error");
_glfw.egl.KHR_gl_colorspace =
extensionSupportedEGL("EGL_KHR_gl_colorspace");
_glfw.egl.KHR_get_all_proc_addresses =
extensionSupportedEGL("EGL_KHR_get_all_proc_addresses");
_glfw.egl.KHR_context_flush_control =
extensionSupportedEGL("EGL_KHR_context_flush_control");
_glfw.egl.EXT_present_opaque =
extensionSupportedEGL("EGL_EXT_present_opaque");
return GLFW_TRUE;
}
// Terminate EGL
//
void _glfwTerminateEGL(void)
{
if (_glfw.egl.display)
{
eglTerminate(_glfw.egl.display);
_glfw.egl.display = EGL_NO_DISPLAY;
}
if (_glfw.egl.handle)
{
_glfw_dlclose(_glfw.egl.handle);
_glfw.egl.handle = NULL;
}
}
#define setAttrib(a, v) \
{ \
assert(((size_t) index + 1) < sizeof(attribs) / sizeof(attribs[0])); \
attribs[index++] = a; \
attribs[index++] = v; \
}
// Create the OpenGL or OpenGL ES context
//
GLFWbool _glfwCreateContextEGL(_GLFWwindow* window,
const _GLFWctxconfig* ctxconfig,
const _GLFWfbconfig* fbconfig)
{
EGLint attribs[40];
EGLConfig config;
EGLContext share = NULL;
int index = 0;
if (!_glfw.egl.display)
{
_glfwInputError(GLFW_API_UNAVAILABLE, "EGL: API not available");
return GLFW_FALSE;
}
if (ctxconfig->share)
share = ctxconfig->share->context.egl.handle;
if (!chooseEGLConfig(ctxconfig, fbconfig, &config))
{
_glfwInputError(GLFW_FORMAT_UNAVAILABLE,
"EGL: Failed to find a suitable EGLConfig");
return GLFW_FALSE;
}
if (ctxconfig->client == GLFW_OPENGL_ES_API)
{
if (!eglBindAPI(EGL_OPENGL_ES_API))
{
_glfwInputError(GLFW_API_UNAVAILABLE,
"EGL: Failed to bind OpenGL ES: %s",
getEGLErrorString(eglGetError()));
return GLFW_FALSE;
}
}
else
{
if (!eglBindAPI(EGL_OPENGL_API))
{
_glfwInputError(GLFW_API_UNAVAILABLE,
"EGL: Failed to bind OpenGL: %s",
getEGLErrorString(eglGetError()));
return GLFW_FALSE;
}
}
if (_glfw.egl.KHR_create_context)
{
int mask = 0, flags = 0;
if (ctxconfig->client == GLFW_OPENGL_API)
{
if (ctxconfig->forward)
flags |= EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR;
if (ctxconfig->profile == GLFW_OPENGL_CORE_PROFILE)
mask |= EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR;
else if (ctxconfig->profile == GLFW_OPENGL_COMPAT_PROFILE)
mask |= EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR;
}
if (ctxconfig->debug)
flags |= EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR;
if (ctxconfig->robustness)
{
if (ctxconfig->robustness == GLFW_NO_RESET_NOTIFICATION)
{
setAttrib(EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR,
EGL_NO_RESET_NOTIFICATION_KHR);
}
else if (ctxconfig->robustness == GLFW_LOSE_CONTEXT_ON_RESET)
{
setAttrib(EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR,
EGL_LOSE_CONTEXT_ON_RESET_KHR);
}
flags |= EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR;
}
if (ctxconfig->noerror)
{
if (_glfw.egl.KHR_create_context_no_error)
setAttrib(EGL_CONTEXT_OPENGL_NO_ERROR_KHR, GLFW_TRUE);
}
if (ctxconfig->major != 1 || ctxconfig->minor != 0)
{
setAttrib(EGL_CONTEXT_MAJOR_VERSION_KHR, ctxconfig->major);
setAttrib(EGL_CONTEXT_MINOR_VERSION_KHR, ctxconfig->minor);
}
if (mask)
setAttrib(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, mask);
if (flags)
setAttrib(EGL_CONTEXT_FLAGS_KHR, flags);
}
else
{
if (ctxconfig->client == GLFW_OPENGL_ES_API)
setAttrib(EGL_CONTEXT_CLIENT_VERSION, ctxconfig->major);
}
if (_glfw.egl.KHR_context_flush_control)
{
if (ctxconfig->release == GLFW_RELEASE_BEHAVIOR_NONE)
{
setAttrib(EGL_CONTEXT_RELEASE_BEHAVIOR_KHR,
EGL_CONTEXT_RELEASE_BEHAVIOR_NONE_KHR);
}
else if (ctxconfig->release == GLFW_RELEASE_BEHAVIOR_FLUSH)
{
setAttrib(EGL_CONTEXT_RELEASE_BEHAVIOR_KHR,
EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR);
}
}
setAttrib(EGL_NONE, EGL_NONE);
window->context.egl.handle = eglCreateContext(_glfw.egl.display,
config, share, attribs);
if (window->context.egl.handle == EGL_NO_CONTEXT)
{
_glfwInputError(GLFW_VERSION_UNAVAILABLE,
"EGL: Failed to create context: %s",
getEGLErrorString(eglGetError()));
return GLFW_FALSE;
}
// Set up attributes for surface creation
index = 0;
if (fbconfig->sRGB)
{
if (_glfw.egl.KHR_gl_colorspace)
setAttrib(EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR);
}
if (!fbconfig->doublebuffer)
setAttrib(EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER);
if (_glfw.egl.EXT_present_opaque)
setAttrib(EGL_PRESENT_OPAQUE_EXT, !fbconfig->transparent);
setAttrib(EGL_NONE, EGL_NONE);
window->context.egl.surface =
eglCreateWindowSurface(_glfw.egl.display,
config,
_GLFW_EGL_NATIVE_WINDOW,
attribs);
if (window->context.egl.surface == EGL_NO_SURFACE)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"EGL: Failed to create window surface: %s",
getEGLErrorString(eglGetError()));
return GLFW_FALSE;
}
window->context.egl.config = config;
// Load the appropriate client library
if (!_glfw.egl.KHR_get_all_proc_addresses)
{
int i;
const char** sonames;
const char* es1sonames[] =
{
#if defined(_GLFW_GLESV1_LIBRARY)
_GLFW_GLESV1_LIBRARY,
#elif defined(_GLFW_COCOA)
"libGLESv1_CM.dylib",
#elif defined(__OpenBSD__) || defined(__NetBSD__)
"libGLESv1_CM.so",
#else
"libGLESv1_CM.so.1",
"libGLES_CM.so.1",
#endif
NULL
};
const char* es2sonames[] =
{
#if defined(_GLFW_GLESV2_LIBRARY)
_GLFW_GLESV2_LIBRARY,
#elif defined(_GLFW_COCOA)
"libGLESv2.dylib",
#elif defined(__CYGWIN__)
"libGLESv2-2.so",
#elif defined(__OpenBSD__) || defined(__NetBSD__)
"libGLESv2.so",
#else
"libGLESv2.so.2",
#endif
NULL
};
const char* glsonames[] =
{
#if defined(_GLFW_OPENGL_LIBRARY)
_GLFW_OPENGL_LIBRARY,
#elif defined(_GLFW_COCOA)
#elif defined(__OpenBSD__) || defined(__NetBSD__)
"libGL.so",
#else
"libGL.so.1",
#endif
NULL
};
if (ctxconfig->client == GLFW_OPENGL_ES_API)
{
if (ctxconfig->major == 1)
sonames = es1sonames;
else
sonames = es2sonames;
}
else
sonames = glsonames;
for (i = 0; sonames[i]; i++)
{
// HACK: Match presence of lib prefix to increase chance of finding
// a matching pair in the jungle that is Win32 EGL/GLES
if (_glfw.egl.prefix != (strncmp(sonames[i], "lib", 3) == 0))
continue;
window->context.egl.client = _glfw_dlopen(sonames[i]);
if (window->context.egl.client)
break;
}
if (!window->context.egl.client)
{
_glfwInputError(GLFW_API_UNAVAILABLE,
"EGL: Failed to load client library");
return GLFW_FALSE;
}
}
window->context.makeCurrent = makeContextCurrentEGL;
window->context.swapBuffers = swapBuffersEGL;
window->context.swapInterval = swapIntervalEGL;
window->context.extensionSupported = extensionSupportedEGL;
window->context.getProcAddress = getProcAddressEGL;
window->context.destroy = destroyContextEGL;
return GLFW_TRUE;
}
#undef setAttrib
// Returns the Visual and depth of the chosen EGLConfig
//
#if defined(_GLFW_X11)
GLFWbool _glfwChooseVisualEGL(const _GLFWwndconfig* wndconfig,
const _GLFWctxconfig* ctxconfig,
const _GLFWfbconfig* fbconfig,
Visual** visual, int* depth)
{
XVisualInfo* result;
XVisualInfo desired;
EGLConfig native;
EGLint visualID = 0, count = 0;
const long vimask = VisualScreenMask | VisualIDMask;
if (!chooseEGLConfig(ctxconfig, fbconfig, &native))
{
_glfwInputError(GLFW_FORMAT_UNAVAILABLE,
"EGL: Failed to find a suitable EGLConfig");
return GLFW_FALSE;
}
eglGetConfigAttrib(_glfw.egl.display, native,
EGL_NATIVE_VISUAL_ID, &visualID);
desired.screen = _glfw.x11.screen;
desired.visualid = visualID;
result = XGetVisualInfo(_glfw.x11.display, vimask, &desired, &count);
if (!result)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"EGL: Failed to retrieve Visual for EGLConfig");
return GLFW_FALSE;
}
*visual = result->visual;
*depth = result->depth;
XFree(result);
return GLFW_TRUE;
}
#endif // _GLFW_X11
//////////////////////////////////////////////////////////////////////////
////// GLFW native API //////
//////////////////////////////////////////////////////////////////////////
GLFWAPI EGLDisplay glfwGetEGLDisplay(void)
{
_GLFW_REQUIRE_INIT_OR_RETURN(EGL_NO_DISPLAY);
return _glfw.egl.display;
}
GLFWAPI EGLContext glfwGetEGLContext(GLFWwindow* handle)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
_GLFW_REQUIRE_INIT_OR_RETURN(EGL_NO_CONTEXT);
if (window->context.source != GLFW_EGL_CONTEXT_API)
{
_glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL);
return EGL_NO_CONTEXT;
}
return window->context.egl.handle;
}
GLFWAPI EGLSurface glfwGetEGLSurface(GLFWwindow* handle)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
_GLFW_REQUIRE_INIT_OR_RETURN(EGL_NO_SURFACE);
if (window->context.source != GLFW_EGL_CONTEXT_API)
{
_glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL);
return EGL_NO_SURFACE;
}
return window->context.egl.surface;
}
@@ -0,0 +1,189 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard
// SPDX-FileCopyrightText: 2006-2017 Camilla Löwy <elmindreda@glfw.org>
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
//go:build darwin || freebsd || linux || netbsd || openbsd
#if defined(_GLFW_USE_EGLPLATFORM_H)
#include <EGL/eglplatform.h>
#elif defined(_GLFW_COCOA)
#define EGLAPIENTRY
typedef void* EGLNativeDisplayType;
typedef id EGLNativeWindowType;
#elif defined(_GLFW_X11)
#define EGLAPIENTRY
typedef Display* EGLNativeDisplayType;
typedef Window EGLNativeWindowType;
#else
#error "No supported EGL platform selected"
#endif
#define EGL_SUCCESS 0x3000
#define EGL_NOT_INITIALIZED 0x3001
#define EGL_BAD_ACCESS 0x3002
#define EGL_BAD_ALLOC 0x3003
#define EGL_BAD_ATTRIBUTE 0x3004
#define EGL_BAD_CONFIG 0x3005
#define EGL_BAD_CONTEXT 0x3006
#define EGL_BAD_CURRENT_SURFACE 0x3007
#define EGL_BAD_DISPLAY 0x3008
#define EGL_BAD_MATCH 0x3009
#define EGL_BAD_NATIVE_PIXMAP 0x300a
#define EGL_BAD_NATIVE_WINDOW 0x300b
#define EGL_BAD_PARAMETER 0x300c
#define EGL_BAD_SURFACE 0x300d
#define EGL_CONTEXT_LOST 0x300e
#define EGL_COLOR_BUFFER_TYPE 0x303f
#define EGL_RGB_BUFFER 0x308e
#define EGL_SURFACE_TYPE 0x3033
#define EGL_WINDOW_BIT 0x0004
#define EGL_RENDERABLE_TYPE 0x3040
#define EGL_OPENGL_ES_BIT 0x0001
#define EGL_OPENGL_ES2_BIT 0x0004
#define EGL_OPENGL_BIT 0x0008
#define EGL_ALPHA_SIZE 0x3021
#define EGL_BLUE_SIZE 0x3022
#define EGL_GREEN_SIZE 0x3023
#define EGL_RED_SIZE 0x3024
#define EGL_DEPTH_SIZE 0x3025
#define EGL_STENCIL_SIZE 0x3026
#define EGL_SAMPLES 0x3031
#define EGL_OPENGL_ES_API 0x30a0
#define EGL_OPENGL_API 0x30a2
#define EGL_NONE 0x3038
#define EGL_RENDER_BUFFER 0x3086
#define EGL_SINGLE_BUFFER 0x3085
#define EGL_EXTENSIONS 0x3055
#define EGL_CONTEXT_CLIENT_VERSION 0x3098
#define EGL_NATIVE_VISUAL_ID 0x302e
#define EGL_NO_SURFACE ((EGLSurface) 0)
#define EGL_NO_DISPLAY ((EGLDisplay) 0)
#define EGL_NO_CONTEXT ((EGLContext) 0)
#define EGL_DEFAULT_DISPLAY ((EGLNativeDisplayType) 0)
#define EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR 0x00000002
#define EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR 0x00000001
#define EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR 0x00000002
#define EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR 0x00000001
#define EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR 0x31bd
#define EGL_NO_RESET_NOTIFICATION_KHR 0x31be
#define EGL_LOSE_CONTEXT_ON_RESET_KHR 0x31bf
#define EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR 0x00000004
#define EGL_CONTEXT_MAJOR_VERSION_KHR 0x3098
#define EGL_CONTEXT_MINOR_VERSION_KHR 0x30fb
#define EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR 0x30fd
#define EGL_CONTEXT_FLAGS_KHR 0x30fc
#define EGL_CONTEXT_OPENGL_NO_ERROR_KHR 0x31b3
#define EGL_GL_COLORSPACE_KHR 0x309d
#define EGL_GL_COLORSPACE_SRGB_KHR 0x3089
#define EGL_CONTEXT_RELEASE_BEHAVIOR_KHR 0x2097
#define EGL_CONTEXT_RELEASE_BEHAVIOR_NONE_KHR 0
#define EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR 0x2098
#define EGL_PRESENT_OPAQUE_EXT 0x31df
typedef int EGLint;
typedef unsigned int EGLBoolean;
typedef unsigned int EGLenum;
typedef void* EGLConfig;
typedef void* EGLContext;
typedef void* EGLDisplay;
typedef void* EGLSurface;
// EGL function pointer typedefs
typedef EGLBoolean (EGLAPIENTRY * PFN_eglGetConfigAttrib)(EGLDisplay,EGLConfig,EGLint,EGLint*);
typedef EGLBoolean (EGLAPIENTRY * PFN_eglGetConfigs)(EGLDisplay,EGLConfig*,EGLint,EGLint*);
typedef EGLDisplay (EGLAPIENTRY * PFN_eglGetDisplay)(EGLNativeDisplayType);
typedef EGLint (EGLAPIENTRY * PFN_eglGetError)(void);
typedef EGLBoolean (EGLAPIENTRY * PFN_eglInitialize)(EGLDisplay,EGLint*,EGLint*);
typedef EGLBoolean (EGLAPIENTRY * PFN_eglTerminate)(EGLDisplay);
typedef EGLBoolean (EGLAPIENTRY * PFN_eglBindAPI)(EGLenum);
typedef EGLContext (EGLAPIENTRY * PFN_eglCreateContext)(EGLDisplay,EGLConfig,EGLContext,const EGLint*);
typedef EGLBoolean (EGLAPIENTRY * PFN_eglDestroySurface)(EGLDisplay,EGLSurface);
typedef EGLBoolean (EGLAPIENTRY * PFN_eglDestroyContext)(EGLDisplay,EGLContext);
typedef EGLSurface (EGLAPIENTRY * PFN_eglCreateWindowSurface)(EGLDisplay,EGLConfig,EGLNativeWindowType,const EGLint*);
typedef EGLBoolean (EGLAPIENTRY * PFN_eglMakeCurrent)(EGLDisplay,EGLSurface,EGLSurface,EGLContext);
typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapBuffers)(EGLDisplay,EGLSurface);
typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapInterval)(EGLDisplay,EGLint);
typedef const char* (EGLAPIENTRY * PFN_eglQueryString)(EGLDisplay,EGLint);
typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*);
#define eglGetConfigAttrib _glfw.egl.GetConfigAttrib
#define eglGetConfigs _glfw.egl.GetConfigs
#define eglGetDisplay _glfw.egl.GetDisplay
#define eglGetError _glfw.egl.GetError
#define eglInitialize _glfw.egl.Initialize
#define eglTerminate _glfw.egl.Terminate
#define eglBindAPI _glfw.egl.BindAPI
#define eglCreateContext _glfw.egl.CreateContext
#define eglDestroySurface _glfw.egl.DestroySurface
#define eglDestroyContext _glfw.egl.DestroyContext
#define eglCreateWindowSurface _glfw.egl.CreateWindowSurface
#define eglMakeCurrent _glfw.egl.MakeCurrent
#define eglSwapBuffers _glfw.egl.SwapBuffers
#define eglSwapInterval _glfw.egl.SwapInterval
#define eglQueryString _glfw.egl.QueryString
#define eglGetProcAddress _glfw.egl.GetProcAddress
#define _GLFW_EGL_CONTEXT_STATE _GLFWcontextEGL egl
#define _GLFW_EGL_LIBRARY_CONTEXT_STATE _GLFWlibraryEGL egl
// EGL-specific per-context data
//
typedef struct _GLFWcontextEGL
{
EGLConfig config;
EGLContext handle;
EGLSurface surface;
void* client;
} _GLFWcontextEGL;
// EGL-specific global data
//
typedef struct _GLFWlibraryEGL
{
EGLDisplay display;
EGLint major, minor;
GLFWbool prefix;
GLFWbool KHR_create_context;
GLFWbool KHR_create_context_no_error;
GLFWbool KHR_gl_colorspace;
GLFWbool KHR_get_all_proc_addresses;
GLFWbool KHR_context_flush_control;
GLFWbool EXT_present_opaque;
void* handle;
PFN_eglGetConfigAttrib GetConfigAttrib;
PFN_eglGetConfigs GetConfigs;
PFN_eglGetDisplay GetDisplay;
PFN_eglGetError GetError;
PFN_eglInitialize Initialize;
PFN_eglTerminate Terminate;
PFN_eglBindAPI BindAPI;
PFN_eglCreateContext CreateContext;
PFN_eglDestroySurface DestroySurface;
PFN_eglDestroyContext DestroyContext;
PFN_eglCreateWindowSurface CreateWindowSurface;
PFN_eglMakeCurrent MakeCurrent;
PFN_eglSwapBuffers SwapBuffers;
PFN_eglSwapInterval SwapInterval;
PFN_eglQueryString QueryString;
PFN_eglGetProcAddress GetProcAddress;
} _GLFWlibraryEGL;
GLFWbool _glfwInitEGL(void);
void _glfwTerminateEGL(void);
GLFWbool _glfwCreateContextEGL(_GLFWwindow* window,
const _GLFWctxconfig* ctxconfig,
const _GLFWfbconfig* fbconfig);
#if defined(_GLFW_X11)
GLFWbool _glfwChooseVisualEGL(const _GLFWwndconfig* wndconfig,
const _GLFWctxconfig* ctxconfig,
const _GLFWfbconfig* fbconfig,
Visual** visual, int* depth);
#endif /*_GLFW_X11*/
+59
View File
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2012 The glfw3-go Authors
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
//go:build darwin || freebsd || linux || netbsd || openbsd
package glfw
// #define GLFW_INCLUDE_NONE
// #include "glfw3_unix.h"
//
// void goErrorCB(int code, char* desc);
//
// static void glfwSetErrorCallbackCB() {
// glfwSetErrorCallback((GLFWerrorfun)goErrorCB);
// }
import "C"
import (
"errors"
"fmt"
"os"
)
// Note: There are many cryptic caveats to proper error handling here.
// See: https://github.com/go-gl/glfw3/pull/86
// lastError holds the value of the last error.
var lastError = make(chan error, 1)
//export goErrorCB
func goErrorCB(code C.int, desc *C.char) {
err := fmt.Errorf("glfw: %s: %w", C.GoString(desc), ErrorCode(code))
select {
case lastError <- err:
default:
fmt.Fprintln(os.Stderr, "GLFW: An uncaught error has occurred:", err)
fmt.Fprintln(os.Stderr, "GLFW: Please report this bug in the Go package immediately.")
}
}
// Set the glfw callback internally
func init() {
C.glfwSetErrorCallbackCB()
}
// fetchErrorIgnoringPlatformError is fetchError ignoring platformError.
func fetchErrorIgnoringPlatformError() error {
select {
case err := <-lastError:
if errors.Is(err, PlatformError) {
fmt.Fprintln(os.Stderr, err)
return nil
}
return err
default:
return nil
}
}
+23
View File
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard
// SPDX-FileCopyrightText: 2006-2019 Camilla Löwy <elmindreda@glfw.org>
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build darwin || freebsd || linux || netbsd || openbsd || windows
package glfw
type VidMode struct {
Width int
Height int
RedBits int
GreenBits int
BlueBits int
RefreshRate int
}
type Image struct {
Width int
Height int
Pixels []byte
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,475 @@
/*************************************************************************
* GLFW 3.3 - www.glfw.org
* A library for OpenGL, window and input
*------------------------------------------------------------------------
* Copyright (c) 2002-2006 Marcus Geelnard
* Copyright (c) 2006-2018 Camilla Löwy <elmindreda@glfw.org>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would
* be appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not
* be misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source
* distribution.
*
*************************************************************************/
#ifndef _glfw3_native_h_
#define _glfw3_native_h_
#ifdef __cplusplus
extern "C" {
#endif
/*************************************************************************
* Doxygen documentation
*************************************************************************/
/*! @file glfw3native_unix.h
* @brief The header of the native access functions.
*
* This is the header file of the native access functions. See @ref native for
* more information.
*/
/*! @defgroup native Native access
* @brief Functions related to accessing native handles.
*
* **By using the native access functions you assert that you know what you're
* doing and how to fix problems caused by using them. If you don't, you
* shouldn't be using them.**
*
* Before the inclusion of @ref glfw3native_unix.h, you may define zero or more
* window system API macro and zero or more context creation API macros.
*
* The chosen backends must match those the library was compiled for. Failure
* to do this will cause a link-time error.
*
* The available window API macros are:
* * `GLFW_EXPOSE_NATIVE_WIN32`
* * `GLFW_EXPOSE_NATIVE_COCOA`
* * `GLFW_EXPOSE_NATIVE_X11`
*
* The available context API macros are:
* * `GLFW_EXPOSE_NATIVE_WGL`
* * `GLFW_EXPOSE_NATIVE_NSGL`
* * `GLFW_EXPOSE_NATIVE_GLX`
* * `GLFW_EXPOSE_NATIVE_EGL`
* * `GLFW_EXPOSE_NATIVE_OSMESA`
*
* These macros select which of the native access functions that are declared
* and which platform-specific headers to include. It is then up your (by
* definition platform-specific) code to handle which of these should be
* defined.
*
* If you do not want the platform-specific headers to be included, define
* `GLFW_NATIVE_INCLUDE_NONE` before including the @ref glfw3native_unix.h header.
*
* @code
* #define GLFW_EXPOSE_NATIVE_WIN32
* #define GLFW_EXPOSE_NATIVE_WGL
* #define GLFW_NATIVE_INCLUDE_NONE
* #include <GLFW/glfw3native_unix.h>
* @endcode
*/
/*************************************************************************
* System headers and types
*************************************************************************/
#if !defined(GLFW_NATIVE_INCLUDE_NONE)
#if defined(GLFW_EXPOSE_NATIVE_COCOA) || defined(GLFW_EXPOSE_NATIVE_NSGL)
#if defined(__OBJC__)
#import <Cocoa/Cocoa.h>
#else
#include <ApplicationServices/ApplicationServices.h>
#include <objc/objc.h>
#endif
#elif defined(GLFW_EXPOSE_NATIVE_X11) || defined(GLFW_EXPOSE_NATIVE_GLX)
#include <X11/Xlib.h>
#include <X11/extensions/Xrandr.h>
#endif
#if defined(GLFW_EXPOSE_NATIVE_NSGL)
/* NSGL is declared by Cocoa.h */
#endif
#if defined(GLFW_EXPOSE_NATIVE_GLX)
/* This is a workaround for the fact that glfw3_unix.h defines GLAPIENTRY because by
* default it also acts as an OpenGL header
* However, glx.h will include gl.h, which will define it unconditionally
*/
#if defined(GLFW_GLAPIENTRY_DEFINED)
#undef GLAPIENTRY
#undef GLFW_GLAPIENTRY_DEFINED
#endif
#include <GL/glx.h>
#endif
#if defined(GLFW_EXPOSE_NATIVE_EGL)
#include <EGL/egl.h>
#endif
#if defined(GLFW_EXPOSE_NATIVE_OSMESA)
/* This is a workaround for the fact that glfw3_unix.h defines GLAPIENTRY because by
* default it also acts as an OpenGL header
* However, osmesa.h will include gl.h, which will define it unconditionally
*/
#if defined(GLFW_GLAPIENTRY_DEFINED)
#undef GLAPIENTRY
#undef GLFW_GLAPIENTRY_DEFINED
#endif
#include <GL/osmesa.h>
#endif
#endif /*GLFW_NATIVE_INCLUDE_NONE*/
/*************************************************************************
* Functions
*************************************************************************/
#if defined(GLFW_EXPOSE_NATIVE_COCOA)
/*! @brief Returns the `CGDirectDisplayID` of the specified monitor.
*
* @return The `CGDirectDisplayID` of the specified monitor, or
* `kCGNullDirectDisplay` if an [error](@ref error_handling) occurred.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.1.
*
* @ingroup native
*/
GLFWAPI CGDirectDisplayID glfwGetCocoaMonitor(GLFWmonitor* monitor);
/*! @brief Returns the `NSWindow` of the specified window.
*
* @return The `NSWindow` of the specified window, or `nil` if an
* [error](@ref error_handling) occurred.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.0.
*
* @ingroup native
*/
GLFWAPI id glfwGetCocoaWindow(GLFWwindow* window);
#endif
#if defined(GLFW_EXPOSE_NATIVE_NSGL)
/*! @brief Returns the `NSOpenGLContext` of the specified window.
*
* @return The `NSOpenGLContext` of the specified window, or `nil` if an
* [error](@ref error_handling) occurred.
*
* @errors Possible errors include @ref GLFW_NO_WINDOW_CONTEXT and @ref
* GLFW_NOT_INITIALIZED.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.0.
*
* @ingroup native
*/
GLFWAPI id glfwGetNSGLContext(GLFWwindow* window);
#endif
#if defined(GLFW_EXPOSE_NATIVE_X11)
/*! @brief Returns the `Display` used by GLFW.
*
* @return The `Display` used by GLFW, or `NULL` if an
* [error](@ref error_handling) occurred.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.0.
*
* @ingroup native
*/
GLFWAPI Display* glfwGetX11Display(void);
/*! @brief Returns the `RRCrtc` of the specified monitor.
*
* @return The `RRCrtc` of the specified monitor, or `None` if an
* [error](@ref error_handling) occurred.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.1.
*
* @ingroup native
*/
GLFWAPI RRCrtc glfwGetX11Adapter(GLFWmonitor* monitor);
/*! @brief Returns the `RROutput` of the specified monitor.
*
* @return The `RROutput` of the specified monitor, or `None` if an
* [error](@ref error_handling) occurred.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.1.
*
* @ingroup native
*/
GLFWAPI RROutput glfwGetX11Monitor(GLFWmonitor* monitor);
/*! @brief Returns the `Window` of the specified window.
*
* @return The `Window` of the specified window, or `None` if an
* [error](@ref error_handling) occurred.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.0.
*
* @ingroup native
*/
GLFWAPI Window glfwGetX11Window(GLFWwindow* window);
/*! @brief Sets the current primary selection to the specified string.
*
* @param[in] string A UTF-8 encoded string.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_PLATFORM_ERROR.
*
* @pointer_lifetime The specified string is copied before this function
* returns.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref clipboard
* @sa glfwGetX11SelectionString
* @sa glfwSetClipboardString
*
* @since Added in version 3.3.
*
* @ingroup native
*/
GLFWAPI void glfwSetX11SelectionString(const char* string);
/*! @brief Returns the contents of the current primary selection as a string.
*
* If the selection is empty or if its contents cannot be converted, `NULL`
* is returned and a @ref GLFW_FORMAT_UNAVAILABLE error is generated.
*
* @return The contents of the selection as a UTF-8 encoded string, or `NULL`
* if an [error](@ref error_handling) occurred.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_PLATFORM_ERROR.
*
* @pointer_lifetime The returned string is allocated and freed by GLFW. You
* should not free it yourself. It is valid until the next call to @ref
* glfwGetX11SelectionString or @ref glfwSetX11SelectionString, or until the
* library is terminated.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref clipboard
* @sa glfwSetX11SelectionString
* @sa glfwGetClipboardString
*
* @since Added in version 3.3.
*
* @ingroup native
*/
GLFWAPI const char* glfwGetX11SelectionString(void);
#endif
#if defined(GLFW_EXPOSE_NATIVE_GLX)
/*! @brief Returns the `GLXContext` of the specified window.
*
* @return The `GLXContext` of the specified window, or `NULL` if an
* [error](@ref error_handling) occurred.
*
* @errors Possible errors include @ref GLFW_NO_WINDOW_CONTEXT and @ref
* GLFW_NOT_INITIALIZED.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.0.
*
* @ingroup native
*/
GLFWAPI GLXContext glfwGetGLXContext(GLFWwindow* window);
/*! @brief Returns the `GLXWindow` of the specified window.
*
* @return The `GLXWindow` of the specified window, or `None` if an
* [error](@ref error_handling) occurred.
*
* @errors Possible errors include @ref GLFW_NO_WINDOW_CONTEXT and @ref
* GLFW_NOT_INITIALIZED.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.2.
*
* @ingroup native
*/
GLFWAPI GLXWindow glfwGetGLXWindow(GLFWwindow* window);
#endif
#if defined(GLFW_EXPOSE_NATIVE_EGL)
/*! @brief Returns the `EGLDisplay` used by GLFW.
*
* @return The `EGLDisplay` used by GLFW, or `EGL_NO_DISPLAY` if an
* [error](@ref error_handling) occurred.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
* @remark Because EGL is initialized on demand, this function will return
* `EGL_NO_DISPLAY` until the first context has been created via EGL.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.0.
*
* @ingroup native
*/
GLFWAPI EGLDisplay glfwGetEGLDisplay(void);
/*! @brief Returns the `EGLContext` of the specified window.
*
* @return The `EGLContext` of the specified window, or `EGL_NO_CONTEXT` if an
* [error](@ref error_handling) occurred.
*
* @errors Possible errors include @ref GLFW_NO_WINDOW_CONTEXT and @ref
* GLFW_NOT_INITIALIZED.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.0.
*
* @ingroup native
*/
GLFWAPI EGLContext glfwGetEGLContext(GLFWwindow* window);
/*! @brief Returns the `EGLSurface` of the specified window.
*
* @return The `EGLSurface` of the specified window, or `EGL_NO_SURFACE` if an
* [error](@ref error_handling) occurred.
*
* @errors Possible errors include @ref GLFW_NO_WINDOW_CONTEXT and @ref
* GLFW_NOT_INITIALIZED.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.0.
*
* @ingroup native
*/
GLFWAPI EGLSurface glfwGetEGLSurface(GLFWwindow* window);
#endif
#if defined(GLFW_EXPOSE_NATIVE_OSMESA)
/*! @brief Retrieves the color buffer associated with the specified window.
*
* @param[in] window The window whose color buffer to retrieve.
* @param[out] width Where to store the width of the color buffer, or `NULL`.
* @param[out] height Where to store the height of the color buffer, or `NULL`.
* @param[out] format Where to store the OSMesa pixel format of the color
* buffer, or `NULL`.
* @param[out] buffer Where to store the address of the color buffer, or
* `NULL`.
* @return `GLFW_TRUE` if successful, or `GLFW_FALSE` if an
* [error](@ref error_handling) occurred.
*
* @errors Possible errors include @ref GLFW_NO_WINDOW_CONTEXT and @ref
* GLFW_NOT_INITIALIZED.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.3.
*
* @ingroup native
*/
GLFWAPI int glfwGetOSMesaColorBuffer(GLFWwindow* window, int* width, int* height, int* format, void** buffer);
/*! @brief Retrieves the depth buffer associated with the specified window.
*
* @param[in] window The window whose depth buffer to retrieve.
* @param[out] width Where to store the width of the depth buffer, or `NULL`.
* @param[out] height Where to store the height of the depth buffer, or `NULL`.
* @param[out] bytesPerValue Where to store the number of bytes per depth
* buffer element, or `NULL`.
* @param[out] buffer Where to store the address of the depth buffer, or
* `NULL`.
* @return `GLFW_TRUE` if successful, or `GLFW_FALSE` if an
* [error](@ref error_handling) occurred.
*
* @errors Possible errors include @ref GLFW_NO_WINDOW_CONTEXT and @ref
* GLFW_NOT_INITIALIZED.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.3.
*
* @ingroup native
*/
GLFWAPI int glfwGetOSMesaDepthBuffer(GLFWwindow* window, int* width, int* height, int* bytesPerValue, void** buffer);
/*! @brief Returns the `OSMesaContext` of the specified window.
*
* @return The `OSMesaContext` of the specified window, or `NULL` if an
* [error](@ref error_handling) occurred.
*
* @errors Possible errors include @ref GLFW_NO_WINDOW_CONTEXT and @ref
* GLFW_NOT_INITIALIZED.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @since Added in version 3.3.
*
* @ingroup native
*/
GLFWAPI OSMesaContext glfwGetOSMesaContext(GLFWwindow* window);
#endif
#ifdef __cplusplus
}
#endif
#endif /* _glfw3_native_h_ */
+139
View File
@@ -0,0 +1,139 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2012 The glfw3-go Authors
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
//go:build darwin || freebsd || linux || netbsd || openbsd
package glfw
//#include <stdlib.h>
//#define GLFW_INCLUDE_NONE
//#include "glfw3_unix.h"
import "C"
import (
"errors"
"unsafe"
)
// Version constants.
const (
VersionMajor = C.GLFW_VERSION_MAJOR // This is incremented when the API is changed in non-compatible ways.
VersionMinor = C.GLFW_VERSION_MINOR // This is incremented when features are added to the API but it remains backward-compatible.
VersionRevision = C.GLFW_VERSION_REVISION // This is incremented when a bug fix release is made that does not contain any API changes.
)
// Init initializes the GLFW library. Before most GLFW functions can be used,
// GLFW must be initialized, and before a program terminates GLFW should be
// terminated in order to free any resources allocated during or after
// initialization.
//
// If this function fails, it calls Terminate before returning. If it succeeds,
// you should call Terminate before the program exits.
//
// Additional calls to this function after successful initialization but before
// termination will succeed but will do nothing.
//
// This function may take several seconds to complete on some systems, while on
// other systems it may take only a fraction of a second to complete.
//
// On Mac OS X, this function will change the current directory of the
// application to the Contents/Resources subdirectory of the application's
// bundle, if present.
//
// This function may only be called from the main thread.
func Init() error {
C.glfwInit()
if err := fetchErrorIgnoringPlatformError(); err != nil {
return err
}
return nil
}
// Terminate destroys all remaining windows, frees any allocated resources and
// sets the library to an uninitialized state. Once this is called, you must
// again call Init successfully before you will be able to use most GLFW
// functions.
//
// If GLFW has been successfully initialized, this function should be called
// before the program exits. If initialization fails, there is no need to call
// this function, as it is called by Init before it returns failure.
//
// This function may only be called from the main thread.
func Terminate() error {
C.glfwTerminate()
if err := fetchErrorIgnoringPlatformError(); err != nil {
return err
}
return nil
}
// InitHint function sets hints for the next initialization of GLFW.
//
// The values you set hints to are never reset by GLFW, but they only take
// effect during initialization. Once GLFW has been initialized, any values you
// set will be ignored until the library is terminated and initialized again.
//
// Some hints are platform specific. These may be set on any platform but they
// will only affect their specific platform. Other platforms will ignore them.
// Setting these hints requires no platform specific headers or functions.
//
// This function must only be called from the main thread.
func InitHint(hint Hint, value int) {
C.glfwInitHint(C.int(hint), C.int(value))
}
// GetVersion retrieves the major, minor and revision numbers of the GLFW
// library. It is intended for when you are using GLFW as a shared library and
// want to ensure that you are using the minimum required version.
//
// This function may be called before Init.
func GetVersion() (major, minor, revision int) {
var (
maj C.int
min C.int
rev C.int
)
C.glfwGetVersion(&maj, &min, &rev)
return int(maj), int(min), int(rev)
}
// GetVersionString returns a static string generated at compile-time according
// to which configuration macros were defined. This is intended for use when
// submitting bug reports, to allow developers to see which code paths are
// enabled in a binary.
//
// This function may be called before Init.
func GetVersionString() string {
return C.GoString(C.glfwGetVersionString())
}
// GetClipboardString returns the contents of the system clipboard, if it
// contains or is convertible to a UTF-8 encoded string.
//
// This function may only be called from the main thread.
func GetClipboardString() (string, error) {
cs := C.glfwGetClipboardString(nil)
if cs == nil {
if err := fetchErrorIgnoringPlatformError(); err != nil {
if errors.Is(err, FormatUnavailable) {
return "", nil
}
return "", err
}
return "", nil
}
return C.GoString(cs), nil
}
// SetClipboardString sets the system clipboard to the specified UTF-8 encoded
// string.
//
// This function may only be called from the main thread.
func SetClipboardString(str string) error {
cp := C.CString(str)
defer C.free(unsafe.Pointer(cp))
C.glfwSetClipboardString(nil, cp)
return fetchErrorIgnoringPlatformError()
}
@@ -0,0 +1,679 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard
// SPDX-FileCopyrightText: 2006-2019 Camilla Löwy <elmindreda@glfw.org>
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
//go:build freebsd || linux || netbsd || openbsd
#include "internal_unix.h"
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#ifndef GLXBadProfileARB
#define GLXBadProfileARB 13
#endif
// Returns the specified attribute of the specified GLXFBConfig
//
static int getGLXFBConfigAttrib(GLXFBConfig fbconfig, int attrib)
{
int value;
glXGetFBConfigAttrib(_glfw.x11.display, fbconfig, attrib, &value);
return value;
}
// Return the GLXFBConfig most closely matching the specified hints
//
static GLFWbool chooseGLXFBConfig(const _GLFWfbconfig* desired,
GLXFBConfig* result)
{
GLXFBConfig* nativeConfigs;
_GLFWfbconfig* usableConfigs;
const _GLFWfbconfig* closest;
int i, nativeCount, usableCount;
const char* vendor;
GLFWbool trustWindowBit = GLFW_TRUE;
// HACK: This is a (hopefully temporary) workaround for Chromium
// (VirtualBox GL) not setting the window bit on any GLXFBConfigs
vendor = glXGetClientString(_glfw.x11.display, GLX_VENDOR);
if (vendor && strcmp(vendor, "Chromium") == 0)
trustWindowBit = GLFW_FALSE;
nativeConfigs =
glXGetFBConfigs(_glfw.x11.display, _glfw.x11.screen, &nativeCount);
if (!nativeConfigs || !nativeCount)
{
_glfwInputError(GLFW_API_UNAVAILABLE, "GLX: No GLXFBConfigs returned");
return GLFW_FALSE;
}
usableConfigs = calloc(nativeCount, sizeof(_GLFWfbconfig));
usableCount = 0;
for (i = 0; i < nativeCount; i++)
{
const GLXFBConfig n = nativeConfigs[i];
_GLFWfbconfig* u = usableConfigs + usableCount;
// Only consider RGBA GLXFBConfigs
if (!(getGLXFBConfigAttrib(n, GLX_RENDER_TYPE) & GLX_RGBA_BIT))
continue;
// Only consider window GLXFBConfigs
if (!(getGLXFBConfigAttrib(n, GLX_DRAWABLE_TYPE) & GLX_WINDOW_BIT))
{
if (trustWindowBit)
continue;
}
if (getGLXFBConfigAttrib(n, GLX_DOUBLEBUFFER) != desired->doublebuffer)
continue;
if (desired->transparent)
{
XVisualInfo* vi = glXGetVisualFromFBConfig(_glfw.x11.display, n);
if (vi)
{
u->transparent = _glfwIsVisualTransparentX11(vi->visual);
XFree(vi);
}
}
u->redBits = getGLXFBConfigAttrib(n, GLX_RED_SIZE);
u->greenBits = getGLXFBConfigAttrib(n, GLX_GREEN_SIZE);
u->blueBits = getGLXFBConfigAttrib(n, GLX_BLUE_SIZE);
u->alphaBits = getGLXFBConfigAttrib(n, GLX_ALPHA_SIZE);
u->depthBits = getGLXFBConfigAttrib(n, GLX_DEPTH_SIZE);
u->stencilBits = getGLXFBConfigAttrib(n, GLX_STENCIL_SIZE);
u->accumRedBits = getGLXFBConfigAttrib(n, GLX_ACCUM_RED_SIZE);
u->accumGreenBits = getGLXFBConfigAttrib(n, GLX_ACCUM_GREEN_SIZE);
u->accumBlueBits = getGLXFBConfigAttrib(n, GLX_ACCUM_BLUE_SIZE);
u->accumAlphaBits = getGLXFBConfigAttrib(n, GLX_ACCUM_ALPHA_SIZE);
u->auxBuffers = getGLXFBConfigAttrib(n, GLX_AUX_BUFFERS);
if (getGLXFBConfigAttrib(n, GLX_STEREO))
u->stereo = GLFW_TRUE;
if (_glfw.glx.ARB_multisample)
u->samples = getGLXFBConfigAttrib(n, GLX_SAMPLES);
if (_glfw.glx.ARB_framebuffer_sRGB || _glfw.glx.EXT_framebuffer_sRGB)
u->sRGB = getGLXFBConfigAttrib(n, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB);
u->handle = (uintptr_t) n;
usableCount++;
}
closest = _glfwChooseFBConfig(desired, usableConfigs, usableCount);
if (closest)
*result = (GLXFBConfig) closest->handle;
XFree(nativeConfigs);
free(usableConfigs);
return closest != NULL;
}
// Create the OpenGL context using legacy API
//
static GLXContext createLegacyContextGLX(_GLFWwindow* window,
GLXFBConfig fbconfig,
GLXContext share)
{
return glXCreateNewContext(_glfw.x11.display,
fbconfig,
GLX_RGBA_TYPE,
share,
True);
}
static void makeContextCurrentGLX(_GLFWwindow* window)
{
if (window)
{
if (!glXMakeCurrent(_glfw.x11.display,
window->context.glx.window,
window->context.glx.handle))
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"GLX: Failed to make context current");
return;
}
}
else
{
if (!glXMakeCurrent(_glfw.x11.display, None, NULL))
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"GLX: Failed to clear current context");
return;
}
}
_glfwPlatformSetTls(&_glfw.contextSlot, window);
}
static void swapBuffersGLX(_GLFWwindow* window)
{
glXSwapBuffers(_glfw.x11.display, window->context.glx.window);
}
static void swapIntervalGLX(int interval)
{
_GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot);
if (_glfw.glx.EXT_swap_control)
{
_glfw.glx.SwapIntervalEXT(_glfw.x11.display,
window->context.glx.window,
interval);
}
else if (_glfw.glx.MESA_swap_control)
_glfw.glx.SwapIntervalMESA(interval);
else if (_glfw.glx.SGI_swap_control)
{
if (interval > 0)
_glfw.glx.SwapIntervalSGI(interval);
}
}
static int extensionSupportedGLX(const char* extension)
{
const char* extensions =
glXQueryExtensionsString(_glfw.x11.display, _glfw.x11.screen);
if (extensions)
{
if (_glfwStringInExtensionString(extension, extensions))
return GLFW_TRUE;
}
return GLFW_FALSE;
}
static GLFWglproc getProcAddressGLX(const char* procname)
{
if (_glfw.glx.GetProcAddress)
return _glfw.glx.GetProcAddress((const GLubyte*) procname);
else if (_glfw.glx.GetProcAddressARB)
return _glfw.glx.GetProcAddressARB((const GLubyte*) procname);
else
return _glfw_dlsym(_glfw.glx.handle, procname);
}
static void destroyContextGLX(_GLFWwindow* window)
{
if (window->context.glx.window)
{
glXDestroyWindow(_glfw.x11.display, window->context.glx.window);
window->context.glx.window = None;
}
if (window->context.glx.handle)
{
glXDestroyContext(_glfw.x11.display, window->context.glx.handle);
window->context.glx.handle = NULL;
}
}
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
// Initialize GLX
//
GLFWbool _glfwInitGLX(void)
{
int i;
const char* sonames[] =
{
#if defined(_GLFW_GLX_LIBRARY)
_GLFW_GLX_LIBRARY,
#elif defined(__CYGWIN__)
"libGL-1.so",
#elif defined(__OpenBSD__) || defined(__NetBSD__)
"libGL.so",
#else
"libGL.so.1",
"libGL.so",
#endif
NULL
};
if (_glfw.glx.handle)
return GLFW_TRUE;
for (i = 0; sonames[i]; i++)
{
_glfw.glx.handle = _glfw_dlopen(sonames[i]);
if (_glfw.glx.handle)
break;
}
if (!_glfw.glx.handle)
{
_glfwInputError(GLFW_API_UNAVAILABLE, "GLX: Failed to load GLX");
return GLFW_FALSE;
}
_glfw.glx.GetFBConfigs =
_glfw_dlsym(_glfw.glx.handle, "glXGetFBConfigs");
_glfw.glx.GetFBConfigAttrib =
_glfw_dlsym(_glfw.glx.handle, "glXGetFBConfigAttrib");
_glfw.glx.GetClientString =
_glfw_dlsym(_glfw.glx.handle, "glXGetClientString");
_glfw.glx.QueryExtension =
_glfw_dlsym(_glfw.glx.handle, "glXQueryExtension");
_glfw.glx.QueryVersion =
_glfw_dlsym(_glfw.glx.handle, "glXQueryVersion");
_glfw.glx.DestroyContext =
_glfw_dlsym(_glfw.glx.handle, "glXDestroyContext");
_glfw.glx.MakeCurrent =
_glfw_dlsym(_glfw.glx.handle, "glXMakeCurrent");
_glfw.glx.SwapBuffers =
_glfw_dlsym(_glfw.glx.handle, "glXSwapBuffers");
_glfw.glx.QueryExtensionsString =
_glfw_dlsym(_glfw.glx.handle, "glXQueryExtensionsString");
_glfw.glx.CreateNewContext =
_glfw_dlsym(_glfw.glx.handle, "glXCreateNewContext");
_glfw.glx.CreateWindow =
_glfw_dlsym(_glfw.glx.handle, "glXCreateWindow");
_glfw.glx.DestroyWindow =
_glfw_dlsym(_glfw.glx.handle, "glXDestroyWindow");
_glfw.glx.GetVisualFromFBConfig =
_glfw_dlsym(_glfw.glx.handle, "glXGetVisualFromFBConfig");
if (!_glfw.glx.GetFBConfigs ||
!_glfw.glx.GetFBConfigAttrib ||
!_glfw.glx.GetClientString ||
!_glfw.glx.QueryExtension ||
!_glfw.glx.QueryVersion ||
!_glfw.glx.DestroyContext ||
!_glfw.glx.MakeCurrent ||
!_glfw.glx.SwapBuffers ||
!_glfw.glx.QueryExtensionsString ||
!_glfw.glx.CreateNewContext ||
!_glfw.glx.CreateWindow ||
!_glfw.glx.DestroyWindow ||
!_glfw.glx.GetVisualFromFBConfig)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"GLX: Failed to load required entry points");
return GLFW_FALSE;
}
// NOTE: Unlike GLX 1.3 entry points these are not required to be present
_glfw.glx.GetProcAddress = (PFNGLXGETPROCADDRESSPROC)
_glfw_dlsym(_glfw.glx.handle, "glXGetProcAddress");
_glfw.glx.GetProcAddressARB = (PFNGLXGETPROCADDRESSPROC)
_glfw_dlsym(_glfw.glx.handle, "glXGetProcAddressARB");
if (!glXQueryExtension(_glfw.x11.display,
&_glfw.glx.errorBase,
&_glfw.glx.eventBase))
{
_glfwInputError(GLFW_API_UNAVAILABLE, "GLX: GLX extension not found");
return GLFW_FALSE;
}
if (!glXQueryVersion(_glfw.x11.display, &_glfw.glx.major, &_glfw.glx.minor))
{
_glfwInputError(GLFW_API_UNAVAILABLE,
"GLX: Failed to query GLX version");
return GLFW_FALSE;
}
if (_glfw.glx.major == 1 && _glfw.glx.minor < 3)
{
_glfwInputError(GLFW_API_UNAVAILABLE,
"GLX: GLX version 1.3 is required");
return GLFW_FALSE;
}
if (extensionSupportedGLX("GLX_EXT_swap_control"))
{
_glfw.glx.SwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC)
getProcAddressGLX("glXSwapIntervalEXT");
if (_glfw.glx.SwapIntervalEXT)
_glfw.glx.EXT_swap_control = GLFW_TRUE;
}
if (extensionSupportedGLX("GLX_SGI_swap_control"))
{
_glfw.glx.SwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC)
getProcAddressGLX("glXSwapIntervalSGI");
if (_glfw.glx.SwapIntervalSGI)
_glfw.glx.SGI_swap_control = GLFW_TRUE;
}
if (extensionSupportedGLX("GLX_MESA_swap_control"))
{
_glfw.glx.SwapIntervalMESA = (PFNGLXSWAPINTERVALMESAPROC)
getProcAddressGLX("glXSwapIntervalMESA");
if (_glfw.glx.SwapIntervalMESA)
_glfw.glx.MESA_swap_control = GLFW_TRUE;
}
if (extensionSupportedGLX("GLX_ARB_multisample"))
_glfw.glx.ARB_multisample = GLFW_TRUE;
if (extensionSupportedGLX("GLX_ARB_framebuffer_sRGB"))
_glfw.glx.ARB_framebuffer_sRGB = GLFW_TRUE;
if (extensionSupportedGLX("GLX_EXT_framebuffer_sRGB"))
_glfw.glx.EXT_framebuffer_sRGB = GLFW_TRUE;
if (extensionSupportedGLX("GLX_ARB_create_context"))
{
_glfw.glx.CreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC)
getProcAddressGLX("glXCreateContextAttribsARB");
if (_glfw.glx.CreateContextAttribsARB)
_glfw.glx.ARB_create_context = GLFW_TRUE;
}
if (extensionSupportedGLX("GLX_ARB_create_context_robustness"))
_glfw.glx.ARB_create_context_robustness = GLFW_TRUE;
if (extensionSupportedGLX("GLX_ARB_create_context_profile"))
_glfw.glx.ARB_create_context_profile = GLFW_TRUE;
if (extensionSupportedGLX("GLX_EXT_create_context_es2_profile"))
_glfw.glx.EXT_create_context_es2_profile = GLFW_TRUE;
if (extensionSupportedGLX("GLX_ARB_create_context_no_error"))
_glfw.glx.ARB_create_context_no_error = GLFW_TRUE;
if (extensionSupportedGLX("GLX_ARB_context_flush_control"))
_glfw.glx.ARB_context_flush_control = GLFW_TRUE;
return GLFW_TRUE;
}
// Terminate GLX
//
void _glfwTerminateGLX(void)
{
// NOTE: This function must not call any X11 functions, as it is called
// after XCloseDisplay (see _glfwPlatformTerminate for details)
if (_glfw.glx.handle)
{
_glfw_dlclose(_glfw.glx.handle);
_glfw.glx.handle = NULL;
}
}
#define setAttrib(a, v) \
{ \
assert(((size_t) index + 1) < sizeof(attribs) / sizeof(attribs[0])); \
attribs[index++] = a; \
attribs[index++] = v; \
}
// Create the OpenGL or OpenGL ES context
//
GLFWbool _glfwCreateContextGLX(_GLFWwindow* window,
const _GLFWctxconfig* ctxconfig,
const _GLFWfbconfig* fbconfig)
{
int attribs[40];
GLXFBConfig native = NULL;
GLXContext share = NULL;
if (ctxconfig->share)
share = ctxconfig->share->context.glx.handle;
if (!chooseGLXFBConfig(fbconfig, &native))
{
_glfwInputError(GLFW_FORMAT_UNAVAILABLE,
"GLX: Failed to find a suitable GLXFBConfig");
return GLFW_FALSE;
}
if (ctxconfig->client == GLFW_OPENGL_ES_API)
{
if (!_glfw.glx.ARB_create_context ||
!_glfw.glx.ARB_create_context_profile ||
!_glfw.glx.EXT_create_context_es2_profile)
{
_glfwInputError(GLFW_API_UNAVAILABLE,
"GLX: OpenGL ES requested but GLX_EXT_create_context_es2_profile is unavailable");
return GLFW_FALSE;
}
}
if (ctxconfig->forward)
{
if (!_glfw.glx.ARB_create_context)
{
_glfwInputError(GLFW_VERSION_UNAVAILABLE,
"GLX: Forward compatibility requested but GLX_ARB_create_context_profile is unavailable");
return GLFW_FALSE;
}
}
if (ctxconfig->profile)
{
if (!_glfw.glx.ARB_create_context ||
!_glfw.glx.ARB_create_context_profile)
{
_glfwInputError(GLFW_VERSION_UNAVAILABLE,
"GLX: An OpenGL profile requested but GLX_ARB_create_context_profile is unavailable");
return GLFW_FALSE;
}
}
_glfwGrabErrorHandlerX11();
if (_glfw.glx.ARB_create_context)
{
int index = 0, mask = 0, flags = 0;
if (ctxconfig->client == GLFW_OPENGL_API)
{
if (ctxconfig->forward)
flags |= GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB;
if (ctxconfig->profile == GLFW_OPENGL_CORE_PROFILE)
mask |= GLX_CONTEXT_CORE_PROFILE_BIT_ARB;
else if (ctxconfig->profile == GLFW_OPENGL_COMPAT_PROFILE)
mask |= GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB;
}
else
mask |= GLX_CONTEXT_ES2_PROFILE_BIT_EXT;
if (ctxconfig->debug)
flags |= GLX_CONTEXT_DEBUG_BIT_ARB;
if (ctxconfig->robustness)
{
if (_glfw.glx.ARB_create_context_robustness)
{
if (ctxconfig->robustness == GLFW_NO_RESET_NOTIFICATION)
{
setAttrib(GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB,
GLX_NO_RESET_NOTIFICATION_ARB);
}
else if (ctxconfig->robustness == GLFW_LOSE_CONTEXT_ON_RESET)
{
setAttrib(GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB,
GLX_LOSE_CONTEXT_ON_RESET_ARB);
}
flags |= GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB;
}
}
if (ctxconfig->release)
{
if (_glfw.glx.ARB_context_flush_control)
{
if (ctxconfig->release == GLFW_RELEASE_BEHAVIOR_NONE)
{
setAttrib(GLX_CONTEXT_RELEASE_BEHAVIOR_ARB,
GLX_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB);
}
else if (ctxconfig->release == GLFW_RELEASE_BEHAVIOR_FLUSH)
{
setAttrib(GLX_CONTEXT_RELEASE_BEHAVIOR_ARB,
GLX_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB);
}
}
}
if (ctxconfig->noerror)
{
if (_glfw.glx.ARB_create_context_no_error)
setAttrib(GLX_CONTEXT_OPENGL_NO_ERROR_ARB, GLFW_TRUE);
}
// NOTE: Only request an explicitly versioned context when necessary, as
// explicitly requesting version 1.0 does not always return the
// highest version supported by the driver
if (ctxconfig->major != 1 || ctxconfig->minor != 0)
{
setAttrib(GLX_CONTEXT_MAJOR_VERSION_ARB, ctxconfig->major);
setAttrib(GLX_CONTEXT_MINOR_VERSION_ARB, ctxconfig->minor);
}
if (mask)
setAttrib(GLX_CONTEXT_PROFILE_MASK_ARB, mask);
if (flags)
setAttrib(GLX_CONTEXT_FLAGS_ARB, flags);
setAttrib(None, None);
window->context.glx.handle =
_glfw.glx.CreateContextAttribsARB(_glfw.x11.display,
native,
share,
True,
attribs);
// HACK: This is a fallback for broken versions of the Mesa
// implementation of GLX_ARB_create_context_profile that fail
// default 1.0 context creation with a GLXBadProfileARB error in
// violation of the extension spec
if (!window->context.glx.handle)
{
if (_glfw.x11.errorCode == _glfw.glx.errorBase + GLXBadProfileARB &&
ctxconfig->client == GLFW_OPENGL_API &&
ctxconfig->profile == GLFW_OPENGL_ANY_PROFILE &&
ctxconfig->forward == GLFW_FALSE)
{
window->context.glx.handle =
createLegacyContextGLX(window, native, share);
}
}
}
else
{
window->context.glx.handle =
createLegacyContextGLX(window, native, share);
}
_glfwReleaseErrorHandlerX11();
if (!window->context.glx.handle)
{
_glfwInputErrorX11(GLFW_VERSION_UNAVAILABLE, "GLX: Failed to create context");
return GLFW_FALSE;
}
window->context.glx.window =
glXCreateWindow(_glfw.x11.display, native, window->x11.handle, NULL);
if (!window->context.glx.window)
{
_glfwInputError(GLFW_PLATFORM_ERROR, "GLX: Failed to create window");
return GLFW_FALSE;
}
window->context.makeCurrent = makeContextCurrentGLX;
window->context.swapBuffers = swapBuffersGLX;
window->context.swapInterval = swapIntervalGLX;
window->context.extensionSupported = extensionSupportedGLX;
window->context.getProcAddress = getProcAddressGLX;
window->context.destroy = destroyContextGLX;
return GLFW_TRUE;
}
#undef setAttrib
// Returns the Visual and depth of the chosen GLXFBConfig
//
GLFWbool _glfwChooseVisualGLX(const _GLFWwndconfig* wndconfig,
const _GLFWctxconfig* ctxconfig,
const _GLFWfbconfig* fbconfig,
Visual** visual, int* depth)
{
GLXFBConfig native;
XVisualInfo* result;
if (!chooseGLXFBConfig(fbconfig, &native))
{
_glfwInputError(GLFW_FORMAT_UNAVAILABLE,
"GLX: Failed to find a suitable GLXFBConfig");
return GLFW_FALSE;
}
result = glXGetVisualFromFBConfig(_glfw.x11.display, native);
if (!result)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"GLX: Failed to retrieve Visual for GLXFBConfig");
return GLFW_FALSE;
}
*visual = result->visual;
*depth = result->depth;
XFree(result);
return GLFW_TRUE;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW native API //////
//////////////////////////////////////////////////////////////////////////
GLFWAPI GLXContext glfwGetGLXContext(GLFWwindow* handle)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
if (window->context.source != GLFW_NATIVE_CONTEXT_API)
{
_glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL);
return NULL;
}
return window->context.glx.handle;
}
GLFWAPI GLXWindow glfwGetGLXWindow(GLFWwindow* handle)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
_GLFW_REQUIRE_INIT_OR_RETURN(None);
if (window->context.source != GLFW_NATIVE_CONTEXT_API)
{
_glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL);
return None;
}
return window->context.glx.window;
}
@@ -0,0 +1,159 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard
// SPDX-FileCopyrightText: 2006-2017 Camilla Löwy <elmindreda@glfw.org>
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
//go:build freebsd || linux || netbsd || openbsd
#define GLX_VENDOR 1
#define GLX_RGBA_BIT 0x00000001
#define GLX_WINDOW_BIT 0x00000001
#define GLX_DRAWABLE_TYPE 0x8010
#define GLX_RENDER_TYPE 0x8011
#define GLX_RGBA_TYPE 0x8014
#define GLX_DOUBLEBUFFER 5
#define GLX_STEREO 6
#define GLX_AUX_BUFFERS 7
#define GLX_RED_SIZE 8
#define GLX_GREEN_SIZE 9
#define GLX_BLUE_SIZE 10
#define GLX_ALPHA_SIZE 11
#define GLX_DEPTH_SIZE 12
#define GLX_STENCIL_SIZE 13
#define GLX_ACCUM_RED_SIZE 14
#define GLX_ACCUM_GREEN_SIZE 15
#define GLX_ACCUM_BLUE_SIZE 16
#define GLX_ACCUM_ALPHA_SIZE 17
#define GLX_SAMPLES 0x186a1
#define GLX_VISUAL_ID 0x800b
#define GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20b2
#define GLX_CONTEXT_DEBUG_BIT_ARB 0x00000001
#define GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002
#define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
#define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126
#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002
#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092
#define GLX_CONTEXT_FLAGS_ARB 0x2094
#define GLX_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004
#define GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB 0x00000004
#define GLX_LOSE_CONTEXT_ON_RESET_ARB 0x8252
#define GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB 0x8256
#define GLX_NO_RESET_NOTIFICATION_ARB 0x8261
#define GLX_CONTEXT_RELEASE_BEHAVIOR_ARB 0x2097
#define GLX_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB 0
#define GLX_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB 0x2098
#define GLX_CONTEXT_OPENGL_NO_ERROR_ARB 0x31b3
typedef XID GLXWindow;
typedef XID GLXDrawable;
typedef struct __GLXFBConfig* GLXFBConfig;
typedef struct __GLXcontext* GLXContext;
typedef void (*__GLXextproc)(void);
typedef int (*PFNGLXGETFBCONFIGATTRIBPROC)(Display*,GLXFBConfig,int,int*);
typedef const char* (*PFNGLXGETCLIENTSTRINGPROC)(Display*,int);
typedef Bool (*PFNGLXQUERYEXTENSIONPROC)(Display*,int*,int*);
typedef Bool (*PFNGLXQUERYVERSIONPROC)(Display*,int*,int*);
typedef void (*PFNGLXDESTROYCONTEXTPROC)(Display*,GLXContext);
typedef Bool (*PFNGLXMAKECURRENTPROC)(Display*,GLXDrawable,GLXContext);
typedef void (*PFNGLXSWAPBUFFERSPROC)(Display*,GLXDrawable);
typedef const char* (*PFNGLXQUERYEXTENSIONSSTRINGPROC)(Display*,int);
typedef GLXFBConfig* (*PFNGLXGETFBCONFIGSPROC)(Display*,int,int*);
typedef GLXContext (*PFNGLXCREATENEWCONTEXTPROC)(Display*,GLXFBConfig,int,GLXContext,Bool);
typedef __GLXextproc (* PFNGLXGETPROCADDRESSPROC)(const GLubyte *procName);
typedef void (*PFNGLXSWAPINTERVALEXTPROC)(Display*,GLXDrawable,int);
typedef XVisualInfo* (*PFNGLXGETVISUALFROMFBCONFIGPROC)(Display*,GLXFBConfig);
typedef GLXWindow (*PFNGLXCREATEWINDOWPROC)(Display*,GLXFBConfig,Window,const int*);
typedef void (*PFNGLXDESTROYWINDOWPROC)(Display*,GLXWindow);
typedef int (*PFNGLXSWAPINTERVALMESAPROC)(int);
typedef int (*PFNGLXSWAPINTERVALSGIPROC)(int);
typedef GLXContext (*PFNGLXCREATECONTEXTATTRIBSARBPROC)(Display*,GLXFBConfig,GLXContext,Bool,const int*);
// libGL.so function pointer typedefs
#define glXGetFBConfigs _glfw.glx.GetFBConfigs
#define glXGetFBConfigAttrib _glfw.glx.GetFBConfigAttrib
#define glXGetClientString _glfw.glx.GetClientString
#define glXQueryExtension _glfw.glx.QueryExtension
#define glXQueryVersion _glfw.glx.QueryVersion
#define glXDestroyContext _glfw.glx.DestroyContext
#define glXMakeCurrent _glfw.glx.MakeCurrent
#define glXSwapBuffers _glfw.glx.SwapBuffers
#define glXQueryExtensionsString _glfw.glx.QueryExtensionsString
#define glXCreateNewContext _glfw.glx.CreateNewContext
#define glXGetVisualFromFBConfig _glfw.glx.GetVisualFromFBConfig
#define glXCreateWindow _glfw.glx.CreateWindow
#define glXDestroyWindow _glfw.glx.DestroyWindow
#define _GLFW_PLATFORM_CONTEXT_STATE _GLFWcontextGLX glx
#define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE _GLFWlibraryGLX glx
// GLX-specific per-context data
//
typedef struct _GLFWcontextGLX
{
GLXContext handle;
GLXWindow window;
} _GLFWcontextGLX;
// GLX-specific global data
//
typedef struct _GLFWlibraryGLX
{
int major, minor;
int eventBase;
int errorBase;
// dlopen handle for libGL.so.1
void* handle;
// GLX 1.3 functions
PFNGLXGETFBCONFIGSPROC GetFBConfigs;
PFNGLXGETFBCONFIGATTRIBPROC GetFBConfigAttrib;
PFNGLXGETCLIENTSTRINGPROC GetClientString;
PFNGLXQUERYEXTENSIONPROC QueryExtension;
PFNGLXQUERYVERSIONPROC QueryVersion;
PFNGLXDESTROYCONTEXTPROC DestroyContext;
PFNGLXMAKECURRENTPROC MakeCurrent;
PFNGLXSWAPBUFFERSPROC SwapBuffers;
PFNGLXQUERYEXTENSIONSSTRINGPROC QueryExtensionsString;
PFNGLXCREATENEWCONTEXTPROC CreateNewContext;
PFNGLXGETVISUALFROMFBCONFIGPROC GetVisualFromFBConfig;
PFNGLXCREATEWINDOWPROC CreateWindow;
PFNGLXDESTROYWINDOWPROC DestroyWindow;
// GLX 1.4 and extension functions
PFNGLXGETPROCADDRESSPROC GetProcAddress;
PFNGLXGETPROCADDRESSPROC GetProcAddressARB;
PFNGLXSWAPINTERVALSGIPROC SwapIntervalSGI;
PFNGLXSWAPINTERVALEXTPROC SwapIntervalEXT;
PFNGLXSWAPINTERVALMESAPROC SwapIntervalMESA;
PFNGLXCREATECONTEXTATTRIBSARBPROC CreateContextAttribsARB;
GLFWbool SGI_swap_control;
GLFWbool EXT_swap_control;
GLFWbool MESA_swap_control;
GLFWbool ARB_multisample;
GLFWbool ARB_framebuffer_sRGB;
GLFWbool EXT_framebuffer_sRGB;
GLFWbool ARB_create_context;
GLFWbool ARB_create_context_profile;
GLFWbool ARB_create_context_robustness;
GLFWbool EXT_create_context_es2_profile;
GLFWbool ARB_create_context_no_error;
GLFWbool ARB_context_flush_control;
} _GLFWlibraryGLX;
GLFWbool _glfwInitGLX(void);
void _glfwTerminateGLX(void);
GLFWbool _glfwCreateContextGLX(_GLFWwindow* window,
const _GLFWctxconfig* ctxconfig,
const _GLFWfbconfig* fbconfig);
void _glfwDestroyContextGLX(_GLFWwindow* window);
GLFWbool _glfwChooseVisualGLX(const _GLFWwndconfig* wndconfig,
const _GLFWctxconfig* ctxconfig,
const _GLFWfbconfig* fbconfig,
Visual** visual, int* depth);
+387
View File
@@ -0,0 +1,387 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard
// SPDX-FileCopyrightText: 2006-2018 Camilla Löwy <elmindreda@glfw.org>
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
//go:build darwin || freebsd || linux || netbsd || openbsd
#include "internal_unix.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
// NOTE: The global variables below comprise all mutable global data in GLFW
// Any other mutable global variable is a bug
// This contains all mutable state shared between compilation units of GLFW
//
_GLFWlibrary _glfw = { GLFW_FALSE };
// These are outside of _glfw so they can be used before initialization and
// after termination without special handling when _glfw is cleared to zero
//
static _GLFWerror _glfwMainThreadError;
static GLFWerrorfun _glfwErrorCallback;
static _GLFWinitconfig _glfwInitHints =
{
{
GLFW_TRUE, // macOS menu bar
GLFW_TRUE // macOS bundle chdir
}
};
// Terminate the library
//
static void terminate(void)
{
int i;
memset(&_glfw.callbacks, 0, sizeof(_glfw.callbacks));
while (_glfw.windowListHead)
glfwDestroyWindow((GLFWwindow*) _glfw.windowListHead);
while (_glfw.cursorListHead)
glfwDestroyCursor((GLFWcursor*) _glfw.cursorListHead);
for (i = 0; i < _glfw.monitorCount; i++)
{
_GLFWmonitor* monitor = _glfw.monitors[i];
if (monitor->originalRamp.size)
_glfwPlatformSetGammaRamp(monitor, &monitor->originalRamp);
_glfwFreeMonitor(monitor);
}
free(_glfw.monitors);
_glfw.monitors = NULL;
_glfw.monitorCount = 0;
_glfwPlatformTerminate();
_glfw.initialized = GLFW_FALSE;
while (_glfw.errorListHead)
{
_GLFWerror* error = _glfw.errorListHead;
_glfw.errorListHead = error->next;
free(error);
}
_glfwPlatformDestroyTls(&_glfw.contextSlot);
_glfwPlatformDestroyTls(&_glfw.errorSlot);
_glfwPlatformDestroyMutex(&_glfw.errorLock);
memset(&_glfw, 0, sizeof(_glfw));
}
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
// Encode a Unicode code point to a UTF-8 stream
// Based on cutef8 by Jeff Bezanson (Public Domain)
//
size_t _glfwEncodeUTF8(char* s, uint32_t codepoint)
{
size_t count = 0;
if (codepoint < 0x80)
s[count++] = (char) codepoint;
else if (codepoint < 0x800)
{
s[count++] = (codepoint >> 6) | 0xc0;
s[count++] = (codepoint & 0x3f) | 0x80;
}
else if (codepoint < 0x10000)
{
s[count++] = (codepoint >> 12) | 0xe0;
s[count++] = ((codepoint >> 6) & 0x3f) | 0x80;
s[count++] = (codepoint & 0x3f) | 0x80;
}
else if (codepoint < 0x110000)
{
s[count++] = (codepoint >> 18) | 0xf0;
s[count++] = ((codepoint >> 12) & 0x3f) | 0x80;
s[count++] = ((codepoint >> 6) & 0x3f) | 0x80;
s[count++] = (codepoint & 0x3f) | 0x80;
}
return count;
}
// Splits and translates a text/uri-list into separate file paths
// NOTE: This function destroys the provided string
//
char** _glfwParseUriList(char* text, int* count)
{
const char* prefix = "file://";
char** paths = NULL;
char* line;
*count = 0;
while ((line = strtok(text, "\r\n")))
{
char* path;
text = NULL;
if (line[0] == '#')
continue;
if (strncmp(line, prefix, strlen(prefix)) == 0)
{
line += strlen(prefix);
// TODO: Validate hostname
while (*line != '/')
line++;
}
(*count)++;
path = calloc(strlen(line) + 1, 1);
paths = realloc(paths, *count * sizeof(char*));
paths[*count - 1] = path;
while (*line)
{
if (line[0] == '%' && line[1] && line[2])
{
const char digits[3] = { line[1], line[2], '\0' };
*path = (char) strtol(digits, NULL, 16);
line += 2;
}
else
*path = *line;
path++;
line++;
}
}
return paths;
}
char* _glfw_strdup(const char* source)
{
const size_t length = strlen(source);
char* result = calloc(length + 1, 1);
strcpy(result, source);
return result;
}
int _glfw_min(int a, int b)
{
return a < b ? a : b;
}
int _glfw_max(int a, int b)
{
return a > b ? a : b;
}
float _glfw_fminf(float a, float b)
{
if (a != a)
return b;
else if (b != b)
return a;
else if (a < b)
return a;
else
return b;
}
float _glfw_fmaxf(float a, float b)
{
if (a != a)
return b;
else if (b != b)
return a;
else if (a > b)
return a;
else
return b;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW event API //////
//////////////////////////////////////////////////////////////////////////
// Notifies shared code of an error
//
void _glfwInputError(int code, const char* format, ...)
{
_GLFWerror* error;
char description[_GLFW_MESSAGE_SIZE];
if (format)
{
va_list vl;
va_start(vl, format);
vsnprintf(description, sizeof(description), format, vl);
va_end(vl);
description[sizeof(description) - 1] = '\0';
}
else
{
if (code == GLFW_NOT_INITIALIZED)
strcpy(description, "The GLFW library is not initialized");
else if (code == GLFW_NO_CURRENT_CONTEXT)
strcpy(description, "There is no current context");
else if (code == GLFW_INVALID_ENUM)
strcpy(description, "Invalid argument for enum parameter");
else if (code == GLFW_INVALID_VALUE)
strcpy(description, "Invalid value for parameter");
else if (code == GLFW_OUT_OF_MEMORY)
strcpy(description, "Out of memory");
else if (code == GLFW_API_UNAVAILABLE)
strcpy(description, "The requested API is unavailable");
else if (code == GLFW_VERSION_UNAVAILABLE)
strcpy(description, "The requested API version is unavailable");
else if (code == GLFW_PLATFORM_ERROR)
strcpy(description, "A platform-specific error occurred");
else if (code == GLFW_FORMAT_UNAVAILABLE)
strcpy(description, "The requested format is unavailable");
else if (code == GLFW_NO_WINDOW_CONTEXT)
strcpy(description, "The specified window has no context");
else
strcpy(description, "ERROR: UNKNOWN GLFW ERROR");
}
if (_glfw.initialized)
{
error = _glfwPlatformGetTls(&_glfw.errorSlot);
if (!error)
{
error = calloc(1, sizeof(_GLFWerror));
_glfwPlatformSetTls(&_glfw.errorSlot, error);
_glfwPlatformLockMutex(&_glfw.errorLock);
error->next = _glfw.errorListHead;
_glfw.errorListHead = error;
_glfwPlatformUnlockMutex(&_glfw.errorLock);
}
}
else
error = &_glfwMainThreadError;
error->code = code;
strcpy(error->description, description);
if (_glfwErrorCallback)
_glfwErrorCallback(code, description);
}
//////////////////////////////////////////////////////////////////////////
////// GLFW public API //////
//////////////////////////////////////////////////////////////////////////
GLFWAPI int glfwInit(void)
{
if (_glfw.initialized)
return GLFW_TRUE;
memset(&_glfw, 0, sizeof(_glfw));
_glfw.hints.init = _glfwInitHints;
if (!_glfwPlatformInit())
{
terminate();
return GLFW_FALSE;
}
if (!_glfwPlatformCreateMutex(&_glfw.errorLock) ||
!_glfwPlatformCreateTls(&_glfw.errorSlot) ||
!_glfwPlatformCreateTls(&_glfw.contextSlot))
{
terminate();
return GLFW_FALSE;
}
_glfwPlatformSetTls(&_glfw.errorSlot, &_glfwMainThreadError);
_glfw.initialized = GLFW_TRUE;
_glfw.timer.offset = _glfwPlatformGetTimerValue();
glfwDefaultWindowHints();
return GLFW_TRUE;
}
GLFWAPI void glfwTerminate(void)
{
if (!_glfw.initialized)
return;
terminate();
}
GLFWAPI void glfwInitHint(int hint, int value)
{
switch (hint)
{
case GLFW_COCOA_CHDIR_RESOURCES:
_glfwInitHints.ns.chdir = value;
return;
case GLFW_COCOA_MENUBAR:
_glfwInitHints.ns.menubar = value;
return;
}
_glfwInputError(GLFW_INVALID_ENUM,
"Invalid init hint 0x%08X", hint);
}
GLFWAPI void glfwGetVersion(int* major, int* minor, int* rev)
{
if (major != NULL)
*major = GLFW_VERSION_MAJOR;
if (minor != NULL)
*minor = GLFW_VERSION_MINOR;
if (rev != NULL)
*rev = GLFW_VERSION_REVISION;
}
GLFWAPI const char* glfwGetVersionString(void)
{
return _glfwPlatformGetVersionString();
}
GLFWAPI int glfwGetError(const char** description)
{
_GLFWerror* error;
int code = GLFW_NO_ERROR;
if (description)
*description = NULL;
if (_glfw.initialized)
error = _glfwPlatformGetTls(&_glfw.errorSlot);
else
error = &_glfwMainThreadError;
if (error)
{
code = error->code;
error->code = GLFW_NO_ERROR;
if (description && code)
*description = error->description;
}
return code;
}
GLFWAPI GLFWerrorfun glfwSetErrorCallback(GLFWerrorfun cbfun)
{
_GLFW_SWAP_POINTERS(_glfwErrorCallback, cbfun);
return cbfun;
}
+85
View File
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard
// SPDX-FileCopyrightText: 2006-2018 Camilla Löwy <elmindreda@glfw.org>
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
package glfw
import (
"errors"
)
func terminate() error {
for _, w := range _glfw.windows {
if err := w.Destroy(); err != nil {
return err
}
}
for _, c := range _glfw.cursors {
if err := c.Destroy(); err != nil {
return err
}
}
_glfw.monitors = nil
if err := platformTerminate(); err != nil {
return err
}
_glfw.initialized = false
if err := _glfw.contextSlot.destroy(); err != nil {
return err
}
return nil
}
func Init() (ferr error) {
defer func() {
if ferr != nil {
// InvalidValue can happen when specific joysticks are used. This issue
// will be fixed in GLFW 3.3.5. As a temporary fix, ignore this error.
// See go-gl/glfw#292, go-gl/glfw#324, and glfw/glfw#1763
// (#1229).
if errors.Is(ferr, InvalidValue) {
ferr = nil
return
}
_ = terminate()
}
}()
if _glfw.initialized {
return nil
}
_glfw.hints.init.hatButtons = true
if err := platformInit(); err != nil {
return err
}
if err := _glfw.contextSlot.create(); err != nil {
return err
}
_glfw.initialized = true
if err := defaultWindowHints(); err != nil {
return err
}
return nil
}
func Terminate() error {
if !_glfw.initialized {
return nil
}
if err := terminate(); err != nil {
return err
}
return nil
}
+616
View File
@@ -0,0 +1,616 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard
// SPDX-FileCopyrightText: 2006-2019 Camilla Löwy <elmindreda@glfw.org>
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
//go:build darwin || freebsd || linux || netbsd || openbsd
#include "internal_unix.h"
#include <assert.h>
#include <float.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
// Internal key state used for sticky keys
#define _GLFW_STICK 3
//////////////////////////////////////////////////////////////////////////
////// GLFW event API //////
//////////////////////////////////////////////////////////////////////////
// Notifies shared code of a physical key event
//
void _glfwInputKey(_GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (key >= 0 && key <= GLFW_KEY_LAST)
{
GLFWbool repeated = GLFW_FALSE;
if (action == GLFW_RELEASE && window->keys[key] == GLFW_RELEASE)
return;
if (action == GLFW_PRESS && window->keys[key] == GLFW_PRESS)
repeated = GLFW_TRUE;
if (action == GLFW_RELEASE && window->stickyKeys)
window->keys[key] = _GLFW_STICK;
else
window->keys[key] = (char) action;
if (repeated)
action = GLFW_REPEAT;
}
if (!window->lockKeyMods)
mods &= ~(GLFW_MOD_CAPS_LOCK | GLFW_MOD_NUM_LOCK);
if (window->callbacks.key)
window->callbacks.key((GLFWwindow*) window, key, scancode, action, mods);
}
// Notifies shared code of a Unicode codepoint input event
// The 'plain' parameter determines whether to emit a regular character event
//
void _glfwInputChar(_GLFWwindow* window, uint32_t codepoint, int mods, GLFWbool plain)
{
if (codepoint < 32 || (codepoint > 126 && codepoint < 160))
return;
if (!window->lockKeyMods)
mods &= ~(GLFW_MOD_CAPS_LOCK | GLFW_MOD_NUM_LOCK);
if (window->callbacks.charmods)
window->callbacks.charmods((GLFWwindow*) window, codepoint, mods);
if (plain)
{
if (window->callbacks.character)
window->callbacks.character((GLFWwindow*) window, codepoint);
}
}
// Notifies shared code of a scroll event
//
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset)
{
if (window->callbacks.scroll)
window->callbacks.scroll((GLFWwindow*) window, xoffset, yoffset);
}
// Notifies shared code of a mouse button click event
//
void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods)
{
if (button < 0 || button > GLFW_MOUSE_BUTTON_LAST)
return;
if (!window->lockKeyMods)
mods &= ~(GLFW_MOD_CAPS_LOCK | GLFW_MOD_NUM_LOCK);
if (action == GLFW_RELEASE && window->stickyMouseButtons)
window->mouseButtons[button] = _GLFW_STICK;
else
window->mouseButtons[button] = (char) action;
if (window->callbacks.mouseButton)
window->callbacks.mouseButton((GLFWwindow*) window, button, action, mods);
}
// Notifies shared code of a cursor motion event
// The position is specified in content area relative screen coordinates
//
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos)
{
if (window->virtualCursorPosX == xpos && window->virtualCursorPosY == ypos)
return;
window->virtualCursorPosX = xpos;
window->virtualCursorPosY = ypos;
if (window->callbacks.cursorPos)
window->callbacks.cursorPos((GLFWwindow*) window, xpos, ypos);
}
// Notifies shared code of a cursor enter/leave event
//
void _glfwInputCursorEnter(_GLFWwindow* window, GLFWbool entered)
{
if (window->callbacks.cursorEnter)
window->callbacks.cursorEnter((GLFWwindow*) window, entered);
}
// Notifies shared code of files or directories dropped on a window
//
void _glfwInputDrop(_GLFWwindow* window, int count, const char** paths)
{
if (window->callbacks.drop)
window->callbacks.drop((GLFWwindow*) window, count, paths);
}
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
// Center the cursor in the content area of the specified window
//
void _glfwCenterCursorInContentArea(_GLFWwindow* window)
{
int width, height;
_glfwPlatformGetWindowSize(window, &width, &height);
_glfwPlatformSetCursorPos(window, width / 2.0, height / 2.0);
}
//////////////////////////////////////////////////////////////////////////
////// GLFW public API //////
//////////////////////////////////////////////////////////////////////////
GLFWAPI int glfwGetInputMode(GLFWwindow* handle, int mode)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(0);
switch (mode)
{
case GLFW_CURSOR:
return window->cursorMode;
case GLFW_STICKY_KEYS:
return window->stickyKeys;
case GLFW_STICKY_MOUSE_BUTTONS:
return window->stickyMouseButtons;
case GLFW_LOCK_KEY_MODS:
return window->lockKeyMods;
case GLFW_RAW_MOUSE_MOTION:
return window->rawMouseMotion;
}
_glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode);
return 0;
}
GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT();
if (mode == GLFW_CURSOR)
{
if (value != GLFW_CURSOR_NORMAL &&
value != GLFW_CURSOR_HIDDEN &&
value != GLFW_CURSOR_DISABLED)
{
_glfwInputError(GLFW_INVALID_ENUM,
"Invalid cursor mode 0x%08X",
value);
return;
}
if (window->cursorMode == value)
return;
window->cursorMode = value;
_glfwPlatformGetCursorPos(window,
&window->virtualCursorPosX,
&window->virtualCursorPosY);
_glfwPlatformSetCursorMode(window, value);
}
else if (mode == GLFW_STICKY_KEYS)
{
value = value ? GLFW_TRUE : GLFW_FALSE;
if (window->stickyKeys == value)
return;
if (!value)
{
int i;
// Release all sticky keys
for (i = 0; i <= GLFW_KEY_LAST; i++)
{
if (window->keys[i] == _GLFW_STICK)
window->keys[i] = GLFW_RELEASE;
}
}
window->stickyKeys = value;
}
else if (mode == GLFW_STICKY_MOUSE_BUTTONS)
{
value = value ? GLFW_TRUE : GLFW_FALSE;
if (window->stickyMouseButtons == value)
return;
if (!value)
{
int i;
// Release all sticky mouse buttons
for (i = 0; i <= GLFW_MOUSE_BUTTON_LAST; i++)
{
if (window->mouseButtons[i] == _GLFW_STICK)
window->mouseButtons[i] = GLFW_RELEASE;
}
}
window->stickyMouseButtons = value;
}
else if (mode == GLFW_LOCK_KEY_MODS)
{
window->lockKeyMods = value ? GLFW_TRUE : GLFW_FALSE;
}
else if (mode == GLFW_RAW_MOUSE_MOTION)
{
if (!_glfwPlatformRawMouseMotionSupported())
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Raw mouse motion is not supported on this system");
return;
}
value = value ? GLFW_TRUE : GLFW_FALSE;
if (window->rawMouseMotion == value)
return;
window->rawMouseMotion = value;
_glfwPlatformSetRawMouseMotion(window, value);
}
else
_glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode);
}
GLFWAPI int glfwRawMouseMotionSupported(void)
{
_GLFW_REQUIRE_INIT_OR_RETURN(GLFW_FALSE);
return _glfwPlatformRawMouseMotionSupported();
}
GLFWAPI const char* glfwGetKeyName(int key, int scancode)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
if (key != GLFW_KEY_UNKNOWN)
{
if (key != GLFW_KEY_KP_EQUAL &&
(key < GLFW_KEY_KP_0 || key > GLFW_KEY_KP_ADD) &&
(key < GLFW_KEY_APOSTROPHE || key > GLFW_KEY_WORLD_2))
{
return NULL;
}
scancode = _glfwPlatformGetKeyScancode(key);
}
return _glfwPlatformGetScancodeName(scancode);
}
GLFWAPI int glfwGetKeyScancode(int key)
{
_GLFW_REQUIRE_INIT_OR_RETURN(-1);
if (key < GLFW_KEY_SPACE || key > GLFW_KEY_LAST)
{
_glfwInputError(GLFW_INVALID_ENUM, "Invalid key %i", key);
return GLFW_RELEASE;
}
return _glfwPlatformGetKeyScancode(key);
}
GLFWAPI int glfwGetKey(GLFWwindow* handle, int key)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(GLFW_RELEASE);
if (key < GLFW_KEY_SPACE || key > GLFW_KEY_LAST)
{
_glfwInputError(GLFW_INVALID_ENUM, "Invalid key %i", key);
return GLFW_RELEASE;
}
if (window->keys[key] == _GLFW_STICK)
{
// Sticky mode: release key now
window->keys[key] = GLFW_RELEASE;
return GLFW_PRESS;
}
return (int) window->keys[key];
}
GLFWAPI int glfwGetMouseButton(GLFWwindow* handle, int button)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(GLFW_RELEASE);
if (button < GLFW_MOUSE_BUTTON_1 || button > GLFW_MOUSE_BUTTON_LAST)
{
_glfwInputError(GLFW_INVALID_ENUM, "Invalid mouse button %i", button);
return GLFW_RELEASE;
}
if (window->mouseButtons[button] == _GLFW_STICK)
{
// Sticky mode: release mouse button now
window->mouseButtons[button] = GLFW_RELEASE;
return GLFW_PRESS;
}
return (int) window->mouseButtons[button];
}
GLFWAPI void glfwGetCursorPos(GLFWwindow* handle, double* xpos, double* ypos)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
if (xpos)
*xpos = 0;
if (ypos)
*ypos = 0;
_GLFW_REQUIRE_INIT();
if (window->cursorMode == GLFW_CURSOR_DISABLED)
{
if (xpos)
*xpos = window->virtualCursorPosX;
if (ypos)
*ypos = window->virtualCursorPosY;
}
else
_glfwPlatformGetCursorPos(window, xpos, ypos);
}
GLFWAPI void glfwSetCursorPos(GLFWwindow* handle, double xpos, double ypos)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT();
if (xpos != xpos || xpos < -DBL_MAX || xpos > DBL_MAX ||
ypos != ypos || ypos < -DBL_MAX || ypos > DBL_MAX)
{
_glfwInputError(GLFW_INVALID_VALUE,
"Invalid cursor position %f %f",
xpos, ypos);
return;
}
if (!_glfwPlatformWindowFocused(window))
return;
if (window->cursorMode == GLFW_CURSOR_DISABLED)
{
// Only update the accumulated position if the cursor is disabled
window->virtualCursorPosX = xpos;
window->virtualCursorPosY = ypos;
}
else
{
// Update system cursor position
_glfwPlatformSetCursorPos(window, xpos, ypos);
}
}
GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot)
{
_GLFWcursor* cursor;
assert(image != NULL);
assert(image->pixels != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
if (image->width <= 0 || image->height <= 0)
{
_glfwInputError(GLFW_INVALID_VALUE, "Invalid image dimensions for cursor");
return NULL;
}
cursor = calloc(1, sizeof(_GLFWcursor));
cursor->next = _glfw.cursorListHead;
_glfw.cursorListHead = cursor;
if (!_glfwPlatformCreateCursor(cursor, image, xhot, yhot))
{
glfwDestroyCursor((GLFWcursor*) cursor);
return NULL;
}
return (GLFWcursor*) cursor;
}
GLFWAPI GLFWcursor* glfwCreateStandardCursor(int shape)
{
_GLFWcursor* cursor;
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
if (shape != GLFW_ARROW_CURSOR &&
shape != GLFW_IBEAM_CURSOR &&
shape != GLFW_CROSSHAIR_CURSOR &&
shape != GLFW_HAND_CURSOR &&
shape != GLFW_HRESIZE_CURSOR &&
shape != GLFW_VRESIZE_CURSOR &&
shape != GLFW_RESIZE_NWSE_CURSOR &&
shape != GLFW_RESIZE_NESW_CURSOR &&
shape != GLFW_RESIZE_ALL_CURSOR &&
shape != GLFW_NOT_ALLOWED_CURSOR)
{
_glfwInputError(GLFW_INVALID_ENUM, "Invalid standard cursor 0x%08X", shape);
return NULL;
}
cursor = calloc(1, sizeof(_GLFWcursor));
cursor->next = _glfw.cursorListHead;
_glfw.cursorListHead = cursor;
if (!_glfwPlatformCreateStandardCursor(cursor, shape))
{
glfwDestroyCursor((GLFWcursor*) cursor);
return NULL;
}
return (GLFWcursor*) cursor;
}
GLFWAPI void glfwDestroyCursor(GLFWcursor* handle)
{
_GLFWcursor* cursor = (_GLFWcursor*) handle;
_GLFW_REQUIRE_INIT();
if (cursor == NULL)
return;
// Make sure the cursor is not being used by any window
{
_GLFWwindow* window;
for (window = _glfw.windowListHead; window; window = window->next)
{
if (window->cursor == cursor)
glfwSetCursor((GLFWwindow*) window, NULL);
}
}
_glfwPlatformDestroyCursor(cursor);
// Unlink cursor from global linked list
{
_GLFWcursor** prev = &_glfw.cursorListHead;
while (*prev != cursor)
prev = &((*prev)->next);
*prev = cursor->next;
}
free(cursor);
}
GLFWAPI void glfwSetCursor(GLFWwindow* windowHandle, GLFWcursor* cursorHandle)
{
_GLFWwindow* window = (_GLFWwindow*) windowHandle;
_GLFWcursor* cursor = (_GLFWcursor*) cursorHandle;
assert(window != NULL);
_GLFW_REQUIRE_INIT();
window->cursor = cursor;
_glfwPlatformSetCursor(window, cursor);
}
GLFWAPI GLFWkeyfun glfwSetKeyCallback(GLFWwindow* handle, GLFWkeyfun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(window->callbacks.key, cbfun);
return cbfun;
}
GLFWAPI GLFWcharfun glfwSetCharCallback(GLFWwindow* handle, GLFWcharfun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(window->callbacks.character, cbfun);
return cbfun;
}
GLFWAPI GLFWcharmodsfun glfwSetCharModsCallback(GLFWwindow* handle, GLFWcharmodsfun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(window->callbacks.charmods, cbfun);
return cbfun;
}
GLFWAPI GLFWmousebuttonfun glfwSetMouseButtonCallback(GLFWwindow* handle,
GLFWmousebuttonfun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(window->callbacks.mouseButton, cbfun);
return cbfun;
}
GLFWAPI GLFWcursorposfun glfwSetCursorPosCallback(GLFWwindow* handle,
GLFWcursorposfun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(window->callbacks.cursorPos, cbfun);
return cbfun;
}
GLFWAPI GLFWcursorenterfun glfwSetCursorEnterCallback(GLFWwindow* handle,
GLFWcursorenterfun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(window->callbacks.cursorEnter, cbfun);
return cbfun;
}
GLFWAPI GLFWscrollfun glfwSetScrollCallback(GLFWwindow* handle,
GLFWscrollfun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(window->callbacks.scroll, cbfun);
return cbfun;
}
GLFWAPI GLFWdropfun glfwSetDropCallback(GLFWwindow* handle, GLFWdropfun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(window->callbacks.drop, cbfun);
return cbfun;
}
GLFWAPI void glfwSetClipboardString(GLFWwindow* handle, const char* string)
{
assert(string != NULL);
_GLFW_REQUIRE_INIT();
_glfwPlatformSetClipboardString(string);
}
GLFWAPI const char* glfwGetClipboardString(GLFWwindow* handle)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return _glfwPlatformGetClipboardString();
}
+486
View File
@@ -0,0 +1,486 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2012 The glfw3-go Authors
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
//go:build darwin || freebsd || linux || netbsd || openbsd
package glfw
// #include <stdlib.h>
// #define GLFW_INCLUDE_NONE
// #include "glfw3_unix.h"
//
// void goKeyCB(void* window, int key, int scancode, int action, int mods);
// void goCharCB(void* window, unsigned int character);
// void goCharModsCB(void* window, unsigned int character, int mods);
// void goMouseButtonCB(void* window, int button, int action, int mods);
// void goCursorPosCB(void* window, double xpos, double ypos);
// void goCursorEnterCB(void* window, int entered);
// void goScrollCB(void* window, double xoff, double yoff);
// void goDropCB(void* window, int count, char** names);
//
// static void glfwSetKeyCallbackCB(GLFWwindow *window) {
// glfwSetKeyCallback(window, (GLFWkeyfun)goKeyCB);
// }
//
// static void glfwSetCharCallbackCB(GLFWwindow *window) {
// glfwSetCharCallback(window, (GLFWcharfun)goCharCB);
// }
//
// static void glfwSetCharModsCallbackCB(GLFWwindow *window) {
// glfwSetCharModsCallback(window, (GLFWcharmodsfun)goCharModsCB);
// }
//
// static void glfwSetMouseButtonCallbackCB(GLFWwindow *window) {
// glfwSetMouseButtonCallback(window, (GLFWmousebuttonfun)goMouseButtonCB);
// }
//
// static void glfwSetCursorPosCallbackCB(GLFWwindow *window) {
// glfwSetCursorPosCallback(window, (GLFWcursorposfun)goCursorPosCB);
// }
//
// static void glfwSetCursorEnterCallbackCB(GLFWwindow *window) {
// glfwSetCursorEnterCallback(window, (GLFWcursorenterfun)goCursorEnterCB);
// }
//
// static void glfwSetScrollCallbackCB(GLFWwindow *window) {
// glfwSetScrollCallback(window, (GLFWscrollfun)goScrollCB);
// }
//
// static void glfwSetDropCallbackCB(GLFWwindow *window) {
// glfwSetDropCallback(window, (GLFWdropfun)goDropCB);
// }
import "C"
import (
"image"
"unsafe"
)
// Cursor represents a cursor.
type Cursor struct {
data *C.GLFWcursor
}
//export goMouseButtonCB
func goMouseButtonCB(window unsafe.Pointer, button, action, mods C.int) {
w := windows.get((*C.GLFWwindow)(window))
w.fMouseButtonHolder(w, MouseButton(button), Action(action), ModifierKey(mods))
}
//export goCursorPosCB
func goCursorPosCB(window unsafe.Pointer, xpos, ypos C.double) {
w := windows.get((*C.GLFWwindow)(window))
w.fCursorPosHolder(w, float64(xpos), float64(ypos))
}
//export goCursorEnterCB
func goCursorEnterCB(window unsafe.Pointer, entered C.int) {
w := windows.get((*C.GLFWwindow)(window))
hasEntered := entered != 0
w.fCursorEnterHolder(w, hasEntered)
}
//export goScrollCB
func goScrollCB(window unsafe.Pointer, xoff, yoff C.double) {
w := windows.get((*C.GLFWwindow)(window))
w.fScrollHolder(w, float64(xoff), float64(yoff))
}
//export goKeyCB
func goKeyCB(window unsafe.Pointer, key, scancode, action, mods C.int) {
w := windows.get((*C.GLFWwindow)(window))
w.fKeyHolder(w, Key(key), int(scancode), Action(action), ModifierKey(mods))
}
//export goCharCB
func goCharCB(window unsafe.Pointer, character C.uint) {
w := windows.get((*C.GLFWwindow)(window))
w.fCharHolder(w, rune(character))
}
//export goCharModsCB
func goCharModsCB(window unsafe.Pointer, character C.uint, mods C.int) {
w := windows.get((*C.GLFWwindow)(window))
w.fCharModsHolder(w, rune(character), ModifierKey(mods))
}
//export goDropCB
func goDropCB(window unsafe.Pointer, count C.int, names **C.char) { // TODO: The types of name can be `**C.char` or `unsafe.Pointer`, use whichever is better.
w := windows.get((*C.GLFWwindow)(window))
namesSlice := make([]string, int(count)) // TODO: Make this better. This part is unfinished, hacky, probably not correct, and not idiomatic.
for i := 0; i < int(count); i++ { // TODO: Make this better. It should be cleaned up and vetted.
var x *C.char // TODO: Make this better.
p := (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(names)) + uintptr(i)*unsafe.Sizeof(x))) // TODO: Make this better.
namesSlice[i] = C.GoString(*p) // TODO: Make this better.
}
w.fDropHolder(w, namesSlice)
}
// GetInputMode returns the value of an input option of the window.
func (w *Window) GetInputMode(mode InputMode) (int, error) {
ret := int(C.glfwGetInputMode(w.data, C.int(mode)))
if err := fetchErrorIgnoringPlatformError(); err != nil {
return 0, err
}
return ret, nil
}
// SetInputMode sets an input option for the window.
func (w *Window) SetInputMode(mode InputMode, value int) error {
C.glfwSetInputMode(w.data, C.int(mode), C.int(value))
if err := fetchErrorIgnoringPlatformError(); err != nil {
return err
}
return nil
}
// RawMouseMotionSupported returns whether raw mouse motion is supported on the
// current system. This status does not change after GLFW has been initialized
// so you only need to check this once. If you attempt to enable raw motion on
// a system that does not support it, PlatformError will be emitted.
//
// Raw mouse motion is closer to the actual motion of the mouse across a
// surface. It is not affected by the scaling and acceleration applied to the
// motion of the desktop cursor. That processing is suitable for a cursor while
// raw motion is better for controlling for example a 3D camera. Because of
// this, raw mouse motion is only provided when the cursor is disabled.
//
// This function must only be called from the main thread.
func RawMouseMotionSupported() bool {
return int(C.glfwRawMouseMotionSupported()) == True
}
// GetKeyScancode function returns the platform-specific scancode of the
// specified key.
//
// If the key is KeyUnknown or does not exist on the keyboard this method will
// return -1.
func GetKeyScancode(key Key) int {
return int(C.glfwGetKeyScancode(C.int(key)))
}
// GetKey returns the last reported state of a keyboard key. The returned state
// is one of Press or Release. The higher-level state Repeat is only reported to
// the key callback.
//
// If the StickyKeys input mode is enabled, this function returns Press the first
// time you call this function after a key has been pressed, even if the key has
// already been released.
//
// The key functions deal with physical keys, with key tokens named after their
// use on the standard US keyboard layout. If you want to input text, use the
// Unicode character callback instead.
func (w *Window) GetKey(key Key) (Action, error) {
ret := Action(C.glfwGetKey(w.data, C.int(key)))
if err := fetchErrorIgnoringPlatformError(); err != nil {
return 0, err
}
return ret, nil
}
// GetKeyName returns the localized name of the specified printable key.
//
// If the key is glfw.KeyUnknown, the scancode is used, otherwise the scancode is ignored.
func GetKeyName(key Key, scancode int) (string, error) {
ret := C.glfwGetKeyName(C.int(key), C.int(scancode))
if err := fetchErrorIgnoringPlatformError(); err != nil {
return "", err
}
return C.GoString(ret), nil
}
// GetMouseButton returns the last state reported for the specified mouse button.
//
// If the StickyMouseButtons input mode is enabled, this function returns Press
// the first time you call this function after a mouse button has been pressed,
// even if the mouse button has already been released.
func (w *Window) GetMouseButton(button MouseButton) (Action, error) {
ret := Action(C.glfwGetMouseButton(w.data, C.int(button)))
if err := fetchErrorIgnoringPlatformError(); err != nil {
return 0, err
}
return ret, nil
}
// GetCursorPos returns the last reported position of the cursor.
//
// If the cursor is disabled (with CursorDisabled) then the cursor position is
// unbounded and limited only by the minimum and maximum values of a double.
//
// The coordinate can be converted to their integer equivalents with the floor
// function. Casting directly to an integer type works for positive coordinates,
// but fails for negative ones.
func (w *Window) GetCursorPos() (x, y float64, err error) {
var xpos, ypos C.double
C.glfwGetCursorPos(w.data, &xpos, &ypos)
if err := fetchErrorIgnoringPlatformError(); err != nil {
return 0, 0, err
}
return float64(xpos), float64(ypos), nil
}
// SetCursorPos sets the position of the cursor. The specified window must
// be focused. If the window does not have focus when this function is called,
// it fails silently.
//
// If the cursor is disabled (with CursorDisabled) then the cursor position is
// unbounded and limited only by the minimum and maximum values of a double.
func (w *Window) SetCursorPos(xpos, ypos float64) error {
C.glfwSetCursorPos(w.data, C.double(xpos), C.double(ypos))
if err := fetchErrorIgnoringPlatformError(); err != nil {
return err
}
return nil
}
// CreateCursor creates a new custom cursor image that can be set for a window with SetCursor.
// The cursor can be destroyed with Destroy. Any remaining cursors are destroyed by Terminate.
//
// The image is ideally provided in the form of *image.NRGBA.
// The pixels are 32-bit, little-endian, non-premultiplied RGBA, i.e. eight
// bits per channel with the red channel first. They are arranged canonically
// as packed sequential rows, starting from the top-left corner. If the image
// type is not *image.NRGBA, it will be converted to it.
//
// The cursor hotspot is specified in pixels, relative to the upper-left corner of the cursor image.
// Like all other coordinate systems in GLFW, the X-axis points to the right and the Y-axis points down.
func CreateCursor(img image.Image, xhot, yhot int) (*Cursor, error) {
glfwImg, free := imageToGLFWImage(img)
defer free()
c := C.glfwCreateCursor(&glfwImg, C.int(xhot), C.int(yhot))
if err := fetchErrorIgnoringPlatformError(); err != nil {
return nil, err
}
return &Cursor{c}, nil
}
// CreateStandardCursor returns a cursor with a standard shape,
// that can be set for a window with SetCursor.
func CreateStandardCursor(shape StandardCursor) (*Cursor, error) {
c := C.glfwCreateStandardCursor(C.int(shape))
if err := fetchErrorIgnoringPlatformError(); err != nil {
return nil, err
}
return &Cursor{c}, nil
}
// Destroy destroys a cursor previously created with CreateCursor.
// Any remaining cursors will be destroyed by Terminate.
func (c *Cursor) Destroy() error {
C.glfwDestroyCursor(c.data)
if err := fetchErrorIgnoringPlatformError(); err != nil {
return err
}
return nil
}
// SetCursor sets the cursor image to be used when the cursor is over the client area
// of the specified window. The set cursor will only be visible when the cursor mode of the
// window is CursorNormal.
//
// On some platforms, the set cursor may not be visible unless the window also has input focus.
func (w *Window) SetCursor(c *Cursor) error {
if c == nil {
C.glfwSetCursor(w.data, nil)
} else {
C.glfwSetCursor(w.data, c.data)
}
if err := fetchErrorIgnoringPlatformError(); err != nil {
return err
}
return nil
}
// KeyCallback is the key callback.
type KeyCallback func(w *Window, key Key, scancode int, action Action, mods ModifierKey)
// SetKeyCallback sets the key callback which is called when a key is pressed,
// repeated or released.
//
// The key functions deal with physical keys, with layout independent key tokens
// named after their values in the standard US keyboard layout. If you want to
// input text, use the SetCharCallback instead.
//
// When a window loses focus, it will generate synthetic key release events for
// all pressed keys. You can tell these events from user-generated events by the
// fact that the synthetic ones are generated after the window has lost focus,
// i.e. Focused will be false and the focus callback will have already been
// called.
func (w *Window) SetKeyCallback(cbfun KeyCallback) (previous KeyCallback, err error) {
previous = w.fKeyHolder
w.fKeyHolder = cbfun
if cbfun == nil {
C.glfwSetKeyCallback(w.data, nil)
} else {
C.glfwSetKeyCallbackCB(w.data)
}
if err := fetchErrorIgnoringPlatformError(); err != nil {
return nil, err
}
return previous, nil
}
// CharCallback is the character callback.
type CharCallback func(w *Window, char rune)
// SetCharCallback sets the character callback which is called when a
// Unicode character is input.
//
// The character callback is intended for Unicode text input. As it deals with
// characters, it is keyboard layout dependent, whereas the
// key callback is not. Characters do not map 1:1
// to physical keys, as a key may produce zero, one or more characters. If you
// want to know whether a specific physical key was pressed or released, see
// the key callback instead.
//
// The character callback behaves as system text input normally does and will
// not be called if modifier keys are held down that would prevent normal text
// input on that platform, for example a Super (Command) key on OS X or Alt key
// on Windows. There is a character with modifiers callback that receives these events.
func (w *Window) SetCharCallback(cbfun CharCallback) (previous CharCallback, err error) {
previous = w.fCharHolder
w.fCharHolder = cbfun
if cbfun == nil {
C.glfwSetCharCallback(w.data, nil)
} else {
C.glfwSetCharCallbackCB(w.data)
}
if err := fetchErrorIgnoringPlatformError(); err != nil {
return nil, err
}
return previous, nil
}
// CharModsCallback is the character with modifiers callback.
type CharModsCallback func(w *Window, char rune, mods ModifierKey)
// SetCharModsCallback sets the character with modifiers callback which is called when a
// Unicode character is input regardless of what modifier keys are used.
//
// Deprecated: Scheduled for removal in version 4.0.
//
// The character with modifiers callback is intended for implementing custom
// Unicode character input. For regular Unicode text input, see the
// character callback. Like the character callback, the character with modifiers callback
// deals with characters and is keyboard layout dependent. Characters do not
// map 1:1 to physical keys, as a key may produce zero, one or more characters.
// If you want to know whether a specific physical key was pressed or released,
// see the key callback instead.
func (w *Window) SetCharModsCallback(cbfun CharModsCallback) (previous CharModsCallback, err error) {
previous = w.fCharModsHolder
w.fCharModsHolder = cbfun
if cbfun == nil {
C.glfwSetCharModsCallback(w.data, nil)
} else {
C.glfwSetCharModsCallbackCB(w.data)
}
if err := fetchErrorIgnoringPlatformError(); err != nil {
return nil, err
}
return previous, nil
}
// MouseButtonCallback is the mouse button callback.
type MouseButtonCallback func(w *Window, button MouseButton, action Action, mods ModifierKey)
// SetMouseButtonCallback sets the mouse button callback which is called when a
// mouse button is pressed or released.
//
// When a window loses focus, it will generate synthetic mouse button release
// events for all pressed mouse buttons. You can tell these events from
// user-generated events by the fact that the synthetic ones are generated after
// the window has lost focus, i.e. Focused will be false and the focus
// callback will have already been called.
func (w *Window) SetMouseButtonCallback(cbfun MouseButtonCallback) (previous MouseButtonCallback, err error) {
previous = w.fMouseButtonHolder
w.fMouseButtonHolder = cbfun
if cbfun == nil {
C.glfwSetMouseButtonCallback(w.data, nil)
} else {
C.glfwSetMouseButtonCallbackCB(w.data)
}
if err := fetchErrorIgnoringPlatformError(); err != nil {
return nil, err
}
return previous, nil
}
// CursorPosCallback the cursor position callback.
type CursorPosCallback func(w *Window, xpos float64, ypos float64)
// SetCursorPosCallback sets the cursor position callback which is called
// when the cursor is moved. The callback is provided with the position relative
// to the upper-left corner of the client area of the window.
func (w *Window) SetCursorPosCallback(cbfun CursorPosCallback) (previous CursorPosCallback, err error) {
previous = w.fCursorPosHolder
w.fCursorPosHolder = cbfun
if cbfun == nil {
C.glfwSetCursorPosCallback(w.data, nil)
} else {
C.glfwSetCursorPosCallbackCB(w.data)
}
if err := fetchErrorIgnoringPlatformError(); err != nil {
return nil, err
}
return previous, nil
}
// CursorEnterCallback is the cursor boundary crossing callback.
type CursorEnterCallback func(w *Window, entered bool)
// SetCursorEnterCallback the cursor boundary crossing callback which is called
// when the cursor enters or leaves the client area of the window.
func (w *Window) SetCursorEnterCallback(cbfun CursorEnterCallback) (previous CursorEnterCallback, err error) {
previous = w.fCursorEnterHolder
w.fCursorEnterHolder = cbfun
if cbfun == nil {
C.glfwSetCursorEnterCallback(w.data, nil)
} else {
C.glfwSetCursorEnterCallbackCB(w.data)
}
if err := fetchErrorIgnoringPlatformError(); err != nil {
return nil, err
}
return previous, nil
}
// ScrollCallback is the scroll callback.
type ScrollCallback func(w *Window, xoff float64, yoff float64)
// SetScrollCallback sets the scroll callback which is called when a scrolling
// device is used, such as a mouse wheel or scrolling area of a touchpad.
func (w *Window) SetScrollCallback(cbfun ScrollCallback) (previous ScrollCallback, err error) {
previous = w.fScrollHolder
w.fScrollHolder = cbfun
if cbfun == nil {
C.glfwSetScrollCallback(w.data, nil)
} else {
C.glfwSetScrollCallbackCB(w.data)
}
if err := fetchErrorIgnoringPlatformError(); err != nil {
return nil, err
}
return previous, nil
}
// DropCallback is the drop callback.
type DropCallback func(w *Window, names []string)
// SetDropCallback sets the drop callback which is called when an object
// is dropped over the window.
func (w *Window) SetDropCallback(cbfun DropCallback) (previous DropCallback, err error) {
previous = w.fDropHolder
w.fDropHolder = cbfun
if cbfun == nil {
C.glfwSetDropCallback(w.data, nil)
} else {
C.glfwSetDropCallbackCB(w.data)
}
if err := fetchErrorIgnoringPlatformError(); err != nil {
return nil, err
}
return previous, nil
}
+502
View File
@@ -0,0 +1,502 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard
// SPDX-FileCopyrightText: 2006-2019 Camilla Löwy <elmindreda@glfw.org>
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
package glfw
import (
"fmt"
"math"
)
const stick = 3
func (w *Window) inputKey(key Key, scancode int, action Action, mods ModifierKey) {
if key >= 0 && key <= KeyLast {
var repeated bool
if action == Release && w.keys[key] == Release {
return
}
if action == Press && w.keys[key] == Press {
repeated = true
}
if action == Release && w.stickyKeys {
w.keys[key] = stick
} else {
w.keys[key] = action
}
if repeated {
action = Repeat
}
}
if !w.lockKeyMods {
mods &^= ModCapsLock | ModNumLock
}
if w.callbacks.key != nil {
w.callbacks.key(w, key, scancode, action, mods)
}
}
func (w *Window) inputChar(codepoint rune, mods ModifierKey, plain bool) {
if codepoint < 32 || (codepoint > 126 && codepoint < 160) {
return
}
if !w.lockKeyMods {
mods &^= ModCapsLock | ModNumLock
}
if w.callbacks.charmods != nil {
w.callbacks.charmods(w, codepoint, mods)
}
if plain {
if w.callbacks.character != nil {
w.callbacks.character(w, codepoint)
}
}
}
func (w *Window) inputScroll(xoffset, yoffset float64) {
if w.callbacks.scroll != nil {
w.callbacks.scroll(w, xoffset, yoffset)
}
}
func (w *Window) inputMouseClick(button MouseButton, action Action, mods ModifierKey) {
if button < 0 || button > MouseButtonLast {
return
}
if !w.lockKeyMods {
mods &^= ModCapsLock | ModNumLock
}
if action == Release && w.stickyMouseButtons {
w.mouseButtons[button] = stick
} else {
w.mouseButtons[button] = action
}
if w.callbacks.mouseButton != nil {
w.callbacks.mouseButton(w, button, action, mods)
}
}
func (w *Window) inputCursorPos(xpos float64, ypos float64) {
if w.virtualCursorPosX == xpos && w.virtualCursorPosY == ypos {
return
}
w.virtualCursorPosX = xpos
w.virtualCursorPosY = ypos
if w.callbacks.cursorPos != nil {
w.callbacks.cursorPos(w, xpos, ypos)
}
}
func (w *Window) inputCursorEnter(entered bool) {
if w.callbacks.cursorEnter != nil {
w.callbacks.cursorEnter(w, entered)
}
}
func (w *Window) inputDrop(paths []string) {
if w.callbacks.drop != nil {
w.callbacks.drop(w, paths)
}
}
func (w *Window) centerCursorInContentArea() error {
width, height, err := w.platformGetWindowSize()
if err != nil {
return err
}
if err := w.platformSetCursorPos(float64(width/2), float64(height/2)); err != nil {
return err
}
return nil
}
func (w *Window) GetInputMode(mode InputMode) (int, error) {
if !_glfw.initialized {
return 0, NotInitialized
}
switch mode {
case CursorMode:
return w.cursorMode, nil
case StickyKeysMode:
return boolToInt(w.stickyKeys), nil
case StickyMouseButtonsMode:
return boolToInt(w.stickyMouseButtons), nil
case LockKeyMods:
return boolToInt(w.lockKeyMods), nil
case RawMouseMotion:
return boolToInt(w.rawMouseMotion), nil
default:
return 0, fmt.Errorf("glfw: invalid input mode 0x%08X: %w", mode, InvalidEnum)
}
}
func (w *Window) SetInputMode(mode InputMode, value int) error {
if !_glfw.initialized {
return NotInitialized
}
switch mode {
case CursorMode:
if value != CursorNormal && value != CursorHidden && value != CursorDisabled {
return fmt.Errorf("glfw: invalid cursor mode 0x%08X: %w", value, InvalidEnum)
}
if w.cursorMode == value {
return nil
}
w.cursorMode = value
x, y, err := w.platformGetCursorPos()
if err != nil {
return err
}
w.virtualCursorPosX = x
w.virtualCursorPosY = y
if err := w.platformSetCursorMode(value); err != nil {
return err
}
return nil
case StickyKeysMode:
if w.stickyKeys == intToBool(value) {
return nil
}
if !intToBool(value) {
// Release all sticky keys
for i := Key(0); i <= KeyLast; i++ {
if w.keys[i] == stick {
w.keys[i] = Release
}
}
}
w.stickyKeys = intToBool(value)
return nil
case StickyMouseButtonsMode:
if w.stickyMouseButtons == intToBool(value) {
return nil
}
if !intToBool(value) {
// Release all sticky mouse buttons
for i := MouseButton(0); i <= MouseButtonLast; i++ {
if w.mouseButtons[i] == stick {
w.mouseButtons[i] = Release
}
}
}
w.stickyMouseButtons = intToBool(value)
return nil
case LockKeyMods:
w.lockKeyMods = intToBool(value)
return nil
case RawMouseMotion:
if !platformRawMouseMotionSupported() {
return fmt.Errorf("glfw: raw mouse motion is not supported on this system: %w", PlatformError)
}
if w.rawMouseMotion == intToBool(value) {
return nil
}
w.rawMouseMotion = intToBool(value)
if err := w.platformSetRawMouseMotion(intToBool(value)); err != nil {
return err
}
return nil
default:
return fmt.Errorf("glfw: invalid input mode 0x%08X: %w", mode, InvalidEnum)
}
}
func RawMouseMotionSupported() (bool, error) {
if !_glfw.initialized {
return false, NotInitialized
}
return platformRawMouseMotionSupported(), nil
}
func GetKeyName(key Key, scancode int) (string, error) {
if !_glfw.initialized {
return "", NotInitialized
}
if key != KeyUnknown {
if key != KeyKPEqual && (key < KeyKP0 || key > KeyKPAdd) && (key < KeyApostrophe || key > KeyWorld2) {
return "", nil
}
scancode = platformGetKeyScancode(key)
}
return platformGetScancodeName(scancode)
}
func GetKeyScancode(key Key) (int, error) {
if !_glfw.initialized {
return 0, NotInitialized
}
if key < KeySpace || key > KeyLast {
return 0, fmt.Errorf("glfw: invalid key %d: %w", key, InvalidEnum)
}
return platformGetKeyScancode(key), nil
}
func (w *Window) GetKey(key Key) (Action, error) {
if !_glfw.initialized {
return 0, NotInitialized
}
if key < KeySpace || key > KeyLast {
return 0, fmt.Errorf("glfw: invalid key %d: %w", key, InvalidEnum)
}
if w.keys[key] == stick {
// Sticky mode: release key now
w.keys[key] = Release
return Press, nil
}
return w.keys[key], nil
}
func (w *Window) GetMouseButton(button MouseButton) (Action, error) {
if !_glfw.initialized {
return 0, NotInitialized
}
if button < MouseButton1 || button > MouseButtonLast {
return 0, fmt.Errorf("glfw: invalid mouse button %d: %w", button, InvalidEnum)
}
if w.mouseButtons[button] == stick {
// Sticky mode: release mouse button now
w.mouseButtons[button] = Release
return Press, nil
}
return w.mouseButtons[button], nil
}
func (w *Window) GetCursorPos() (xpos, ypos float64, err error) {
if !_glfw.initialized {
return 0, 0, NotInitialized
}
if w.cursorMode == CursorDisabled {
return w.virtualCursorPosX, w.virtualCursorPosY, nil
} else {
return w.platformGetCursorPos()
}
}
func (w *Window) SetCursorPos(xpos, ypos float64) error {
if !_glfw.initialized {
return NotInitialized
}
if xpos != xpos || xpos < -math.MaxFloat64 || xpos > math.MaxFloat64 || ypos != ypos || ypos < -math.MaxFloat64 || ypos > math.MaxFloat64 {
return fmt.Errorf("glfw: invalid cursor position %f %f: %w", xpos, ypos, InvalidValue)
}
if !w.platformWindowFocused() {
return nil
}
if w.cursorMode == CursorDisabled {
// Only update the accumulated position if the cursor is disabled
w.virtualCursorPosX = xpos
w.virtualCursorPosY = ypos
return nil
} else {
// Update system cursor position
return w.platformSetCursorPos(xpos, ypos)
}
}
func CreateStandardCursor(shape StandardCursor) (*Cursor, error) {
if !_glfw.initialized {
return nil, NotInitialized
}
if shape != ArrowCursor &&
shape != IBeamCursor &&
shape != CrosshairCursor &&
shape != HandCursor &&
shape != HResizeCursor &&
shape != VResizeCursor &&
shape != ResizeNWSECursor &&
shape != ResizeNESWCursor &&
shape != ResizeAllCursor &&
shape != NotAllowedCursor {
return nil, fmt.Errorf("glfw: invalid standard cursor 0x%08X: %w", shape, InvalidEnum)
}
cursor := &Cursor{}
_glfw.cursors = append(_glfw.cursors, cursor)
if err := cursor.platformCreateStandardCursor(shape); err != nil {
_ = cursor.Destroy()
return nil, err
}
return cursor, nil
}
func (c *Cursor) Destroy() error {
if !_glfw.initialized {
return NotInitialized
}
if c == nil {
return nil
}
// Make sure the cursor is not being used by any window
for _, window := range _glfw.windows {
if window.cursor == c {
if err := window.SetCursor(nil); err != nil {
return err
}
}
}
if err := c.platformDestroyCursor(); err != nil {
return err
}
// Unlink cursor from global linked list
for i, cursor := range _glfw.cursors {
if cursor == c {
copy(_glfw.cursors[i:], _glfw.cursors[i+1:])
_glfw.cursors = _glfw.cursors[:len(_glfw.cursors)-1]
break
}
}
return nil
}
func (w *Window) SetCursor(cursor *Cursor) error {
if !_glfw.initialized {
return NotInitialized
}
w.cursor = cursor
if err := w.platformSetCursor(cursor); err != nil {
return err
}
return nil
}
func (w *Window) SetKeyCallback(cbfun KeyCallback) (KeyCallback, error) {
if !_glfw.initialized {
return nil, NotInitialized
}
old := w.callbacks.key
w.callbacks.key = cbfun
return old, nil
}
func (w *Window) SetCharCallback(cbfun CharCallback) (CharCallback, error) {
if !_glfw.initialized {
return nil, NotInitialized
}
old := w.callbacks.character
w.callbacks.character = cbfun
return old, nil
}
func (w *Window) SetCharModsCallback(cbfun CharModsCallback) (CharModsCallback, error) {
if !_glfw.initialized {
return nil, NotInitialized
}
old := w.callbacks.charmods
w.callbacks.charmods = cbfun
return old, nil
}
func (w *Window) SetMouseButtonCallback(cbfun MouseButtonCallback) (MouseButtonCallback, error) {
if !_glfw.initialized {
return nil, NotInitialized
}
old := w.callbacks.mouseButton
w.callbacks.mouseButton = cbfun
return old, nil
}
func (w *Window) SetCursorPosCallback(cbfun CursorPosCallback) (CursorPosCallback, error) {
if !_glfw.initialized {
return nil, NotInitialized
}
old := w.callbacks.cursorPos
w.callbacks.cursorPos = cbfun
return old, nil
}
func (w *Window) SetCursorEnterCallback(cbfun CursorEnterCallback) (CursorEnterCallback, error) {
if !_glfw.initialized {
return nil, NotInitialized
}
old := w.callbacks.cursorEnter
w.callbacks.cursorEnter = cbfun
return old, nil
}
func (w *Window) SetScrollCallback(cbfun ScrollCallback) (ScrollCallback, error) {
if !_glfw.initialized {
return nil, NotInitialized
}
old := w.callbacks.scroll
w.callbacks.scroll = cbfun
return old, nil
}
func (w *Window) SetDropCallback(cbfun DropCallback) (DropCallback, error) {
if !_glfw.initialized {
return nil, NotInitialized
}
old := w.callbacks.drop
w.callbacks.drop = cbfun
return old, nil
}
func (w *Window) SetClipboardString(str string) error {
if !_glfw.initialized {
return NotInitialized
}
return platformSetClipboardString(str)
}
func GetClipboardString() (string, error) {
if !_glfw.initialized {
return "", NotInitialized
}
return platformGetClipboardString()
}
+595
View File
@@ -0,0 +1,595 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard
// SPDX-FileCopyrightText: 2006-2019 Camilla Löwy <elmindreda@glfw.org>
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
//go:build darwin || freebsd || linux || netbsd || openbsd
#pragma once
#if defined(_GLFW_USE_CONFIG_H)
#include "glfw_config.h"
#endif
#if defined(GLFW_INCLUDE_GLCOREARB) || \
defined(GLFW_INCLUDE_ES1) || \
defined(GLFW_INCLUDE_ES2) || \
defined(GLFW_INCLUDE_ES3) || \
defined(GLFW_INCLUDE_ES31) || \
defined(GLFW_INCLUDE_ES32) || \
defined(GLFW_INCLUDE_NONE) || \
defined(GLFW_INCLUDE_GLEXT) || \
defined(GLFW_INCLUDE_GLU) || \
defined(GLFW_DLL)
#error "You must not define any header option macros when compiling GLFW"
#endif
#define GLFW_INCLUDE_NONE
#include "glfw3_unix.h"
#define _GLFW_INSERT_FIRST 0
#define _GLFW_INSERT_LAST 1
#define _GLFW_POLL_PRESENCE 0
#define _GLFW_POLL_AXES 1
#define _GLFW_POLL_BUTTONS 2
#define _GLFW_POLL_ALL (_GLFW_POLL_AXES | _GLFW_POLL_BUTTONS)
#define _GLFW_MESSAGE_SIZE 1024
typedef int GLFWbool;
typedef struct _GLFWerror _GLFWerror;
typedef struct _GLFWinitconfig _GLFWinitconfig;
typedef struct _GLFWwndconfig _GLFWwndconfig;
typedef struct _GLFWctxconfig _GLFWctxconfig;
typedef struct _GLFWfbconfig _GLFWfbconfig;
typedef struct _GLFWcontext _GLFWcontext;
typedef struct _GLFWwindow _GLFWwindow;
typedef struct _GLFWlibrary _GLFWlibrary;
typedef struct _GLFWmonitor _GLFWmonitor;
typedef struct _GLFWcursor _GLFWcursor;
typedef struct _GLFWmapelement _GLFWmapelement;
typedef struct _GLFWtls _GLFWtls;
typedef struct _GLFWmutex _GLFWmutex;
typedef void (* _GLFWmakecontextcurrentfun)(_GLFWwindow*);
typedef void (* _GLFWswapbuffersfun)(_GLFWwindow*);
typedef void (* _GLFWswapintervalfun)(int);
typedef int (* _GLFWextensionsupportedfun)(const char*);
typedef GLFWglproc (* _GLFWgetprocaddressfun)(const char*);
typedef void (* _GLFWdestroycontextfun)(_GLFWwindow*);
#define GL_VERSION 0x1f02
#define GL_NONE 0
#define GL_COLOR_BUFFER_BIT 0x00004000
#define GL_UNSIGNED_BYTE 0x1401
#define GL_EXTENSIONS 0x1f03
#define GL_NUM_EXTENSIONS 0x821d
#define GL_CONTEXT_FLAGS 0x821e
#define GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT 0x00000001
#define GL_CONTEXT_FLAG_DEBUG_BIT 0x00000002
#define GL_CONTEXT_PROFILE_MASK 0x9126
#define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002
#define GL_CONTEXT_CORE_PROFILE_BIT 0x00000001
#define GL_RESET_NOTIFICATION_STRATEGY_ARB 0x8256
#define GL_LOSE_CONTEXT_ON_RESET_ARB 0x8252
#define GL_NO_RESET_NOTIFICATION_ARB 0x8261
#define GL_CONTEXT_RELEASE_BEHAVIOR 0x82fb
#define GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH 0x82fc
#define GL_CONTEXT_FLAG_NO_ERROR_BIT_KHR 0x00000008
typedef int GLint;
typedef unsigned int GLuint;
typedef unsigned int GLenum;
typedef unsigned int GLbitfield;
typedef unsigned char GLubyte;
typedef void (APIENTRY * PFNGLCLEARPROC)(GLbitfield);
typedef const GLubyte* (APIENTRY * PFNGLGETSTRINGPROC)(GLenum);
typedef void (APIENTRY * PFNGLGETINTEGERVPROC)(GLenum,GLint*);
typedef const GLubyte* (APIENTRY * PFNGLGETSTRINGIPROC)(GLenum,GLuint);
#if defined(_GLFW_COCOA)
#include "cocoa_platform_darwin.h"
#elif defined(_GLFW_X11)
#include "x11_platform_linbsd.h"
#else
#error "No supported window creation API selected"
#endif
// Constructs a version number string from the public header macros
#define _GLFW_CONCAT_VERSION(m, n, r) #m "." #n "." #r
#define _GLFW_MAKE_VERSION(m, n, r) _GLFW_CONCAT_VERSION(m, n, r)
#define _GLFW_VERSION_NUMBER _GLFW_MAKE_VERSION(GLFW_VERSION_MAJOR, \
GLFW_VERSION_MINOR, \
GLFW_VERSION_REVISION)
// Checks for whether the library has been initialized
#define _GLFW_REQUIRE_INIT() \
if (!_glfw.initialized) \
{ \
_glfwInputError(GLFW_NOT_INITIALIZED, NULL); \
return; \
}
#define _GLFW_REQUIRE_INIT_OR_RETURN(x) \
if (!_glfw.initialized) \
{ \
_glfwInputError(GLFW_NOT_INITIALIZED, NULL); \
return x; \
}
// Swaps the provided pointers
#define _GLFW_SWAP_POINTERS(x, y) \
{ \
void* t; \
t = x; \
x = y; \
y = t; \
}
// Per-thread error structure
//
struct _GLFWerror
{
_GLFWerror* next;
int code;
char description[_GLFW_MESSAGE_SIZE];
};
// Initialization configuration
//
// Parameters relating to the initialization of the library
//
struct _GLFWinitconfig
{
struct {
GLFWbool menubar;
GLFWbool chdir;
} ns;
};
// Window configuration
//
// Parameters relating to the creation of the window but not directly related
// to the framebuffer. This is used to pass window creation parameters from
// shared code to the platform API.
//
struct _GLFWwndconfig
{
int width;
int height;
const char* title;
GLFWbool resizable;
GLFWbool visible;
GLFWbool decorated;
GLFWbool focused;
GLFWbool autoIconify;
GLFWbool floating;
GLFWbool maximized;
GLFWbool centerCursor;
GLFWbool focusOnShow;
GLFWbool mousePassthrough;
GLFWbool scaleToMonitor;
struct {
GLFWbool retina;
char frameName[256];
} ns;
struct {
char className[256];
char instanceName[256];
} x11;
};
// Context configuration
//
// Parameters relating to the creation of the context but not directly related
// to the framebuffer. This is used to pass context creation parameters from
// shared code to the platform API.
//
struct _GLFWctxconfig
{
int client;
int source;
int major;
int minor;
GLFWbool forward;
GLFWbool debug;
GLFWbool noerror;
int profile;
int robustness;
int release;
_GLFWwindow* share;
struct {
GLFWbool offline;
} nsgl;
};
// Framebuffer configuration
//
// This describes buffers and their sizes. It also contains
// a platform-specific ID used to map back to the backend API object.
//
// It is used to pass framebuffer parameters from shared code to the platform
// API and also to enumerate and select available framebuffer configs.
//
struct _GLFWfbconfig
{
int redBits;
int greenBits;
int blueBits;
int alphaBits;
int depthBits;
int stencilBits;
int accumRedBits;
int accumGreenBits;
int accumBlueBits;
int accumAlphaBits;
int auxBuffers;
GLFWbool stereo;
int samples;
GLFWbool sRGB;
GLFWbool doublebuffer;
GLFWbool transparent;
uintptr_t handle;
};
// Context structure
//
struct _GLFWcontext
{
int client;
int source;
int major, minor, revision;
GLFWbool forward, debug, noerror;
int profile;
int robustness;
int release;
PFNGLGETSTRINGIPROC GetStringi;
PFNGLGETINTEGERVPROC GetIntegerv;
PFNGLGETSTRINGPROC GetString;
_GLFWmakecontextcurrentfun makeCurrent;
_GLFWswapbuffersfun swapBuffers;
_GLFWswapintervalfun swapInterval;
_GLFWextensionsupportedfun extensionSupported;
_GLFWgetprocaddressfun getProcAddress;
_GLFWdestroycontextfun destroy;
// This is defined in the context API's context.h
_GLFW_PLATFORM_CONTEXT_STATE;
// This is defined in egl_context_unix.h
_GLFW_EGL_CONTEXT_STATE;
// This is defined in osmesa_context_unix.h
_GLFW_OSMESA_CONTEXT_STATE;
};
// Window and context structure
//
struct _GLFWwindow
{
struct _GLFWwindow* next;
// Window settings and state
GLFWbool resizable;
GLFWbool decorated;
GLFWbool autoIconify;
GLFWbool floating;
GLFWbool focusOnShow;
GLFWbool mousePassthrough;
GLFWbool shouldClose;
void* userPointer;
GLFWbool doublebuffer;
GLFWvidmode videoMode;
_GLFWmonitor* monitor;
_GLFWcursor* cursor;
int minwidth, minheight;
int maxwidth, maxheight;
int numer, denom;
GLFWbool stickyKeys;
GLFWbool stickyMouseButtons;
GLFWbool lockKeyMods;
int cursorMode;
char mouseButtons[GLFW_MOUSE_BUTTON_LAST + 1];
char keys[GLFW_KEY_LAST + 1];
// Virtual cursor position when cursor is disabled
double virtualCursorPosX, virtualCursorPosY;
GLFWbool rawMouseMotion;
_GLFWcontext context;
struct {
GLFWwindowposfun pos;
GLFWwindowsizefun size;
GLFWwindowclosefun close;
GLFWwindowrefreshfun refresh;
GLFWwindowfocusfun focus;
GLFWwindowiconifyfun iconify;
GLFWwindowmaximizefun maximize;
GLFWframebuffersizefun fbsize;
GLFWwindowcontentscalefun scale;
GLFWmousebuttonfun mouseButton;
GLFWcursorposfun cursorPos;
GLFWcursorenterfun cursorEnter;
GLFWscrollfun scroll;
GLFWkeyfun key;
GLFWcharfun character;
GLFWcharmodsfun charmods;
GLFWdropfun drop;
} callbacks;
// This is defined in the window API's platform.h
_GLFW_PLATFORM_WINDOW_STATE;
};
// Monitor structure
//
struct _GLFWmonitor
{
char name[128];
void* userPointer;
// Physical dimensions in millimeters.
int widthMM, heightMM;
// The window whose video mode is current on this monitor
_GLFWwindow* window;
GLFWvidmode* modes;
int modeCount;
GLFWvidmode currentMode;
GLFWgammaramp originalRamp;
GLFWgammaramp currentRamp;
// This is defined in the window API's platform.h
_GLFW_PLATFORM_MONITOR_STATE;
};
// Cursor structure
//
struct _GLFWcursor
{
_GLFWcursor* next;
// This is defined in the window API's platform.h
_GLFW_PLATFORM_CURSOR_STATE;
};
// Thread local storage structure
//
struct _GLFWtls
{
// This is defined in the platform's thread.h
_GLFW_PLATFORM_TLS_STATE;
};
// Mutex structure
//
struct _GLFWmutex
{
// This is defined in the platform's thread.h
_GLFW_PLATFORM_MUTEX_STATE;
};
// Library global data
//
struct _GLFWlibrary
{
GLFWbool initialized;
struct {
_GLFWinitconfig init;
_GLFWfbconfig framebuffer;
_GLFWwndconfig window;
_GLFWctxconfig context;
int refreshRate;
} hints;
_GLFWerror* errorListHead;
_GLFWcursor* cursorListHead;
_GLFWwindow* windowListHead;
_GLFWmonitor** monitors;
int monitorCount;
_GLFWtls errorSlot;
_GLFWtls contextSlot;
_GLFWmutex errorLock;
struct {
uint64_t offset;
// This is defined in the platform's time.h
_GLFW_PLATFORM_LIBRARY_TIMER_STATE;
} timer;
struct {
GLFWmonitorfun monitor;
} callbacks;
// This is defined in the window API's platform.h
_GLFW_PLATFORM_LIBRARY_WINDOW_STATE;
// This is defined in the context API's context.h
_GLFW_PLATFORM_LIBRARY_CONTEXT_STATE;
// This is defined in egl_context_unix.h
_GLFW_EGL_LIBRARY_CONTEXT_STATE;
// This is defined in osmesa_context_unix.h
_GLFW_OSMESA_LIBRARY_CONTEXT_STATE;
};
// Global state shared between compilation units of GLFW
//
extern _GLFWlibrary _glfw;
//////////////////////////////////////////////////////////////////////////
////// GLFW platform API //////
//////////////////////////////////////////////////////////////////////////
int _glfwPlatformInit(void);
void _glfwPlatformTerminate(void);
const char* _glfwPlatformGetVersionString(void);
void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos);
void _glfwPlatformSetCursorPos(_GLFWwindow* window, double xpos, double ypos);
void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode);
void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window, GLFWbool enabled);
GLFWbool _glfwPlatformRawMouseMotionSupported(void);
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
const GLFWimage* image, int xhot, int yhot);
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape);
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor);
void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor);
const char* _glfwPlatformGetScancodeName(int scancode);
int _glfwPlatformGetKeyScancode(int key);
void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor);
void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos);
void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor,
float* xscale, float* yscale);
void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor, int* xpos, int* ypos, int *width, int *height);
GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count);
void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode);
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp);
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp);
void _glfwPlatformSetClipboardString(const char* string);
const char* _glfwPlatformGetClipboardString(void);
uint64_t _glfwPlatformGetTimerValue(void);
uint64_t _glfwPlatformGetTimerFrequency(void);
int _glfwPlatformCreateWindow(_GLFWwindow* window,
const _GLFWwndconfig* wndconfig,
const _GLFWctxconfig* ctxconfig,
const _GLFWfbconfig* fbconfig);
void _glfwPlatformDestroyWindow(_GLFWwindow* window);
void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title);
void _glfwPlatformSetWindowIcon(_GLFWwindow* window,
int count, const GLFWimage* images);
void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos);
void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos);
void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height);
void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height);
void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
int minwidth, int minheight,
int maxwidth, int maxheight);
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom);
void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height);
void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
int* left, int* top,
int* right, int* bottom);
void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
float* xscale, float* yscale);
void _glfwPlatformIconifyWindow(_GLFWwindow* window);
void _glfwPlatformRestoreWindow(_GLFWwindow* window);
void _glfwPlatformMaximizeWindow(_GLFWwindow* window);
void _glfwPlatformShowWindow(_GLFWwindow* window);
void _glfwPlatformHideWindow(_GLFWwindow* window);
void _glfwPlatformRequestWindowAttention(_GLFWwindow* window);
void _glfwPlatformFocusWindow(_GLFWwindow* window);
void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor,
int xpos, int ypos, int width, int height,
int refreshRate);
int _glfwPlatformWindowFocused(_GLFWwindow* window);
int _glfwPlatformWindowIconified(_GLFWwindow* window);
int _glfwPlatformWindowVisible(_GLFWwindow* window);
int _glfwPlatformWindowMaximized(_GLFWwindow* window);
int _glfwPlatformWindowHovered(_GLFWwindow* window);
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window);
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window);
void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled);
void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled);
void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled);
void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity);
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, GLFWbool enabled);
void _glfwPlatformPollEvents(void);
void _glfwPlatformWaitEvents(void);
void _glfwPlatformWaitEventsTimeout(double timeout);
void _glfwPlatformPostEmptyEvent(void);
GLFWbool _glfwPlatformCreateTls(_GLFWtls* tls);
void _glfwPlatformDestroyTls(_GLFWtls* tls);
void* _glfwPlatformGetTls(_GLFWtls* tls);
void _glfwPlatformSetTls(_GLFWtls* tls, void* value);
GLFWbool _glfwPlatformCreateMutex(_GLFWmutex* mutex);
void _glfwPlatformDestroyMutex(_GLFWmutex* mutex);
void _glfwPlatformLockMutex(_GLFWmutex* mutex);
void _glfwPlatformUnlockMutex(_GLFWmutex* mutex);
//////////////////////////////////////////////////////////////////////////
////// GLFW event API //////
//////////////////////////////////////////////////////////////////////////
void _glfwInputWindowFocus(_GLFWwindow* window, GLFWbool focused);
void _glfwInputWindowPos(_GLFWwindow* window, int xpos, int ypos);
void _glfwInputWindowSize(_GLFWwindow* window, int width, int height);
void _glfwInputFramebufferSize(_GLFWwindow* window, int width, int height);
void _glfwInputWindowContentScale(_GLFWwindow* window,
float xscale, float yscale);
void _glfwInputWindowIconify(_GLFWwindow* window, GLFWbool iconified);
void _glfwInputWindowMaximize(_GLFWwindow* window, GLFWbool maximized);
void _glfwInputWindowDamage(_GLFWwindow* window);
void _glfwInputWindowCloseRequest(_GLFWwindow* window);
void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor);
void _glfwInputKey(_GLFWwindow* window,
int key, int scancode, int action, int mods);
void _glfwInputChar(_GLFWwindow* window,
uint32_t codepoint, int mods, GLFWbool plain);
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset);
void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods);
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos);
void _glfwInputCursorEnter(_GLFWwindow* window, GLFWbool entered);
void _glfwInputDrop(_GLFWwindow* window, int count, const char** names);
void _glfwInputMonitor(_GLFWmonitor* monitor, int action, int placement);
void _glfwInputMonitorWindow(_GLFWmonitor* monitor, _GLFWwindow* window);
#if defined(__GNUC__)
void _glfwInputError(int code, const char* format, ...)
__attribute__((format(printf, 2, 3)));
#else
void _glfwInputError(int code, const char* format, ...);
#endif
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
GLFWbool _glfwStringInExtensionString(const char* string, const char* extensions);
const _GLFWfbconfig* _glfwChooseFBConfig(const _GLFWfbconfig* desired,
const _GLFWfbconfig* alternatives,
unsigned int count);
GLFWbool _glfwRefreshContextAttribs(_GLFWwindow* window,
const _GLFWctxconfig* ctxconfig);
GLFWbool _glfwIsValidContextConfig(const _GLFWctxconfig* ctxconfig);
const GLFWvidmode* _glfwChooseVideoMode(_GLFWmonitor* monitor,
const GLFWvidmode* desired);
int _glfwCompareVideoModes(const GLFWvidmode* first, const GLFWvidmode* second);
_GLFWmonitor* _glfwAllocMonitor(const char* name, int widthMM, int heightMM);
void _glfwFreeMonitor(_GLFWmonitor* monitor);
void _glfwAllocGammaArrays(GLFWgammaramp* ramp, unsigned int size);
void _glfwFreeGammaArrays(GLFWgammaramp* ramp);
void _glfwSplitBPP(int bpp, int* red, int* green, int* blue);
void _glfwCenterCursorInContentArea(_GLFWwindow* window);
size_t _glfwEncodeUTF8(char* s, uint32_t codepoint);
char** _glfwParseUriList(char* text, int* count);
char* _glfw_strdup(const char* source);
int _glfw_min(int a, int b);
int _glfw_max(int a, int b);
float _glfw_fminf(float a, float b);
float _glfw_fmaxf(float a, float b);
@@ -0,0 +1,230 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard
// SPDX-FileCopyrightText: 2006-2019 Camilla Löwy <elmindreda@glfw.org>
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
package glfw
import (
"unsafe"
)
const (
_GLFW_INSERT_FIRST = 0
_GLFW_INSERT_LAST = 1
)
var _glfw library
type initconfig struct {
hatButtons bool
}
type wndconfig struct {
width int
height int
title string
resizable bool
visible bool
decorated bool
focused bool
autoIconify bool
floating bool
maximized bool
centerCursor bool
focusOnShow bool
mousePassthrough bool
scaleToMonitor bool
}
type ctxconfig struct {
client int
source int
major int
minor int
forward bool
debug bool
noerror bool
profile int
robustness int
release int
share *Window
}
type fbconfig struct {
redBits int
greenBits int
blueBits int
alphaBits int
depthBits int
stencilBits int
accumRedBits int
accumGreenBits int
accumBlueBits int
accumAlphaBits int
auxBuffers int
stereo bool
samples int
sRGB bool
doublebuffer bool
transparent bool
handle uintptr
}
type context struct {
client int
source int
major int
minor int
revision int
forward bool
debug bool
noerror bool
profile int
robustness int
release int
// TODO: Put these functions in an interface type.
makeCurrent func(*Window) error
swapBuffers func(*Window) error
swapInterval func(int) error
extensionSupported func(string) bool
getProcAddress func(string) uintptr
destroy func(*Window) error
platform platformContextState
}
type (
PosCallback func(w *Window, xpos int, ypos int)
SizeCallback func(w *Window, width int, height int)
CloseCallback func(w *Window)
RefreshCallback func(w *Window)
FocusCallback func(w *Window, focused bool)
IconifyCallback func(w *Window, iconified bool)
MaximizeCallback func(w *Window, iconified bool)
FramebufferSizeCallback func(w *Window, width int, height int)
ContentScaleCallback func(w *Window, x float32, y float32)
MouseButtonCallback func(w *Window, button MouseButton, action Action, mods ModifierKey)
CursorPosCallback func(w *Window, xpos float64, ypos float64)
CursorEnterCallback func(w *Window, entered bool)
ScrollCallback func(w *Window, xoff float64, yoff float64)
KeyCallback func(w *Window, key Key, scancode int, action Action, mods ModifierKey)
CharCallback func(w *Window, char rune)
CharModsCallback func(w *Window, char rune, mods ModifierKey)
DropCallback func(w *Window, names []string)
MonitorCallback func(monitor *Monitor, event PeripheralEvent)
)
type Window struct {
resizable bool
decorated bool
autoIconify bool
floating bool
focusOnShow bool
mousePassthrough bool
shouldClose bool
userPointer unsafe.Pointer
doublebuffer bool
videoMode VidMode
monitor *Monitor
cursor *Cursor
minwidth int
minheight int
maxwidth int
maxheight int
numer int
denom int
stickyKeys bool
stickyMouseButtons bool
lockKeyMods bool
cursorMode int
mouseButtons [MouseButtonLast + 1]Action
keys [KeyLast + 1]Action
// Virtual cursor position when cursor is disabled
virtualCursorPosX float64
virtualCursorPosY float64
rawMouseMotion bool
context context
callbacks struct {
pos PosCallback
size SizeCallback
close CloseCallback
refresh RefreshCallback
focus FocusCallback
iconify IconifyCallback
maximize MaximizeCallback
fbsize FramebufferSizeCallback
scale ContentScaleCallback
mouseButton MouseButtonCallback
cursorPos CursorPosCallback
cursorEnter CursorEnterCallback
scroll ScrollCallback
key KeyCallback
character CharCallback
charmods CharModsCallback
drop DropCallback
}
platform platformWindowState
}
type Monitor struct {
name string
window *Window
modes []*VidMode
platform platformMonitorState
}
type Cursor struct {
platform platformCursorState
}
type tls struct {
platform platformTLSState
}
type library struct {
initialized bool
hints struct {
init initconfig
framebuffer fbconfig
window wndconfig
context ctxconfig
refreshRate int
}
errors []error // TODO: Check the error at polling?
cursors []*Cursor
windows []*Window
monitors []*Monitor
contextSlot tls
callbacks struct {
monitor MonitorCallback
}
platformWindow platformLibraryWindowState
platformContext platformLibraryContextState
}
func boolToInt(x bool) int {
if x {
return 1
}
return 0
}
func intToBool(x int) bool {
return x != 0
}
+143
View File
@@ -0,0 +1,143 @@
// Copyright 2013 The Ebitengine 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.
// Code generated by genkeys.go using 'go generate'. DO NOT EDIT.
//go:build darwin || freebsd || linux || netbsd || openbsd || windows
package glfw
const (
Key0 = Key(48)
Key1 = Key(49)
Key2 = Key(50)
Key3 = Key(51)
Key4 = Key(52)
Key5 = Key(53)
Key6 = Key(54)
Key7 = Key(55)
Key8 = Key(56)
Key9 = Key(57)
KeyA = Key(65)
KeyApostrophe = Key(39)
KeyB = Key(66)
KeyBackslash = Key(92)
KeyBackspace = Key(259)
KeyC = Key(67)
KeyCapsLock = Key(280)
KeyComma = Key(44)
KeyD = Key(68)
KeyDelete = Key(261)
KeyDown = Key(264)
KeyE = Key(69)
KeyEnd = Key(269)
KeyEnter = Key(257)
KeyEqual = Key(61)
KeyEscape = Key(256)
KeyF = Key(70)
KeyF1 = Key(290)
KeyF10 = Key(299)
KeyF11 = Key(300)
KeyF12 = Key(301)
KeyF13 = Key(302)
KeyF14 = Key(303)
KeyF15 = Key(304)
KeyF16 = Key(305)
KeyF17 = Key(306)
KeyF18 = Key(307)
KeyF19 = Key(308)
KeyF2 = Key(291)
KeyF20 = Key(309)
KeyF21 = Key(310)
KeyF22 = Key(311)
KeyF23 = Key(312)
KeyF24 = Key(313)
KeyF3 = Key(292)
KeyF4 = Key(293)
KeyF5 = Key(294)
KeyF6 = Key(295)
KeyF7 = Key(296)
KeyF8 = Key(297)
KeyF9 = Key(298)
KeyG = Key(71)
KeyGraveAccent = Key(96)
KeyH = Key(72)
KeyHome = Key(268)
KeyI = Key(73)
KeyInsert = Key(260)
KeyJ = Key(74)
KeyK = Key(75)
KeyKP0 = Key(320)
KeyKP1 = Key(321)
KeyKP2 = Key(322)
KeyKP3 = Key(323)
KeyKP4 = Key(324)
KeyKP5 = Key(325)
KeyKP6 = Key(326)
KeyKP7 = Key(327)
KeyKP8 = Key(328)
KeyKP9 = Key(329)
KeyKPAdd = Key(334)
KeyKPDecimal = Key(330)
KeyKPDivide = Key(331)
KeyKPEnter = Key(335)
KeyKPEqual = Key(336)
KeyKPMultiply = Key(332)
KeyKPSubtract = Key(333)
KeyL = Key(76)
KeyLast = Key(348)
KeyLeft = Key(263)
KeyLeftAlt = Key(342)
KeyLeftBracket = Key(91)
KeyLeftControl = Key(341)
KeyLeftShift = Key(340)
KeyLeftSuper = Key(343)
KeyM = Key(77)
KeyMenu = Key(348)
KeyMinus = Key(45)
KeyN = Key(78)
KeyNumLock = Key(282)
KeyO = Key(79)
KeyP = Key(80)
KeyPageDown = Key(267)
KeyPageUp = Key(266)
KeyPause = Key(284)
KeyPeriod = Key(46)
KeyPrintScreen = Key(283)
KeyQ = Key(81)
KeyR = Key(82)
KeyRight = Key(262)
KeyRightAlt = Key(346)
KeyRightBracket = Key(93)
KeyRightControl = Key(345)
KeyRightShift = Key(344)
KeyRightSuper = Key(347)
KeyS = Key(83)
KeyScrollLock = Key(281)
KeySemicolon = Key(59)
KeySlash = Key(47)
KeySpace = Key(32)
KeyT = Key(84)
KeyTab = Key(258)
KeyU = Key(85)
KeyUnknown = Key(-1)
KeyUp = Key(265)
KeyV = Key(86)
KeyW = Key(87)
KeyWorld1 = Key(161)
KeyWorld2 = Key(162)
KeyX = Key(88)
KeyY = Key(89)
KeyZ = Key(90)
)
+520
View File
@@ -0,0 +1,520 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard
// SPDX-FileCopyrightText: 2006-2019 Camilla Löwy <elmindreda@glfw.org>
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
//go:build darwin || freebsd || linux || netbsd || openbsd
#include "internal_unix.h"
#include <assert.h>
#include <math.h>
#include <float.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
// Lexically compare video modes, used by qsort
//
static int compareVideoModes(const void* fp, const void* sp)
{
const GLFWvidmode* fm = fp;
const GLFWvidmode* sm = sp;
const int fbpp = fm->redBits + fm->greenBits + fm->blueBits;
const int sbpp = sm->redBits + sm->greenBits + sm->blueBits;
const int farea = fm->width * fm->height;
const int sarea = sm->width * sm->height;
// First sort on color bits per pixel
if (fbpp != sbpp)
return fbpp - sbpp;
// Then sort on screen area
if (farea != sarea)
return farea - sarea;
// Then sort on width
if (fm->width != sm->width)
return fm->width - sm->width;
// Lastly sort on refresh rate
return fm->refreshRate - sm->refreshRate;
}
// Retrieves the available modes for the specified monitor
//
static GLFWbool refreshVideoModes(_GLFWmonitor* monitor)
{
int modeCount;
GLFWvidmode* modes;
if (monitor->modes)
return GLFW_TRUE;
modes = _glfwPlatformGetVideoModes(monitor, &modeCount);
if (!modes)
return GLFW_FALSE;
qsort(modes, modeCount, sizeof(GLFWvidmode), compareVideoModes);
free(monitor->modes);
monitor->modes = modes;
monitor->modeCount = modeCount;
return GLFW_TRUE;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW event API //////
//////////////////////////////////////////////////////////////////////////
// Notifies shared code of a monitor connection or disconnection
//
void _glfwInputMonitor(_GLFWmonitor* monitor, int action, int placement)
{
if (action == GLFW_CONNECTED)
{
_glfw.monitorCount++;
_glfw.monitors =
realloc(_glfw.monitors, sizeof(_GLFWmonitor*) * _glfw.monitorCount);
if (placement == _GLFW_INSERT_FIRST)
{
memmove(_glfw.monitors + 1,
_glfw.monitors,
((size_t) _glfw.monitorCount - 1) * sizeof(_GLFWmonitor*));
_glfw.monitors[0] = monitor;
}
else
_glfw.monitors[_glfw.monitorCount - 1] = monitor;
}
else if (action == GLFW_DISCONNECTED)
{
int i;
_GLFWwindow* window;
for (window = _glfw.windowListHead; window; window = window->next)
{
if (window->monitor == monitor)
{
int width, height, xoff, yoff;
_glfwPlatformGetWindowSize(window, &width, &height);
_glfwPlatformSetWindowMonitor(window, NULL, 0, 0, width, height, 0);
_glfwPlatformGetWindowFrameSize(window, &xoff, &yoff, NULL, NULL);
_glfwPlatformSetWindowPos(window, xoff, yoff);
}
}
for (i = 0; i < _glfw.monitorCount; i++)
{
if (_glfw.monitors[i] == monitor)
{
_glfw.monitorCount--;
memmove(_glfw.monitors + i,
_glfw.monitors + i + 1,
((size_t) _glfw.monitorCount - i) * sizeof(_GLFWmonitor*));
break;
}
}
}
if (_glfw.callbacks.monitor)
_glfw.callbacks.monitor((GLFWmonitor*) monitor, action);
if (action == GLFW_DISCONNECTED)
_glfwFreeMonitor(monitor);
}
// Notifies shared code that a full screen window has acquired or released
// a monitor
//
void _glfwInputMonitorWindow(_GLFWmonitor* monitor, _GLFWwindow* window)
{
monitor->window = window;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
// Allocates and returns a monitor object with the specified name and dimensions
//
_GLFWmonitor* _glfwAllocMonitor(const char* name, int widthMM, int heightMM)
{
_GLFWmonitor* monitor = calloc(1, sizeof(_GLFWmonitor));
monitor->widthMM = widthMM;
monitor->heightMM = heightMM;
strncpy(monitor->name, name, sizeof(monitor->name) - 1);
return monitor;
}
// Frees a monitor object and any data associated with it
//
void _glfwFreeMonitor(_GLFWmonitor* monitor)
{
if (monitor == NULL)
return;
_glfwPlatformFreeMonitor(monitor);
_glfwFreeGammaArrays(&monitor->originalRamp);
_glfwFreeGammaArrays(&monitor->currentRamp);
free(monitor->modes);
free(monitor);
}
// Allocates red, green and blue value arrays of the specified size
//
void _glfwAllocGammaArrays(GLFWgammaramp* ramp, unsigned int size)
{
ramp->red = calloc(size, sizeof(unsigned short));
ramp->green = calloc(size, sizeof(unsigned short));
ramp->blue = calloc(size, sizeof(unsigned short));
ramp->size = size;
}
// Frees the red, green and blue value arrays and clears the struct
//
void _glfwFreeGammaArrays(GLFWgammaramp* ramp)
{
free(ramp->red);
free(ramp->green);
free(ramp->blue);
memset(ramp, 0, sizeof(GLFWgammaramp));
}
// Chooses the video mode most closely matching the desired one
//
const GLFWvidmode* _glfwChooseVideoMode(_GLFWmonitor* monitor,
const GLFWvidmode* desired)
{
int i;
unsigned int sizeDiff, leastSizeDiff = UINT_MAX;
unsigned int rateDiff, leastRateDiff = UINT_MAX;
unsigned int colorDiff, leastColorDiff = UINT_MAX;
const GLFWvidmode* current;
const GLFWvidmode* closest = NULL;
if (!refreshVideoModes(monitor))
return NULL;
for (i = 0; i < monitor->modeCount; i++)
{
current = monitor->modes + i;
colorDiff = 0;
if (desired->redBits != GLFW_DONT_CARE)
colorDiff += abs(current->redBits - desired->redBits);
if (desired->greenBits != GLFW_DONT_CARE)
colorDiff += abs(current->greenBits - desired->greenBits);
if (desired->blueBits != GLFW_DONT_CARE)
colorDiff += abs(current->blueBits - desired->blueBits);
sizeDiff = abs((current->width - desired->width) *
(current->width - desired->width) +
(current->height - desired->height) *
(current->height - desired->height));
if (desired->refreshRate != GLFW_DONT_CARE)
rateDiff = abs(current->refreshRate - desired->refreshRate);
else
rateDiff = UINT_MAX - current->refreshRate;
if ((colorDiff < leastColorDiff) ||
(colorDiff == leastColorDiff && sizeDiff < leastSizeDiff) ||
(colorDiff == leastColorDiff && sizeDiff == leastSizeDiff && rateDiff < leastRateDiff))
{
closest = current;
leastSizeDiff = sizeDiff;
leastRateDiff = rateDiff;
leastColorDiff = colorDiff;
}
}
return closest;
}
// Performs lexical comparison between two @ref GLFWvidmode structures
//
int _glfwCompareVideoModes(const GLFWvidmode* fm, const GLFWvidmode* sm)
{
return compareVideoModes(fm, sm);
}
// Splits a color depth into red, green and blue bit depths
//
void _glfwSplitBPP(int bpp, int* red, int* green, int* blue)
{
int delta;
// We assume that by 32 the user really meant 24
if (bpp == 32)
bpp = 24;
// Convert "bits per pixel" to red, green & blue sizes
*red = *green = *blue = bpp / 3;
delta = bpp - (*red * 3);
if (delta >= 1)
*green = *green + 1;
if (delta == 2)
*red = *red + 1;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW public API //////
//////////////////////////////////////////////////////////////////////////
GLFWAPI GLFWmonitor** glfwGetMonitors(int* count)
{
assert(count != NULL);
*count = 0;
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
*count = _glfw.monitorCount;
return (GLFWmonitor**) _glfw.monitors;
}
GLFWAPI GLFWmonitor* glfwGetPrimaryMonitor(void)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
if (!_glfw.monitorCount)
return NULL;
return (GLFWmonitor*) _glfw.monitors[0];
}
GLFWAPI void glfwGetMonitorPos(GLFWmonitor* handle, int* xpos, int* ypos)
{
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
assert(monitor != NULL);
if (xpos)
*xpos = 0;
if (ypos)
*ypos = 0;
_GLFW_REQUIRE_INIT();
_glfwPlatformGetMonitorPos(monitor, xpos, ypos);
}
GLFWAPI void glfwGetMonitorWorkarea(GLFWmonitor* handle,
int* xpos, int* ypos,
int* width, int* height)
{
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
assert(monitor != NULL);
if (xpos)
*xpos = 0;
if (ypos)
*ypos = 0;
if (width)
*width = 0;
if (height)
*height = 0;
_GLFW_REQUIRE_INIT();
_glfwPlatformGetMonitorWorkarea(monitor, xpos, ypos, width, height);
}
GLFWAPI void glfwGetMonitorPhysicalSize(GLFWmonitor* handle, int* widthMM, int* heightMM)
{
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
assert(monitor != NULL);
if (widthMM)
*widthMM = 0;
if (heightMM)
*heightMM = 0;
_GLFW_REQUIRE_INIT();
if (widthMM)
*widthMM = monitor->widthMM;
if (heightMM)
*heightMM = monitor->heightMM;
}
GLFWAPI void glfwGetMonitorContentScale(GLFWmonitor* handle,
float* xscale, float* yscale)
{
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
assert(monitor != NULL);
if (xscale)
*xscale = 0.f;
if (yscale)
*yscale = 0.f;
_GLFW_REQUIRE_INIT();
_glfwPlatformGetMonitorContentScale(monitor, xscale, yscale);
}
GLFWAPI const char* glfwGetMonitorName(GLFWmonitor* handle)
{
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
assert(monitor != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return monitor->name;
}
GLFWAPI void glfwSetMonitorUserPointer(GLFWmonitor* handle, void* pointer)
{
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
assert(monitor != NULL);
_GLFW_REQUIRE_INIT();
monitor->userPointer = pointer;
}
GLFWAPI void* glfwGetMonitorUserPointer(GLFWmonitor* handle)
{
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
assert(monitor != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return monitor->userPointer;
}
GLFWAPI GLFWmonitorfun glfwSetMonitorCallback(GLFWmonitorfun cbfun)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(_glfw.callbacks.monitor, cbfun);
return cbfun;
}
GLFWAPI const GLFWvidmode* glfwGetVideoModes(GLFWmonitor* handle, int* count)
{
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
assert(monitor != NULL);
assert(count != NULL);
*count = 0;
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
if (!refreshVideoModes(monitor))
return NULL;
*count = monitor->modeCount;
return monitor->modes;
}
GLFWAPI const GLFWvidmode* glfwGetVideoMode(GLFWmonitor* handle)
{
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
assert(monitor != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_glfwPlatformGetVideoMode(monitor, &monitor->currentMode);
return &monitor->currentMode;
}
GLFWAPI void glfwSetGamma(GLFWmonitor* handle, float gamma)
{
unsigned int i;
unsigned short* values;
GLFWgammaramp ramp;
const GLFWgammaramp* original;
assert(handle != NULL);
assert(gamma > 0.f);
assert(gamma <= FLT_MAX);
_GLFW_REQUIRE_INIT();
if (gamma != gamma || gamma <= 0.f || gamma > FLT_MAX)
{
_glfwInputError(GLFW_INVALID_VALUE, "Invalid gamma value %f", gamma);
return;
}
original = glfwGetGammaRamp(handle);
if (!original)
return;
values = calloc(original->size, sizeof(unsigned short));
for (i = 0; i < original->size; i++)
{
float value;
// Calculate intensity
value = i / (float) (original->size - 1);
// Apply gamma curve
value = powf(value, 1.f / gamma) * 65535.f + 0.5f;
// Clamp to value range
value = _glfw_fminf(value, 65535.f);
values[i] = (unsigned short) value;
}
ramp.red = values;
ramp.green = values;
ramp.blue = values;
ramp.size = original->size;
glfwSetGammaRamp(handle, &ramp);
free(values);
}
GLFWAPI const GLFWgammaramp* glfwGetGammaRamp(GLFWmonitor* handle)
{
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
assert(monitor != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_glfwFreeGammaArrays(&monitor->currentRamp);
if (!_glfwPlatformGetGammaRamp(monitor, &monitor->currentRamp))
return NULL;
return &monitor->currentRamp;
}
GLFWAPI void glfwSetGammaRamp(GLFWmonitor* handle, const GLFWgammaramp* ramp)
{
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
assert(monitor != NULL);
assert(ramp != NULL);
assert(ramp->size > 0);
assert(ramp->red != NULL);
assert(ramp->green != NULL);
assert(ramp->blue != NULL);
_GLFW_REQUIRE_INIT();
if (ramp->size <= 0)
{
_glfwInputError(GLFW_INVALID_VALUE,
"Invalid gamma ramp size %i",
ramp->size);
return;
}
if (!monitor->originalRamp.size)
{
if (!_glfwPlatformGetGammaRamp(monitor, &monitor->originalRamp))
return;
}
_glfwPlatformSetGammaRamp(monitor, ramp);
}
+292
View File
@@ -0,0 +1,292 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2012 The glfw3-go Authors
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
//go:build darwin || freebsd || linux || netbsd || openbsd
package glfw
// #define GLFW_INCLUDE_NONE
// #include "glfw3_unix.h"
//
// void goMonitorCB(void* monitor, int event);
//
// static GLFWmonitor *GetMonitorAtIndex(GLFWmonitor **monitors, int index) {
// return monitors[index];
// }
//
// static GLFWvidmode GetVidmodeAtIndex(GLFWvidmode *vidmodes, int index) {
// return vidmodes[index];
// }
//
// static void glfwSetMonitorCallbackCB() {
// glfwSetMonitorCallback((GLFWmonitorfun)goMonitorCB);
// }
//
// static unsigned int GetGammaAtIndex(unsigned short *color, int i) {
// return color[i];
// }
//
// static void SetGammaAtIndex(unsigned short *color, int i, unsigned short value) {
// color[i] = value;
// }
import "C"
import (
"unsafe"
)
// Monitor represents a monitor.
type Monitor struct {
data *C.GLFWmonitor
}
// GammaRamp describes the gamma ramp for a monitor.
type GammaRamp struct {
Red []uint16 // A slice of value describing the response of the red channel.
Green []uint16 // A slice of value describing the response of the green channel.
Blue []uint16 // A slice of value describing the response of the blue channel.
}
var fMonitorHolder func(monitor *Monitor, event PeripheralEvent)
//export goMonitorCB
func goMonitorCB(monitor unsafe.Pointer, event C.int) {
fMonitorHolder(&Monitor{(*C.GLFWmonitor)(monitor)}, PeripheralEvent(event))
}
// GetMonitors returns a slice of handles for all currently connected monitors.
func GetMonitors() ([]*Monitor, error) {
var length int
mC := C.glfwGetMonitors((*C.int)(unsafe.Pointer(&length)))
if err := fetchErrorIgnoringPlatformError(); err != nil {
return nil, err
}
if mC == nil {
return nil, nil
}
m := make([]*Monitor, length)
for i := 0; i < length; i++ {
m[i] = &Monitor{C.GetMonitorAtIndex(mC, C.int(i))}
}
return m, nil
}
// GetPrimaryMonitor returns the primary monitor. This is usually the monitor
// where elements like the Windows task bar or the OS X menu bar is located.
func GetPrimaryMonitor() (*Monitor, error) {
m := C.glfwGetPrimaryMonitor()
if err := fetchErrorIgnoringPlatformError(); err != nil {
return nil, err
}
if m == nil {
return nil, nil
}
return &Monitor{m}, nil
}
// GetPos returns the position, in screen coordinates, of the upper-left
// corner of the monitor.
func (m *Monitor) GetPos() (x, y int, err error) {
var xpos, ypos C.int
C.glfwGetMonitorPos(m.data, &xpos, &ypos)
if err := fetchErrorIgnoringPlatformError(); err != nil {
return 0, 0, err
}
return int(xpos), int(ypos), nil
}
// GetWorkarea returns the position, in screen coordinates, of the upper-left
// corner of the work area of the specified monitor along with the work area
// size in screen coordinates. The work area is defined as the area of the
// monitor not occluded by the operating system task bar where present. If no
// task bar exists then the work area is the monitor resolution in screen
// coordinates.
//
// This function must only be called from the main thread.
func (m *Monitor) GetWorkarea() (x, y, width, height int) {
var cX, cY, cWidth, cHeight C.int
C.glfwGetMonitorWorkarea(m.data, &cX, &cY, &cWidth, &cHeight)
x, y, width, height = int(cX), int(cY), int(cWidth), int(cHeight)
return
}
// GetContentScale function retrieves the content scale for the specified monitor.
// The content scale is the ratio between the current DPI and the platform's
// default DPI. If you scale all pixel dimensions by this scale then your content
// should appear at an appropriate size. This is especially important for text
// and any UI elements.
//
// This function must only be called from the main thread.
func (m *Monitor) GetContentScale() (float32, float32, error) {
var x, y C.float
C.glfwGetMonitorContentScale(m.data, &x, &y)
return float32(x), float32(y), nil
}
// SetUserPointer sets the user-defined pointer of the monitor. The current value
// is retained until the monitor is disconnected. The initial value is nil.
//
// This function may be called from the monitor callback, even for a monitor
// that is being disconnected.
//
// This function may be called from any thread. Access is not synchronized.
func (m *Monitor) SetUserPointer(pointer unsafe.Pointer) {
C.glfwSetMonitorUserPointer(m.data, pointer)
}
// GetUserPointer returns the current value of the user-defined pointer of the
// monitor. The initial value is nil.
//
// This function may be called from the monitor callback, even for a monitor
// that is being disconnected.
//
// This function may be called from any thread. Access is not synchronized.
func (m *Monitor) GetUserPointer() unsafe.Pointer {
return C.glfwGetMonitorUserPointer(m.data)
}
// GetPhysicalSize returns the size, in millimetres, of the display area of the
// monitor.
//
// Note: Some operating systems do not provide accurate information, either
// because the monitor's EDID data is incorrect, or because the driver does not
// report it accurately.
func (m *Monitor) GetPhysicalSize() (width, height int, err error) {
var wi, h C.int
C.glfwGetMonitorPhysicalSize(m.data, &wi, &h)
if err := fetchErrorIgnoringPlatformError(); err != nil {
return 0, 0, err
}
return int(wi), int(h), nil
}
// GetName returns a human-readable name of the monitor, encoded as UTF-8.
func (m *Monitor) GetName() (string, error) {
mn := C.glfwGetMonitorName(m.data)
if err := fetchErrorIgnoringPlatformError(); err != nil {
return "", err
}
if mn == nil {
return "", nil
}
return C.GoString(mn), nil
}
// MonitorCallback is the signature for monitor configuration callback
// functions.
type MonitorCallback func(monitor *Monitor, event PeripheralEvent)
// SetMonitorCallback sets the monitor configuration callback, or removes the
// currently set callback. This is called when a monitor is connected to or
// disconnected from the system.
//
// This function must only be called from the main thread.
func SetMonitorCallback(cbfun MonitorCallback) (MonitorCallback, error) {
previous := fMonitorHolder
fMonitorHolder = cbfun
if cbfun == nil {
C.glfwSetMonitorCallback(nil)
} else {
C.glfwSetMonitorCallbackCB()
}
return previous, nil
}
// GetVideoModes returns an array of all video modes supported by the monitor.
// The returned array is sorted in ascending order, first by color bit depth
// (the sum of all channel depths) and then by resolution area (the product of
// width and height).
func (m *Monitor) GetVideoModes() ([]*VidMode, error) {
var length int
vC := C.glfwGetVideoModes(m.data, (*C.int)(unsafe.Pointer(&length)))
if err := fetchErrorIgnoringPlatformError(); err != nil {
return nil, err
}
if vC == nil {
return nil, nil
}
v := make([]*VidMode, length)
for i := 0; i < length; i++ {
t := C.GetVidmodeAtIndex(vC, C.int(i))
v[i] = &VidMode{int(t.width), int(t.height), int(t.redBits), int(t.greenBits), int(t.blueBits), int(t.refreshRate)}
}
return v, nil
}
// GetVideoMode returns the current video mode of the monitor. If you
// are using a full screen window, the return value will therefore depend on
// whether it is focused.
func (m *Monitor) GetVideoMode() (*VidMode, error) {
t := C.glfwGetVideoMode(m.data)
if t == nil {
return nil, nil
}
if err := fetchErrorIgnoringPlatformError(); err != nil {
return nil, err
}
return &VidMode{int(t.width), int(t.height), int(t.redBits), int(t.greenBits), int(t.blueBits), int(t.refreshRate)}, nil
}
// SetGamma generates a 256-element gamma ramp from the specified exponent and then calls
// SetGamma with it.
func (m *Monitor) SetGamma(gamma float32) error {
C.glfwSetGamma(m.data, C.float(gamma))
if err := fetchErrorIgnoringPlatformError(); err != nil {
return err
}
return nil
}
// GetGammaRamp retrieves the current gamma ramp of the monitor.
func (m *Monitor) GetGammaRamp() (*GammaRamp, error) {
var ramp GammaRamp
rampC := C.glfwGetGammaRamp(m.data)
if err := fetchErrorIgnoringPlatformError(); err != nil {
return nil, err
}
if rampC == nil {
return nil, nil
}
length := int(rampC.size)
ramp.Red = make([]uint16, length)
ramp.Green = make([]uint16, length)
ramp.Blue = make([]uint16, length)
for i := 0; i < length; i++ {
ramp.Red[i] = uint16(C.GetGammaAtIndex(rampC.red, C.int(i)))
ramp.Green[i] = uint16(C.GetGammaAtIndex(rampC.green, C.int(i)))
ramp.Blue[i] = uint16(C.GetGammaAtIndex(rampC.blue, C.int(i)))
}
return &ramp, nil
}
// SetGammaRamp sets the current gamma ramp for the monitor.
func (m *Monitor) SetGammaRamp(ramp *GammaRamp) error {
var rampC C.GLFWgammaramp
length := len(ramp.Red)
for i := 0; i < length; i++ {
C.SetGammaAtIndex(rampC.red, C.int(i), C.ushort(ramp.Red[i]))
C.SetGammaAtIndex(rampC.green, C.int(i), C.ushort(ramp.Green[i]))
C.SetGammaAtIndex(rampC.blue, C.int(i), C.ushort(ramp.Blue[i]))
}
C.glfwSetGammaRamp(m.data, &rampC)
if err := fetchErrorIgnoringPlatformError(); err != nil {
return err
}
return nil
}
@@ -0,0 +1,274 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard
// SPDX-FileCopyrightText: 2006-2019 Camilla Löwy <elmindreda@glfw.org>
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
package glfw
import (
"sort"
)
func abs(x int) uint {
if x < 0 {
return uint(-x)
}
return uint(x)
}
func (v *VidMode) equals(other *VidMode) bool {
if v.RedBits+v.GreenBits+v.BlueBits != other.RedBits+other.GreenBits+other.BlueBits {
return false
}
if v.Width != other.Width {
return false
}
if v.Height != other.Height {
return false
}
if v.RefreshRate != other.RefreshRate {
return false
}
return true
}
func (m *Monitor) refreshVideoModes() error {
m.modes = m.modes[:0]
modes, err := m.platformAppendVideoModes(m.modes)
if err != nil {
return err
}
sort.Slice(modes, func(i, j int) bool {
a := modes[i]
b := modes[j]
abpp := a.RedBits + a.GreenBits + a.BlueBits
bbpp := b.RedBits + b.GreenBits + b.BlueBits
if abpp != bbpp {
return abpp < bbpp
}
aarea := a.Width * a.Height
barea := b.Width * b.Height
if aarea != barea {
return aarea < barea
}
if a.Width != b.Width {
return a.Width < b.Width
}
return a.RefreshRate < b.RefreshRate
})
m.modes = modes
return nil
}
func inputMonitor(monitor *Monitor, action PeripheralEvent, placement int) error {
switch action {
case Connected:
switch placement {
case _GLFW_INSERT_FIRST:
_glfw.monitors = append(_glfw.monitors, nil)
copy(_glfw.monitors[1:], _glfw.monitors)
_glfw.monitors[0] = monitor
case _GLFW_INSERT_LAST:
_glfw.monitors = append(_glfw.monitors, monitor)
}
case Disconnected:
for _, window := range _glfw.windows {
if window.monitor == monitor {
width, height, err := window.platformGetWindowSize()
if err != nil {
return err
}
if err := window.platformSetWindowMonitor(nil, 0, 0, width, height, 0); err != nil {
return err
}
xoff, yoff, _, _, err := window.platformGetWindowFrameSize()
if err != nil {
return err
}
if err := window.platformSetWindowPos(xoff, yoff); err != nil {
return err
}
}
}
for i, m := range _glfw.monitors {
if m == monitor {
copy(_glfw.monitors[i:], _glfw.monitors[i+1:])
_glfw.monitors[len(_glfw.monitors)-1] = nil
_glfw.monitors = _glfw.monitors[:len(_glfw.monitors)-1]
break
}
}
}
if _glfw.callbacks.monitor != nil {
_glfw.callbacks.monitor(monitor, action)
}
return nil
}
func (m *Monitor) inputMonitorWindow(window *Window) {
m.window = window
}
func (m *Monitor) chooseVideoMode(desired *VidMode) (*VidMode, error) {
if err := m.refreshVideoModes(); err != nil {
return nil, err
}
// math.MaxUint was added at Go 1.17. See https://github.com/golang/go/issues/28538
const (
intSize = 32 << (^uint(0) >> 63)
maxUint = 1<<intSize - 1
)
var (
leastColorDiff uint = maxUint
leastSizeDiff uint = maxUint
leastRateDiff uint = maxUint
)
var closest *VidMode
for _, v := range m.modes {
var colorDiff uint
if desired.RedBits != DontCare {
colorDiff += abs(v.RedBits - desired.RedBits)
}
if desired.GreenBits != DontCare {
colorDiff += abs(v.GreenBits - desired.GreenBits)
}
if desired.BlueBits != DontCare {
colorDiff += abs(v.BlueBits - desired.BlueBits)
}
sizeDiff := abs((v.Width-desired.Width)*(v.Width-desired.Width) +
(v.Height-desired.Height)*(v.Height-desired.Height))
var rateDiff uint
if desired.RefreshRate != DontCare {
rateDiff = abs(v.RefreshRate - desired.RefreshRate)
} else {
rateDiff = maxUint - uint(v.RefreshRate)
}
if colorDiff < leastColorDiff ||
colorDiff == leastColorDiff && sizeDiff < leastSizeDiff ||
colorDiff == leastColorDiff && sizeDiff == leastSizeDiff && rateDiff < leastRateDiff {
closest = v
leastColorDiff = colorDiff
leastSizeDiff = sizeDiff
leastRateDiff = rateDiff
}
}
return closest, nil
}
func splitBPP(bpp int) (red, green, blue int) {
// We assume that by 32 the user really meant 24
if bpp == 32 {
bpp = 24
}
// Convert "bits per pixel" to red, green & blue sizes
red = bpp / 3
green = bpp / 3
blue = bpp / 3
delta := bpp - (red * 3)
if delta >= 1 {
green++
}
if delta == 2 {
red++
}
return
}
// GLFW public APIs
func GetMonitors() ([]*Monitor, error) {
if !_glfw.initialized {
return nil, NotInitialized
}
return _glfw.monitors, nil
}
func GetPrimaryMonitor() (*Monitor, error) {
if !_glfw.initialized {
return nil, NotInitialized
}
if len(_glfw.monitors) == 0 {
return nil, nil
}
return _glfw.monitors[0], nil
}
func (m *Monitor) GetPos() (xpos, ypos int, err error) {
if !_glfw.initialized {
return 0, 0, NotInitialized
}
xpos, ypos, ok := m.platformGetMonitorPos()
if !ok {
return 0, 0, nil
}
return xpos, ypos, nil
}
func (m *Monitor) GetWorkarea() (xpos, ypos, width, height int, err error) {
if !_glfw.initialized {
return 0, 0, 0, 0, NotInitialized
}
xpos, ypos, width, height = m.platformGetMonitorWorkarea()
return
}
// GetPhysicalSize is not implemented.
func (m *Monitor) GetContentScale() (xscale, yscale float32, err error) {
if !_glfw.initialized {
return 0, 0, NotInitialized
}
xscale, yscale, err = m.platformGetMonitorContentScale()
return
}
func (m *Monitor) GetName() (string, error) {
if !_glfw.initialized {
return "", NotInitialized
}
return m.name, nil
}
// SetUserPointer is not implemented.
// GetUserPointer is not implemented.
func SetMonitorCallback(cbfun MonitorCallback) (MonitorCallback, error) {
if !_glfw.initialized {
return nil, NotInitialized
}
old := _glfw.callbacks.monitor
_glfw.callbacks.monitor = cbfun
return old, nil
}
func (m *Monitor) GetVideoModes() ([]*VidMode, error) {
if !_glfw.initialized {
return nil, NotInitialized
}
return m.modes, nil
}
func (m *Monitor) GetVideoMode() (*VidMode, error) {
if !_glfw.initialized {
return nil, NotInitialized
}
return m.platformGetVideoMode(), nil
}
// SetGamma is not implemented.
// GetGammaRamp is not implemented.
// SetGammaRamp is not implemented.
+41
View File
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2012 The glfw3-go Authors
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
package glfw
/*
#define GLFW_EXPOSE_NATIVE_COCOA
#define GLFW_EXPOSE_NATIVE_NSGL
#include "glfw3_unix.h"
#include "glfw3native_unix.h"
// workaround wrappers needed due to a cgo and/or LLVM bug.
// See: https://github.com/go-gl/glfw/issues/136
static void *workaround_glfwGetCocoaWindow(GLFWwindow *w) {
return (void *)glfwGetCocoaWindow(w);
}
static void *workaround_glfwGetNSGLContext(GLFWwindow *w) {
return (void *)glfwGetNSGLContext(w);
}
*/
import "C"
import "unsafe"
// GetCocoaMonitor returns the CGDirectDisplayID of the monitor.
func (m *Monitor) GetCocoaMonitor() (uintptr, error) {
ret := uintptr(C.glfwGetCocoaMonitor(m.data))
return ret, fetchErrorIgnoringPlatformError()
}
// GetCocoaWindow returns the NSWindow of the window.
func (w *Window) GetCocoaWindow() (uintptr, error) {
ret := uintptr(C.workaround_glfwGetCocoaWindow(w.data))
return ret, fetchErrorIgnoringPlatformError()
}
// GetNSGLContext returns the NSOpenGLContext of the window.
func (w *Window) GetNSGLContext() (unsafe.Pointer, error) {
ret := C.workaround_glfwGetNSGLContext(w.data)
return ret, fetchErrorIgnoringPlatformError()
}
+82
View File
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2012 The glfw3-go Authors
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
//go:build freebsd || linux || netbsd || openbsd
package glfw
//#include <stdlib.h>
//#define GLFW_EXPOSE_NATIVE_X11
//#define GLFW_EXPOSE_NATIVE_GLX
//#define GLFW_INCLUDE_NONE
//#include "glfw3_unix.h"
//#include "glfw3native_unix.h"
import "C"
import "unsafe"
func GetX11Display() (*C.Display, error) {
ret := C.glfwGetX11Display()
if err := fetchErrorIgnoringPlatformError(); err != nil {
return nil, err
}
return ret, nil
}
// GetX11Adapter returns the RRCrtc of the monitor.
func (m *Monitor) GetX11Adapter() (C.RRCrtc, error) {
ret := C.glfwGetX11Adapter(m.data)
if err := fetchErrorIgnoringPlatformError(); err != nil {
return 0, err
}
return ret, nil
}
// GetX11Monitor returns the RROutput of the monitor.
func (m *Monitor) GetX11Monitor() (C.RROutput, error) {
ret := C.glfwGetX11Monitor(m.data)
if err := fetchErrorIgnoringPlatformError(); err != nil {
return 0, err
}
return ret, nil
}
// GetX11Window returns the Window of the window.
func (w *Window) GetX11Window() (C.Window, error) {
ret := C.glfwGetX11Window(w.data)
if err := fetchErrorIgnoringPlatformError(); err != nil {
return 0, err
}
return ret, nil
}
// GetGLXContext returns the GLXContext of the window.
func (w *Window) GetGLXContext() (C.GLXContext, error) {
ret := C.glfwGetGLXContext(w.data)
if err := fetchErrorIgnoringPlatformError(); err != nil {
return nil, err
}
return ret, nil
}
// GetGLXWindow returns the GLXWindow of the window.
func (w *Window) GetGLXWindow() (C.GLXWindow, error) {
ret := C.glfwGetGLXWindow(w.data)
if err := fetchErrorIgnoringPlatformError(); err != nil {
return 0, err
}
return ret, nil
}
// SetX11SelectionString sets the X11 selection string.
func SetX11SelectionString(str string) {
s := C.CString(str)
defer C.free(unsafe.Pointer(s))
C.glfwSetX11SelectionString(s)
}
// GetX11SelectionString gets the X11 selection string.
func GetX11SelectionString() string {
s := C.glfwGetX11SelectionString()
return C.GoString(s)
}
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2009-2019 Camilla Löwy <elmindreda@glfw.org>
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
// NOTE: Many Cocoa enum values have been renamed and we need to build across
// SDK versions where one is unavailable or deprecated.
// We use the newer names in code and replace them with the older names if
// the base SDK does not provide the newer names.
#if MAC_OS_X_VERSION_MAX_ALLOWED < 101400
#define NSOpenGLContextParameterSwapInterval NSOpenGLCPSwapInterval
#define NSOpenGLContextParameterSurfaceOpacity NSOpenGLCPSurfaceOpacity
#endif
#define _GLFW_PLATFORM_CONTEXT_STATE _GLFWcontextNSGL nsgl
#define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE _GLFWlibraryNSGL nsgl
#include <stdatomic.h>
// NSGL-specific per-context data
//
typedef struct _GLFWcontextNSGL
{
id pixelFormat;
id object;
} _GLFWcontextNSGL;
// NSGL-specific global data
//
typedef struct _GLFWlibraryNSGL
{
// dlopen handle for OpenGL.framework (for glfwGetProcAddress)
CFBundleRef framework;
} _GLFWlibraryNSGL;
GLFWbool _glfwInitNSGL(void);
void _glfwTerminateNSGL(void);
GLFWbool _glfwCreateContextNSGL(_GLFWwindow* window,
const _GLFWctxconfig* ctxconfig,
const _GLFWfbconfig* fbconfig);
void _glfwDestroyContextNSGL(_GLFWwindow* window);

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