vendor dependencies, make some changes to how input is done
This commit is contained in:
+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);
|
||||
|
||||
+352
@@ -0,0 +1,352 @@
|
||||
// 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 <unistd.h>
|
||||
#include <math.h>
|
||||
|
||||
static void makeContextCurrentNSGL(_GLFWwindow* window)
|
||||
{
|
||||
@autoreleasepool {
|
||||
|
||||
if (window)
|
||||
[window->context.nsgl.object makeCurrentContext];
|
||||
else
|
||||
[NSOpenGLContext clearCurrentContext];
|
||||
|
||||
_glfwPlatformSetTls(&_glfw.contextSlot, window);
|
||||
|
||||
} // autoreleasepool
|
||||
}
|
||||
|
||||
static void swapBuffersNSGL(_GLFWwindow* window)
|
||||
{
|
||||
@autoreleasepool {
|
||||
|
||||
// HACK: Simulate vsync with usleep as NSGL swap interval does not apply to
|
||||
// windows with a non-visible occlusion state
|
||||
if (window->ns.occluded)
|
||||
{
|
||||
int interval = 0;
|
||||
[window->context.nsgl.object getValues:&interval
|
||||
forParameter:NSOpenGLContextParameterSwapInterval];
|
||||
|
||||
if (interval > 0)
|
||||
{
|
||||
const double framerate = 60.0;
|
||||
const uint64_t frequency = _glfwPlatformGetTimerFrequency();
|
||||
const uint64_t value = _glfwPlatformGetTimerValue();
|
||||
|
||||
const double elapsed = value / (double) frequency;
|
||||
const double period = 1.0 / framerate;
|
||||
const double delay = period - fmod(elapsed, period);
|
||||
|
||||
usleep(floorl(delay * 1e6));
|
||||
}
|
||||
}
|
||||
|
||||
[window->context.nsgl.object flushBuffer];
|
||||
|
||||
} // autoreleasepool
|
||||
}
|
||||
|
||||
static void swapIntervalNSGL(int interval)
|
||||
{
|
||||
@autoreleasepool {
|
||||
|
||||
_GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot);
|
||||
if (window)
|
||||
{
|
||||
[window->context.nsgl.object setValues:&interval
|
||||
forParameter:NSOpenGLContextParameterSwapInterval];
|
||||
}
|
||||
|
||||
} // autoreleasepool
|
||||
}
|
||||
|
||||
static int extensionSupportedNSGL(const char* extension)
|
||||
{
|
||||
// There are no NSGL extensions
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
static GLFWglproc getProcAddressNSGL(const char* procname)
|
||||
{
|
||||
CFStringRef symbolName = CFStringCreateWithCString(kCFAllocatorDefault,
|
||||
procname,
|
||||
kCFStringEncodingASCII);
|
||||
|
||||
GLFWglproc symbol = CFBundleGetFunctionPointerForName(_glfw.nsgl.framework,
|
||||
symbolName);
|
||||
|
||||
CFRelease(symbolName);
|
||||
|
||||
return symbol;
|
||||
}
|
||||
|
||||
static void destroyContextNSGL(_GLFWwindow* window)
|
||||
{
|
||||
@autoreleasepool {
|
||||
|
||||
[window->context.nsgl.pixelFormat release];
|
||||
window->context.nsgl.pixelFormat = nil;
|
||||
|
||||
[window->context.nsgl.object release];
|
||||
window->context.nsgl.object = nil;
|
||||
|
||||
} // autoreleasepool
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW internal API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Initialize OpenGL support
|
||||
//
|
||||
GLFWbool _glfwInitNSGL(void)
|
||||
{
|
||||
if (_glfw.nsgl.framework)
|
||||
return GLFW_TRUE;
|
||||
|
||||
_glfw.nsgl.framework =
|
||||
CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl"));
|
||||
if (_glfw.nsgl.framework == NULL)
|
||||
{
|
||||
_glfwInputError(GLFW_API_UNAVAILABLE,
|
||||
"NSGL: Failed to locate OpenGL framework");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
// Terminate OpenGL support
|
||||
//
|
||||
void _glfwTerminateNSGL(void)
|
||||
{
|
||||
}
|
||||
|
||||
// Create the OpenGL context
|
||||
//
|
||||
GLFWbool _glfwCreateContextNSGL(_GLFWwindow* window,
|
||||
const _GLFWctxconfig* ctxconfig,
|
||||
const _GLFWfbconfig* fbconfig)
|
||||
{
|
||||
if (ctxconfig->client == GLFW_OPENGL_ES_API)
|
||||
{
|
||||
_glfwInputError(GLFW_API_UNAVAILABLE,
|
||||
"NSGL: OpenGL ES is not available on macOS");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
if (ctxconfig->major > 2)
|
||||
{
|
||||
if (ctxconfig->major == 3 && ctxconfig->minor < 2)
|
||||
{
|
||||
_glfwInputError(GLFW_VERSION_UNAVAILABLE,
|
||||
"NSGL: The targeted version of macOS does not support OpenGL 3.0 or 3.1 but may support 3.2 and above");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
if (!ctxconfig->forward || ctxconfig->profile != GLFW_OPENGL_CORE_PROFILE)
|
||||
{
|
||||
_glfwInputError(GLFW_VERSION_UNAVAILABLE,
|
||||
"NSGL: The targeted version of macOS only supports forward-compatible core profile contexts for OpenGL 3.2 and above");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// Context robustness modes (GL_KHR_robustness) are not yet supported by
|
||||
// macOS but are not a hard constraint, so ignore and continue
|
||||
|
||||
// Context release behaviors (GL_KHR_context_flush_control) are not yet
|
||||
// supported by macOS but are not a hard constraint, so ignore and continue
|
||||
|
||||
// Debug contexts (GL_KHR_debug) are not yet supported by macOS but are not
|
||||
// a hard constraint, so ignore and continue
|
||||
|
||||
// No-error contexts (GL_KHR_no_error) are not yet supported by macOS but
|
||||
// are not a hard constraint, so ignore and continue
|
||||
|
||||
#define addAttrib(a) \
|
||||
{ \
|
||||
assert((size_t) index < sizeof(attribs) / sizeof(attribs[0])); \
|
||||
attribs[index++] = a; \
|
||||
}
|
||||
#define setAttrib(a, v) { addAttrib(a); addAttrib(v); }
|
||||
|
||||
NSOpenGLPixelFormatAttribute attribs[40];
|
||||
int index = 0;
|
||||
|
||||
addAttrib(NSOpenGLPFAAccelerated);
|
||||
addAttrib(NSOpenGLPFAClosestPolicy);
|
||||
|
||||
if (ctxconfig->nsgl.offline)
|
||||
{
|
||||
addAttrib(NSOpenGLPFAAllowOfflineRenderers);
|
||||
// NOTE: This replaces the NSSupportsAutomaticGraphicsSwitching key in
|
||||
// Info.plist for unbundled applications
|
||||
// HACK: This assumes that NSOpenGLPixelFormat will remain
|
||||
// a straightforward wrapper of its CGL counterpart
|
||||
addAttrib(kCGLPFASupportsAutomaticGraphicsSwitching);
|
||||
}
|
||||
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000
|
||||
if (ctxconfig->major >= 4)
|
||||
{
|
||||
setAttrib(NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion4_1Core);
|
||||
}
|
||||
else
|
||||
#endif /*MAC_OS_X_VERSION_MAX_ALLOWED*/
|
||||
if (ctxconfig->major >= 3)
|
||||
{
|
||||
setAttrib(NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core);
|
||||
}
|
||||
|
||||
if (ctxconfig->major <= 2)
|
||||
{
|
||||
if (fbconfig->auxBuffers != GLFW_DONT_CARE)
|
||||
setAttrib(NSOpenGLPFAAuxBuffers, fbconfig->auxBuffers);
|
||||
|
||||
if (fbconfig->accumRedBits != GLFW_DONT_CARE &&
|
||||
fbconfig->accumGreenBits != GLFW_DONT_CARE &&
|
||||
fbconfig->accumBlueBits != GLFW_DONT_CARE &&
|
||||
fbconfig->accumAlphaBits != GLFW_DONT_CARE)
|
||||
{
|
||||
const int accumBits = fbconfig->accumRedBits +
|
||||
fbconfig->accumGreenBits +
|
||||
fbconfig->accumBlueBits +
|
||||
fbconfig->accumAlphaBits;
|
||||
|
||||
setAttrib(NSOpenGLPFAAccumSize, accumBits);
|
||||
}
|
||||
}
|
||||
|
||||
if (fbconfig->redBits != GLFW_DONT_CARE &&
|
||||
fbconfig->greenBits != GLFW_DONT_CARE &&
|
||||
fbconfig->blueBits != GLFW_DONT_CARE)
|
||||
{
|
||||
int colorBits = fbconfig->redBits +
|
||||
fbconfig->greenBits +
|
||||
fbconfig->blueBits;
|
||||
|
||||
// macOS needs non-zero color size, so set reasonable values
|
||||
if (colorBits == 0)
|
||||
colorBits = 24;
|
||||
else if (colorBits < 15)
|
||||
colorBits = 15;
|
||||
|
||||
setAttrib(NSOpenGLPFAColorSize, colorBits);
|
||||
}
|
||||
|
||||
if (fbconfig->alphaBits != GLFW_DONT_CARE)
|
||||
setAttrib(NSOpenGLPFAAlphaSize, fbconfig->alphaBits);
|
||||
|
||||
if (fbconfig->depthBits != GLFW_DONT_CARE)
|
||||
setAttrib(NSOpenGLPFADepthSize, fbconfig->depthBits);
|
||||
|
||||
if (fbconfig->stencilBits != GLFW_DONT_CARE)
|
||||
setAttrib(NSOpenGLPFAStencilSize, fbconfig->stencilBits);
|
||||
|
||||
if (fbconfig->stereo)
|
||||
{
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200
|
||||
_glfwInputError(GLFW_FORMAT_UNAVAILABLE,
|
||||
"NSGL: Stereo rendering is deprecated");
|
||||
return GLFW_FALSE;
|
||||
#else
|
||||
addAttrib(NSOpenGLPFAStereo);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (fbconfig->doublebuffer)
|
||||
addAttrib(NSOpenGLPFADoubleBuffer);
|
||||
|
||||
if (fbconfig->samples != GLFW_DONT_CARE)
|
||||
{
|
||||
if (fbconfig->samples == 0)
|
||||
{
|
||||
setAttrib(NSOpenGLPFASampleBuffers, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
setAttrib(NSOpenGLPFASampleBuffers, 1);
|
||||
setAttrib(NSOpenGLPFASamples, fbconfig->samples);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: All NSOpenGLPixelFormats on the relevant cards support sRGB
|
||||
// framebuffer, so there's no need (and no way) to request it
|
||||
|
||||
addAttrib(0);
|
||||
|
||||
#undef addAttrib
|
||||
#undef setAttrib
|
||||
|
||||
window->context.nsgl.pixelFormat =
|
||||
[[NSOpenGLPixelFormat alloc] initWithAttributes:attribs];
|
||||
if (window->context.nsgl.pixelFormat == nil)
|
||||
{
|
||||
_glfwInputError(GLFW_FORMAT_UNAVAILABLE,
|
||||
"NSGL: Failed to find a suitable pixel format");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
NSOpenGLContext* share = nil;
|
||||
|
||||
if (ctxconfig->share)
|
||||
share = ctxconfig->share->context.nsgl.object;
|
||||
|
||||
window->context.nsgl.object =
|
||||
[[NSOpenGLContext alloc] initWithFormat:window->context.nsgl.pixelFormat
|
||||
shareContext:share];
|
||||
if (window->context.nsgl.object == nil)
|
||||
{
|
||||
_glfwInputError(GLFW_VERSION_UNAVAILABLE,
|
||||
"NSGL: Failed to create OpenGL context");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
if (fbconfig->transparent)
|
||||
{
|
||||
GLint opaque = 0;
|
||||
[window->context.nsgl.object setValues:&opaque
|
||||
forParameter:NSOpenGLContextParameterSurfaceOpacity];
|
||||
}
|
||||
|
||||
[window->ns.view setWantsBestResolutionOpenGLSurface:window->ns.retina];
|
||||
|
||||
[window->context.nsgl.object setView:window->ns.view];
|
||||
|
||||
window->context.makeCurrent = makeContextCurrentNSGL;
|
||||
window->context.swapBuffers = swapBuffersNSGL;
|
||||
window->context.swapInterval = swapIntervalNSGL;
|
||||
window->context.extensionSupported = extensionSupportedNSGL;
|
||||
window->context.getProcAddress = getProcAddressNSGL;
|
||||
window->context.destroy = destroyContextNSGL;
|
||||
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW native API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
GLFWAPI id glfwGetNSGLContext(GLFWwindow* handle)
|
||||
{
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(nil);
|
||||
|
||||
if (window->context.source != GLFW_NATIVE_CONTEXT_API)
|
||||
{
|
||||
_glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL);
|
||||
return nil;
|
||||
}
|
||||
|
||||
return window->context.nsgl.object;
|
||||
}
|
||||
|
||||
+361
@@ -0,0 +1,361 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2016 Google Inc.
|
||||
// SPDX-FileCopyrightText: 2016-2017 Camilla Löwy <elmindreda@glfw.org>
|
||||
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
|
||||
|
||||
//go:build darwin || freebsd || linux || netbsd || openbsd
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "internal_unix.h"
|
||||
|
||||
|
||||
static void makeContextCurrentOSMesa(_GLFWwindow* window)
|
||||
{
|
||||
if (window)
|
||||
{
|
||||
int width, height;
|
||||
_glfwPlatformGetFramebufferSize(window, &width, &height);
|
||||
|
||||
// Check to see if we need to allocate a new buffer
|
||||
if ((window->context.osmesa.buffer == NULL) ||
|
||||
(width != window->context.osmesa.width) ||
|
||||
(height != window->context.osmesa.height))
|
||||
{
|
||||
free(window->context.osmesa.buffer);
|
||||
|
||||
// Allocate the new buffer (width * height * 8-bit RGBA)
|
||||
window->context.osmesa.buffer = calloc(4, (size_t) width * height);
|
||||
window->context.osmesa.width = width;
|
||||
window->context.osmesa.height = height;
|
||||
}
|
||||
|
||||
if (!OSMesaMakeCurrent(window->context.osmesa.handle,
|
||||
window->context.osmesa.buffer,
|
||||
GL_UNSIGNED_BYTE,
|
||||
width, height))
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"OSMesa: Failed to make context current");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_glfwPlatformSetTls(&_glfw.contextSlot, window);
|
||||
}
|
||||
|
||||
static GLFWglproc getProcAddressOSMesa(const char* procname)
|
||||
{
|
||||
return (GLFWglproc) OSMesaGetProcAddress(procname);
|
||||
}
|
||||
|
||||
static void destroyContextOSMesa(_GLFWwindow* window)
|
||||
{
|
||||
if (window->context.osmesa.handle)
|
||||
{
|
||||
OSMesaDestroyContext(window->context.osmesa.handle);
|
||||
window->context.osmesa.handle = NULL;
|
||||
}
|
||||
|
||||
if (window->context.osmesa.buffer)
|
||||
{
|
||||
free(window->context.osmesa.buffer);
|
||||
window->context.osmesa.width = 0;
|
||||
window->context.osmesa.height = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void swapBuffersOSMesa(_GLFWwindow* window)
|
||||
{
|
||||
// No double buffering on OSMesa
|
||||
}
|
||||
|
||||
static void swapIntervalOSMesa(int interval)
|
||||
{
|
||||
// No swap interval on OSMesa
|
||||
}
|
||||
|
||||
static int extensionSupportedOSMesa(const char* extension)
|
||||
{
|
||||
// OSMesa does not have extensions
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW internal API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
GLFWbool _glfwInitOSMesa(void)
|
||||
{
|
||||
int i;
|
||||
const char* sonames[] =
|
||||
{
|
||||
#if defined(_GLFW_OSMESA_LIBRARY)
|
||||
_GLFW_OSMESA_LIBRARY,
|
||||
#elif defined(__APPLE__)
|
||||
"libOSMesa.8.dylib",
|
||||
#elif defined(__CYGWIN__)
|
||||
"libOSMesa-8.so",
|
||||
#elif defined(__OpenBSD__) || defined(__NetBSD__)
|
||||
"libOSMesa.so",
|
||||
#else
|
||||
"libOSMesa.so.8",
|
||||
"libOSMesa.so.6",
|
||||
#endif
|
||||
NULL
|
||||
};
|
||||
|
||||
if (_glfw.osmesa.handle)
|
||||
return GLFW_TRUE;
|
||||
|
||||
for (i = 0; sonames[i]; i++)
|
||||
{
|
||||
_glfw.osmesa.handle = _glfw_dlopen(sonames[i]);
|
||||
if (_glfw.osmesa.handle)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!_glfw.osmesa.handle)
|
||||
{
|
||||
_glfwInputError(GLFW_API_UNAVAILABLE, "OSMesa: Library not found");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
_glfw.osmesa.CreateContextExt = (PFN_OSMesaCreateContextExt)
|
||||
_glfw_dlsym(_glfw.osmesa.handle, "OSMesaCreateContextExt");
|
||||
_glfw.osmesa.CreateContextAttribs = (PFN_OSMesaCreateContextAttribs)
|
||||
_glfw_dlsym(_glfw.osmesa.handle, "OSMesaCreateContextAttribs");
|
||||
_glfw.osmesa.DestroyContext = (PFN_OSMesaDestroyContext)
|
||||
_glfw_dlsym(_glfw.osmesa.handle, "OSMesaDestroyContext");
|
||||
_glfw.osmesa.MakeCurrent = (PFN_OSMesaMakeCurrent)
|
||||
_glfw_dlsym(_glfw.osmesa.handle, "OSMesaMakeCurrent");
|
||||
_glfw.osmesa.GetColorBuffer = (PFN_OSMesaGetColorBuffer)
|
||||
_glfw_dlsym(_glfw.osmesa.handle, "OSMesaGetColorBuffer");
|
||||
_glfw.osmesa.GetDepthBuffer = (PFN_OSMesaGetDepthBuffer)
|
||||
_glfw_dlsym(_glfw.osmesa.handle, "OSMesaGetDepthBuffer");
|
||||
_glfw.osmesa.GetProcAddress = (PFN_OSMesaGetProcAddress)
|
||||
_glfw_dlsym(_glfw.osmesa.handle, "OSMesaGetProcAddress");
|
||||
|
||||
if (!_glfw.osmesa.CreateContextExt ||
|
||||
!_glfw.osmesa.DestroyContext ||
|
||||
!_glfw.osmesa.MakeCurrent ||
|
||||
!_glfw.osmesa.GetColorBuffer ||
|
||||
!_glfw.osmesa.GetDepthBuffer ||
|
||||
!_glfw.osmesa.GetProcAddress)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"OSMesa: Failed to load required entry points");
|
||||
|
||||
_glfwTerminateOSMesa();
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
void _glfwTerminateOSMesa(void)
|
||||
{
|
||||
if (_glfw.osmesa.handle)
|
||||
{
|
||||
_glfw_dlclose(_glfw.osmesa.handle);
|
||||
_glfw.osmesa.handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#define setAttrib(a, v) \
|
||||
{ \
|
||||
assert(((size_t) index + 1) < sizeof(attribs) / sizeof(attribs[0])); \
|
||||
attribs[index++] = a; \
|
||||
attribs[index++] = v; \
|
||||
}
|
||||
|
||||
GLFWbool _glfwCreateContextOSMesa(_GLFWwindow* window,
|
||||
const _GLFWctxconfig* ctxconfig,
|
||||
const _GLFWfbconfig* fbconfig)
|
||||
{
|
||||
OSMesaContext share = NULL;
|
||||
const int accumBits = fbconfig->accumRedBits +
|
||||
fbconfig->accumGreenBits +
|
||||
fbconfig->accumBlueBits +
|
||||
fbconfig->accumAlphaBits;
|
||||
|
||||
if (ctxconfig->client == GLFW_OPENGL_ES_API)
|
||||
{
|
||||
_glfwInputError(GLFW_API_UNAVAILABLE,
|
||||
"OSMesa: OpenGL ES is not available on OSMesa");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
if (ctxconfig->share)
|
||||
share = ctxconfig->share->context.osmesa.handle;
|
||||
|
||||
if (OSMesaCreateContextAttribs)
|
||||
{
|
||||
int index = 0, attribs[40];
|
||||
|
||||
setAttrib(OSMESA_FORMAT, OSMESA_RGBA);
|
||||
setAttrib(OSMESA_DEPTH_BITS, fbconfig->depthBits);
|
||||
setAttrib(OSMESA_STENCIL_BITS, fbconfig->stencilBits);
|
||||
setAttrib(OSMESA_ACCUM_BITS, accumBits);
|
||||
|
||||
if (ctxconfig->profile == GLFW_OPENGL_CORE_PROFILE)
|
||||
{
|
||||
setAttrib(OSMESA_PROFILE, OSMESA_CORE_PROFILE);
|
||||
}
|
||||
else if (ctxconfig->profile == GLFW_OPENGL_COMPAT_PROFILE)
|
||||
{
|
||||
setAttrib(OSMESA_PROFILE, OSMESA_COMPAT_PROFILE);
|
||||
}
|
||||
|
||||
if (ctxconfig->major != 1 || ctxconfig->minor != 0)
|
||||
{
|
||||
setAttrib(OSMESA_CONTEXT_MAJOR_VERSION, ctxconfig->major);
|
||||
setAttrib(OSMESA_CONTEXT_MINOR_VERSION, ctxconfig->minor);
|
||||
}
|
||||
|
||||
if (ctxconfig->forward)
|
||||
{
|
||||
_glfwInputError(GLFW_VERSION_UNAVAILABLE,
|
||||
"OSMesa: Forward-compatible contexts not supported");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
setAttrib(0, 0);
|
||||
|
||||
window->context.osmesa.handle =
|
||||
OSMesaCreateContextAttribs(attribs, share);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ctxconfig->profile)
|
||||
{
|
||||
_glfwInputError(GLFW_VERSION_UNAVAILABLE,
|
||||
"OSMesa: OpenGL profiles unavailable");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
window->context.osmesa.handle =
|
||||
OSMesaCreateContextExt(OSMESA_RGBA,
|
||||
fbconfig->depthBits,
|
||||
fbconfig->stencilBits,
|
||||
accumBits,
|
||||
share);
|
||||
}
|
||||
|
||||
if (window->context.osmesa.handle == NULL)
|
||||
{
|
||||
_glfwInputError(GLFW_VERSION_UNAVAILABLE,
|
||||
"OSMesa: Failed to create context");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
window->context.makeCurrent = makeContextCurrentOSMesa;
|
||||
window->context.swapBuffers = swapBuffersOSMesa;
|
||||
window->context.swapInterval = swapIntervalOSMesa;
|
||||
window->context.extensionSupported = extensionSupportedOSMesa;
|
||||
window->context.getProcAddress = getProcAddressOSMesa;
|
||||
window->context.destroy = destroyContextOSMesa;
|
||||
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
#undef setAttrib
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW native API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
GLFWAPI int glfwGetOSMesaColorBuffer(GLFWwindow* handle, int* width,
|
||||
int* height, int* format, void** buffer)
|
||||
{
|
||||
void* mesaBuffer;
|
||||
GLint mesaWidth, mesaHeight, mesaFormat;
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
assert(window != NULL);
|
||||
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(GLFW_FALSE);
|
||||
|
||||
if (window->context.source != GLFW_OSMESA_CONTEXT_API)
|
||||
{
|
||||
_glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL);
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
if (!OSMesaGetColorBuffer(window->context.osmesa.handle,
|
||||
&mesaWidth, &mesaHeight,
|
||||
&mesaFormat, &mesaBuffer))
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"OSMesa: Failed to retrieve color buffer");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
if (width)
|
||||
*width = mesaWidth;
|
||||
if (height)
|
||||
*height = mesaHeight;
|
||||
if (format)
|
||||
*format = mesaFormat;
|
||||
if (buffer)
|
||||
*buffer = mesaBuffer;
|
||||
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
GLFWAPI int glfwGetOSMesaDepthBuffer(GLFWwindow* handle,
|
||||
int* width, int* height,
|
||||
int* bytesPerValue,
|
||||
void** buffer)
|
||||
{
|
||||
void* mesaBuffer;
|
||||
GLint mesaWidth, mesaHeight, mesaBytes;
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
assert(window != NULL);
|
||||
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(GLFW_FALSE);
|
||||
|
||||
if (window->context.source != GLFW_OSMESA_CONTEXT_API)
|
||||
{
|
||||
_glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL);
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
if (!OSMesaGetDepthBuffer(window->context.osmesa.handle,
|
||||
&mesaWidth, &mesaHeight,
|
||||
&mesaBytes, &mesaBuffer))
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"OSMesa: Failed to retrieve depth buffer");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
if (width)
|
||||
*width = mesaWidth;
|
||||
if (height)
|
||||
*height = mesaHeight;
|
||||
if (bytesPerValue)
|
||||
*bytesPerValue = mesaBytes;
|
||||
if (buffer)
|
||||
*buffer = mesaBuffer;
|
||||
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
GLFWAPI OSMesaContext glfwGetOSMesaContext(GLFWwindow* handle)
|
||||
{
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
|
||||
if (window->context.source != GLFW_OSMESA_CONTEXT_API)
|
||||
{
|
||||
_glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return window->context.osmesa.handle;
|
||||
}
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2016 Google Inc.
|
||||
// SPDX-FileCopyrightText: 2016-2017 Camilla Löwy <elmindreda@glfw.org>
|
||||
// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
|
||||
|
||||
//go:build darwin || freebsd || linux || netbsd || openbsd
|
||||
|
||||
#define OSMESA_RGBA 0x1908
|
||||
#define OSMESA_FORMAT 0x22
|
||||
#define OSMESA_DEPTH_BITS 0x30
|
||||
#define OSMESA_STENCIL_BITS 0x31
|
||||
#define OSMESA_ACCUM_BITS 0x32
|
||||
#define OSMESA_PROFILE 0x33
|
||||
#define OSMESA_CORE_PROFILE 0x34
|
||||
#define OSMESA_COMPAT_PROFILE 0x35
|
||||
#define OSMESA_CONTEXT_MAJOR_VERSION 0x36
|
||||
#define OSMESA_CONTEXT_MINOR_VERSION 0x37
|
||||
|
||||
typedef void* OSMesaContext;
|
||||
typedef void (*OSMESAproc)(void);
|
||||
|
||||
typedef OSMesaContext (GLAPIENTRY * PFN_OSMesaCreateContextExt)(GLenum,GLint,GLint,GLint,OSMesaContext);
|
||||
typedef OSMesaContext (GLAPIENTRY * PFN_OSMesaCreateContextAttribs)(const int*,OSMesaContext);
|
||||
typedef void (GLAPIENTRY * PFN_OSMesaDestroyContext)(OSMesaContext);
|
||||
typedef int (GLAPIENTRY * PFN_OSMesaMakeCurrent)(OSMesaContext,void*,int,int,int);
|
||||
typedef int (GLAPIENTRY * PFN_OSMesaGetColorBuffer)(OSMesaContext,int*,int*,int*,void**);
|
||||
typedef int (GLAPIENTRY * PFN_OSMesaGetDepthBuffer)(OSMesaContext,int*,int*,int*,void**);
|
||||
typedef GLFWglproc (GLAPIENTRY * PFN_OSMesaGetProcAddress)(const char*);
|
||||
#define OSMesaCreateContextExt _glfw.osmesa.CreateContextExt
|
||||
#define OSMesaCreateContextAttribs _glfw.osmesa.CreateContextAttribs
|
||||
#define OSMesaDestroyContext _glfw.osmesa.DestroyContext
|
||||
#define OSMesaMakeCurrent _glfw.osmesa.MakeCurrent
|
||||
#define OSMesaGetColorBuffer _glfw.osmesa.GetColorBuffer
|
||||
#define OSMesaGetDepthBuffer _glfw.osmesa.GetDepthBuffer
|
||||
#define OSMesaGetProcAddress _glfw.osmesa.GetProcAddress
|
||||
|
||||
#define _GLFW_OSMESA_CONTEXT_STATE _GLFWcontextOSMesa osmesa
|
||||
#define _GLFW_OSMESA_LIBRARY_CONTEXT_STATE _GLFWlibraryOSMesa osmesa
|
||||
|
||||
|
||||
// OSMesa-specific per-context data
|
||||
//
|
||||
typedef struct _GLFWcontextOSMesa
|
||||
{
|
||||
OSMesaContext handle;
|
||||
int width;
|
||||
int height;
|
||||
void* buffer;
|
||||
} _GLFWcontextOSMesa;
|
||||
|
||||
// OSMesa-specific global data
|
||||
//
|
||||
typedef struct _GLFWlibraryOSMesa
|
||||
{
|
||||
void* handle;
|
||||
|
||||
PFN_OSMesaCreateContextExt CreateContextExt;
|
||||
PFN_OSMesaCreateContextAttribs CreateContextAttribs;
|
||||
PFN_OSMesaDestroyContext DestroyContext;
|
||||
PFN_OSMesaMakeCurrent MakeCurrent;
|
||||
PFN_OSMesaGetColorBuffer GetColorBuffer;
|
||||
PFN_OSMesaGetDepthBuffer GetDepthBuffer;
|
||||
PFN_OSMesaGetProcAddress GetProcAddress;
|
||||
} _GLFWlibraryOSMesa;
|
||||
|
||||
|
||||
GLFWbool _glfwInitOSMesa(void);
|
||||
void _glfwTerminateOSMesa(void);
|
||||
GLFWbool _glfwCreateContextOSMesa(_GLFWwindow* window,
|
||||
const _GLFWctxconfig* ctxconfig,
|
||||
const _GLFWfbconfig* fbconfig);
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard
|
||||
// SPDX-FileCopyrightText: 2006-2017 Camilla Löwy <elmindreda@glfw.org>
|
||||
// SPDX-FileCopyrightText: 2024 The Ebitengine Authors
|
||||
|
||||
package glfw
|
||||
|
||||
// #include "internal_unix.h"
|
||||
import "C"
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// TODO: make these methods on GLFWtls like on windows
|
||||
// TODO: use uintptr instead of unsafe.Pointer once matching C API is no longer needed
|
||||
|
||||
//export _glfwPlatformCreateTls
|
||||
func _glfwPlatformCreateTls(tls *C._GLFWtls) C.GLFWbool {
|
||||
if tls.posix.allocated != False {
|
||||
panic("glfw: TLS must not be allocated")
|
||||
}
|
||||
if pthread_key_create(&tls.posix.key, 0) != 0 {
|
||||
_glfwInputError(int32(PlatformError),
|
||||
C.CString("POSIX: Failed to create context TLS"))
|
||||
return False
|
||||
}
|
||||
tls.posix.allocated = True
|
||||
return True
|
||||
}
|
||||
|
||||
//export _glfwPlatformDestroyTls
|
||||
func _glfwPlatformDestroyTls(tls *C._GLFWtls) {
|
||||
if tls.posix.allocated != 0 {
|
||||
pthread_key_delete(tls.posix.key)
|
||||
}
|
||||
*tls = C._GLFWtls{}
|
||||
}
|
||||
|
||||
//export _glfwPlatformGetTls
|
||||
func _glfwPlatformGetTls(tls *C._GLFWtls) unsafe.Pointer {
|
||||
if tls.posix.allocated != True {
|
||||
panic("glfw: TLS must be allocated")
|
||||
}
|
||||
var p = pthread_getspecific(tls.posix.key)
|
||||
return *(*unsafe.Pointer)(unsafe.Pointer(&p)) // TODO: replace with uintptr
|
||||
}
|
||||
|
||||
//export _glfwPlatformSetTls
|
||||
func _glfwPlatformSetTls(tls *C._GLFWtls, value unsafe.Pointer) {
|
||||
if tls.posix.allocated != True {
|
||||
panic("glfw: TLS must be allocated")
|
||||
}
|
||||
pthread_setspecific(tls.posix.key, uintptr(value))
|
||||
}
|
||||
|
||||
//export _glfwPlatformCreateMutex
|
||||
func _glfwPlatformCreateMutex(mutex *C._GLFWmutex) C.GLFWbool {
|
||||
if mutex.posix.allocated != False {
|
||||
panic("glfw: mutex must not be allocated")
|
||||
}
|
||||
if pthread_mutex_init(&mutex.posix.handle, nil) != 0 {
|
||||
_glfwInputError(int32(PlatformError), C.CString("POSIX: Failed to create mutex"))
|
||||
return False
|
||||
}
|
||||
mutex.posix.allocated = True
|
||||
return True
|
||||
}
|
||||
|
||||
//export _glfwPlatformDestroyMutex
|
||||
func _glfwPlatformDestroyMutex(mutex *C._GLFWmutex) {
|
||||
if mutex.posix.allocated != 0 {
|
||||
pthread_mutex_destroy(&mutex.posix.handle)
|
||||
}
|
||||
*mutex = C._GLFWmutex{}
|
||||
}
|
||||
|
||||
//export _glfwPlatformLockMutex
|
||||
func _glfwPlatformLockMutex(mutex *C._GLFWmutex) {
|
||||
if mutex.posix.allocated != True {
|
||||
panic("glfw: mutex must be allocated")
|
||||
}
|
||||
pthread_mutex_lock(&mutex.posix.handle)
|
||||
}
|
||||
|
||||
//export _glfwPlatformUnlockMutex
|
||||
func _glfwPlatformUnlockMutex(mutex *C._GLFWmutex) {
|
||||
if mutex.posix.allocated != True {
|
||||
panic("glfw: mutex must be allocated")
|
||||
}
|
||||
pthread_mutex_unlock(&mutex.posix.handle)
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
// 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
|
||||
|
||||
#include "internal_unix.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW platform API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
GLFWbool _glfwPlatformCreateTls(_GLFWtls* tls)
|
||||
{
|
||||
assert(tls->posix.allocated == GLFW_FALSE);
|
||||
|
||||
if (pthread_key_create(&tls->posix.key, NULL) != 0)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"POSIX: Failed to create context TLS");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
tls->posix.allocated = GLFW_TRUE;
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
void _glfwPlatformDestroyTls(_GLFWtls* tls)
|
||||
{
|
||||
if (tls->posix.allocated)
|
||||
pthread_key_delete(tls->posix.key);
|
||||
memset(tls, 0, sizeof(_GLFWtls));
|
||||
}
|
||||
|
||||
void* _glfwPlatformGetTls(_GLFWtls* tls)
|
||||
{
|
||||
assert(tls->posix.allocated == GLFW_TRUE);
|
||||
return pthread_getspecific(tls->posix.key);
|
||||
}
|
||||
|
||||
void _glfwPlatformSetTls(_GLFWtls* tls, void* value)
|
||||
{
|
||||
assert(tls->posix.allocated == GLFW_TRUE);
|
||||
pthread_setspecific(tls->posix.key, value);
|
||||
}
|
||||
|
||||
GLFWbool _glfwPlatformCreateMutex(_GLFWmutex* mutex)
|
||||
{
|
||||
assert(mutex->posix.allocated == GLFW_FALSE);
|
||||
|
||||
if (pthread_mutex_init(&mutex->posix.handle, NULL) != 0)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "POSIX: Failed to create mutex");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
return mutex->posix.allocated = GLFW_TRUE;
|
||||
}
|
||||
|
||||
void _glfwPlatformDestroyMutex(_GLFWmutex* mutex)
|
||||
{
|
||||
if (mutex->posix.allocated)
|
||||
pthread_mutex_destroy(&mutex->posix.handle);
|
||||
memset(mutex, 0, sizeof(_GLFWmutex));
|
||||
}
|
||||
|
||||
void _glfwPlatformLockMutex(_GLFWmutex* mutex)
|
||||
{
|
||||
assert(mutex->posix.allocated == GLFW_TRUE);
|
||||
pthread_mutex_lock(&mutex->posix.handle);
|
||||
}
|
||||
|
||||
void _glfwPlatformUnlockMutex(_GLFWmutex* mutex)
|
||||
{
|
||||
assert(mutex->posix.allocated == GLFW_TRUE);
|
||||
pthread_mutex_unlock(&mutex->posix.handle);
|
||||
}
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// 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
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#define _GLFW_PLATFORM_TLS_STATE _GLFWtlsPOSIX posix
|
||||
#define _GLFW_PLATFORM_MUTEX_STATE _GLFWmutexPOSIX posix
|
||||
|
||||
|
||||
// POSIX-specific thread local storage data
|
||||
//
|
||||
typedef struct _GLFWtlsPOSIX
|
||||
{
|
||||
GLFWbool allocated;
|
||||
pthread_key_t key;
|
||||
} _GLFWtlsPOSIX;
|
||||
|
||||
// POSIX-specific mutex data
|
||||
//
|
||||
typedef struct _GLFWmutexPOSIX
|
||||
{
|
||||
GLFWbool allocated;
|
||||
pthread_mutex_t handle;
|
||||
} _GLFWmutexPOSIX;
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
// 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
|
||||
|
||||
#include "internal_unix.h"
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW internal API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Initialise timer
|
||||
//
|
||||
void _glfwInitTimerPOSIX(void)
|
||||
{
|
||||
#if defined(CLOCK_MONOTONIC)
|
||||
struct timespec ts;
|
||||
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0)
|
||||
{
|
||||
_glfw.timer.posix.monotonic = GLFW_TRUE;
|
||||
_glfw.timer.posix.frequency = 1000000000;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
_glfw.timer.posix.monotonic = GLFW_FALSE;
|
||||
_glfw.timer.posix.frequency = 1000000;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW platform API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
uint64_t _glfwPlatformGetTimerValue(void)
|
||||
{
|
||||
#if defined(CLOCK_MONOTONIC)
|
||||
if (_glfw.timer.posix.monotonic)
|
||||
{
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (uint64_t) ts.tv_sec * (uint64_t) 1000000000 + (uint64_t) ts.tv_nsec;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
return (uint64_t) tv.tv_sec * (uint64_t) 1000000 + (uint64_t) tv.tv_usec;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t _glfwPlatformGetTimerFrequency(void)
|
||||
{
|
||||
return _glfw.timer.posix.frequency;
|
||||
}
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// 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 _GLFW_PLATFORM_LIBRARY_TIMER_STATE _GLFWtimerPOSIX posix
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
// POSIX-specific global timer data
|
||||
//
|
||||
typedef struct _GLFWtimerPOSIX
|
||||
{
|
||||
GLFWbool monotonic;
|
||||
uint64_t frequency;
|
||||
} _GLFWtimerPOSIX;
|
||||
|
||||
|
||||
void _glfwInitTimerPOSIX(void);
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// 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 (
|
||||
"image"
|
||||
"image/draw"
|
||||
)
|
||||
|
||||
func imageToGLFWImage(img image.Image) (glfwImg C.GLFWimage, free func()) {
|
||||
b := img.Bounds()
|
||||
if b.Dx() == 0 || b.Dy() == 0 {
|
||||
return C.GLFWimage{
|
||||
width: C.int(b.Dx()),
|
||||
height: C.int(b.Dy()),
|
||||
}, func() {}
|
||||
}
|
||||
|
||||
m := image.NewNRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))
|
||||
draw.Draw(m, m.Bounds(), img, b.Min, draw.Src)
|
||||
pixels := m.Pix
|
||||
|
||||
cpixels := C.CBytes(pixels)
|
||||
free = func() {
|
||||
C.free(cpixels)
|
||||
}
|
||||
|
||||
return C.GLFWimage{
|
||||
width: C.int(b.Dx()),
|
||||
height: C.int(b.Dy()),
|
||||
pixels: (*C.uchar)(cpixels),
|
||||
}, free
|
||||
}
|
||||
+596
@@ -0,0 +1,596 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/microsoftgdk"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/winver"
|
||||
)
|
||||
|
||||
type platformContextState struct {
|
||||
dc _HDC
|
||||
handle _HGLRC
|
||||
interval int
|
||||
}
|
||||
|
||||
type platformLibraryContextState struct {
|
||||
inited bool
|
||||
|
||||
EXT_swap_control bool
|
||||
EXT_colorspace bool
|
||||
ARB_multisample bool
|
||||
ARB_framebuffer_sRGB bool
|
||||
EXT_framebuffer_sRGB bool
|
||||
ARB_pixel_format bool
|
||||
ARB_create_context bool
|
||||
ARB_create_context_profile bool
|
||||
EXT_create_context_es2_profile bool
|
||||
ARB_create_context_robustness bool
|
||||
ARB_create_context_no_error bool
|
||||
ARB_context_flush_control bool
|
||||
}
|
||||
|
||||
func findPixelFormatAttribValue(attribs []int32, values []int32, attrib int32) int32 {
|
||||
for i := range attribs {
|
||||
if attribs[i] == attrib {
|
||||
return values[i]
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (w *Window) choosePixelFormat(ctxconfig *ctxconfig, fbconfig_ *fbconfig) (int, error) {
|
||||
var nativeCount int32
|
||||
var attribs []int32
|
||||
|
||||
if _glfw.platformContext.ARB_pixel_format {
|
||||
var attrib int32 = _WGL_NUMBER_PIXEL_FORMATS_ARB
|
||||
if err := wglGetPixelFormatAttribivARB(w.context.platform.dc, 1, 0, 1, &attrib, &nativeCount); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
attribs = append(attribs,
|
||||
_WGL_SUPPORT_OPENGL_ARB,
|
||||
_WGL_DRAW_TO_WINDOW_ARB,
|
||||
_WGL_PIXEL_TYPE_ARB,
|
||||
_WGL_ACCELERATION_ARB,
|
||||
_WGL_RED_BITS_ARB,
|
||||
_WGL_RED_SHIFT_ARB,
|
||||
_WGL_GREEN_BITS_ARB,
|
||||
_WGL_GREEN_SHIFT_ARB,
|
||||
_WGL_BLUE_BITS_ARB,
|
||||
_WGL_BLUE_SHIFT_ARB,
|
||||
_WGL_ALPHA_BITS_ARB,
|
||||
_WGL_ALPHA_SHIFT_ARB,
|
||||
_WGL_DEPTH_BITS_ARB,
|
||||
_WGL_STENCIL_BITS_ARB,
|
||||
_WGL_ACCUM_BITS_ARB,
|
||||
_WGL_ACCUM_RED_BITS_ARB,
|
||||
_WGL_ACCUM_GREEN_BITS_ARB,
|
||||
_WGL_ACCUM_BLUE_BITS_ARB,
|
||||
_WGL_ACCUM_ALPHA_BITS_ARB,
|
||||
_WGL_AUX_BUFFERS_ARB,
|
||||
_WGL_STEREO_ARB,
|
||||
_WGL_DOUBLE_BUFFER_ARB)
|
||||
|
||||
if _glfw.platformContext.ARB_multisample {
|
||||
attribs = append(attribs, _WGL_SAMPLES_ARB)
|
||||
}
|
||||
|
||||
if ctxconfig.client == OpenGLAPI {
|
||||
if _glfw.platformContext.ARB_framebuffer_sRGB || _glfw.platformContext.EXT_framebuffer_sRGB {
|
||||
attribs = append(attribs, _WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB)
|
||||
}
|
||||
} else {
|
||||
if _glfw.platformContext.EXT_colorspace {
|
||||
attribs = append(attribs, _WGL_COLORSPACE_EXT)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
c, err := _DescribePixelFormat(w.context.platform.dc, 1, uint32(unsafe.Sizeof(_PIXELFORMATDESCRIPTOR{})), nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
nativeCount = c
|
||||
}
|
||||
|
||||
usableConfigs := make([]*fbconfig, 0, nativeCount)
|
||||
for i := int32(0); i < nativeCount; i++ {
|
||||
var u fbconfig
|
||||
pixelFormat := uintptr(i) + 1
|
||||
|
||||
if _glfw.platformContext.ARB_pixel_format {
|
||||
// Get pixel format attributes through "modern" extension
|
||||
values := make([]int32, len(attribs))
|
||||
if err := wglGetPixelFormatAttribivARB(w.context.platform.dc, int32(pixelFormat), 0, uint32(len(attribs)), &attribs[0], &values[0]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
findAttribValue := func(attrib int32) int32 {
|
||||
return findPixelFormatAttribValue(attribs, values, attrib)
|
||||
}
|
||||
|
||||
if findAttribValue(_WGL_SUPPORT_OPENGL_ARB) == 0 || findAttribValue(_WGL_DRAW_TO_WINDOW_ARB) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if findAttribValue(_WGL_PIXEL_TYPE_ARB) != _WGL_TYPE_RGBA_ARB {
|
||||
continue
|
||||
}
|
||||
|
||||
if findAttribValue(_WGL_ACCELERATION_ARB) == _WGL_NO_ACCELERATION_ARB {
|
||||
continue
|
||||
}
|
||||
|
||||
if (findAttribValue(_WGL_DOUBLE_BUFFER_ARB) != 0) != fbconfig_.doublebuffer {
|
||||
continue
|
||||
}
|
||||
|
||||
u.redBits = int(findAttribValue(_WGL_RED_BITS_ARB))
|
||||
u.greenBits = int(findAttribValue(_WGL_GREEN_BITS_ARB))
|
||||
u.blueBits = int(findAttribValue(_WGL_BLUE_BITS_ARB))
|
||||
u.alphaBits = int(findAttribValue(_WGL_ALPHA_BITS_ARB))
|
||||
|
||||
u.depthBits = int(findAttribValue(_WGL_DEPTH_BITS_ARB))
|
||||
u.stencilBits = int(findAttribValue(_WGL_STENCIL_BITS_ARB))
|
||||
|
||||
u.accumRedBits = int(findAttribValue(_WGL_ACCUM_RED_BITS_ARB))
|
||||
u.accumGreenBits = int(findAttribValue(_WGL_ACCUM_GREEN_BITS_ARB))
|
||||
u.accumBlueBits = int(findAttribValue(_WGL_ACCUM_BLUE_BITS_ARB))
|
||||
u.accumAlphaBits = int(findAttribValue(_WGL_ACCUM_ALPHA_BITS_ARB))
|
||||
|
||||
u.auxBuffers = int(findAttribValue(_WGL_AUX_BUFFERS_ARB))
|
||||
|
||||
if findAttribValue(_WGL_STEREO_ARB) != 0 {
|
||||
u.stereo = true
|
||||
}
|
||||
|
||||
if _glfw.platformContext.ARB_multisample {
|
||||
u.samples = int(findAttribValue(_WGL_SAMPLES_ARB))
|
||||
}
|
||||
|
||||
if ctxconfig.client == OpenGLAPI {
|
||||
if _glfw.platformContext.ARB_framebuffer_sRGB || _glfw.platformContext.EXT_framebuffer_sRGB {
|
||||
if findAttribValue(_WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB) != 0 {
|
||||
u.sRGB = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _glfw.platformContext.EXT_colorspace {
|
||||
if findAttribValue(_WGL_COLORSPACE_EXT) == _WGL_COLORSPACE_SRGB_EXT {
|
||||
u.sRGB = true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Get pixel format attributes through legacy PFDs
|
||||
|
||||
var pfd _PIXELFORMATDESCRIPTOR
|
||||
if _, err := _DescribePixelFormat(w.context.platform.dc, int32(pixelFormat), uint32(unsafe.Sizeof(pfd)), &pfd); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if pfd.dwFlags&_PFD_DRAW_TO_WINDOW == 0 || pfd.dwFlags&_PFD_SUPPORT_OPENGL == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if pfd.dwFlags&_PFD_GENERIC_ACCELERATED == 0 && pfd.dwFlags&_PFD_GENERIC_FORMAT != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if pfd.iPixelType != _PFD_TYPE_RGBA {
|
||||
continue
|
||||
}
|
||||
|
||||
if (pfd.dwFlags&_PFD_DOUBLEBUFFER != 0) != fbconfig_.doublebuffer {
|
||||
continue
|
||||
}
|
||||
|
||||
u.redBits = int(pfd.cRedBits)
|
||||
u.greenBits = int(pfd.cGreenBits)
|
||||
u.blueBits = int(pfd.cBlueBits)
|
||||
u.alphaBits = int(pfd.cAlphaBits)
|
||||
|
||||
u.depthBits = int(pfd.cDepthBits)
|
||||
u.stencilBits = int(pfd.cStencilBits)
|
||||
|
||||
u.accumRedBits = int(pfd.cAccumRedBits)
|
||||
u.accumGreenBits = int(pfd.cAccumGreenBits)
|
||||
u.accumBlueBits = int(pfd.cAccumBlueBits)
|
||||
u.accumAlphaBits = int(pfd.cAccumAlphaBits)
|
||||
|
||||
u.auxBuffers = int(pfd.cAuxBuffers)
|
||||
|
||||
if pfd.dwFlags&_PFD_STEREO != 0 {
|
||||
u.stereo = true
|
||||
}
|
||||
}
|
||||
|
||||
u.handle = pixelFormat
|
||||
usableConfigs = append(usableConfigs, &u)
|
||||
}
|
||||
|
||||
if len(usableConfigs) == 0 {
|
||||
return 0, fmt.Errorf("glfw: the driver does not appear to support OpenGL")
|
||||
}
|
||||
|
||||
closest := chooseFBConfig(fbconfig_, usableConfigs)
|
||||
if closest == nil {
|
||||
return 0, fmt.Errorf("glfw: failed to find a suitable pixel format")
|
||||
}
|
||||
|
||||
return int(closest.handle), nil
|
||||
}
|
||||
|
||||
func makeContextCurrentWGL(window *Window) error {
|
||||
if window != nil {
|
||||
if err := wglMakeCurrent(window.context.platform.dc, window.context.platform.handle); err != nil {
|
||||
_ = _glfw.contextSlot.set(0)
|
||||
return err
|
||||
}
|
||||
if err := _glfw.contextSlot.set(uintptr(unsafe.Pointer(window))); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := wglMakeCurrent(0, 0); err != nil {
|
||||
_ = _glfw.contextSlot.set(0)
|
||||
return err
|
||||
}
|
||||
if err := _glfw.contextSlot.set(0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func swapBuffersWGL(window *Window) error {
|
||||
if window.monitor == nil && winver.IsWindowsVistaOrGreater() {
|
||||
// DWM Composition is always enabled on Win8+
|
||||
enabled := winver.IsWindows8OrGreater()
|
||||
|
||||
if !enabled {
|
||||
var err error
|
||||
enabled, err = _DwmIsCompositionEnabled()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// HACK: Use DwmFlush when desktop composition is enabled
|
||||
if enabled {
|
||||
for i := 0; i < window.context.platform.interval; i++ {
|
||||
// Ignore an error from DWM functions as they might not be implemented e.g. on Proton (#2113).
|
||||
_ = _DwmFlush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := _SwapBuffers(window.context.platform.dc); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func swapIntervalWGL(interval int) error {
|
||||
ptr, err := _glfw.contextSlot.get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
window := (*Window)(unsafe.Pointer(ptr))
|
||||
|
||||
window.context.platform.interval = interval
|
||||
|
||||
if window.monitor == nil && winver.IsWindowsVistaOrGreater() {
|
||||
// DWM Composition is always enabled on Win8+
|
||||
enabled := winver.IsWindows8OrGreater()
|
||||
|
||||
if !enabled {
|
||||
e, err := _DwmIsCompositionEnabled()
|
||||
// Ignore an error from DWM functions as they might not be implemented e.g. on Proton (#2113).
|
||||
if err == nil {
|
||||
enabled = e
|
||||
}
|
||||
}
|
||||
|
||||
// HACK: Disable WGL swap interval when desktop composition is enabled to
|
||||
// avoid interfering with DWM vsync
|
||||
if enabled {
|
||||
interval = 0
|
||||
}
|
||||
}
|
||||
|
||||
if _glfw.platformContext.EXT_swap_control {
|
||||
if err := wglSwapIntervalEXT(int32(interval)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func extensionSupportedWGL(extension string) bool {
|
||||
var extensions string
|
||||
|
||||
if wglGetExtensionsStringARB_Available() {
|
||||
extensions = wglGetExtensionsStringARB(wglGetCurrentDC())
|
||||
} else if wglGetExtensionsStringEXT_Available() {
|
||||
extensions = wglGetExtensionsStringEXT()
|
||||
}
|
||||
|
||||
if len(extensions) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, str := range strings.Split(extensions, " ") {
|
||||
if extension == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getProcAddressWGL(procname string) uintptr {
|
||||
proc := wglGetProcAddress(procname)
|
||||
if proc != 0 {
|
||||
return proc
|
||||
}
|
||||
return opengl32.NewProc(procname).Addr()
|
||||
}
|
||||
|
||||
func destroyContextWGL(window *Window) error {
|
||||
if window.context.platform.handle != 0 {
|
||||
// Ignore ERROR_BUSY. This happens when the thread is different from the context thread (#2518).
|
||||
// This is a known issue of GLFW (glfw/glfw#2239).
|
||||
// TODO: Delete the context on an appropriate thread.
|
||||
if err := wglDeleteContext(window.context.platform.handle); err != nil && !errors.Is(err, windows.ERROR_BUSY) {
|
||||
return err
|
||||
}
|
||||
window.context.platform.handle = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func initWGL() error {
|
||||
if microsoftgdk.IsXbox() {
|
||||
return fmt.Errorf("glfw: WGL is not available in Xbox")
|
||||
}
|
||||
|
||||
if _glfw.platformContext.inited {
|
||||
return nil
|
||||
}
|
||||
|
||||
// opengl32.dll must be loaded first. The loading state might affect Windows APIs.
|
||||
// This is needed at least before SetPixelFormat.
|
||||
if err := opengl32.Load(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// NOTE: A dummy context has to be created for opengl32.dll to load the
|
||||
// OpenGL ICD, from which we can then query WGL extensions
|
||||
// NOTE: This code will accept the Microsoft GDI ICD; accelerated context
|
||||
// creation failure occurs during manual pixel format enumeration
|
||||
|
||||
dc, err := _GetDC(_glfw.platformWindow.helperWindowHandle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pfd := _PIXELFORMATDESCRIPTOR{
|
||||
nVersion: 1,
|
||||
dwFlags: _PFD_DRAW_TO_WINDOW | _PFD_SUPPORT_OPENGL | _PFD_DOUBLEBUFFER,
|
||||
iPixelType: _PFD_TYPE_RGBA,
|
||||
cColorBits: 24,
|
||||
}
|
||||
pfd.nSize = uint16(unsafe.Sizeof(pfd))
|
||||
|
||||
format, err := _ChoosePixelFormat(dc, &pfd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := _SetPixelFormat(dc, format, &pfd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rc, err := wglCreateContext(dc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pdc := wglGetCurrentDC()
|
||||
prc := wglGetCurrentContext()
|
||||
|
||||
if err := wglMakeCurrent(dc, rc); err != nil {
|
||||
_ = wglMakeCurrent(pdc, prc)
|
||||
_ = wglDeleteContext(rc)
|
||||
return err
|
||||
}
|
||||
|
||||
// NOTE: Functions must be loaded first as they're needed to retrieve the
|
||||
// extension string that tells us whether the functions are supported
|
||||
//
|
||||
// Interestingly, wglGetProcAddress might return 0 after extensionSupportedWGL is called.
|
||||
initWGLExtensionFunctions()
|
||||
|
||||
// NOTE: WGL_ARB_extensions_string and WGL_EXT_extensions_string are not
|
||||
// checked below as we are already using them
|
||||
_glfw.platformContext.ARB_multisample = extensionSupportedWGL("WGL_ARB_multisample")
|
||||
_glfw.platformContext.ARB_framebuffer_sRGB = extensionSupportedWGL("WGL_ARB_framebuffer_sRGB")
|
||||
_glfw.platformContext.EXT_framebuffer_sRGB = extensionSupportedWGL("WGL_EXT_framebuffer_sRGB")
|
||||
_glfw.platformContext.ARB_create_context = extensionSupportedWGL("WGL_ARB_create_context")
|
||||
_glfw.platformContext.ARB_create_context_profile = extensionSupportedWGL("WGL_ARB_create_context_profile")
|
||||
_glfw.platformContext.EXT_create_context_es2_profile = extensionSupportedWGL("WGL_EXT_create_context_es2_profile")
|
||||
_glfw.platformContext.ARB_create_context_robustness = extensionSupportedWGL("WGL_ARB_create_context_robustness")
|
||||
_glfw.platformContext.ARB_create_context_no_error = extensionSupportedWGL("WGL_ARB_create_context_no_error")
|
||||
_glfw.platformContext.EXT_swap_control = extensionSupportedWGL("WGL_EXT_swap_control")
|
||||
_glfw.platformContext.EXT_colorspace = extensionSupportedWGL("WGL_EXT_colorspace")
|
||||
_glfw.platformContext.ARB_pixel_format = extensionSupportedWGL("WGL_ARB_pixel_format")
|
||||
_glfw.platformContext.ARB_context_flush_control = extensionSupportedWGL("WGL_ARB_context_flush_control")
|
||||
|
||||
if err := wglMakeCurrent(pdc, prc); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := wglDeleteContext(rc); err != nil {
|
||||
return err
|
||||
}
|
||||
_glfw.platformContext.inited = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func terminateWGL() {
|
||||
}
|
||||
|
||||
func (w *Window) createContextWGL(ctxconfig *ctxconfig, fbconfig *fbconfig) error {
|
||||
var share _HGLRC
|
||||
if ctxconfig.share != nil {
|
||||
share = ctxconfig.share.context.platform.handle
|
||||
}
|
||||
|
||||
dc, err := _GetDC(w.platform.handle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.context.platform.dc = dc
|
||||
|
||||
pixelFormat, err := w.choosePixelFormat(ctxconfig, fbconfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var pfd _PIXELFORMATDESCRIPTOR
|
||||
if _, err := _DescribePixelFormat(w.context.platform.dc, int32(pixelFormat), uint32(unsafe.Sizeof(pfd)), &pfd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := _SetPixelFormat(w.context.platform.dc, int32(pixelFormat), &pfd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ctxconfig.client == OpenGLAPI {
|
||||
if ctxconfig.forward && !_glfw.platformContext.ARB_create_context {
|
||||
return fmt.Errorf("glfw: a forward compatible OpenGL context requested but WGL_ARB_create_context is unavailable: %w", VersionUnavailable)
|
||||
}
|
||||
|
||||
if ctxconfig.profile != 0 && !_glfw.platformContext.ARB_create_context_profile {
|
||||
return fmt.Errorf("glfw: OpenGL profile requested but WGL_ARB_create_context_profile is unavailable: %w", VersionUnavailable)
|
||||
}
|
||||
} else {
|
||||
if !_glfw.platformContext.ARB_create_context || !_glfw.platformContext.ARB_create_context_profile || !_glfw.platformContext.EXT_create_context_es2_profile {
|
||||
return fmt.Errorf("glfw: OpenGL ES requested but WGL_ARB_create_context_es2_profile is unavailable: %w", APIUnavailable)
|
||||
}
|
||||
}
|
||||
|
||||
if _glfw.platformContext.ARB_create_context {
|
||||
var flags int32
|
||||
var mask int32
|
||||
if ctxconfig.client == OpenGLAPI {
|
||||
if ctxconfig.forward {
|
||||
flags |= _WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB
|
||||
}
|
||||
|
||||
if ctxconfig.profile == OpenGLCoreProfile {
|
||||
mask |= _WGL_CONTEXT_CORE_PROFILE_BIT_ARB
|
||||
} else if ctxconfig.profile == OpenGLCompatProfile {
|
||||
mask |= _WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB
|
||||
}
|
||||
} else {
|
||||
mask |= _WGL_CONTEXT_ES2_PROFILE_BIT_EXT
|
||||
}
|
||||
|
||||
if ctxconfig.debug {
|
||||
flags |= _WGL_CONTEXT_DEBUG_BIT_ARB
|
||||
}
|
||||
|
||||
var attribs []int32
|
||||
if ctxconfig.robustness != 0 {
|
||||
if _glfw.platformContext.ARB_create_context_robustness {
|
||||
if ctxconfig.robustness == NoResetNotification {
|
||||
attribs = append(attribs, _WGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, _WGL_NO_RESET_NOTIFICATION_ARB)
|
||||
} else if ctxconfig.robustness == LoseContextOnReset {
|
||||
attribs = append(attribs, _WGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, _WGL_LOSE_CONTEXT_ON_RESET_ARB)
|
||||
}
|
||||
flags |= _WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB
|
||||
}
|
||||
}
|
||||
|
||||
if ctxconfig.release != 0 {
|
||||
if _glfw.platformContext.ARB_context_flush_control {
|
||||
if ctxconfig.release == ReleaseBehaviorNone {
|
||||
attribs = append(attribs, _WGL_CONTEXT_RELEASE_BEHAVIOR_ARB, _WGL_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB)
|
||||
} else if ctxconfig.release == ReleaseBehaviorFlush {
|
||||
attribs = append(attribs, _WGL_CONTEXT_RELEASE_BEHAVIOR_ARB, _WGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ctxconfig.noerror {
|
||||
if _glfw.platformContext.ARB_create_context_no_error {
|
||||
attribs = append(attribs, _WGL_CONTEXT_OPENGL_NO_ERROR_ARB, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
attribs = append(attribs, _WGL_CONTEXT_MAJOR_VERSION_ARB, int32(ctxconfig.major))
|
||||
attribs = append(attribs, _WGL_CONTEXT_MINOR_VERSION_ARB, int32(ctxconfig.minor))
|
||||
}
|
||||
|
||||
if flags != 0 {
|
||||
attribs = append(attribs, _WGL_CONTEXT_FLAGS_ARB, flags)
|
||||
}
|
||||
|
||||
if mask != 0 {
|
||||
attribs = append(attribs, _WGL_CONTEXT_PROFILE_MASK_ARB, mask)
|
||||
}
|
||||
|
||||
attribs = append(attribs, 0, 0)
|
||||
|
||||
var err error
|
||||
w.context.platform.handle, err = wglCreateContextAttribsARB(w.context.platform.dc, share, &attribs[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
w.context.platform.handle, err = wglCreateContext(w.context.platform.dc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if share != 0 {
|
||||
if err := wglShareLists(share, w.context.platform.handle); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
w.context.makeCurrent = makeContextCurrentWGL
|
||||
w.context.swapBuffers = swapBuffersWGL
|
||||
w.context.swapInterval = swapIntervalWGL
|
||||
w.context.extensionSupported = extensionSupportedWGL
|
||||
w.context.getProcAddress = getProcAddressWGL
|
||||
w.context.destroy = destroyContextWGL
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getWGLContext(handle *Window) _HGLRC {
|
||||
window := handle
|
||||
if !_glfw.initialized {
|
||||
panic(NotInitialized)
|
||||
}
|
||||
if window.context.source != NativeContextAPI {
|
||||
// TODO: Should this return an error?
|
||||
return 0
|
||||
}
|
||||
return window.context.platform.handle
|
||||
}
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/microsoftgdk"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/winver"
|
||||
)
|
||||
|
||||
func createKeyTables() {
|
||||
for i := range _glfw.platformWindow.keycodes {
|
||||
_glfw.platformWindow.keycodes[i] = -1
|
||||
}
|
||||
for i := range _glfw.platformWindow.scancodes {
|
||||
_glfw.platformWindow.keycodes[i] = -1
|
||||
}
|
||||
|
||||
_glfw.platformWindow.keycodes[0x00B] = Key0
|
||||
_glfw.platformWindow.keycodes[0x002] = Key1
|
||||
_glfw.platformWindow.keycodes[0x003] = Key2
|
||||
_glfw.platformWindow.keycodes[0x004] = Key3
|
||||
_glfw.platformWindow.keycodes[0x005] = Key4
|
||||
_glfw.platformWindow.keycodes[0x006] = Key5
|
||||
_glfw.platformWindow.keycodes[0x007] = Key6
|
||||
_glfw.platformWindow.keycodes[0x008] = Key7
|
||||
_glfw.platformWindow.keycodes[0x009] = Key8
|
||||
_glfw.platformWindow.keycodes[0x00A] = Key9
|
||||
_glfw.platformWindow.keycodes[0x01E] = KeyA
|
||||
_glfw.platformWindow.keycodes[0x030] = KeyB
|
||||
_glfw.platformWindow.keycodes[0x02E] = KeyC
|
||||
_glfw.platformWindow.keycodes[0x020] = KeyD
|
||||
_glfw.platformWindow.keycodes[0x012] = KeyE
|
||||
_glfw.platformWindow.keycodes[0x021] = KeyF
|
||||
_glfw.platformWindow.keycodes[0x022] = KeyG
|
||||
_glfw.platformWindow.keycodes[0x023] = KeyH
|
||||
_glfw.platformWindow.keycodes[0x017] = KeyI
|
||||
_glfw.platformWindow.keycodes[0x024] = KeyJ
|
||||
_glfw.platformWindow.keycodes[0x025] = KeyK
|
||||
_glfw.platformWindow.keycodes[0x026] = KeyL
|
||||
_glfw.platformWindow.keycodes[0x032] = KeyM
|
||||
_glfw.platformWindow.keycodes[0x031] = KeyN
|
||||
_glfw.platformWindow.keycodes[0x018] = KeyO
|
||||
_glfw.platformWindow.keycodes[0x019] = KeyP
|
||||
_glfw.platformWindow.keycodes[0x010] = KeyQ
|
||||
_glfw.platformWindow.keycodes[0x013] = KeyR
|
||||
_glfw.platformWindow.keycodes[0x01F] = KeyS
|
||||
_glfw.platformWindow.keycodes[0x014] = KeyT
|
||||
_glfw.platformWindow.keycodes[0x016] = KeyU
|
||||
_glfw.platformWindow.keycodes[0x02F] = KeyV
|
||||
_glfw.platformWindow.keycodes[0x011] = KeyW
|
||||
_glfw.platformWindow.keycodes[0x02D] = KeyX
|
||||
_glfw.platformWindow.keycodes[0x015] = KeyY
|
||||
_glfw.platformWindow.keycodes[0x02C] = KeyZ
|
||||
|
||||
_glfw.platformWindow.keycodes[0x028] = KeyApostrophe
|
||||
_glfw.platformWindow.keycodes[0x02B] = KeyBackslash
|
||||
_glfw.platformWindow.keycodes[0x033] = KeyComma
|
||||
_glfw.platformWindow.keycodes[0x00D] = KeyEqual
|
||||
_glfw.platformWindow.keycodes[0x029] = KeyGraveAccent
|
||||
_glfw.platformWindow.keycodes[0x01A] = KeyLeftBracket
|
||||
_glfw.platformWindow.keycodes[0x00C] = KeyMinus
|
||||
_glfw.platformWindow.keycodes[0x034] = KeyPeriod
|
||||
_glfw.platformWindow.keycodes[0x01B] = KeyRightBracket
|
||||
_glfw.platformWindow.keycodes[0x027] = KeySemicolon
|
||||
_glfw.platformWindow.keycodes[0x035] = KeySlash
|
||||
_glfw.platformWindow.keycodes[0x056] = KeyWorld2
|
||||
|
||||
_glfw.platformWindow.keycodes[0x00E] = KeyBackspace
|
||||
_glfw.platformWindow.keycodes[0x153] = KeyDelete
|
||||
_glfw.platformWindow.keycodes[0x14F] = KeyEnd
|
||||
_glfw.platformWindow.keycodes[0x01C] = KeyEnter
|
||||
_glfw.platformWindow.keycodes[0x001] = KeyEscape
|
||||
_glfw.platformWindow.keycodes[0x147] = KeyHome
|
||||
_glfw.platformWindow.keycodes[0x152] = KeyInsert
|
||||
_glfw.platformWindow.keycodes[0x15D] = KeyMenu
|
||||
_glfw.platformWindow.keycodes[0x151] = KeyPageDown
|
||||
_glfw.platformWindow.keycodes[0x149] = KeyPageUp
|
||||
_glfw.platformWindow.keycodes[0x045] = KeyPause
|
||||
_glfw.platformWindow.keycodes[0x039] = KeySpace
|
||||
_glfw.platformWindow.keycodes[0x00F] = KeyTab
|
||||
_glfw.platformWindow.keycodes[0x03A] = KeyCapsLock
|
||||
_glfw.platformWindow.keycodes[0x145] = KeyNumLock
|
||||
_glfw.platformWindow.keycodes[0x046] = KeyScrollLock
|
||||
_glfw.platformWindow.keycodes[0x03B] = KeyF1
|
||||
_glfw.platformWindow.keycodes[0x03C] = KeyF2
|
||||
_glfw.platformWindow.keycodes[0x03D] = KeyF3
|
||||
_glfw.platformWindow.keycodes[0x03E] = KeyF4
|
||||
_glfw.platformWindow.keycodes[0x03F] = KeyF5
|
||||
_glfw.platformWindow.keycodes[0x040] = KeyF6
|
||||
_glfw.platformWindow.keycodes[0x041] = KeyF7
|
||||
_glfw.platformWindow.keycodes[0x042] = KeyF8
|
||||
_glfw.platformWindow.keycodes[0x043] = KeyF9
|
||||
_glfw.platformWindow.keycodes[0x044] = KeyF10
|
||||
_glfw.platformWindow.keycodes[0x057] = KeyF11
|
||||
_glfw.platformWindow.keycodes[0x058] = KeyF12
|
||||
_glfw.platformWindow.keycodes[0x064] = KeyF13
|
||||
_glfw.platformWindow.keycodes[0x065] = KeyF14
|
||||
_glfw.platformWindow.keycodes[0x066] = KeyF15
|
||||
_glfw.platformWindow.keycodes[0x067] = KeyF16
|
||||
_glfw.platformWindow.keycodes[0x068] = KeyF17
|
||||
_glfw.platformWindow.keycodes[0x069] = KeyF18
|
||||
_glfw.platformWindow.keycodes[0x06A] = KeyF19
|
||||
_glfw.platformWindow.keycodes[0x06B] = KeyF20
|
||||
_glfw.platformWindow.keycodes[0x06C] = KeyF21
|
||||
_glfw.platformWindow.keycodes[0x06D] = KeyF22
|
||||
_glfw.platformWindow.keycodes[0x06E] = KeyF23
|
||||
_glfw.platformWindow.keycodes[0x076] = KeyF24
|
||||
_glfw.platformWindow.keycodes[0x038] = KeyLeftAlt
|
||||
_glfw.platformWindow.keycodes[0x01D] = KeyLeftControl
|
||||
_glfw.platformWindow.keycodes[0x02A] = KeyLeftShift
|
||||
_glfw.platformWindow.keycodes[0x15B] = KeyLeftSuper
|
||||
_glfw.platformWindow.keycodes[0x137] = KeyPrintScreen
|
||||
_glfw.platformWindow.keycodes[0x138] = KeyRightAlt
|
||||
_glfw.platformWindow.keycodes[0x11D] = KeyRightControl
|
||||
_glfw.platformWindow.keycodes[0x036] = KeyRightShift
|
||||
_glfw.platformWindow.keycodes[0x15C] = KeyRightSuper
|
||||
_glfw.platformWindow.keycodes[0x150] = KeyDown
|
||||
_glfw.platformWindow.keycodes[0x14B] = KeyLeft
|
||||
_glfw.platformWindow.keycodes[0x14D] = KeyRight
|
||||
_glfw.platformWindow.keycodes[0x148] = KeyUp
|
||||
|
||||
_glfw.platformWindow.keycodes[0x052] = KeyKP0
|
||||
_glfw.platformWindow.keycodes[0x04F] = KeyKP1
|
||||
_glfw.platformWindow.keycodes[0x050] = KeyKP2
|
||||
_glfw.platformWindow.keycodes[0x051] = KeyKP3
|
||||
_glfw.platformWindow.keycodes[0x04B] = KeyKP4
|
||||
_glfw.platformWindow.keycodes[0x04C] = KeyKP5
|
||||
_glfw.platformWindow.keycodes[0x04D] = KeyKP6
|
||||
_glfw.platformWindow.keycodes[0x047] = KeyKP7
|
||||
_glfw.platformWindow.keycodes[0x048] = KeyKP8
|
||||
_glfw.platformWindow.keycodes[0x049] = KeyKP9
|
||||
_glfw.platformWindow.keycodes[0x04E] = KeyKPAdd
|
||||
_glfw.platformWindow.keycodes[0x053] = KeyKPDecimal
|
||||
_glfw.platformWindow.keycodes[0x135] = KeyKPDivide
|
||||
_glfw.platformWindow.keycodes[0x11C] = KeyKPEnter
|
||||
_glfw.platformWindow.keycodes[0x059] = KeyKPEqual
|
||||
_glfw.platformWindow.keycodes[0x037] = KeyKPMultiply
|
||||
_glfw.platformWindow.keycodes[0x04A] = KeyKPSubtract
|
||||
|
||||
for scancode := 0; scancode < 512; scancode++ {
|
||||
if _glfw.platformWindow.keycodes[scancode] > 0 {
|
||||
_glfw.platformWindow.scancodes[_glfw.platformWindow.keycodes[scancode]] = scancode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateKeyNamesWin32() {
|
||||
// MapVirtualKeyW is not implemented in Xbox.
|
||||
if microsoftgdk.IsXbox() {
|
||||
return
|
||||
}
|
||||
|
||||
for i := range _glfw.platformWindow.keynames {
|
||||
_glfw.platformWindow.keynames[i] = ""
|
||||
}
|
||||
|
||||
var state [256]byte
|
||||
|
||||
for key := KeySpace; key <= KeyLast; key++ {
|
||||
scancode := _glfw.platformWindow.scancodes[key]
|
||||
if scancode == -1 {
|
||||
continue
|
||||
}
|
||||
|
||||
var vk uint32
|
||||
if key >= KeyKP0 && key <= KeyKPAdd {
|
||||
vks := []uint32{
|
||||
_VK_NUMPAD0, _VK_NUMPAD1, _VK_NUMPAD2, _VK_NUMPAD3,
|
||||
_VK_NUMPAD4, _VK_NUMPAD5, _VK_NUMPAD6, _VK_NUMPAD7,
|
||||
_VK_NUMPAD8, _VK_NUMPAD9, _VK_DECIMAL, _VK_DIVIDE,
|
||||
_VK_MULTIPLY, _VK_SUBTRACT, _VK_ADD,
|
||||
}
|
||||
vk = vks[key-KeyKP0]
|
||||
} else {
|
||||
vk = _MapVirtualKeyW(uint32(scancode), _MAPVK_VSC_TO_VK)
|
||||
}
|
||||
|
||||
var chars [16]uint16
|
||||
length := _ToUnicode(vk, uint32(scancode), state[:], chars[:], int32(len(chars)), 0)
|
||||
if length == -1 {
|
||||
// This is a dead key, so we need a second simulated key press
|
||||
// to make it output its own character (usually a diacritic)
|
||||
length = _ToUnicode(vk, uint32(scancode), state[:], chars[:], int32(len(chars)), 0)
|
||||
}
|
||||
if length < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
_glfw.platformWindow.keynames[key] = windows.UTF16ToString(chars[:length])
|
||||
}
|
||||
}
|
||||
|
||||
func createHelperWindow() error {
|
||||
h, err := _CreateWindowExW(_WS_EX_OVERLAPPEDWINDOW, _GLFW_WNDCLASSNAME, "GLFW message window", _WS_CLIPSIBLINGS|_WS_CLIPCHILDREN, 0, 0, 1, 1, 0, 0, _glfw.platformWindow.instance, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_glfw.platformWindow.helperWindowHandle = h
|
||||
|
||||
// HACK: The command to the first ShowWindow call is ignored if the parent
|
||||
// process passed along a STARTUPINFO, so clear that with a no-op call
|
||||
_ShowWindow(_glfw.platformWindow.helperWindowHandle, _SW_HIDE)
|
||||
|
||||
// Register for HID device notifications
|
||||
if !microsoftgdk.IsXbox() {
|
||||
_GUID_DEVINTERFACE_HID := windows.GUID{
|
||||
Data1: 0x4d1e55b2,
|
||||
Data2: 0xf16f,
|
||||
Data3: 0x11cf,
|
||||
Data4: [...]byte{0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30},
|
||||
}
|
||||
|
||||
var dbi _DEV_BROADCAST_DEVICEINTERFACE_W
|
||||
dbi.dbcc_size = uint32(unsafe.Sizeof(dbi))
|
||||
dbi.dbcc_devicetype = _DBT_DEVTYP_DEVICEINTERFACE
|
||||
dbi.dbcc_classguid = _GUID_DEVINTERFACE_HID
|
||||
notify, err := _RegisterDeviceNotificationW(windows.Handle(_glfw.platformWindow.helperWindowHandle), unsafe.Pointer(&dbi), _DEVICE_NOTIFY_WINDOW_HANDLE)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_glfw.platformWindow.deviceNotificationHandle = notify
|
||||
}
|
||||
|
||||
var msg _MSG
|
||||
for _PeekMessageW(&msg, _glfw.platformWindow.helperWindowHandle, 0, 0, _PM_REMOVE) {
|
||||
_TranslateMessage(&msg)
|
||||
_DispatchMessageW(&msg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func platformInit() error {
|
||||
// Changing the foreground lock timeout was removed from the original code.
|
||||
// See https://github.com/glfw/glfw/commit/58b48a3a00d9c2a5ca10cc23069a71d8773cc7a4
|
||||
|
||||
m, err := _GetModuleHandleExW(_GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS|_GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, unsafe.Pointer(&_glfw))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_glfw.platformWindow.instance = _HINSTANCE(m)
|
||||
|
||||
createKeyTables()
|
||||
updateKeyNamesWin32()
|
||||
|
||||
if winver.IsWindows10CreatorsUpdateOrGreater() {
|
||||
if !microsoftgdk.IsXbox() {
|
||||
// Ignore the error as SetProcessDpiAwarenessContext returns an error on Steam Deck (#2113).
|
||||
// This seems an issue in Wine and/or Proton, but there is nothing we can do.
|
||||
_ = _SetProcessDpiAwarenessContext(_DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
|
||||
}
|
||||
} else if winver.IsWindows8Point1OrGreater() {
|
||||
if err := _SetProcessDpiAwareness(_PROCESS_PER_MONITOR_DPI_AWARE); err != nil && !errors.Is(err, handleError(windows.E_ACCESSDENIED)) {
|
||||
return err
|
||||
}
|
||||
} else if winver.IsWindowsVistaOrGreater() {
|
||||
_SetProcessDPIAware()
|
||||
}
|
||||
|
||||
if err := registerWindowClassWin32(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := createHelperWindow(); err != nil {
|
||||
return err
|
||||
}
|
||||
if microsoftgdk.IsXbox() {
|
||||
// On Xbox, APIs to get monitors are not available.
|
||||
// Create a pseudo monitor instance instead.
|
||||
w, h := microsoftgdk.MonitorResolution()
|
||||
mode := &VidMode{
|
||||
Width: w,
|
||||
Height: h,
|
||||
RedBits: 8,
|
||||
GreenBits: 8,
|
||||
BlueBits: 8,
|
||||
RefreshRate: 0, // TODO: Is it possible to get an appropriate refresh rate?
|
||||
}
|
||||
m := &Monitor{
|
||||
name: "Xbox Monitor",
|
||||
modes: []*VidMode{mode},
|
||||
}
|
||||
if err := inputMonitor(m, Connected, _GLFW_INSERT_LAST); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := pollMonitorsWin32(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func platformTerminate() error {
|
||||
if _glfw.platformWindow.deviceNotificationHandle != 0 {
|
||||
if err := _UnregisterDeviceNotification(_glfw.platformWindow.deviceNotificationHandle); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _glfw.platformWindow.helperWindowHandle != 0 {
|
||||
if !microsoftgdk.IsXbox() {
|
||||
// An error 'invalid window handle' can occur without any specific reasons (#2551).
|
||||
// As there is nothing to do, just ignore this error.
|
||||
if err := _DestroyWindow(_glfw.platformWindow.helperWindowHandle); err != nil && !errors.Is(err, windows.ERROR_INVALID_WINDOW_HANDLE) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := unregisterWindowClassWin32(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
terminateWGL()
|
||||
|
||||
return nil
|
||||
}
|
||||
+357
@@ -0,0 +1,357 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2021 Camilla Löwy <elmindreda@glfw.org>
|
||||
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
|
||||
|
||||
package glfw
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/microsoftgdk"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/winver"
|
||||
)
|
||||
|
||||
func monitorCallback(handle _HMONITOR, dc _HDC, rect *_RECT, monitor *Monitor /* _LPARAM */) uintptr /* _BOOL */ {
|
||||
if mi, ok := _GetMonitorInfoW_Ex(handle); ok {
|
||||
if windows.UTF16ToString(mi.szDevice[:]) == monitor.platform.adapterName {
|
||||
monitor.platform.handle = handle
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
var monitorCallbackPtr = windows.NewCallbackCDecl(monitorCallback)
|
||||
|
||||
func createMonitor(adapter *_DISPLAY_DEVICEW, display *_DISPLAY_DEVICEW) (*Monitor, error) {
|
||||
var name string
|
||||
if display != nil {
|
||||
name = windows.UTF16ToString(display.DeviceString[:])
|
||||
} else {
|
||||
name = windows.UTF16ToString(adapter.DeviceString[:])
|
||||
}
|
||||
if name == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
adapterDeviceName := windows.UTF16ToString(adapter.DeviceName[:])
|
||||
dm, ok := _EnumDisplaySettingsW(adapterDeviceName, _ENUM_CURRENT_SETTINGS)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
monitor := &Monitor{
|
||||
name: name,
|
||||
}
|
||||
|
||||
if adapter.StateFlags&_DISPLAY_DEVICE_MODESPRUNED != 0 {
|
||||
monitor.platform.modesPruned = true
|
||||
}
|
||||
|
||||
monitor.platform.adapterName = adapterDeviceName
|
||||
if display != nil {
|
||||
monitor.platform.displayName = windows.UTF16ToString(display.DeviceName[:])
|
||||
}
|
||||
|
||||
rect := _RECT{
|
||||
left: dm.dmPosition.x,
|
||||
top: dm.dmPosition.y,
|
||||
right: dm.dmPosition.x + int32(dm.dmPelsWidth),
|
||||
bottom: dm.dmPosition.y + int32(dm.dmPelsHeight),
|
||||
}
|
||||
if err := _EnumDisplayMonitors(0, &rect, monitorCallbackPtr, _LPARAM(unsafe.Pointer(monitor))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return monitor, nil
|
||||
}
|
||||
|
||||
func pollMonitorsWin32() error {
|
||||
if microsoftgdk.IsXbox() {
|
||||
return nil
|
||||
}
|
||||
|
||||
disconnected := make([]*Monitor, len(_glfw.monitors))
|
||||
copy(disconnected, _glfw.monitors)
|
||||
|
||||
adapterLoop:
|
||||
for adapterIndex := uint32(0); ; adapterIndex++ {
|
||||
adapter, ok := _EnumDisplayDevicesW("", adapterIndex, 0)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if adapter.StateFlags&_DISPLAY_DEVICE_ACTIVE == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
typ := _GLFW_INSERT_LAST
|
||||
if adapter.StateFlags&_DISPLAY_DEVICE_PRIMARY_DEVICE != 0 {
|
||||
typ = _GLFW_INSERT_FIRST
|
||||
}
|
||||
|
||||
var found bool
|
||||
displayLoop:
|
||||
for displayIndex := uint32(0); ; displayIndex++ {
|
||||
display, ok := _EnumDisplayDevicesW(windows.UTF16ToString(adapter.DeviceName[:]), displayIndex, 0)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
found = true
|
||||
if display.StateFlags&_DISPLAY_DEVICE_ACTIVE == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
for i, monitor := range disconnected {
|
||||
if monitor != nil && monitor.platform.displayName == windows.UTF16ToString(display.DeviceName[:]) {
|
||||
disconnected[i] = nil
|
||||
err := _EnumDisplayMonitors(0, nil, monitorCallbackPtr, _LPARAM(unsafe.Pointer(_glfw.monitors[i])))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
continue displayLoop
|
||||
}
|
||||
}
|
||||
|
||||
monitor, err := createMonitor(&adapter, &display)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if monitor == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := inputMonitor(monitor, Connected, typ); err != nil {
|
||||
return err
|
||||
}
|
||||
typ = _GLFW_INSERT_LAST
|
||||
}
|
||||
|
||||
// HACK: If an active adapter does not have any display devices
|
||||
// (as sometimes happens), add it directly as a monitor
|
||||
if !found {
|
||||
for i, monitor := range disconnected {
|
||||
if monitor != nil && monitor.platform.displayName == windows.UTF16ToString(adapter.DeviceName[:]) {
|
||||
disconnected[i] = nil
|
||||
continue adapterLoop
|
||||
}
|
||||
}
|
||||
|
||||
monitor, err := createMonitor(&adapter, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if monitor == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := inputMonitor(monitor, Connected, typ); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, monitor := range disconnected {
|
||||
if monitor != nil {
|
||||
if err := inputMonitor(monitor, Disconnected, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Monitor) setVideoModeWin32(desired *VidMode) error {
|
||||
best, err := m.chooseVideoMode(desired)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
current := m.platformGetVideoMode()
|
||||
if best.equals(current) {
|
||||
return nil
|
||||
}
|
||||
|
||||
dm := _DEVMODEW{
|
||||
dmFields: _DM_PELSWIDTH | _DM_PELSHEIGHT | _DM_BITSPERPEL | _DM_DISPLAYFREQUENCY,
|
||||
dmPelsWidth: uint32(best.Width),
|
||||
dmPelsHeight: uint32(best.Height),
|
||||
dmBitsPerPel: uint32(best.RedBits + best.GreenBits + best.BlueBits),
|
||||
dmDisplayFrequency: uint32(best.RefreshRate),
|
||||
}
|
||||
dm.dmSize = uint16(unsafe.Sizeof(dm))
|
||||
if dm.dmBitsPerPel < 15 || dm.dmBitsPerPel >= 24 {
|
||||
dm.dmBitsPerPel = 32
|
||||
}
|
||||
switch _ChangeDisplaySettingsExW(m.platform.adapterName, &dm, 0, _CDS_FULLSCREEN, nil) {
|
||||
case _DISP_CHANGE_SUCCESSFUL:
|
||||
m.platform.modeChanged = true
|
||||
return nil
|
||||
case _DISP_CHANGE_BADDUALVIEW:
|
||||
return errors.New("glfw: the system uses DualView at Monitor.setVideoModeWin32")
|
||||
case _DISP_CHANGE_BADFLAGS:
|
||||
return errors.New("glfw: invalid flags at Monitor.setVideoModeWin32")
|
||||
case _DISP_CHANGE_BADMODE:
|
||||
return errors.New("glfw: graphics mode not supported at Monitor.setVideoModeWin32")
|
||||
case _DISP_CHANGE_BADPARAM:
|
||||
return errors.New("glfw: invalid parameter at Monitor.setVideoModeWin32")
|
||||
case _DISP_CHANGE_FAILED:
|
||||
return errors.New("glfw: graphics mode failed at Monitor.setVideoModeWin32")
|
||||
case _DISP_CHANGE_NOTUPDATED:
|
||||
return errors.New("glfw: failed to write to registry at Monitor.setVideoModeWin32")
|
||||
case _DISP_CHANGE_RESTART:
|
||||
return errors.New("glfw: computer restart required at Monitor.setVideoModeWin32")
|
||||
default:
|
||||
return errors.New("glfw: unknown error at Monitor.setVideoModeWin32")
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Monitor) restoreVideoModeWin32() {
|
||||
if m.platform.modeChanged {
|
||||
_ChangeDisplaySettingsExW(m.platform.adapterName, nil, 0, _CDS_FULLSCREEN, nil)
|
||||
m.platform.modeChanged = false
|
||||
}
|
||||
}
|
||||
|
||||
func getMonitorContentScaleWin32(handle _HMONITOR) (xscale, yscale float32, err error) {
|
||||
var xdpi, ydpi uint32
|
||||
|
||||
if winver.IsWindows8Point1OrGreater() {
|
||||
var err error
|
||||
xdpi, ydpi, err = _GetDpiForMonitor(handle, _MDT_EFFECTIVE_DPI)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
} else {
|
||||
dc, err := _GetDC(0)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
defer _ReleaseDC(0, dc)
|
||||
|
||||
xdpi = uint32(_GetDeviceCaps(dc, _LOGPIXELSX))
|
||||
ydpi = uint32(_GetDeviceCaps(dc, _LOGPIXELSY))
|
||||
}
|
||||
|
||||
xscale = float32(xdpi) / _USER_DEFAULT_SCREEN_DPI
|
||||
yscale = float32(ydpi) / _USER_DEFAULT_SCREEN_DPI
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Monitor) platformGetMonitorPos() (xpos, ypos int, ok bool) {
|
||||
if microsoftgdk.IsXbox() {
|
||||
return 0, 0, true
|
||||
}
|
||||
|
||||
dm, ok := _EnumDisplaySettingsExW(m.platform.adapterName, _ENUM_CURRENT_SETTINGS, _EDS_ROTATEDMODE)
|
||||
if !ok {
|
||||
return 0, 0, false
|
||||
}
|
||||
return int(dm.dmPosition.x), int(dm.dmPosition.y), true
|
||||
}
|
||||
|
||||
func (m *Monitor) platformGetMonitorContentScale() (xscale, yscale float32, err error) {
|
||||
if microsoftgdk.IsXbox() {
|
||||
return 1, 1, nil
|
||||
}
|
||||
|
||||
return getMonitorContentScaleWin32(m.platform.handle)
|
||||
}
|
||||
|
||||
func (m *Monitor) platformGetMonitorWorkarea() (xpos, ypos, width, height int) {
|
||||
if microsoftgdk.IsXbox() {
|
||||
w, h := microsoftgdk.MonitorResolution()
|
||||
return 0, 0, w, h
|
||||
}
|
||||
|
||||
mi, ok := _GetMonitorInfoW(m.platform.handle)
|
||||
if !ok {
|
||||
return 0, 0, 0, 0
|
||||
}
|
||||
xpos = int(mi.rcWork.left)
|
||||
ypos = int(mi.rcWork.top)
|
||||
width = int(mi.rcWork.right - mi.rcWork.left)
|
||||
height = int(mi.rcWork.bottom - mi.rcWork.top)
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Monitor) platformAppendVideoModes(monitors []*VidMode) ([]*VidMode, error) {
|
||||
origLen := len(monitors)
|
||||
loop:
|
||||
for modeIndex := uint32(0); ; modeIndex++ {
|
||||
dm, ok := _EnumDisplaySettingsW(m.platform.adapterName, modeIndex)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
// Skip modes with less than 15 BPP
|
||||
if dm.dmBitsPerPel < 15 {
|
||||
continue
|
||||
}
|
||||
|
||||
r, g, b := splitBPP(int(dm.dmBitsPerPel))
|
||||
mode := &VidMode{
|
||||
Width: int(dm.dmPelsWidth),
|
||||
Height: int(dm.dmPelsHeight),
|
||||
RefreshRate: int(dm.dmDisplayFrequency),
|
||||
RedBits: r,
|
||||
GreenBits: g,
|
||||
BlueBits: b,
|
||||
}
|
||||
|
||||
// Skip duplicate modes
|
||||
for _, m := range monitors[origLen:] {
|
||||
if m.equals(mode) {
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
|
||||
if m.platform.modesPruned {
|
||||
// Skip modes not supported by the connected displays
|
||||
if _ChangeDisplaySettingsExW(m.platform.adapterName, &dm, 0, _CDS_TEST, nil) != _DISP_CHANGE_SUCCESSFUL {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
monitors = append(monitors, mode)
|
||||
}
|
||||
|
||||
if len(monitors) == origLen {
|
||||
// HACK: Report the current mode if no valid modes were found
|
||||
monitors = append(monitors, m.platformGetVideoMode())
|
||||
}
|
||||
|
||||
return monitors, nil
|
||||
}
|
||||
|
||||
func (m *Monitor) platformGetVideoMode() *VidMode {
|
||||
if microsoftgdk.IsXbox() {
|
||||
return m.modes[0]
|
||||
}
|
||||
|
||||
dm, _ := _EnumDisplaySettingsW(m.platform.adapterName, _ENUM_CURRENT_SETTINGS)
|
||||
r, g, b := splitBPP(int(dm.dmBitsPerPel))
|
||||
return &VidMode{
|
||||
Width: int(dm.dmPelsWidth),
|
||||
Height: int(dm.dmPelsHeight),
|
||||
RefreshRate: int(dm.dmDisplayFrequency),
|
||||
RedBits: r,
|
||||
GreenBits: g,
|
||||
BlueBits: b,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Monitor) in32Adapter() (string, error) {
|
||||
if !_glfw.initialized {
|
||||
return "", NotInitialized
|
||||
}
|
||||
return m.platform.adapterName, nil
|
||||
}
|
||||
|
||||
func (m *Monitor) win32Monitor() (string, error) {
|
||||
if !_glfw.initialized {
|
||||
return "", NotInitialized
|
||||
}
|
||||
return m.platform.displayName, nil
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
// 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 (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
_GLFW_WNDCLASSNAME = "GLFW30"
|
||||
)
|
||||
|
||||
type platformWindowState struct {
|
||||
handle windows.HWND
|
||||
bigIcon _HICON
|
||||
smallIcon _HICON
|
||||
|
||||
cursorTracked bool
|
||||
frameAction bool
|
||||
iconified bool
|
||||
maximized bool
|
||||
transparent bool // Whether to enable framebuffer transparency on DWM
|
||||
scaleToMonitor bool
|
||||
|
||||
// Cached size used to filter out duplicate events
|
||||
width int
|
||||
height int
|
||||
|
||||
// The last received cursor position, regardless of source
|
||||
lastCursorPosX int
|
||||
lastCursorPosY int
|
||||
|
||||
// The last received high surrogate when decoding pairs of UTF-16 messages
|
||||
highSurrogate uint16
|
||||
}
|
||||
|
||||
type platformMonitorState struct {
|
||||
handle _HMONITOR
|
||||
|
||||
// This size matches the static size of DISPLAY_DEVICE.DeviceName
|
||||
adapterName string
|
||||
displayName string
|
||||
modesPruned bool
|
||||
modeChanged bool
|
||||
}
|
||||
|
||||
type platformCursorState struct {
|
||||
handle _HCURSOR
|
||||
}
|
||||
|
||||
type platformTLSState struct {
|
||||
allocated bool
|
||||
index uint32
|
||||
}
|
||||
|
||||
type platformLibraryWindowState struct {
|
||||
instance _HINSTANCE
|
||||
helperWindowHandle windows.HWND
|
||||
deviceNotificationHandle _HDEVNOTIFY
|
||||
acquiredMonitorCount int
|
||||
clipboardString string
|
||||
keycodes [512]Key
|
||||
scancodes [KeyLast + 1]int
|
||||
keynames [KeyLast + 1]string
|
||||
|
||||
// Where to place the cursor when re-enabled
|
||||
restoreCursorPosX float64
|
||||
restoreCursorPosY float64
|
||||
|
||||
// The window whose disabled cursor mode is active
|
||||
disabledCursorWindow *Window
|
||||
rawInput []byte
|
||||
mouseTrailSize uint32
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard
|
||||
// SPDX-FileCopyrightText: 2006-2017 Camilla Löwy <elmindreda@glfw.org>
|
||||
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
|
||||
|
||||
package glfw
|
||||
|
||||
func (t *tls) create() error {
|
||||
if t.platform.allocated {
|
||||
panic("glfw: TLS must not be allocated")
|
||||
}
|
||||
|
||||
i, err := _TlsAlloc()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.platform.index = i
|
||||
t.platform.allocated = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tls) destroy() error {
|
||||
if t.platform.allocated {
|
||||
if err := _TlsFree(t.platform.index); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
t.platform.allocated = false
|
||||
t.platform.index = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *tls) get() (uintptr, error) {
|
||||
if !t.platform.allocated {
|
||||
panic("glfw: TLS must be allocated")
|
||||
}
|
||||
|
||||
return _TlsGetValue(t.platform.index)
|
||||
}
|
||||
|
||||
func (t *tls) set(value uintptr) error {
|
||||
if !t.platform.allocated {
|
||||
panic("glfw: TLS must be allocated")
|
||||
}
|
||||
|
||||
return _TlsSetValue(t.platform.index, value)
|
||||
}
|
||||
+2353
File diff suppressed because it is too large
Load Diff
+1090
File diff suppressed because it is too large
Load Diff
+1023
File diff suppressed because it is too large
Load Diff
+884
@@ -0,0 +1,884 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard
|
||||
// SPDX-FileCopyrightText: 2006-2019 Camilla Löwy <elmindreda@glfw.org>
|
||||
// SPDX-FileCopyrightText: 2012 Torsten Walluhn <tw@mad-cad.net>
|
||||
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
|
||||
|
||||
package glfw
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/draw"
|
||||
"math"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func (w *Window) inputWindowFocus(focused bool) {
|
||||
if w.callbacks.focus != nil {
|
||||
w.callbacks.focus(w, focused)
|
||||
}
|
||||
|
||||
if !focused {
|
||||
for key := Key(0); key <= KeyLast; key++ {
|
||||
if w.keys[key] == Press {
|
||||
scancode := platformGetKeyScancode(key)
|
||||
w.inputKey(key, scancode, Release, 0)
|
||||
}
|
||||
}
|
||||
for button := MouseButton(0); button <= MouseButtonLast; button++ {
|
||||
if w.mouseButtons[button] == Press {
|
||||
w.inputMouseClick(button, Release, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) inputWindowPos(x, y int) {
|
||||
if w.callbacks.pos != nil {
|
||||
w.callbacks.pos(w, x, y)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) inputWindowSize(width, height int) {
|
||||
if w.callbacks.size != nil {
|
||||
w.callbacks.size(w, width, height)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) inputWindowIconify(iconified bool) {
|
||||
if w.callbacks.iconify != nil {
|
||||
w.callbacks.iconify(w, iconified)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) inputWindowMaximize(maximized bool) {
|
||||
if w.callbacks.maximize != nil {
|
||||
w.callbacks.maximize(w, maximized)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) inputFramebufferSize(width, height int) {
|
||||
if w.callbacks.fbsize != nil {
|
||||
w.callbacks.fbsize(w, width, height)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) inputWindowContentScale(xscale, yscale float32) {
|
||||
if w.callbacks.scale != nil {
|
||||
w.callbacks.scale(w, xscale, yscale)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) inputWindowDamage() {
|
||||
if w.callbacks.refresh != nil {
|
||||
w.callbacks.refresh(w)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) inputWindowCloseRequest() {
|
||||
w.shouldClose = true
|
||||
|
||||
if w.callbacks.close != nil {
|
||||
w.callbacks.close(w)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) inputWindowMonitor(monitor *Monitor) {
|
||||
w.monitor = monitor
|
||||
}
|
||||
|
||||
func CreateWindow(width, height int, title string, monitor *Monitor, share *Window) (window *Window, ferr error) {
|
||||
if !_glfw.initialized {
|
||||
return nil, NotInitialized
|
||||
}
|
||||
|
||||
if width <= 0 || height <= 0 {
|
||||
return nil, fmt.Errorf("glfw: invalid window size %dx%d: %w", width, height, InvalidValue)
|
||||
}
|
||||
|
||||
fbconfig := _glfw.hints.framebuffer
|
||||
ctxconfig := _glfw.hints.context
|
||||
wndconfig := _glfw.hints.window
|
||||
|
||||
wndconfig.width = width
|
||||
wndconfig.height = height
|
||||
wndconfig.title = title
|
||||
ctxconfig.share = share
|
||||
|
||||
if err := checkValidContextConfig(&ctxconfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
window = &Window{
|
||||
videoMode: VidMode{
|
||||
Width: width,
|
||||
Height: height,
|
||||
RedBits: fbconfig.redBits,
|
||||
GreenBits: fbconfig.greenBits,
|
||||
BlueBits: fbconfig.blueBits,
|
||||
RefreshRate: _glfw.hints.refreshRate,
|
||||
},
|
||||
|
||||
monitor: monitor,
|
||||
resizable: wndconfig.resizable,
|
||||
decorated: wndconfig.decorated,
|
||||
autoIconify: wndconfig.autoIconify,
|
||||
floating: wndconfig.floating,
|
||||
focusOnShow: wndconfig.focusOnShow,
|
||||
mousePassthrough: wndconfig.mousePassthrough,
|
||||
cursorMode: CursorNormal,
|
||||
|
||||
doublebuffer: fbconfig.doublebuffer,
|
||||
|
||||
minwidth: DontCare,
|
||||
minheight: DontCare,
|
||||
maxwidth: DontCare,
|
||||
maxheight: DontCare,
|
||||
numer: DontCare,
|
||||
denom: DontCare,
|
||||
}
|
||||
defer func() {
|
||||
if ferr != nil {
|
||||
_ = window.Destroy()
|
||||
}
|
||||
}()
|
||||
_glfw.windows = append(_glfw.windows, window)
|
||||
|
||||
// Open the actual window and create its context
|
||||
if err := window.platformCreateWindow(&wndconfig, &ctxconfig, &fbconfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return window, nil
|
||||
}
|
||||
|
||||
func defaultWindowHints() error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
|
||||
// The default is OpenGL with minimum version 1.0
|
||||
_glfw.hints.context = ctxconfig{
|
||||
client: NoAPI, // This is different from the original GLFW, which uses OpenGLAPI by default.
|
||||
source: NativeContextAPI,
|
||||
major: 1,
|
||||
minor: 0,
|
||||
}
|
||||
|
||||
// The default is a focused, visible, resizable window with decorations
|
||||
_glfw.hints.window = wndconfig{
|
||||
resizable: true,
|
||||
visible: true,
|
||||
decorated: true,
|
||||
focused: true,
|
||||
autoIconify: true,
|
||||
centerCursor: true,
|
||||
focusOnShow: true,
|
||||
}
|
||||
|
||||
// The default is 24 bits of color, 24 bits of depth and 8 bits of stencil,
|
||||
// double buffered
|
||||
_glfw.hints.framebuffer = fbconfig{
|
||||
redBits: 8,
|
||||
greenBits: 8,
|
||||
blueBits: 8,
|
||||
alphaBits: 8,
|
||||
depthBits: 24,
|
||||
stencilBits: 8,
|
||||
doublebuffer: true,
|
||||
}
|
||||
|
||||
// The default is to select the highest available refresh rate
|
||||
_glfw.hints.refreshRate = DontCare
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func WindowHint(hint Hint, value int) error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
|
||||
switch hint {
|
||||
case RedBits:
|
||||
_glfw.hints.framebuffer.redBits = value
|
||||
case GreenBits:
|
||||
_glfw.hints.framebuffer.greenBits = value
|
||||
case BlueBits:
|
||||
_glfw.hints.framebuffer.blueBits = value
|
||||
case AlphaBits:
|
||||
_glfw.hints.framebuffer.alphaBits = value
|
||||
case DepthBits:
|
||||
_glfw.hints.framebuffer.depthBits = value
|
||||
case StencilBits:
|
||||
_glfw.hints.framebuffer.stencilBits = value
|
||||
case AccumRedBits:
|
||||
_glfw.hints.framebuffer.accumRedBits = value
|
||||
case AccumGreenBits:
|
||||
_glfw.hints.framebuffer.accumGreenBits = value
|
||||
case AccumBlueBits:
|
||||
_glfw.hints.framebuffer.accumBlueBits = value
|
||||
case AccumAlphaBits:
|
||||
_glfw.hints.framebuffer.accumAlphaBits = value
|
||||
case AuxBuffers:
|
||||
_glfw.hints.framebuffer.auxBuffers = value
|
||||
case Stereo:
|
||||
_glfw.hints.framebuffer.stereo = intToBool(value)
|
||||
case DoubleBuffer:
|
||||
_glfw.hints.framebuffer.doublebuffer = intToBool(value)
|
||||
case TransparentFramebuffer:
|
||||
_glfw.hints.framebuffer.transparent = intToBool(value)
|
||||
case Samples:
|
||||
_glfw.hints.framebuffer.samples = value
|
||||
case SRGBCapable:
|
||||
_glfw.hints.framebuffer.sRGB = intToBool(value)
|
||||
case Resizable:
|
||||
_glfw.hints.window.resizable = intToBool(value)
|
||||
case Decorated:
|
||||
_glfw.hints.window.decorated = intToBool(value)
|
||||
case Focused:
|
||||
_glfw.hints.window.focused = intToBool(value)
|
||||
case AutoIconify:
|
||||
_glfw.hints.window.autoIconify = intToBool(value)
|
||||
case Floating:
|
||||
_glfw.hints.window.floating = intToBool(value)
|
||||
case Maximized:
|
||||
_glfw.hints.window.maximized = intToBool(value)
|
||||
case Visible:
|
||||
_glfw.hints.window.visible = intToBool(value)
|
||||
case ScaleToMonitor:
|
||||
_glfw.hints.window.scaleToMonitor = intToBool(value)
|
||||
case CenterCursor:
|
||||
_glfw.hints.window.centerCursor = intToBool(value)
|
||||
case FocusOnShow:
|
||||
_glfw.hints.window.focusOnShow = intToBool(value)
|
||||
case MousePassthrough:
|
||||
_glfw.hints.window.mousePassthrough = intToBool(value)
|
||||
case ClientAPI:
|
||||
_glfw.hints.context.client = value
|
||||
case ContextCreationAPI:
|
||||
_glfw.hints.context.source = value
|
||||
case ContextVersionMajor:
|
||||
_glfw.hints.context.major = value
|
||||
case ContextVersionMinor:
|
||||
_glfw.hints.context.minor = value
|
||||
case ContextRobustness:
|
||||
_glfw.hints.context.robustness = value
|
||||
case OpenGLForwardCompat:
|
||||
_glfw.hints.context.forward = intToBool(value)
|
||||
case OpenGLDebugContext:
|
||||
_glfw.hints.context.debug = intToBool(value)
|
||||
case ContextNoError:
|
||||
_glfw.hints.context.noerror = intToBool(value)
|
||||
case OpenGLProfile:
|
||||
_glfw.hints.context.profile = value
|
||||
case ContextReleaseBehavior:
|
||||
_glfw.hints.context.release = value
|
||||
case RefreshRate:
|
||||
_glfw.hints.refreshRate = value
|
||||
default:
|
||||
return fmt.Errorf("glfw: invalid window hint 0x%08X: %w", hint, InvalidEnum)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WindowHintString is not implemented.
|
||||
func WindowHintString(hint Hint, value string) error {
|
||||
// Do nothing.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Window) Destroy() error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
|
||||
// Allow closing of NULL (to match the behavior of free)
|
||||
if w == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear all callbacks to avoid exposing a half torn-down w object
|
||||
// TODO: Clear w.callbacks
|
||||
|
||||
// The w's context must not be current on another thread when the
|
||||
// w is destroyed
|
||||
current, err := _glfw.contextSlot.get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if uintptr(unsafe.Pointer(w)) == current {
|
||||
if err := (*Window)(nil).MakeContextCurrent(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for i, window := range _glfw.windows {
|
||||
if window == w {
|
||||
copy(_glfw.windows[i:], _glfw.windows[i+1:])
|
||||
_glfw.windows[len(_glfw.windows)-1] = nil
|
||||
_glfw.windows = _glfw.windows[:len(_glfw.windows)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err := w.platformDestroyWindow(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Window) ShouldClose() (bool, error) {
|
||||
if !_glfw.initialized {
|
||||
return false, NotInitialized
|
||||
}
|
||||
return w.shouldClose, nil
|
||||
}
|
||||
|
||||
func (w *Window) SetShouldClose(value bool) error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
w.shouldClose = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Window) SetTitle(title string) error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
if err := w.platformSetWindowTitle(title); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Window) SetIcon(images []image.Image) error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
|
||||
gimgs := make([]*Image, len(images))
|
||||
for i, img := range images {
|
||||
b := img.Bounds()
|
||||
m := image.NewNRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))
|
||||
draw.Draw(m, m.Bounds(), img, b.Min, draw.Src)
|
||||
gimgs[i] = &Image{
|
||||
Width: b.Dx(),
|
||||
Height: b.Dy(),
|
||||
Pixels: m.Pix,
|
||||
}
|
||||
}
|
||||
|
||||
if err := w.platformSetWindowIcon(gimgs); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Window) GetPos() (xpos, ypos int, err error) {
|
||||
if !_glfw.initialized {
|
||||
return 0, 0, NotInitialized
|
||||
}
|
||||
return w.platformGetWindowPos()
|
||||
}
|
||||
|
||||
func (w *Window) SetPos(xpos, ypos int) error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
if w.monitor != nil {
|
||||
return nil
|
||||
}
|
||||
if err := w.platformSetWindowPos(xpos, ypos); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Window) GetSize() (width, height int, err error) {
|
||||
if !_glfw.initialized {
|
||||
return 0, 0, NotInitialized
|
||||
}
|
||||
return w.platformGetWindowSize()
|
||||
}
|
||||
|
||||
func (w *Window) SetSize(width, height int) error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
w.videoMode.Width = width
|
||||
w.videoMode.Height = height
|
||||
if err := w.platformSetWindowSize(width, height); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Window) SetSizeLimits(minwidth, minheight, maxwidth, maxheight int) error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
|
||||
if minwidth != DontCare && minheight != DontCare {
|
||||
if minwidth < 0 || minheight < 0 {
|
||||
return fmt.Errorf("glfw: invalid window minimum size %dx%d: %w", minwidth, minheight, InvalidValue)
|
||||
}
|
||||
}
|
||||
|
||||
if maxwidth != DontCare && maxheight != DontCare {
|
||||
if maxwidth < 0 || maxheight < 0 || maxwidth < minwidth || maxheight < minheight {
|
||||
return fmt.Errorf("glfw: invalid window maximum size %dx%d: %w", maxwidth, maxheight, InvalidValue)
|
||||
}
|
||||
}
|
||||
|
||||
w.minwidth = minwidth
|
||||
w.minheight = minheight
|
||||
w.maxwidth = maxwidth
|
||||
w.maxheight = maxheight
|
||||
|
||||
if w.monitor != nil || !w.resizable {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := w.platformSetWindowSizeLimits(minwidth, minheight, maxwidth, maxheight); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Window) SetAspectRatio(numer, denom int) error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
|
||||
if numer != DontCare && denom != DontCare {
|
||||
if numer <= 0 || denom <= 0 {
|
||||
return fmt.Errorf("glfw: invalid window aspect ratio %d:%d: %w", numer, denom, InvalidValue)
|
||||
}
|
||||
}
|
||||
|
||||
w.numer = numer
|
||||
w.denom = denom
|
||||
|
||||
if w.monitor != nil || !w.resizable {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := w.platformSetWindowAspectRatio(numer, denom); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Window) GetFramebufferSize() (width, height int, err error) {
|
||||
if !_glfw.initialized {
|
||||
return 0, 0, NotInitialized
|
||||
}
|
||||
return w.platformGetFramebufferSize()
|
||||
}
|
||||
|
||||
func (w *Window) GetFrameSize() (left, top, right, bottom int, err error) {
|
||||
if !_glfw.initialized {
|
||||
return 0, 0, 0, 0, NotInitialized
|
||||
}
|
||||
return w.platformGetWindowFrameSize()
|
||||
}
|
||||
|
||||
func (w *Window) GetContentScale() (xscale, yscale float32, err error) {
|
||||
if !_glfw.initialized {
|
||||
return 0, 0, NotInitialized
|
||||
}
|
||||
return w.platformGetWindowContentScale()
|
||||
}
|
||||
|
||||
func (w *Window) GetOpacity() (float32, error) {
|
||||
if !_glfw.initialized {
|
||||
return 0, NotInitialized
|
||||
}
|
||||
return w.platformGetWindowOpacity()
|
||||
}
|
||||
|
||||
func (w *Window) SetOpacity(opacity float32) error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
|
||||
if opacity != opacity || opacity < 0 || opacity > 1 {
|
||||
return fmt.Errorf("glfw: invalid window opacity %f: %w", opacity, InvalidValue)
|
||||
}
|
||||
|
||||
if err := w.platformSetWindowOpacity(opacity); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Window) Iconify() error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
w.platformIconifyWindow()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Window) Restore() error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
w.platformRestoreWindow()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Window) Maximize() error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
if w.monitor != nil {
|
||||
return nil
|
||||
}
|
||||
if err := w.platformMaximizeWindow(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Window) Show() error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
if w.monitor != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
w.platformShowWindow()
|
||||
|
||||
if w.focusOnShow {
|
||||
if err := w.platformFocusWindow(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Window) RequestAttention() error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
w.platformRequestWindowAttention()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Window) Hide() error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
|
||||
if w.monitor != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
w.platformHideWindow()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Window) Focus() error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
if err := w.platformFocusWindow(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Window) GetAttrib(attrib Hint) (int, error) {
|
||||
if !_glfw.initialized {
|
||||
return 0, NotInitialized
|
||||
}
|
||||
|
||||
switch attrib {
|
||||
case Focused:
|
||||
return boolToInt(w.platformWindowFocused()), nil
|
||||
case Iconified:
|
||||
return boolToInt(w.platformWindowIconified()), nil
|
||||
case Visible:
|
||||
return boolToInt(w.platformWindowVisible()), nil
|
||||
case Maximized:
|
||||
return boolToInt(w.platformWindowMaximized()), nil
|
||||
case Hovered:
|
||||
b, err := w.platformWindowHovered()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return boolToInt(b), nil
|
||||
case FocusOnShow:
|
||||
return boolToInt(w.focusOnShow), nil
|
||||
case MousePassthrough:
|
||||
return boolToInt(w.mousePassthrough), nil
|
||||
case TransparentFramebuffer:
|
||||
return boolToInt(w.platformFramebufferTransparent()), nil
|
||||
case Resizable:
|
||||
return boolToInt(w.resizable), nil
|
||||
case Decorated:
|
||||
return boolToInt(w.decorated), nil
|
||||
case Floating:
|
||||
return boolToInt(w.floating), nil
|
||||
case AutoIconify:
|
||||
return boolToInt(w.autoIconify), nil
|
||||
case ClientAPI:
|
||||
return w.context.client, nil
|
||||
case ContextCreationAPI:
|
||||
return w.context.source, nil
|
||||
case ContextVersionMajor:
|
||||
return w.context.major, nil
|
||||
case ContextVersionMinor:
|
||||
return w.context.minor, nil
|
||||
case ContextRevision:
|
||||
return w.context.revision, nil
|
||||
case ContextRobustness:
|
||||
return w.context.robustness, nil
|
||||
case OpenGLForwardCompat:
|
||||
return boolToInt(w.context.forward), nil
|
||||
case OpenGLDebugContext:
|
||||
return boolToInt(w.context.debug), nil
|
||||
case OpenGLProfile:
|
||||
return w.context.profile, nil
|
||||
case ContextReleaseBehavior:
|
||||
return w.context.release, nil
|
||||
case ContextNoError:
|
||||
return boolToInt(w.context.noerror), nil
|
||||
default:
|
||||
return 0, fmt.Errorf("glfw: invalid window attribute 0x%08X: %w", attrib, InvalidEnum)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) SetAttrib(attrib Hint, value int) error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
|
||||
bValue := intToBool(value)
|
||||
|
||||
switch attrib {
|
||||
case AutoIconify:
|
||||
w.autoIconify = bValue
|
||||
return nil
|
||||
case Resizable:
|
||||
if w.resizable == bValue {
|
||||
return nil
|
||||
}
|
||||
w.resizable = bValue
|
||||
if w.monitor == nil {
|
||||
if err := w.platformSetWindowResizable(bValue); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case Decorated:
|
||||
if w.decorated == bValue {
|
||||
return nil
|
||||
}
|
||||
w.decorated = bValue
|
||||
if w.monitor == nil {
|
||||
if err := w.platformSetWindowDecorated(bValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case Floating:
|
||||
if w.floating == bValue {
|
||||
return nil
|
||||
}
|
||||
w.floating = bValue
|
||||
if w.monitor == nil {
|
||||
if err := w.platformSetWindowFloating(bValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case FocusOnShow:
|
||||
w.focusOnShow = bValue
|
||||
return nil
|
||||
case MousePassthrough:
|
||||
w.mousePassthrough = bValue
|
||||
if err := w.platformSetWindowMousePassthrough(bValue); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("glfw: invalid window attribute 0x%08X: %w", attrib, InvalidEnum)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) GetMonitor() (*Monitor, error) {
|
||||
if !_glfw.initialized {
|
||||
return nil, NotInitialized
|
||||
}
|
||||
return w.monitor, nil
|
||||
}
|
||||
|
||||
func (w *Window) SetMonitor(monitor *Monitor, xpos, ypos, width, height, refreshRate int) error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
|
||||
if width <= 0 || height <= 0 {
|
||||
return fmt.Errorf("glfw: invalid window size %dx%d: %w", width, height, InvalidValue)
|
||||
}
|
||||
|
||||
if refreshRate < 0 && refreshRate != DontCare {
|
||||
return fmt.Errorf("glfw: invalid refresh rate %d: %w", refreshRate, InvalidValue)
|
||||
}
|
||||
|
||||
w.videoMode.Width = width
|
||||
w.videoMode.Height = height
|
||||
w.videoMode.RefreshRate = refreshRate
|
||||
|
||||
if err := w.platformSetWindowMonitor(monitor, xpos, ypos, width, height, refreshRate); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Window) SetUserPointer(pointer unsafe.Pointer) error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
w.userPointer = pointer
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Window) GetUserPointer() (unsafe.Pointer, error) {
|
||||
if !_glfw.initialized {
|
||||
return nil, NotInitialized
|
||||
}
|
||||
return w.userPointer, nil
|
||||
}
|
||||
|
||||
func (w *Window) SetPosCallback(cbfun PosCallback) (PosCallback, error) {
|
||||
if !_glfw.initialized {
|
||||
return nil, NotInitialized
|
||||
}
|
||||
old := w.callbacks.pos
|
||||
w.callbacks.pos = cbfun
|
||||
return old, nil
|
||||
}
|
||||
|
||||
func (w *Window) SetSizeCallback(cbfun SizeCallback) (SizeCallback, error) {
|
||||
if !_glfw.initialized {
|
||||
return nil, NotInitialized
|
||||
}
|
||||
old := w.callbacks.size
|
||||
w.callbacks.size = cbfun
|
||||
return old, nil
|
||||
}
|
||||
|
||||
func (w *Window) SetCloseCallback(cbfun CloseCallback) (CloseCallback, error) {
|
||||
if !_glfw.initialized {
|
||||
return nil, NotInitialized
|
||||
}
|
||||
old := w.callbacks.close
|
||||
w.callbacks.close = cbfun
|
||||
return old, nil
|
||||
}
|
||||
|
||||
func (w *Window) SetRefreshCallback(cbfun RefreshCallback) (RefreshCallback, error) {
|
||||
if !_glfw.initialized {
|
||||
return nil, NotInitialized
|
||||
}
|
||||
old := w.callbacks.refresh
|
||||
w.callbacks.refresh = cbfun
|
||||
return old, nil
|
||||
}
|
||||
|
||||
func (w *Window) SetFocusCallback(cbfun FocusCallback) (FocusCallback, error) {
|
||||
if !_glfw.initialized {
|
||||
return nil, NotInitialized
|
||||
}
|
||||
old := w.callbacks.focus
|
||||
w.callbacks.focus = cbfun
|
||||
return old, nil
|
||||
}
|
||||
|
||||
func (w *Window) SetIconifyCallback(cbfun IconifyCallback) (IconifyCallback, error) {
|
||||
if !_glfw.initialized {
|
||||
return nil, NotInitialized
|
||||
}
|
||||
old := w.callbacks.iconify
|
||||
w.callbacks.iconify = cbfun
|
||||
return old, nil
|
||||
}
|
||||
|
||||
func (w *Window) SetMaximizeCallback(cbfun MaximizeCallback) (MaximizeCallback, error) {
|
||||
if !_glfw.initialized {
|
||||
return nil, NotInitialized
|
||||
}
|
||||
old := w.callbacks.maximize
|
||||
w.callbacks.maximize = cbfun
|
||||
return old, nil
|
||||
}
|
||||
|
||||
func (w *Window) SetFramebufferSizeCallback(cbfun FramebufferSizeCallback) (FramebufferSizeCallback, error) {
|
||||
if !_glfw.initialized {
|
||||
return nil, NotInitialized
|
||||
}
|
||||
old := w.callbacks.fbsize
|
||||
w.callbacks.fbsize = cbfun
|
||||
return old, nil
|
||||
}
|
||||
|
||||
func (w *Window) SetContentScaleCallback(cbfun ContentScaleCallback) (ContentScaleCallback, error) {
|
||||
if !_glfw.initialized {
|
||||
return nil, NotInitialized
|
||||
}
|
||||
old := w.callbacks.scale
|
||||
w.callbacks.scale = cbfun
|
||||
return old, nil
|
||||
}
|
||||
|
||||
func PollEvents() error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
if err := platformPollEvents(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func WaitEvents() error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
if err := platformWaitEvents(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func WaitEventsTimeout(timeout float64) error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
if timeout != timeout || timeout < 0.0 || timeout > math.MaxFloat64 {
|
||||
return fmt.Errorf("glfw: invalid time %f: %w", timeout, InvalidValue)
|
||||
}
|
||||
if err := platformWaitEventsTimeout(timeout); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func PostEmptyEvent() error {
|
||||
if !_glfw.initialized {
|
||||
return NotInitialized
|
||||
}
|
||||
if err := platformPostEmptyEvent(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+1284
File diff suppressed because it is too large
Load Diff
+592
@@ -0,0 +1,592 @@
|
||||
// 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 <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
|
||||
// Check whether the display mode should be included in enumeration
|
||||
//
|
||||
static GLFWbool modeIsGood(const XRRModeInfo* mi)
|
||||
{
|
||||
return (mi->modeFlags & RR_Interlace) == 0;
|
||||
}
|
||||
|
||||
// Calculates the refresh rate, in Hz, from the specified RandR mode info
|
||||
//
|
||||
static int calculateRefreshRate(const XRRModeInfo* mi)
|
||||
{
|
||||
if (mi->hTotal && mi->vTotal)
|
||||
return (int) round((double) mi->dotClock / ((double) mi->hTotal * (double) mi->vTotal));
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Returns the mode info for a RandR mode XID
|
||||
//
|
||||
static const XRRModeInfo* getModeInfo(const XRRScreenResources* sr, RRMode id)
|
||||
{
|
||||
for (int i = 0; i < sr->nmode; i++)
|
||||
{
|
||||
if (sr->modes[i].id == id)
|
||||
return sr->modes + i;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Convert RandR mode info to GLFW video mode
|
||||
//
|
||||
static GLFWvidmode vidmodeFromModeInfo(const XRRModeInfo* mi,
|
||||
const XRRCrtcInfo* ci)
|
||||
{
|
||||
GLFWvidmode mode;
|
||||
|
||||
if (ci->rotation == RR_Rotate_90 || ci->rotation == RR_Rotate_270)
|
||||
{
|
||||
mode.width = mi->height;
|
||||
mode.height = mi->width;
|
||||
}
|
||||
else
|
||||
{
|
||||
mode.width = mi->width;
|
||||
mode.height = mi->height;
|
||||
}
|
||||
|
||||
mode.refreshRate = calculateRefreshRate(mi);
|
||||
|
||||
_glfwSplitBPP(DefaultDepth(_glfw.x11.display, _glfw.x11.screen),
|
||||
&mode.redBits, &mode.greenBits, &mode.blueBits);
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW internal API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Poll for changes in the set of connected monitors
|
||||
//
|
||||
void _glfwPollMonitorsX11(void)
|
||||
{
|
||||
if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
|
||||
{
|
||||
int disconnectedCount, screenCount = 0;
|
||||
_GLFWmonitor** disconnected = NULL;
|
||||
XineramaScreenInfo* screens = NULL;
|
||||
XRRScreenResources* sr = XRRGetScreenResourcesCurrent(_glfw.x11.display,
|
||||
_glfw.x11.root);
|
||||
RROutput primary = XRRGetOutputPrimary(_glfw.x11.display,
|
||||
_glfw.x11.root);
|
||||
|
||||
if (_glfw.x11.xinerama.available)
|
||||
screens = XineramaQueryScreens(_glfw.x11.display, &screenCount);
|
||||
|
||||
disconnectedCount = _glfw.monitorCount;
|
||||
if (disconnectedCount)
|
||||
{
|
||||
disconnected = calloc(_glfw.monitorCount, sizeof(_GLFWmonitor*));
|
||||
memcpy(disconnected,
|
||||
_glfw.monitors,
|
||||
_glfw.monitorCount * sizeof(_GLFWmonitor*));
|
||||
}
|
||||
|
||||
for (int i = 0; i < sr->noutput; i++)
|
||||
{
|
||||
int j, type, widthMM, heightMM;
|
||||
|
||||
XRROutputInfo* oi = XRRGetOutputInfo(_glfw.x11.display, sr, sr->outputs[i]);
|
||||
if (oi->connection != RR_Connected || oi->crtc == None)
|
||||
{
|
||||
XRRFreeOutputInfo(oi);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (j = 0; j < disconnectedCount; j++)
|
||||
{
|
||||
if (disconnected[j] &&
|
||||
disconnected[j]->x11.output == sr->outputs[i])
|
||||
{
|
||||
disconnected[j] = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (j < disconnectedCount)
|
||||
{
|
||||
XRRFreeOutputInfo(oi);
|
||||
continue;
|
||||
}
|
||||
|
||||
XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, oi->crtc);
|
||||
if (ci->rotation == RR_Rotate_90 || ci->rotation == RR_Rotate_270)
|
||||
{
|
||||
widthMM = oi->mm_height;
|
||||
heightMM = oi->mm_width;
|
||||
}
|
||||
else
|
||||
{
|
||||
widthMM = oi->mm_width;
|
||||
heightMM = oi->mm_height;
|
||||
}
|
||||
|
||||
if (widthMM <= 0 || heightMM <= 0)
|
||||
{
|
||||
// HACK: If RandR does not provide a physical size, assume the
|
||||
// X11 default 96 DPI and calculate from the CRTC viewport
|
||||
// NOTE: These members are affected by rotation, unlike the mode
|
||||
// info and output info members
|
||||
widthMM = (int) (ci->width * 25.4f / 96.f);
|
||||
heightMM = (int) (ci->height * 25.4f / 96.f);
|
||||
}
|
||||
|
||||
_GLFWmonitor* monitor = _glfwAllocMonitor(oi->name, widthMM, heightMM);
|
||||
monitor->x11.output = sr->outputs[i];
|
||||
monitor->x11.crtc = oi->crtc;
|
||||
|
||||
for (j = 0; j < screenCount; j++)
|
||||
{
|
||||
if (screens[j].x_org == ci->x &&
|
||||
screens[j].y_org == ci->y &&
|
||||
screens[j].width == ci->width &&
|
||||
screens[j].height == ci->height)
|
||||
{
|
||||
monitor->x11.index = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (monitor->x11.output == primary)
|
||||
type = _GLFW_INSERT_FIRST;
|
||||
else
|
||||
type = _GLFW_INSERT_LAST;
|
||||
|
||||
_glfwInputMonitor(monitor, GLFW_CONNECTED, type);
|
||||
|
||||
XRRFreeOutputInfo(oi);
|
||||
XRRFreeCrtcInfo(ci);
|
||||
}
|
||||
|
||||
XRRFreeScreenResources(sr);
|
||||
|
||||
if (screens)
|
||||
XFree(screens);
|
||||
|
||||
for (int i = 0; i < disconnectedCount; i++)
|
||||
{
|
||||
if (disconnected[i])
|
||||
_glfwInputMonitor(disconnected[i], GLFW_DISCONNECTED, 0);
|
||||
}
|
||||
|
||||
free(disconnected);
|
||||
}
|
||||
else
|
||||
{
|
||||
const int widthMM = DisplayWidthMM(_glfw.x11.display, _glfw.x11.screen);
|
||||
const int heightMM = DisplayHeightMM(_glfw.x11.display, _glfw.x11.screen);
|
||||
|
||||
_glfwInputMonitor(_glfwAllocMonitor("Display", widthMM, heightMM),
|
||||
GLFW_CONNECTED,
|
||||
_GLFW_INSERT_FIRST);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the current video mode for the specified monitor
|
||||
//
|
||||
void _glfwSetVideoModeX11(_GLFWmonitor* monitor, const GLFWvidmode* desired)
|
||||
{
|
||||
if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
|
||||
{
|
||||
GLFWvidmode current;
|
||||
RRMode native = None;
|
||||
|
||||
const GLFWvidmode* best = _glfwChooseVideoMode(monitor, desired);
|
||||
_glfwPlatformGetVideoMode(monitor, ¤t);
|
||||
if (_glfwCompareVideoModes(¤t, best) == 0)
|
||||
return;
|
||||
|
||||
XRRScreenResources* sr =
|
||||
XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
|
||||
XRROutputInfo* oi = XRRGetOutputInfo(_glfw.x11.display, sr, monitor->x11.output);
|
||||
|
||||
for (int i = 0; i < oi->nmode; i++)
|
||||
{
|
||||
const XRRModeInfo* mi = getModeInfo(sr, oi->modes[i]);
|
||||
if (!modeIsGood(mi))
|
||||
continue;
|
||||
|
||||
const GLFWvidmode mode = vidmodeFromModeInfo(mi, ci);
|
||||
if (_glfwCompareVideoModes(best, &mode) == 0)
|
||||
{
|
||||
native = mi->id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (native)
|
||||
{
|
||||
if (monitor->x11.oldMode == None)
|
||||
monitor->x11.oldMode = ci->mode;
|
||||
|
||||
XRRSetCrtcConfig(_glfw.x11.display,
|
||||
sr, monitor->x11.crtc,
|
||||
CurrentTime,
|
||||
ci->x, ci->y,
|
||||
native,
|
||||
ci->rotation,
|
||||
ci->outputs,
|
||||
ci->noutput);
|
||||
}
|
||||
|
||||
XRRFreeOutputInfo(oi);
|
||||
XRRFreeCrtcInfo(ci);
|
||||
XRRFreeScreenResources(sr);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the saved (original) video mode for the specified monitor
|
||||
//
|
||||
void _glfwRestoreVideoModeX11(_GLFWmonitor* monitor)
|
||||
{
|
||||
if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
|
||||
{
|
||||
if (monitor->x11.oldMode == None)
|
||||
return;
|
||||
|
||||
XRRScreenResources* sr =
|
||||
XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
|
||||
|
||||
XRRSetCrtcConfig(_glfw.x11.display,
|
||||
sr, monitor->x11.crtc,
|
||||
CurrentTime,
|
||||
ci->x, ci->y,
|
||||
monitor->x11.oldMode,
|
||||
ci->rotation,
|
||||
ci->outputs,
|
||||
ci->noutput);
|
||||
|
||||
XRRFreeCrtcInfo(ci);
|
||||
XRRFreeScreenResources(sr);
|
||||
|
||||
monitor->x11.oldMode = None;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW platform API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor)
|
||||
{
|
||||
}
|
||||
|
||||
void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
|
||||
{
|
||||
if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
|
||||
{
|
||||
XRRScreenResources* sr =
|
||||
XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
|
||||
|
||||
if (ci)
|
||||
{
|
||||
if (xpos)
|
||||
*xpos = ci->x;
|
||||
if (ypos)
|
||||
*ypos = ci->y;
|
||||
|
||||
XRRFreeCrtcInfo(ci);
|
||||
}
|
||||
|
||||
XRRFreeScreenResources(sr);
|
||||
}
|
||||
}
|
||||
|
||||
void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor,
|
||||
float* xscale, float* yscale)
|
||||
{
|
||||
if (xscale)
|
||||
*xscale = _glfw.x11.contentScaleX;
|
||||
if (yscale)
|
||||
*yscale = _glfw.x11.contentScaleY;
|
||||
}
|
||||
|
||||
void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor, int* xpos, int* ypos, int* width, int* height)
|
||||
{
|
||||
int areaX = 0, areaY = 0, areaWidth = 0, areaHeight = 0;
|
||||
|
||||
if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
|
||||
{
|
||||
XRRScreenResources* sr =
|
||||
XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
|
||||
|
||||
areaX = ci->x;
|
||||
areaY = ci->y;
|
||||
|
||||
const XRRModeInfo* mi = getModeInfo(sr, ci->mode);
|
||||
|
||||
if (ci->rotation == RR_Rotate_90 || ci->rotation == RR_Rotate_270)
|
||||
{
|
||||
areaWidth = mi->height;
|
||||
areaHeight = mi->width;
|
||||
}
|
||||
else
|
||||
{
|
||||
areaWidth = mi->width;
|
||||
areaHeight = mi->height;
|
||||
}
|
||||
|
||||
XRRFreeCrtcInfo(ci);
|
||||
XRRFreeScreenResources(sr);
|
||||
}
|
||||
else
|
||||
{
|
||||
areaWidth = DisplayWidth(_glfw.x11.display, _glfw.x11.screen);
|
||||
areaHeight = DisplayHeight(_glfw.x11.display, _glfw.x11.screen);
|
||||
}
|
||||
|
||||
if (_glfw.x11.NET_WORKAREA && _glfw.x11.NET_CURRENT_DESKTOP)
|
||||
{
|
||||
Atom* extents = NULL;
|
||||
Atom* desktop = NULL;
|
||||
const unsigned long extentCount =
|
||||
_glfwGetWindowPropertyX11(_glfw.x11.root,
|
||||
_glfw.x11.NET_WORKAREA,
|
||||
XA_CARDINAL,
|
||||
(unsigned char**) &extents);
|
||||
|
||||
if (_glfwGetWindowPropertyX11(_glfw.x11.root,
|
||||
_glfw.x11.NET_CURRENT_DESKTOP,
|
||||
XA_CARDINAL,
|
||||
(unsigned char**) &desktop) > 0)
|
||||
{
|
||||
if (extentCount >= 4 && *desktop < extentCount / 4)
|
||||
{
|
||||
const int globalX = extents[*desktop * 4 + 0];
|
||||
const int globalY = extents[*desktop * 4 + 1];
|
||||
const int globalWidth = extents[*desktop * 4 + 2];
|
||||
const int globalHeight = extents[*desktop * 4 + 3];
|
||||
|
||||
if (areaX < globalX)
|
||||
{
|
||||
areaWidth -= globalX - areaX;
|
||||
areaX = globalX;
|
||||
}
|
||||
|
||||
if (areaY < globalY)
|
||||
{
|
||||
areaHeight -= globalY - areaY;
|
||||
areaY = globalY;
|
||||
}
|
||||
|
||||
if (areaX + areaWidth > globalX + globalWidth)
|
||||
areaWidth = globalX - areaX + globalWidth;
|
||||
if (areaY + areaHeight > globalY + globalHeight)
|
||||
areaHeight = globalY - areaY + globalHeight;
|
||||
}
|
||||
}
|
||||
|
||||
if (extents)
|
||||
XFree(extents);
|
||||
if (desktop)
|
||||
XFree(desktop);
|
||||
}
|
||||
|
||||
if (xpos)
|
||||
*xpos = areaX;
|
||||
if (ypos)
|
||||
*ypos = areaY;
|
||||
if (width)
|
||||
*width = areaWidth;
|
||||
if (height)
|
||||
*height = areaHeight;
|
||||
}
|
||||
|
||||
GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
|
||||
{
|
||||
GLFWvidmode* result;
|
||||
|
||||
*count = 0;
|
||||
|
||||
if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
|
||||
{
|
||||
XRRScreenResources* sr =
|
||||
XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
|
||||
XRROutputInfo* oi = XRRGetOutputInfo(_glfw.x11.display, sr, monitor->x11.output);
|
||||
|
||||
result = calloc(oi->nmode, sizeof(GLFWvidmode));
|
||||
|
||||
for (int i = 0; i < oi->nmode; i++)
|
||||
{
|
||||
const XRRModeInfo* mi = getModeInfo(sr, oi->modes[i]);
|
||||
if (!modeIsGood(mi))
|
||||
continue;
|
||||
|
||||
const GLFWvidmode mode = vidmodeFromModeInfo(mi, ci);
|
||||
int 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;
|
||||
}
|
||||
|
||||
XRRFreeOutputInfo(oi);
|
||||
XRRFreeCrtcInfo(ci);
|
||||
XRRFreeScreenResources(sr);
|
||||
}
|
||||
else
|
||||
{
|
||||
*count = 1;
|
||||
result = calloc(1, sizeof(GLFWvidmode));
|
||||
_glfwPlatformGetVideoMode(monitor, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
|
||||
{
|
||||
if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
|
||||
{
|
||||
XRRScreenResources* sr =
|
||||
XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
|
||||
|
||||
if (ci)
|
||||
{
|
||||
const XRRModeInfo* mi = getModeInfo(sr, ci->mode);
|
||||
if (mi) // mi can be NULL if the monitor has been disconnected
|
||||
*mode = vidmodeFromModeInfo(mi, ci);
|
||||
|
||||
XRRFreeCrtcInfo(ci);
|
||||
}
|
||||
|
||||
XRRFreeScreenResources(sr);
|
||||
}
|
||||
else
|
||||
{
|
||||
mode->width = DisplayWidth(_glfw.x11.display, _glfw.x11.screen);
|
||||
mode->height = DisplayHeight(_glfw.x11.display, _glfw.x11.screen);
|
||||
mode->refreshRate = 0;
|
||||
|
||||
_glfwSplitBPP(DefaultDepth(_glfw.x11.display, _glfw.x11.screen),
|
||||
&mode->redBits, &mode->greenBits, &mode->blueBits);
|
||||
}
|
||||
}
|
||||
|
||||
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
{
|
||||
if (_glfw.x11.randr.available && !_glfw.x11.randr.gammaBroken)
|
||||
{
|
||||
const size_t size = XRRGetCrtcGammaSize(_glfw.x11.display,
|
||||
monitor->x11.crtc);
|
||||
XRRCrtcGamma* gamma = XRRGetCrtcGamma(_glfw.x11.display,
|
||||
monitor->x11.crtc);
|
||||
|
||||
_glfwAllocGammaArrays(ramp, size);
|
||||
|
||||
memcpy(ramp->red, gamma->red, size * sizeof(unsigned short));
|
||||
memcpy(ramp->green, gamma->green, size * sizeof(unsigned short));
|
||||
memcpy(ramp->blue, gamma->blue, size * sizeof(unsigned short));
|
||||
|
||||
XRRFreeGamma(gamma);
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
else if (_glfw.x11.vidmode.available)
|
||||
{
|
||||
int size;
|
||||
XF86VidModeGetGammaRampSize(_glfw.x11.display, _glfw.x11.screen, &size);
|
||||
|
||||
_glfwAllocGammaArrays(ramp, size);
|
||||
|
||||
XF86VidModeGetGammaRamp(_glfw.x11.display,
|
||||
_glfw.x11.screen,
|
||||
ramp->size, ramp->red, ramp->green, ramp->blue);
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"X11: Gamma ramp access not supported by server");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
|
||||
{
|
||||
if (_glfw.x11.randr.available && !_glfw.x11.randr.gammaBroken)
|
||||
{
|
||||
if (XRRGetCrtcGammaSize(_glfw.x11.display, monitor->x11.crtc) != ramp->size)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"X11: Gamma ramp size must match current ramp size");
|
||||
return;
|
||||
}
|
||||
|
||||
XRRCrtcGamma* gamma = XRRAllocGamma(ramp->size);
|
||||
|
||||
memcpy(gamma->red, ramp->red, ramp->size * sizeof(unsigned short));
|
||||
memcpy(gamma->green, ramp->green, ramp->size * sizeof(unsigned short));
|
||||
memcpy(gamma->blue, ramp->blue, ramp->size * sizeof(unsigned short));
|
||||
|
||||
XRRSetCrtcGamma(_glfw.x11.display, monitor->x11.crtc, gamma);
|
||||
XRRFreeGamma(gamma);
|
||||
}
|
||||
else if (_glfw.x11.vidmode.available)
|
||||
{
|
||||
XF86VidModeSetGammaRamp(_glfw.x11.display,
|
||||
_glfw.x11.screen,
|
||||
ramp->size,
|
||||
(unsigned short*) ramp->red,
|
||||
(unsigned short*) ramp->green,
|
||||
(unsigned short*) ramp->blue);
|
||||
}
|
||||
else
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"X11: Gamma ramp access not supported by server");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW native API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
GLFWAPI RRCrtc glfwGetX11Adapter(GLFWmonitor* handle)
|
||||
{
|
||||
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(None);
|
||||
return monitor->x11.crtc;
|
||||
}
|
||||
|
||||
GLFWAPI RROutput glfwGetX11Monitor(GLFWmonitor* handle)
|
||||
{
|
||||
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(None);
|
||||
return monitor->x11.output;
|
||||
}
|
||||
|
||||
+434
@@ -0,0 +1,434 @@
|
||||
// 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 <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/keysym.h>
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/Xcursor/Xcursor.h>
|
||||
|
||||
// The XRandR extension provides mode setting and gamma control
|
||||
#include <X11/extensions/Xrandr.h>
|
||||
|
||||
// The Xkb extension provides improved keyboard support
|
||||
#include <X11/XKBlib.h>
|
||||
|
||||
// The Xinerama extension provides legacy monitor indices
|
||||
#include <X11/extensions/Xinerama.h>
|
||||
|
||||
// The XInput extension provides raw mouse motion input
|
||||
#include <X11/extensions/XInput2.h>
|
||||
|
||||
// The Shape extension provides custom window shapes
|
||||
#include <X11/extensions/shape.h>
|
||||
|
||||
typedef XRRCrtcGamma* (* PFN_XRRAllocGamma)(int);
|
||||
typedef void (* PFN_XRRFreeCrtcInfo)(XRRCrtcInfo*);
|
||||
typedef void (* PFN_XRRFreeGamma)(XRRCrtcGamma*);
|
||||
typedef void (* PFN_XRRFreeOutputInfo)(XRROutputInfo*);
|
||||
typedef void (* PFN_XRRFreeScreenResources)(XRRScreenResources*);
|
||||
typedef XRRCrtcGamma* (* PFN_XRRGetCrtcGamma)(Display*,RRCrtc);
|
||||
typedef int (* PFN_XRRGetCrtcGammaSize)(Display*,RRCrtc);
|
||||
typedef XRRCrtcInfo* (* PFN_XRRGetCrtcInfo) (Display*,XRRScreenResources*,RRCrtc);
|
||||
typedef XRROutputInfo* (* PFN_XRRGetOutputInfo)(Display*,XRRScreenResources*,RROutput);
|
||||
typedef RROutput (* PFN_XRRGetOutputPrimary)(Display*,Window);
|
||||
typedef XRRScreenResources* (* PFN_XRRGetScreenResourcesCurrent)(Display*,Window);
|
||||
typedef Bool (* PFN_XRRQueryExtension)(Display*,int*,int*);
|
||||
typedef Status (* PFN_XRRQueryVersion)(Display*,int*,int*);
|
||||
typedef void (* PFN_XRRSelectInput)(Display*,Window,int);
|
||||
typedef Status (* PFN_XRRSetCrtcConfig)(Display*,XRRScreenResources*,RRCrtc,Time,int,int,RRMode,Rotation,RROutput*,int);
|
||||
typedef void (* PFN_XRRSetCrtcGamma)(Display*,RRCrtc,XRRCrtcGamma*);
|
||||
typedef int (* PFN_XRRUpdateConfiguration)(XEvent*);
|
||||
#define XRRAllocGamma _glfw.x11.randr.AllocGamma
|
||||
#define XRRFreeCrtcInfo _glfw.x11.randr.FreeCrtcInfo
|
||||
#define XRRFreeGamma _glfw.x11.randr.FreeGamma
|
||||
#define XRRFreeOutputInfo _glfw.x11.randr.FreeOutputInfo
|
||||
#define XRRFreeScreenResources _glfw.x11.randr.FreeScreenResources
|
||||
#define XRRGetCrtcGamma _glfw.x11.randr.GetCrtcGamma
|
||||
#define XRRGetCrtcGammaSize _glfw.x11.randr.GetCrtcGammaSize
|
||||
#define XRRGetCrtcInfo _glfw.x11.randr.GetCrtcInfo
|
||||
#define XRRGetOutputInfo _glfw.x11.randr.GetOutputInfo
|
||||
#define XRRGetOutputPrimary _glfw.x11.randr.GetOutputPrimary
|
||||
#define XRRGetScreenResourcesCurrent _glfw.x11.randr.GetScreenResourcesCurrent
|
||||
#define XRRQueryExtension _glfw.x11.randr.QueryExtension
|
||||
#define XRRQueryVersion _glfw.x11.randr.QueryVersion
|
||||
#define XRRSelectInput _glfw.x11.randr.SelectInput
|
||||
#define XRRSetCrtcConfig _glfw.x11.randr.SetCrtcConfig
|
||||
#define XRRSetCrtcGamma _glfw.x11.randr.SetCrtcGamma
|
||||
#define XRRUpdateConfiguration _glfw.x11.randr.UpdateConfiguration
|
||||
|
||||
typedef XcursorImage* (* PFN_XcursorImageCreate)(int,int);
|
||||
typedef void (* PFN_XcursorImageDestroy)(XcursorImage*);
|
||||
typedef Cursor (* PFN_XcursorImageLoadCursor)(Display*,const XcursorImage*);
|
||||
typedef char* (* PFN_XcursorGetTheme)(Display*);
|
||||
typedef int (* PFN_XcursorGetDefaultSize)(Display*);
|
||||
typedef XcursorImage* (* PFN_XcursorLibraryLoadImage)(const char*,const char*,int);
|
||||
#define XcursorImageCreate _glfw.x11.xcursor.ImageCreate
|
||||
#define XcursorImageDestroy _glfw.x11.xcursor.ImageDestroy
|
||||
#define XcursorImageLoadCursor _glfw.x11.xcursor.ImageLoadCursor
|
||||
#define XcursorGetTheme _glfw.x11.xcursor.GetTheme
|
||||
#define XcursorGetDefaultSize _glfw.x11.xcursor.GetDefaultSize
|
||||
#define XcursorLibraryLoadImage _glfw.x11.xcursor.LibraryLoadImage
|
||||
|
||||
typedef Bool (* PFN_XineramaIsActive)(Display*);
|
||||
typedef Bool (* PFN_XineramaQueryExtension)(Display*,int*,int*);
|
||||
typedef XineramaScreenInfo* (* PFN_XineramaQueryScreens)(Display*,int*);
|
||||
#define XineramaIsActive _glfw.x11.xinerama.IsActive
|
||||
#define XineramaQueryExtension _glfw.x11.xinerama.QueryExtension
|
||||
#define XineramaQueryScreens _glfw.x11.xinerama.QueryScreens
|
||||
|
||||
typedef XID xcb_window_t;
|
||||
typedef XID xcb_visualid_t;
|
||||
typedef struct xcb_connection_t xcb_connection_t;
|
||||
typedef xcb_connection_t* (* PFN_XGetXCBConnection)(Display*);
|
||||
#define XGetXCBConnection _glfw.x11.x11xcb.GetXCBConnection
|
||||
|
||||
typedef Bool (* PFN_XF86VidModeQueryExtension)(Display*,int*,int*);
|
||||
typedef Bool (* PFN_XF86VidModeGetGammaRamp)(Display*,int,int,unsigned short*,unsigned short*,unsigned short*);
|
||||
typedef Bool (* PFN_XF86VidModeSetGammaRamp)(Display*,int,int,unsigned short*,unsigned short*,unsigned short*);
|
||||
typedef Bool (* PFN_XF86VidModeGetGammaRampSize)(Display*,int,int*);
|
||||
#define XF86VidModeQueryExtension _glfw.x11.vidmode.QueryExtension
|
||||
#define XF86VidModeGetGammaRamp _glfw.x11.vidmode.GetGammaRamp
|
||||
#define XF86VidModeSetGammaRamp _glfw.x11.vidmode.SetGammaRamp
|
||||
#define XF86VidModeGetGammaRampSize _glfw.x11.vidmode.GetGammaRampSize
|
||||
|
||||
typedef Status (* PFN_XIQueryVersion)(Display*,int*,int*);
|
||||
typedef int (* PFN_XISelectEvents)(Display*,Window,XIEventMask*,int);
|
||||
#define XIQueryVersion _glfw.x11.xi.QueryVersion
|
||||
#define XISelectEvents _glfw.x11.xi.SelectEvents
|
||||
|
||||
typedef Bool (* PFN_XRenderQueryExtension)(Display*,int*,int*);
|
||||
typedef Status (* PFN_XRenderQueryVersion)(Display*dpy,int*,int*);
|
||||
typedef XRenderPictFormat* (* PFN_XRenderFindVisualFormat)(Display*,Visual const*);
|
||||
#define XRenderQueryExtension _glfw.x11.xrender.QueryExtension
|
||||
#define XRenderQueryVersion _glfw.x11.xrender.QueryVersion
|
||||
#define XRenderFindVisualFormat _glfw.x11.xrender.FindVisualFormat
|
||||
|
||||
typedef Bool (* PFN_XShapeQueryExtension)(Display*,int*,int*);
|
||||
typedef Status (* PFN_XShapeQueryVersion)(Display*dpy,int*,int*);
|
||||
typedef void (* PFN_XShapeCombineRegion)(Display*,Window,int,int,int,Region,int);
|
||||
typedef void (* PFN_XShapeCombineMask)(Display*,Window,int,int,int,Pixmap,int);
|
||||
|
||||
#define XShapeQueryExtension _glfw.x11.xshape.QueryExtension
|
||||
#define XShapeQueryVersion _glfw.x11.xshape.QueryVersion
|
||||
#define XShapeCombineRegion _glfw.x11.xshape.ShapeCombineRegion
|
||||
#define XShapeCombineMask _glfw.x11.xshape.ShapeCombineMask
|
||||
|
||||
#include "posix_thread_unix.h"
|
||||
#include "posix_time_linbsd.h"
|
||||
#include "xkb_unicode_linbsd.h"
|
||||
#include "glx_context_linbsd.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->x11.handle)
|
||||
#define _GLFW_EGL_NATIVE_DISPLAY ((EGLNativeDisplayType) _glfw.x11.display)
|
||||
|
||||
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowX11 x11
|
||||
#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryX11 x11
|
||||
#define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorX11 x11
|
||||
#define _GLFW_PLATFORM_CURSOR_STATE _GLFWcursorX11 x11
|
||||
|
||||
|
||||
// X11-specific per-window data
|
||||
//
|
||||
typedef struct _GLFWwindowX11
|
||||
{
|
||||
Colormap colormap;
|
||||
Window handle;
|
||||
Window parent;
|
||||
XIC ic;
|
||||
|
||||
GLFWbool overrideRedirect;
|
||||
GLFWbool iconified;
|
||||
GLFWbool maximized;
|
||||
|
||||
// Whether the visual supports framebuffer transparency
|
||||
GLFWbool transparent;
|
||||
|
||||
// Cached position and size used to filter out duplicate events
|
||||
int width, height;
|
||||
int xpos, ypos;
|
||||
|
||||
// The last received cursor position, regardless of source
|
||||
int lastCursorPosX, lastCursorPosY;
|
||||
// The last position the cursor was warped to by GLFW
|
||||
int warpCursorPosX, warpCursorPosY;
|
||||
|
||||
// The time of the last KeyPress event per keycode, for discarding
|
||||
// duplicate key events generated for some keys by ibus
|
||||
Time keyPressTimes[256];
|
||||
} _GLFWwindowX11;
|
||||
|
||||
// X11-specific global data
|
||||
//
|
||||
typedef struct _GLFWlibraryX11
|
||||
{
|
||||
Display* display;
|
||||
int screen;
|
||||
Window root;
|
||||
|
||||
// System content scale
|
||||
float contentScaleX, contentScaleY;
|
||||
// Helper window for IPC
|
||||
Window helperWindowHandle;
|
||||
// Invisible cursor for hidden cursor mode
|
||||
Cursor hiddenCursorHandle;
|
||||
// Context for mapping window XIDs to _GLFWwindow pointers
|
||||
XContext context;
|
||||
// XIM input method
|
||||
XIM im;
|
||||
// The previous X error handler, to be restored later
|
||||
XErrorHandler errorHandler;
|
||||
// Most recent error code received by X error handler
|
||||
int errorCode;
|
||||
// Primary selection string (while the primary selection is owned)
|
||||
char* primarySelectionString;
|
||||
// Clipboard string (while the selection is owned)
|
||||
char* clipboardString;
|
||||
// Key name string
|
||||
char keynames[GLFW_KEY_LAST + 1][5];
|
||||
// X11 keycode to GLFW key LUT
|
||||
short int keycodes[256];
|
||||
// GLFW key to X11 keycode LUT
|
||||
short int scancodes[GLFW_KEY_LAST + 1];
|
||||
// Where to place the cursor when re-enabled
|
||||
double restoreCursorPosX, restoreCursorPosY;
|
||||
// The window whose disabled cursor mode is active
|
||||
_GLFWwindow* disabledCursorWindow;
|
||||
int emptyEventPipe[2];
|
||||
|
||||
// Window manager atoms
|
||||
Atom NET_SUPPORTED;
|
||||
Atom NET_SUPPORTING_WM_CHECK;
|
||||
Atom WM_PROTOCOLS;
|
||||
Atom WM_STATE;
|
||||
Atom WM_DELETE_WINDOW;
|
||||
Atom NET_WM_NAME;
|
||||
Atom NET_WM_ICON_NAME;
|
||||
Atom NET_WM_ICON;
|
||||
Atom NET_WM_PID;
|
||||
Atom NET_WM_PING;
|
||||
Atom NET_WM_WINDOW_TYPE;
|
||||
Atom NET_WM_WINDOW_TYPE_NORMAL;
|
||||
Atom NET_WM_STATE;
|
||||
Atom NET_WM_STATE_ABOVE;
|
||||
Atom NET_WM_STATE_FULLSCREEN;
|
||||
Atom NET_WM_STATE_MAXIMIZED_VERT;
|
||||
Atom NET_WM_STATE_MAXIMIZED_HORZ;
|
||||
Atom NET_WM_STATE_DEMANDS_ATTENTION;
|
||||
Atom NET_WM_BYPASS_COMPOSITOR;
|
||||
Atom NET_WM_FULLSCREEN_MONITORS;
|
||||
Atom NET_WM_WINDOW_OPACITY;
|
||||
Atom NET_WM_CM_Sx;
|
||||
Atom NET_WORKAREA;
|
||||
Atom NET_CURRENT_DESKTOP;
|
||||
Atom NET_ACTIVE_WINDOW;
|
||||
Atom NET_FRAME_EXTENTS;
|
||||
Atom NET_REQUEST_FRAME_EXTENTS;
|
||||
Atom MOTIF_WM_HINTS;
|
||||
|
||||
// Xdnd (drag and drop) atoms
|
||||
Atom XdndAware;
|
||||
Atom XdndEnter;
|
||||
Atom XdndPosition;
|
||||
Atom XdndStatus;
|
||||
Atom XdndActionCopy;
|
||||
Atom XdndDrop;
|
||||
Atom XdndFinished;
|
||||
Atom XdndSelection;
|
||||
Atom XdndTypeList;
|
||||
Atom text_uri_list;
|
||||
|
||||
// Selection (clipboard) atoms
|
||||
Atom TARGETS;
|
||||
Atom MULTIPLE;
|
||||
Atom INCR;
|
||||
Atom CLIPBOARD;
|
||||
Atom PRIMARY;
|
||||
Atom CLIPBOARD_MANAGER;
|
||||
Atom SAVE_TARGETS;
|
||||
Atom NULL_;
|
||||
Atom UTF8_STRING;
|
||||
Atom COMPOUND_STRING;
|
||||
Atom ATOM_PAIR;
|
||||
Atom GLFW_SELECTION;
|
||||
|
||||
struct {
|
||||
GLFWbool available;
|
||||
void* handle;
|
||||
int eventBase;
|
||||
int errorBase;
|
||||
int major;
|
||||
int minor;
|
||||
GLFWbool gammaBroken;
|
||||
GLFWbool monitorBroken;
|
||||
PFN_XRRAllocGamma AllocGamma;
|
||||
PFN_XRRFreeCrtcInfo FreeCrtcInfo;
|
||||
PFN_XRRFreeGamma FreeGamma;
|
||||
PFN_XRRFreeOutputInfo FreeOutputInfo;
|
||||
PFN_XRRFreeScreenResources FreeScreenResources;
|
||||
PFN_XRRGetCrtcGamma GetCrtcGamma;
|
||||
PFN_XRRGetCrtcGammaSize GetCrtcGammaSize;
|
||||
PFN_XRRGetCrtcInfo GetCrtcInfo;
|
||||
PFN_XRRGetOutputInfo GetOutputInfo;
|
||||
PFN_XRRGetOutputPrimary GetOutputPrimary;
|
||||
PFN_XRRGetScreenResourcesCurrent GetScreenResourcesCurrent;
|
||||
PFN_XRRQueryExtension QueryExtension;
|
||||
PFN_XRRQueryVersion QueryVersion;
|
||||
PFN_XRRSelectInput SelectInput;
|
||||
PFN_XRRSetCrtcConfig SetCrtcConfig;
|
||||
PFN_XRRSetCrtcGamma SetCrtcGamma;
|
||||
PFN_XRRUpdateConfiguration UpdateConfiguration;
|
||||
} randr;
|
||||
|
||||
struct {
|
||||
GLFWbool available;
|
||||
GLFWbool detectable;
|
||||
int majorOpcode;
|
||||
int eventBase;
|
||||
int errorBase;
|
||||
int major;
|
||||
int minor;
|
||||
unsigned int group;
|
||||
} xkb;
|
||||
|
||||
struct {
|
||||
int count;
|
||||
int timeout;
|
||||
int interval;
|
||||
int blanking;
|
||||
int exposure;
|
||||
} saver;
|
||||
|
||||
struct {
|
||||
int version;
|
||||
Window source;
|
||||
Atom format;
|
||||
} xdnd;
|
||||
|
||||
struct {
|
||||
void* handle;
|
||||
PFN_XcursorImageCreate ImageCreate;
|
||||
PFN_XcursorImageDestroy ImageDestroy;
|
||||
PFN_XcursorImageLoadCursor ImageLoadCursor;
|
||||
PFN_XcursorGetTheme GetTheme;
|
||||
PFN_XcursorGetDefaultSize GetDefaultSize;
|
||||
PFN_XcursorLibraryLoadImage LibraryLoadImage;
|
||||
} xcursor;
|
||||
|
||||
struct {
|
||||
GLFWbool available;
|
||||
void* handle;
|
||||
int major;
|
||||
int minor;
|
||||
PFN_XineramaIsActive IsActive;
|
||||
PFN_XineramaQueryExtension QueryExtension;
|
||||
PFN_XineramaQueryScreens QueryScreens;
|
||||
} xinerama;
|
||||
|
||||
struct {
|
||||
void* handle;
|
||||
PFN_XGetXCBConnection GetXCBConnection;
|
||||
} x11xcb;
|
||||
|
||||
struct {
|
||||
GLFWbool available;
|
||||
void* handle;
|
||||
int eventBase;
|
||||
int errorBase;
|
||||
PFN_XF86VidModeQueryExtension QueryExtension;
|
||||
PFN_XF86VidModeGetGammaRamp GetGammaRamp;
|
||||
PFN_XF86VidModeSetGammaRamp SetGammaRamp;
|
||||
PFN_XF86VidModeGetGammaRampSize GetGammaRampSize;
|
||||
} vidmode;
|
||||
|
||||
struct {
|
||||
GLFWbool available;
|
||||
void* handle;
|
||||
int majorOpcode;
|
||||
int eventBase;
|
||||
int errorBase;
|
||||
int major;
|
||||
int minor;
|
||||
PFN_XIQueryVersion QueryVersion;
|
||||
PFN_XISelectEvents SelectEvents;
|
||||
} xi;
|
||||
|
||||
struct {
|
||||
GLFWbool available;
|
||||
void* handle;
|
||||
int major;
|
||||
int minor;
|
||||
int eventBase;
|
||||
int errorBase;
|
||||
PFN_XRenderQueryExtension QueryExtension;
|
||||
PFN_XRenderQueryVersion QueryVersion;
|
||||
PFN_XRenderFindVisualFormat FindVisualFormat;
|
||||
} xrender;
|
||||
|
||||
struct {
|
||||
GLFWbool available;
|
||||
void* handle;
|
||||
int major;
|
||||
int minor;
|
||||
int eventBase;
|
||||
int errorBase;
|
||||
PFN_XShapeQueryExtension QueryExtension;
|
||||
PFN_XShapeCombineRegion ShapeCombineRegion;
|
||||
PFN_XShapeQueryVersion QueryVersion;
|
||||
PFN_XShapeCombineMask ShapeCombineMask;
|
||||
} xshape;
|
||||
} _GLFWlibraryX11;
|
||||
|
||||
// X11-specific per-monitor data
|
||||
//
|
||||
typedef struct _GLFWmonitorX11
|
||||
{
|
||||
RROutput output;
|
||||
RRCrtc crtc;
|
||||
RRMode oldMode;
|
||||
|
||||
// Index of corresponding Xinerama screen,
|
||||
// for EWMH full screen window placement
|
||||
int index;
|
||||
} _GLFWmonitorX11;
|
||||
|
||||
// X11-specific per-cursor data
|
||||
//
|
||||
typedef struct _GLFWcursorX11
|
||||
{
|
||||
Cursor handle;
|
||||
} _GLFWcursorX11;
|
||||
|
||||
|
||||
void _glfwPollMonitorsX11(void);
|
||||
void _glfwSetVideoModeX11(_GLFWmonitor* monitor, const GLFWvidmode* desired);
|
||||
void _glfwRestoreVideoModeX11(_GLFWmonitor* monitor);
|
||||
|
||||
Cursor _glfwCreateCursorX11(const GLFWimage* image, int xhot, int yhot);
|
||||
|
||||
unsigned long _glfwGetWindowPropertyX11(Window window,
|
||||
Atom property,
|
||||
Atom type,
|
||||
unsigned char** value);
|
||||
GLFWbool _glfwIsVisualTransparentX11(Visual* visual);
|
||||
|
||||
void _glfwGrabErrorHandlerX11(void);
|
||||
void _glfwReleaseErrorHandlerX11(void);
|
||||
void _glfwInputErrorX11(int error, const char* message);
|
||||
|
||||
void _glfwPushSelectionToManagerX11(void);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user