vendor dependencies, make some changes to how input is done
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
.DS_Store
|
||||
*~
|
||||
*.aar
|
||||
*.apk
|
||||
*.tar.gz
|
||||
.idea
|
||||
.vscode
|
||||
go.work
|
||||
go.work.sum
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# Code of Conduct
|
||||
|
||||
We adhere to the [Go Community Code of Conduct](https://go.dev/conduct).
|
||||
+56
@@ -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
@@ -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
@@ -0,0 +1,63 @@
|
||||
# Ebitengine (v2)
|
||||
|
||||
[](https://pkg.go.dev/github.com/hajimehoshi/ebiten/v2)
|
||||
[](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)
|
||||
|
||||

|
||||
|
||||
## 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+173
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
+167
@@ -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
@@ -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
@@ -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))
|
||||
}
|
||||
+306
@@ -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
@@ -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()
|
||||
}
|
||||
+75
@@ -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]
|
||||
}
|
||||
Generated
Vendored
+34
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
+113
@@ -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
|
||||
)
|
||||
+503
@@ -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
|
||||
}
|
||||
+132
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
+268
@@ -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
@@ -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
|
||||
+161
@@ -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
@@ -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)
|
||||
}
|
||||
+112
@@ -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)
|
||||
}
|
||||
+436
@@ -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)
|
||||
}
|
||||
Generated
Vendored
+837
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
+629
@@ -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)
|
||||
}
|
||||
+29
@@ -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) {}
|
||||
+180
@@ -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))
|
||||
}
|
||||
+39
@@ -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
|
||||
+90
@@ -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) {
|
||||
}
|
||||
+32
@@ -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
|
||||
}
|
||||
+26
@@ -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{}
|
||||
}
|
||||
+265
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
+2034
File diff suppressed because it is too large
Load Diff
+743
@@ -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
@@ -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
@@ -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")
|
||||
}
|
||||
+1842
File diff suppressed because it is too large
Load Diff
+14
@@ -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"
|
||||
+9
@@ -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
@@ -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"
|
||||
+573
@@ -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
|
||||
;
|
||||
}
|
||||
|
||||
+603
@@ -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, ¤t);
|
||||
|
||||
const GLFWvidmode* best = _glfwChooseVideoMode(monitor, desired);
|
||||
if (_glfwCompareVideoModes(¤t, 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;
|
||||
}
|
||||
|
||||
+171
@@ -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);
|
||||
+25
@@ -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)
|
||||
}
|
||||
+1854
File diff suppressed because it is too large
Load Diff
+204
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
+610
@@ -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))
|
||||
}
|
||||
+771
@@ -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;
|
||||
}
|
||||
|
||||
+189
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
+4945
File diff suppressed because it is too large
Load Diff
+475
@@ -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
@@ -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()
|
||||
}
|
||||
+679
@@ -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;
|
||||
}
|
||||
|
||||
+159
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
+230
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
+274
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
+44
@@ -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
Reference in New Issue
Block a user