mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
Merge with main - fixed conflicts
This commit is contained in:
commit
6a00b8680a
17
.github/workflows/test.yml
vendored
17
.github/workflows/test.yml
vendored
@ -7,7 +7,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
go: ['1.18.x', '1.19.x', '1.20.x', '1.21.x', '1.22.x']
|
||||
go: ['1.19.x', '1.20.x', '1.21.x', '1.22.x']
|
||||
name: Test with Go ${{ matrix.go }} on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
@ -44,9 +44,10 @@ jobs:
|
||||
|
||||
- name: Install wasmbrowsertest
|
||||
run: |
|
||||
go install github.com/agnivade/wasmbrowsertest@bb77d443e302d06f0d2462336bc2765942e0e862
|
||||
wasmbrowsertest_version=6e494bb3a5ddfe6cccb449250dbdcaa5777b593d
|
||||
go install github.com/agnivade/wasmbrowsertest@${wasmbrowsertest_version}
|
||||
mv $(go env GOPATH)/bin/wasmbrowsertest${{ runner.os == 'Windows' && '.exe' || '' }} $(go env GOPATH)/bin/go_js_wasm_exec${{ runner.os == 'Windows' && '.exe' || '' }}
|
||||
go install github.com/agnivade/wasmbrowsertest/cmd/cleanenv@bb77d443e302d06f0d2462336bc2765942e0e862
|
||||
go install github.com/agnivade/wasmbrowsertest/cmd/cleanenv@${wasmbrowsertest_version}
|
||||
|
||||
- name: Prepare ebitenmobile test
|
||||
run: |
|
||||
@ -77,9 +78,6 @@ jobs:
|
||||
go list ./... | grep -v -x -F -f .github/workflows/govetblock_windows.txt | xargs go vet
|
||||
|
||||
- name: go vet (vettool)
|
||||
# Stop vettools for old Go versions. Apparently this is an issue in golang.org/x/tools (golang/go#62519)
|
||||
# TODO: Update golang.org/x/tools and remove this restriction.
|
||||
if: ${{ !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') }}
|
||||
run: |
|
||||
go install ./internal/vettools
|
||||
go vet -vettool=$(which vettools)${{ runner.os == 'Windows' && '.exe' || '' }} -v ./...
|
||||
@ -144,8 +142,6 @@ jobs:
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update
|
||||
sudo apt-get install ppa-purge
|
||||
sudo ppa-purge -y ppa:ubuntu-toolchain-r/test # Hack for 32bit Linux (#2667)
|
||||
sudo apt-get install gcc-multilib
|
||||
sudo apt-get install libasound2-dev:i386 libgl1-mesa-dev:i386 libxcursor-dev:i386 libxi-dev:i386 libxinerama-dev:i386 libxrandr-dev:i386 libxxf86vm-dev:i386
|
||||
env CGO_ENABLED=1 GOARCH=386 go test -shuffle=on -v -p=1 ./...
|
||||
@ -169,8 +165,11 @@ jobs:
|
||||
env GOARCH=386 EBITENGINE_DIRECTX=version=12 go test -shuffle=on -v ./...
|
||||
|
||||
- name: go test (Wasm)
|
||||
if: ${{ runner.os != 'macOS' && runner.os != 'Windows' }}
|
||||
run: |
|
||||
env GOOS=js GOARCH=wasm cleanenv -remove-prefix GITHUB_ -remove-prefix JAVA_ -- go test -shuffle=on -v ./...
|
||||
# Wasm tests don't work on macOS with the headless mode enabled, but the headless mode cannot be disabled in GitHub Actions (#2972).
|
||||
# Wasm tests don't work on Windows well due to mysterious timeouts (#2982).
|
||||
env GOOS=js GOARCH=wasm cleanenv -remove-prefix GITHUB_ -remove-prefix JAVA_ -remove-prefix PSModulePath -remove-prefix STATS_ -remove-prefix RUNNER_ -- go test -shuffle=on -v ./...
|
||||
|
||||
- name: Install ebitenmobile
|
||||
run: |
|
||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -7,3 +7,8 @@
|
||||
.vscode
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
*.fxc
|
||||
!dummy.fxc
|
||||
*.metallib
|
||||
!dummy.metallib
|
||||
|
129
README.md
129
README.md
@ -61,3 +61,132 @@ For installation on desktops, see [the installation instruction](https://ebiteng
|
||||
Ebitengine is licensed under Apache license version 2.0. See [LICENSE](LICENSE) file.
|
||||
|
||||
[The Ebitengine logo](https://ebitengine.org/images/logo.png) by Hajime Hoshi is licensed under [the Creative Commons Attribution-NoDerivatives 4.0](https://creativecommons.org/licenses/by-nd/4.0/).
|
||||
|
||||
### GLFW
|
||||
|
||||
https://github.com/glfw/glfw
|
||||
|
||||
|
||||
```
|
||||
Copyright (c) 2002-2006 Marcus Geelnard
|
||||
|
||||
Copyright (c) 2006-2019 Camilla Löwy
|
||||
|
||||
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.
|
||||
```
|
||||
|
||||
### Go
|
||||
|
||||
https://cs.opensource.google/go/go
|
||||
|
||||
|
||||
```
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
```
|
||||
|
||||
### go-gl/gl
|
||||
|
||||
https://github.com/go-gl/gl
|
||||
|
||||
|
||||
```
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Eric Woroshow
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
|
||||
### go-gl/glfw
|
||||
|
||||
https://github.com/go-gl/glfw
|
||||
|
||||
|
||||
```
|
||||
Copyright (c) 2012 The glfw3-go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
```
|
||||
|
@ -31,6 +31,7 @@ import (
|
||||
type Stream struct {
|
||||
orig *mp3.Decoder
|
||||
resampling *convert.Resampling
|
||||
sampleRate int
|
||||
}
|
||||
|
||||
// Read is implementation of io.Reader's Read.
|
||||
@ -57,6 +58,11 @@ func (s *Stream) Length() int64 {
|
||||
return s.orig.Length()
|
||||
}
|
||||
|
||||
// SampleRate returns the sample rate of the decoded stream.
|
||||
func (s *Stream) SampleRate() int {
|
||||
return s.sampleRate
|
||||
}
|
||||
|
||||
// DecodeWithoutResampling decodes an MP3 source and returns a decoded stream.
|
||||
//
|
||||
// DecodeWithoutResampling returns error when decoding fails or IO error happens.
|
||||
@ -73,6 +79,7 @@ func DecodeWithoutResampling(src io.Reader) (*Stream, error) {
|
||||
s := &Stream{
|
||||
orig: d,
|
||||
resampling: nil,
|
||||
sampleRate: d.SampleRate(),
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
@ -87,6 +94,9 @@ func DecodeWithoutResampling(src io.Reader) (*Stream, error) {
|
||||
//
|
||||
// A Stream doesn't close src even if src implements io.Closer.
|
||||
// Closing the source is src owner's responsibility.
|
||||
//
|
||||
// Resampling can be a very heavy task. Stream has a cache for resampling, but the size is limited.
|
||||
// Do not expect that Stream has a resampling cache even after whole data is played.
|
||||
func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) {
|
||||
d, err := mp3.NewDecoder(src)
|
||||
if err != nil {
|
||||
@ -100,6 +110,7 @@ func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) {
|
||||
s := &Stream{
|
||||
orig: d,
|
||||
resampling: r,
|
||||
sampleRate: sampleRate,
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
@ -27,50 +27,48 @@ import (
|
||||
|
||||
// Stream is a decoded audio stream.
|
||||
type Stream struct {
|
||||
decoded io.ReadSeeker
|
||||
size int64
|
||||
readSeeker io.ReadSeeker
|
||||
length int64
|
||||
sampleRate int
|
||||
}
|
||||
|
||||
// Read is implementation of io.Reader's Read.
|
||||
func (s *Stream) Read(p []byte) (int, error) {
|
||||
return s.decoded.Read(p)
|
||||
return s.readSeeker.Read(p)
|
||||
}
|
||||
|
||||
// Seek is implementation of io.Seeker's Seek.
|
||||
//
|
||||
// Note that Seek can take long since decoding is a relatively heavy task.
|
||||
func (s *Stream) Seek(offset int64, whence int) (int64, error) {
|
||||
return s.decoded.Seek(offset, whence)
|
||||
return s.readSeeker.Seek(offset, whence)
|
||||
}
|
||||
|
||||
// Length returns the size of decoded stream in bytes.
|
||||
//
|
||||
// If the source is not io.Seeker, Length returns 0.
|
||||
func (s *Stream) Length() int64 {
|
||||
return s.size
|
||||
return s.length
|
||||
}
|
||||
|
||||
type decoder interface {
|
||||
Read([]float32) (int, error)
|
||||
SetPosition(int64) error
|
||||
Length() int64
|
||||
Channels() int
|
||||
SampleRate() int
|
||||
// SampleRate returns the sample rate of the decoded stream.
|
||||
func (s *Stream) SampleRate() int {
|
||||
return s.sampleRate
|
||||
}
|
||||
|
||||
type decoded struct {
|
||||
totalBytes int
|
||||
posInBytes int
|
||||
decoder decoder
|
||||
decoderr io.Reader
|
||||
type i16Stream struct {
|
||||
totalBytes int
|
||||
posInBytes int
|
||||
vorbisReader *oggvorbis.Reader
|
||||
i16Reader io.Reader
|
||||
}
|
||||
|
||||
func (d *decoded) Read(b []byte) (int, error) {
|
||||
if d.decoderr == nil {
|
||||
d.decoderr = convert.NewReaderFromFloat32Reader(d.decoder)
|
||||
func (s *i16Stream) Read(b []byte) (int, error) {
|
||||
if s.i16Reader == nil {
|
||||
s.i16Reader = convert.NewReaderFromFloat32Reader(s.vorbisReader)
|
||||
}
|
||||
|
||||
l := d.totalBytes - d.posInBytes
|
||||
l := s.totalBytes - s.posInBytes
|
||||
if l > len(b) {
|
||||
l = len(b)
|
||||
}
|
||||
@ -79,7 +77,7 @@ func (d *decoded) Read(b []byte) (int, error) {
|
||||
}
|
||||
|
||||
retry:
|
||||
n, err := d.decoderr.Read(b[:l])
|
||||
n, err := s.i16Reader.Read(b[:l])
|
||||
if err != nil && err != io.EOF {
|
||||
return 0, err
|
||||
}
|
||||
@ -88,59 +86,63 @@ retry:
|
||||
goto retry
|
||||
}
|
||||
|
||||
d.posInBytes += n
|
||||
if d.posInBytes == d.totalBytes || err == io.EOF {
|
||||
s.posInBytes += n
|
||||
if s.posInBytes == s.totalBytes || err == io.EOF {
|
||||
return n, io.EOF
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (d *decoded) Seek(offset int64, whence int) (int64, error) {
|
||||
func (s *i16Stream) Seek(offset int64, whence int) (int64, error) {
|
||||
next := int64(0)
|
||||
switch whence {
|
||||
case io.SeekStart:
|
||||
next = offset
|
||||
case io.SeekCurrent:
|
||||
next = int64(d.posInBytes) + offset
|
||||
next = int64(s.posInBytes) + offset
|
||||
case io.SeekEnd:
|
||||
next = int64(d.totalBytes) + offset
|
||||
next = int64(s.totalBytes) + offset
|
||||
}
|
||||
// pos should be always even
|
||||
next = next / 2 * 2
|
||||
d.posInBytes = int(next)
|
||||
if err := d.decoder.SetPosition(next / int64(d.decoder.Channels()) / 2); err != nil {
|
||||
s.posInBytes = int(next)
|
||||
if err := s.vorbisReader.SetPosition(next / int64(s.vorbisReader.Channels()) / 2); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
d.decoderr = nil
|
||||
s.i16Reader = nil
|
||||
return next, nil
|
||||
}
|
||||
|
||||
func (d *decoded) Length() int64 {
|
||||
return int64(d.totalBytes)
|
||||
func (s *i16Stream) Length() int64 {
|
||||
return int64(s.totalBytes)
|
||||
}
|
||||
|
||||
// decode accepts an ogg stream and returns a decorded stream.
|
||||
func decode(in io.Reader) (*decoded, int, int, error) {
|
||||
func decode(in io.Reader) (*i16Stream, int, int, error) {
|
||||
r, err := oggvorbis.NewReader(in)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
d := &decoded{
|
||||
if r.Channels() != 1 && r.Channels() != 2 {
|
||||
return nil, 0, 0, fmt.Errorf("vorbis: number of channels must be 1 or 2 but was %d", r.Channels())
|
||||
}
|
||||
|
||||
s := &i16Stream{
|
||||
// TODO: r.Length() returns 0 when the format is unknown.
|
||||
// Should we check that?
|
||||
totalBytes: int(r.Length()) * r.Channels() * 2, // 2 means 16bit per sample.
|
||||
posInBytes: 0,
|
||||
decoder: r,
|
||||
totalBytes: int(r.Length()) * r.Channels() * 2, // 2 means 16bit per sample.
|
||||
posInBytes: 0,
|
||||
vorbisReader: r,
|
||||
}
|
||||
if _, ok := in.(io.Seeker); ok {
|
||||
if _, err := d.Read(make([]byte, 65536)); err != nil && err != io.EOF {
|
||||
if _, err := s.Read(make([]byte, 65536)); err != nil && err != io.EOF {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
if _, err := d.Seek(0, io.SeekStart); err != nil {
|
||||
if _, err := s.Seek(0, io.SeekStart); err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
}
|
||||
return d, r.Channels(), r.SampleRate(), nil
|
||||
return s, r.Channels(), r.SampleRate(), nil
|
||||
}
|
||||
|
||||
// DecodeWithoutResampling decodes Ogg/Vorbis data to playable stream.
|
||||
@ -152,22 +154,22 @@ func decode(in io.Reader) (*decoded, int, int, error) {
|
||||
// A Stream doesn't close src even if src implements io.Closer.
|
||||
// Closing the source is src owner's responsibility.
|
||||
func DecodeWithoutResampling(src io.Reader) (*Stream, error) {
|
||||
decoded, channelCount, _, err := decode(src)
|
||||
i16Stream, channelCount, sampleRate, err := decode(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if channelCount != 1 && channelCount != 2 {
|
||||
return nil, fmt.Errorf("vorbis: number of channels must be 1 or 2 but was %d", channelCount)
|
||||
}
|
||||
var s io.ReadSeeker = decoded
|
||||
size := decoded.Length()
|
||||
|
||||
var s io.ReadSeeker = i16Stream
|
||||
length := i16Stream.Length()
|
||||
if channelCount == 1 {
|
||||
s = convert.NewStereo16(s, true, false)
|
||||
size *= 2
|
||||
length *= 2
|
||||
}
|
||||
|
||||
stream := &Stream{
|
||||
decoded: s,
|
||||
size: size,
|
||||
readSeeker: s,
|
||||
length: length,
|
||||
sampleRate: sampleRate,
|
||||
}
|
||||
return stream, nil
|
||||
}
|
||||
@ -182,26 +184,31 @@ func DecodeWithoutResampling(src io.Reader) (*Stream, error) {
|
||||
//
|
||||
// A Stream doesn't close src even if src implements io.Closer.
|
||||
// Closing the source is src owner's responsibility.
|
||||
//
|
||||
// Resampling can be a very heavy task. Stream has a cache for resampling, but the size is limited.
|
||||
// Do not expect that Stream has a resampling cache even after whole data is played.
|
||||
func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) {
|
||||
decoded, channelCount, origSampleRate, err := decode(src)
|
||||
i16Stream, channelCount, origSampleRate, err := decode(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if channelCount != 1 && channelCount != 2 {
|
||||
return nil, fmt.Errorf("vorbis: number of channels must be 1 or 2 but was %d", channelCount)
|
||||
}
|
||||
var s io.ReadSeeker = decoded
|
||||
size := decoded.Length()
|
||||
|
||||
var s io.ReadSeeker = i16Stream
|
||||
length := i16Stream.Length()
|
||||
if channelCount == 1 {
|
||||
s = convert.NewStereo16(s, true, false)
|
||||
size *= 2
|
||||
length *= 2
|
||||
}
|
||||
if origSampleRate != sampleRate {
|
||||
r := convert.NewResampling(s, size, origSampleRate, sampleRate)
|
||||
r := convert.NewResampling(s, length, origSampleRate, sampleRate)
|
||||
s = r
|
||||
size = r.Length()
|
||||
length = r.Length()
|
||||
}
|
||||
stream := &Stream{
|
||||
readSeeker: s,
|
||||
length: length,
|
||||
sampleRate: sampleRate,
|
||||
}
|
||||
stream := &Stream{decoded: s, size: size}
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
|
@ -50,16 +50,16 @@ func TestMono(t *testing.T) {
|
||||
}
|
||||
|
||||
// Stream decoded by audio/vorbis.DecodeWithSampleRate() is always 16bit stereo.
|
||||
got := s.Length()
|
||||
|
||||
// On the other hand, the original vorbis package is monoral.
|
||||
// As Length() represents the number of samples,
|
||||
// this needs to be doubled by 2 (= bytes in 16bits).
|
||||
want := r.Length() * 2 * 2
|
||||
|
||||
if got != want {
|
||||
if got, want := s.Length(), r.Length()*2*2; got != want {
|
||||
t.Errorf("s.Length(): got: %d, want: %d", got, want)
|
||||
}
|
||||
|
||||
if got, want := s.SampleRate(), audioContext.SampleRate(); got != want {
|
||||
t.Errorf("s.SampleRate(): got: %d, want: %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTooShort(t *testing.T) {
|
||||
@ -70,11 +70,13 @@ func TestTooShort(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got := s.Length()
|
||||
want := int64(79424)
|
||||
if got != want {
|
||||
if got, want := s.Length(), int64(79424); got != want {
|
||||
t.Errorf("s.Length(): got: %d, want: %d", got, want)
|
||||
}
|
||||
|
||||
if got, want := s.SampleRate(), audioContext.SampleRate(); got != want {
|
||||
t.Errorf("s.SampleRate(): got: %d, want: %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
type reader struct {
|
||||
@ -93,9 +95,11 @@ func TestNonSeeker(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got := s.Length()
|
||||
want := int64(0)
|
||||
if got != want {
|
||||
if got, want := s.Length(), int64(0); got != want {
|
||||
t.Errorf("s.Length(): got: %d, want: %d", got, want)
|
||||
}
|
||||
|
||||
if got, want := s.SampleRate(), audioContext.SampleRate(); got != want {
|
||||
t.Errorf("s.SampleRate(): got: %d, want: %d", got, want)
|
||||
}
|
||||
}
|
||||
|
@ -26,8 +26,9 @@ import (
|
||||
|
||||
// Stream is a decoded audio stream.
|
||||
type Stream struct {
|
||||
inner io.ReadSeeker
|
||||
size int64
|
||||
inner io.ReadSeeker
|
||||
size int64
|
||||
sampleRate int
|
||||
}
|
||||
|
||||
// Read is implementation of io.Reader's Read.
|
||||
@ -49,6 +50,11 @@ func (s *Stream) Length() int64 {
|
||||
return s.size
|
||||
}
|
||||
|
||||
// SampleRate returns the sample rate of the decoded stream.
|
||||
func (s *Stream) SampleRate() int {
|
||||
return s.sampleRate
|
||||
}
|
||||
|
||||
type stream struct {
|
||||
src io.Reader
|
||||
headerSize int64
|
||||
@ -114,7 +120,7 @@ func (s *stream) Seek(offset int64, whence int) (int64, error) {
|
||||
// A Stream doesn't close src even if src implements io.Closer.
|
||||
// Closing the source is src owner's responsibility.
|
||||
func DecodeWithoutResampling(src io.Reader) (*Stream, error) {
|
||||
s, _, err := decode(src)
|
||||
s, err := decode(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -134,37 +140,41 @@ func DecodeWithoutResampling(src io.Reader) (*Stream, error) {
|
||||
//
|
||||
// A Stream doesn't close src even if src implements io.Closer.
|
||||
// Closing the source is src owner's responsibility.
|
||||
//
|
||||
// Resampling can be a very heavy task. Stream has a cache for resampling, but the size is limited.
|
||||
// Do not expect that Stream has a resampling cache even after whole data is played.
|
||||
func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) {
|
||||
s, origSampleRate, err := decode(src)
|
||||
s, err := decode(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sampleRate == origSampleRate {
|
||||
if sampleRate == s.sampleRate {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
r := convert.NewResampling(s.inner, s.size, origSampleRate, sampleRate)
|
||||
r := convert.NewResampling(s.inner, s.size, s.sampleRate, sampleRate)
|
||||
return &Stream{
|
||||
inner: r,
|
||||
size: r.Length(),
|
||||
inner: r,
|
||||
size: r.Length(),
|
||||
sampleRate: sampleRate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func decode(src io.Reader) (*Stream, int, error) {
|
||||
func decode(src io.Reader) (*Stream, error) {
|
||||
buf := make([]byte, 12)
|
||||
n, err := io.ReadFull(src, buf)
|
||||
if n != len(buf) {
|
||||
return nil, 0, fmt.Errorf("wav: invalid header")
|
||||
return nil, fmt.Errorf("wav: invalid header")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
if !bytes.Equal(buf[0:4], []byte("RIFF")) {
|
||||
return nil, 0, fmt.Errorf("wav: invalid header: 'RIFF' not found")
|
||||
return nil, fmt.Errorf("wav: invalid header: 'RIFF' not found")
|
||||
}
|
||||
if !bytes.Equal(buf[8:12], []byte("WAVE")) {
|
||||
return nil, 0, fmt.Errorf("wav: invalid header: 'WAVE' not found")
|
||||
return nil, fmt.Errorf("wav: invalid header: 'WAVE' not found")
|
||||
}
|
||||
|
||||
// Read chunks
|
||||
@ -178,10 +188,10 @@ chunks:
|
||||
buf := make([]byte, 8)
|
||||
n, err := io.ReadFull(src, buf)
|
||||
if n != len(buf) {
|
||||
return nil, 0, fmt.Errorf("wav: invalid header")
|
||||
return nil, fmt.Errorf("wav: invalid header")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
headerSize += 8
|
||||
size := int64(buf[4]) | int64(buf[5])<<8 | int64(buf[6])<<16 | int64(buf[7])<<24
|
||||
@ -189,19 +199,19 @@ chunks:
|
||||
case bytes.Equal(buf[0:4], []byte("fmt ")):
|
||||
// Size of 'fmt' header is usually 16, but can be more than 16.
|
||||
if size < 16 {
|
||||
return nil, 0, fmt.Errorf("wav: invalid header: maybe non-PCM file?")
|
||||
return nil, fmt.Errorf("wav: invalid header: maybe non-PCM file?")
|
||||
}
|
||||
buf := make([]byte, size)
|
||||
n, err := io.ReadFull(src, buf)
|
||||
if n != len(buf) {
|
||||
return nil, 0, fmt.Errorf("wav: invalid header")
|
||||
return nil, fmt.Errorf("wav: invalid header")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
format := int(buf[0]) | int(buf[1])<<8
|
||||
if format != 1 {
|
||||
return nil, 0, fmt.Errorf("wav: format must be linear PCM")
|
||||
return nil, fmt.Errorf("wav: format must be linear PCM")
|
||||
}
|
||||
channelCount := int(buf[2]) | int(buf[3])<<8
|
||||
switch channelCount {
|
||||
@ -210,11 +220,11 @@ chunks:
|
||||
case 2:
|
||||
mono = false
|
||||
default:
|
||||
return nil, 0, fmt.Errorf("wav: number of channels must be 1 or 2 but was %d", channelCount)
|
||||
return nil, fmt.Errorf("wav: number of channels must be 1 or 2 but was %d", channelCount)
|
||||
}
|
||||
bitsPerSample = int(buf[14]) | int(buf[15])<<8
|
||||
if bitsPerSample != 8 && bitsPerSample != 16 {
|
||||
return nil, 0, fmt.Errorf("wav: bits per sample must be 8 or 16 but was %d", bitsPerSample)
|
||||
return nil, fmt.Errorf("wav: bits per sample must be 8 or 16 but was %d", bitsPerSample)
|
||||
}
|
||||
sampleRate = int(buf[4]) | int(buf[5])<<8 | int(buf[6])<<16 | int(buf[7])<<24
|
||||
headerSize += size
|
||||
@ -225,10 +235,10 @@ chunks:
|
||||
buf := make([]byte, size)
|
||||
n, err := io.ReadFull(src, buf)
|
||||
if n != len(buf) {
|
||||
return nil, 0, fmt.Errorf("wav: invalid header")
|
||||
return nil, fmt.Errorf("wav: invalid header")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
headerSize += size
|
||||
}
|
||||
@ -249,7 +259,11 @@ chunks:
|
||||
dataSize *= 2
|
||||
}
|
||||
}
|
||||
return &Stream{inner: s, size: dataSize}, sampleRate, nil
|
||||
return &Stream{
|
||||
inner: s,
|
||||
size: dataSize,
|
||||
sampleRate: sampleRate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Decode decodes WAV (RIFF) data to playable stream.
|
||||
|
@ -23,10 +23,10 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
exec "golang.org/x/sys/execabs"
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
|
@ -18,13 +18,13 @@ import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
|
||||
// Add a dependency on gomobile in order to get the version via debug.ReadBuildInfo().
|
||||
_ "github.com/ebitengine/gomobile/geom"
|
||||
exec "golang.org/x/sys/execabs"
|
||||
)
|
||||
|
||||
//go:embed gobind.go
|
||||
|
@ -27,12 +27,12 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
"unicode"
|
||||
|
||||
exec "golang.org/x/sys/execabs"
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
|
@ -179,7 +179,7 @@ func builtinShader(filter builtinshader.Filter, address builtinshader.Address) *
|
||||
return s
|
||||
}
|
||||
|
||||
src := builtinshader.Shader(filter, address, true)
|
||||
src := builtinshader.ShaderSource(filter, address, true)
|
||||
s, err := ebiten.NewShader(src)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("colorm: NewShader for a built-in shader failed: %v", err))
|
||||
|
@ -72,11 +72,11 @@ type DrawTrianglesOptions struct {
|
||||
|
||||
// FillRule indicates the rule how an overlapped region is rendered.
|
||||
//
|
||||
// The rules NonZero and EvenOdd are useful when you want to render a complex polygon.
|
||||
// The rules FileRuleNonZero and FillRuleEvenOdd are useful when you want to render a complex polygon.
|
||||
// A complex polygon is a non-convex polygon like a concave polygon, a polygon with holes, or a self-intersecting polygon.
|
||||
// See examples/vector for actual usages.
|
||||
//
|
||||
// The default (zero) value is ebiten.FillAll.
|
||||
// The default (zero) value is ebiten.FillRuleFillAll.
|
||||
FillRule ebiten.FillRule
|
||||
|
||||
// AntiAlias indicates whether the rendering uses anti-alias or not.
|
||||
|
32
ebiten_test.go
Normal file
32
ebiten_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
// 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.
|
||||
|
||||
package ebiten_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
func TestScreenSizeInFullscreen(t *testing.T) {
|
||||
// Just call ScreenSizeInFullscreen. There was a crash bug on browsers (#2975).
|
||||
w, h := ebiten.ScreenSizeInFullscreen()
|
||||
if w <= 0 {
|
||||
t.Errorf("w must be positive but not: %d", w)
|
||||
}
|
||||
if h <= 0 {
|
||||
t.Errorf("h must be positive but not: %d", h)
|
||||
}
|
||||
}
|
@ -40,6 +40,7 @@ func OpenFile(path string) (ReadSeekCloser, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -171,7 +171,7 @@ func NewPlayer(game *Game, audioContext *audio.Context, musicType musicType) (*P
|
||||
|
||||
player.audioPlayer.Play()
|
||||
go func() {
|
||||
s, err := wav.DecodeWithSampleRate(sampleRate, bytes.NewReader(raudio.Jab_wav))
|
||||
s, err := wav.DecodeWithoutResampling(bytes.NewReader(raudio.Jab_wav))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
|
33
examples/shaderprecomp/defaultshader.go
Normal file
33
examples/shaderprecomp/defaultshader.go
Normal file
@ -0,0 +1,33 @@
|
||||
// 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 ignore
|
||||
|
||||
//kage:unit pixels
|
||||
|
||||
package main
|
||||
|
||||
var Time float
|
||||
var Cursor vec2
|
||||
|
||||
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
|
||||
pos := (dstPos.xy - imageDstOrigin()) / imageDstSize()
|
||||
pos += Cursor / imageDstSize() / 4
|
||||
clr := 0.0
|
||||
clr += sin(pos.x*cos(Time/15)*80) + cos(pos.y*cos(Time/15)*10)
|
||||
clr += sin(pos.y*sin(Time/10)*40) + cos(pos.x*sin(Time/25)*40)
|
||||
clr += sin(pos.x*sin(Time/5)*10) + sin(pos.y*sin(Time/35)*80)
|
||||
clr *= sin(Time/10) * 0.5
|
||||
return vec4(clr, clr*0.5, sin(clr+Time/3)*0.75, 1)
|
||||
}
|
1
examples/shaderprecomp/fxc/dummy.fxc
Normal file
1
examples/shaderprecomp/fxc/dummy.fxc
Normal file
@ -0,0 +1 @@
|
||||
This is a dummy .fxc file to trick Go's embed package.
|
123
examples/shaderprecomp/fxc/gen.go
Normal file
123
examples/shaderprecomp/fxc/gen.go
Normal file
@ -0,0 +1,123 @@
|
||||
// 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 ignore
|
||||
|
||||
// This is a program to generate precompiled HLSL blobs (FXC files).
|
||||
//
|
||||
// See https://learn.microsoft.com/en-us/windows/win32/direct3dtools/fxc.
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/shaderprecomp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func run() error {
|
||||
if _, err := exec.LookPath("fxc.exe"); err != nil {
|
||||
if errors.Is(err, exec.ErrNotFound) {
|
||||
fmt.Fprintln(os.Stderr, "fxc.exe not found. Please install Windows SDK.")
|
||||
fmt.Fprintln(os.Stderr, "See https://learn.microsoft.com/en-us/windows/win32/direct3dtools/fxc for more details.")
|
||||
fmt.Fprintln(os.Stderr, "HINT: On PowerShell, you can add a path to the PATH environment variable temporarily like:")
|
||||
fmt.Fprintln(os.Stderr)
|
||||
fmt.Fprintln(os.Stderr, ` & (Get-Process -Id $PID).Path { $env:PATH="C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64;"+$env:PATH; go generate .\examples\shaderprecomp\fxc\ }`)
|
||||
fmt.Fprintln(os.Stderr)
|
||||
os.Exit(1)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
tmpdir, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
srcs := shaderprecomp.AppendBuildinShaderSources(nil)
|
||||
|
||||
defaultSrcBytes, err := os.ReadFile(filepath.Join("..", "defaultshader.go"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srcs = append(srcs, shaderprecomp.NewShaderSource(defaultSrcBytes))
|
||||
|
||||
for i, src := range srcs {
|
||||
// Avoid using errgroup.Group.
|
||||
// Compiling sources in parallel causes a mixed error message on the console.
|
||||
if err := compile(src, i, tmpdir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateHSLSFiles(source *shaderprecomp.ShaderSource, index int, tmpdir string) (vs, ps string, err error) {
|
||||
vsHLSLFilePath := filepath.Join(tmpdir, fmt.Sprintf("%d_vs.hlsl", index))
|
||||
vsf, err := os.Create(vsHLSLFilePath)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer vsf.Close()
|
||||
|
||||
psHLSLFilePath := filepath.Join(tmpdir, fmt.Sprintf("%d_ps.hlsl", index))
|
||||
psf, err := os.Create(psHLSLFilePath)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer psf.Close()
|
||||
|
||||
if err := shaderprecomp.CompileToHLSL(vsf, psf, source); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return vsHLSLFilePath, psHLSLFilePath, nil
|
||||
}
|
||||
|
||||
func compile(source *shaderprecomp.ShaderSource, index int, tmpdir string) error {
|
||||
// Generate HLSL files. Make sure this process doesn't have any handlers of the files.
|
||||
// Without closing the files, fxc.exe cannot access the files.
|
||||
vsHLSLFilePath, psHLSLFilePath, err := generateHSLSFiles(source, index, tmpdir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vsFXCFilePath := fmt.Sprintf("%d_vs.fxc", index)
|
||||
cmd := exec.Command("fxc.exe", "/nologo", "/O3", "/T", shaderprecomp.HLSLVertexShaderProfile, "/E", shaderprecomp.HLSLVertexShaderEntryPoint, "/Fo", vsFXCFilePath, vsHLSLFilePath)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
psFXCFilePath := fmt.Sprintf("%d_ps.fxc", index)
|
||||
cmd = exec.Command("fxc.exe", "/nologo", "/O3", "/T", shaderprecomp.HLSLPixelShaderProfile, "/E", shaderprecomp.HLSLPixelShaderEntryPoint, "/Fo", psFXCFilePath, psHLSLFilePath)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
19
examples/shaderprecomp/fxc/generate.go
Normal file
19
examples/shaderprecomp/fxc/generate.go
Normal file
@ -0,0 +1,19 @@
|
||||
// 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 windows
|
||||
|
||||
//go:generate go run gen.go
|
||||
|
||||
package fxc
|
74
examples/shaderprecomp/main.go
Normal file
74
examples/shaderprecomp/main.go
Normal file
@ -0,0 +1,74 @@
|
||||
// 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"log"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||
)
|
||||
|
||||
//go:embed defaultshader.go
|
||||
var defaultShaderSourceBytes []byte
|
||||
|
||||
type Game struct {
|
||||
defaultShader *ebiten.Shader
|
||||
counter int
|
||||
}
|
||||
|
||||
func (g *Game) Update() error {
|
||||
g.counter++
|
||||
|
||||
if g.defaultShader == nil {
|
||||
s, err := ebiten.NewShader(defaultShaderSourceBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g.defaultShader = s
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Game) Draw(screen *ebiten.Image) {
|
||||
cx, cy := ebiten.CursorPosition()
|
||||
w, h := screen.Bounds().Dx(), screen.Bounds().Dy()
|
||||
op := &ebiten.DrawRectShaderOptions{}
|
||||
op.Uniforms = map[string]interface{}{
|
||||
"Time": float32(g.counter) / float32(ebiten.TPS()),
|
||||
"Cursor": []float32{float32(cx), float32(cy)},
|
||||
}
|
||||
screen.DrawRectShader(w, h, g.defaultShader, op)
|
||||
|
||||
msg := `This is a test for shader precompilation.
|
||||
Precompilation works only on macOS so far.
|
||||
Note that this example still works even without shader precompilation.`
|
||||
ebitenutil.DebugPrint(screen, msg)
|
||||
}
|
||||
|
||||
func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
|
||||
return outsideWidth, outsideHeight
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := registerPrecompiledShaders(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
ebiten.SetWindowTitle("Ebitengine Example (Shader Precompilation)")
|
||||
if err := ebiten.RunGame(&Game{}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
1
examples/shaderprecomp/metallib/dummy.metallib
Normal file
1
examples/shaderprecomp/metallib/dummy.metallib
Normal file
@ -0,0 +1 @@
|
||||
This is a dummy .metallib file to trick Go's embed package.
|
93
examples/shaderprecomp/metallib/gen.go
Normal file
93
examples/shaderprecomp/metallib/gen.go
Normal file
@ -0,0 +1,93 @@
|
||||
// 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 ignore
|
||||
|
||||
// This is a program to generate precompiled Metal libraries.
|
||||
//
|
||||
// See https://developer.apple.com/documentation/metal/shader_libraries/building_a_shader_library_by_precompiling_source_files.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/shaderprecomp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func run() error {
|
||||
tmpdir, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
srcs := shaderprecomp.AppendBuildinShaderSources(nil)
|
||||
|
||||
defaultSrcBytes, err := os.ReadFile(filepath.Join("..", "defaultshader.go"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srcs = append(srcs, shaderprecomp.NewShaderSource(defaultSrcBytes))
|
||||
|
||||
for i, src := range srcs {
|
||||
// Avoid using errgroup.Group.
|
||||
// Compiling sources in parallel causes a mixed error message on the console.
|
||||
if err := compile(src, i, tmpdir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func compile(source *shaderprecomp.ShaderSource, index int, tmpdir string) error {
|
||||
metalFilePath := filepath.Join(tmpdir, fmt.Sprintf("%d.metal", index))
|
||||
|
||||
f, err := os.Create(metalFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := shaderprecomp.CompileToMSL(f, source); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
irFilePath := filepath.Join(tmpdir, fmt.Sprintf("%d.ir", index))
|
||||
cmd := exec.Command("xcrun", "-sdk", "macosx", "metal", "-o", irFilePath, "-c", metalFilePath)
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
metallibFilePath := fmt.Sprintf("%d.metallib", index)
|
||||
cmd = exec.Command("xcrun", "-sdk", "macosx", "metallib", "-o", metallibFilePath, irFilePath)
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
19
examples/shaderprecomp/metallib/generate.go
Normal file
19
examples/shaderprecomp/metallib/generate.go
Normal file
@ -0,0 +1,19 @@
|
||||
// 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 darwin
|
||||
|
||||
//go:generate go run gen.go
|
||||
|
||||
package metallib
|
48
examples/shaderprecomp/register_darwin.go
Normal file
48
examples/shaderprecomp/register_darwin.go
Normal file
@ -0,0 +1,48 @@
|
||||
// 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/shaderprecomp"
|
||||
)
|
||||
|
||||
//go:embed metallib/*.metallib
|
||||
var metallibs embed.FS
|
||||
|
||||
func registerPrecompiledShaders() error {
|
||||
srcs := shaderprecomp.AppendBuildinShaderSources(nil)
|
||||
srcs = append(srcs, shaderprecomp.NewShaderSource(defaultShaderSourceBytes))
|
||||
|
||||
for i, src := range srcs {
|
||||
name := fmt.Sprintf("%d.metallib", i)
|
||||
lib, err := metallibs.ReadFile("metallib/" + name)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
fmt.Fprintf(os.Stderr, "precompiled Metal library %s was not found. Run 'go generate' for 'metallib' directory to generate them.\n", name)
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
shaderprecomp.RegisterMetalLibrary(src, lib)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
27
examples/shaderprecomp/register_others.go
Normal file
27
examples/shaderprecomp/register_others.go
Normal file
@ -0,0 +1,27 @@
|
||||
// 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 !darwin && !windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func registerPrecompiledShaders() error {
|
||||
fmt.Fprintf(os.Stderr, "precompiled shaders are not available in this environment.\n")
|
||||
return nil
|
||||
}
|
61
examples/shaderprecomp/register_windows.go
Normal file
61
examples/shaderprecomp/register_windows.go
Normal file
@ -0,0 +1,61 @@
|
||||
// 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/shaderprecomp"
|
||||
)
|
||||
|
||||
// https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/
|
||||
|
||||
//go:embed fxc/*.fxc
|
||||
var fxcs embed.FS
|
||||
|
||||
func registerPrecompiledShaders() error {
|
||||
srcs := shaderprecomp.AppendBuildinShaderSources(nil)
|
||||
srcs = append(srcs, shaderprecomp.NewShaderSource(defaultShaderSourceBytes))
|
||||
|
||||
for i, src := range srcs {
|
||||
vsname := fmt.Sprintf("%d_vs.fxc", i)
|
||||
vs, err := fxcs.ReadFile("fxc/" + vsname)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
fmt.Fprintf(os.Stderr, "precompiled HLSL library %s was not found. Run 'go generate' for 'fxc' directory to generate them.\n", vsname)
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
psname := fmt.Sprintf("%d_ps.fxc", i)
|
||||
ps, err := fxcs.ReadFile("fxc/" + psname)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
fmt.Fprintf(os.Stderr, "precompiled HLSL library %s was not found. Run 'go generate' for 'fxc' directory to generate them.\n", psname)
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
shaderprecomp.RegisterFXCs(src, vs, ps)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -141,14 +141,14 @@ func drawEbitenText(screen *ebiten.Image, x, y int, aa bool, line bool) {
|
||||
op := &ebiten.DrawTrianglesOptions{}
|
||||
op.AntiAlias = aa
|
||||
|
||||
// For strokes (AppendVerticesAndIndicesForStroke), FillAll and NonZero work.
|
||||
// For strokes (AppendVerticesAndIndicesForStroke), FillRuleFillAll and FillRuleNonZero work.
|
||||
//
|
||||
// For filling (AppendVerticesAndIndicesForFilling), NonZero and EvenOdd work.
|
||||
// NonZero and EvenOdd differ when rendering a complex polygons with self-intersections and/or holes.
|
||||
// For filling (AppendVerticesAndIndicesForFilling), FillRuleNonZero and FillRuleEvenOdd work.
|
||||
// FillRuleNonZero and FillRuleEvenOdd differ when rendering a complex polygons with self-intersections and/or holes.
|
||||
// See https://en.wikipedia.org/wiki/Nonzero-rule and https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule .
|
||||
//
|
||||
// For simplicity, this example always uses NonZero, whichever strokes or filling is done.
|
||||
op.FillRule = ebiten.NonZero
|
||||
// For simplicity, this example always uses FillRuleNonZero, whichever strokes or filling is done.
|
||||
op.FillRule = ebiten.FillRuleNonZero
|
||||
|
||||
screen.DrawTriangles(vs, is, whiteSubImage, op)
|
||||
}
|
||||
@ -203,7 +203,7 @@ func drawEbitenLogo(screen *ebiten.Image, x, y int, aa bool, line bool) {
|
||||
|
||||
op := &ebiten.DrawTrianglesOptions{}
|
||||
op.AntiAlias = aa
|
||||
op.FillRule = ebiten.NonZero
|
||||
op.FillRule = ebiten.FillRuleNonZero
|
||||
screen.DrawTriangles(vs, is, whiteSubImage, op)
|
||||
}
|
||||
|
||||
@ -245,7 +245,7 @@ func drawArc(screen *ebiten.Image, count int, aa bool, line bool) {
|
||||
|
||||
op := &ebiten.DrawTrianglesOptions{}
|
||||
op.AntiAlias = aa
|
||||
op.FillRule = ebiten.NonZero
|
||||
op.FillRule = ebiten.FillRuleNonZero
|
||||
screen.DrawTriangles(vs, is, whiteSubImage, op)
|
||||
}
|
||||
|
||||
@ -300,7 +300,7 @@ func drawWave(screen *ebiten.Image, counter int, aa bool, line bool) {
|
||||
|
||||
op := &ebiten.DrawTrianglesOptions{}
|
||||
op.AntiAlias = aa
|
||||
op.FillRule = ebiten.NonZero
|
||||
op.FillRule = ebiten.FillRuleNonZero
|
||||
screen.DrawTriangles(vs, is, whiteSubImage, op)
|
||||
}
|
||||
|
||||
|
42
gameforui.go
42
gameforui.go
@ -21,44 +21,14 @@ import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/atlas"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/builtinshader"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/ui"
|
||||
)
|
||||
|
||||
const screenShaderSrc = `//kage:unit pixels
|
||||
var screenFilterEnabled atomic.Bool
|
||||
|
||||
package main
|
||||
|
||||
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
|
||||
// Blend source colors in a square region, which size is 1/scale.
|
||||
scale := imageDstSize()/imageSrc0Size()
|
||||
pos := srcPos
|
||||
p0 := pos - 1/2.0/scale
|
||||
p1 := pos + 1/2.0/scale
|
||||
|
||||
// Texels must be in the source rect, so it is not necessary to check.
|
||||
c0 := imageSrc0UnsafeAt(p0)
|
||||
c1 := imageSrc0UnsafeAt(vec2(p1.x, p0.y))
|
||||
c2 := imageSrc0UnsafeAt(vec2(p0.x, p1.y))
|
||||
c3 := imageSrc0UnsafeAt(p1)
|
||||
|
||||
// p is the p1 value in one pixel assuming that the pixel's upper-left is (0, 0) and the lower-right is (1, 1).
|
||||
rate := clamp(fract(p1)*scale, 0, 1)
|
||||
return mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y)
|
||||
}
|
||||
`
|
||||
|
||||
var screenFilterEnabled = int32(1)
|
||||
|
||||
func isScreenFilterEnabled() bool {
|
||||
return atomic.LoadInt32(&screenFilterEnabled) != 0
|
||||
}
|
||||
|
||||
func setScreenFilterEnabled(enabled bool) {
|
||||
v := int32(0)
|
||||
if enabled {
|
||||
v = 1
|
||||
}
|
||||
atomic.StoreInt32(&screenFilterEnabled, v)
|
||||
func init() {
|
||||
screenFilterEnabled.Store(true)
|
||||
}
|
||||
|
||||
type gameForUI struct {
|
||||
@ -76,7 +46,7 @@ func newGameForUI(game Game, transparent bool) *gameForUI {
|
||||
transparent: transparent,
|
||||
}
|
||||
|
||||
s, err := NewShader([]byte(screenShaderSrc))
|
||||
s, err := NewShader(builtinshader.ScreenShaderSource)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("ebiten: compiling the screen shader failed: %v", err))
|
||||
}
|
||||
@ -167,7 +137,7 @@ func (g *gameForUI) DrawFinalScreen(scale, offsetX, offsetY float64) {
|
||||
}
|
||||
|
||||
switch {
|
||||
case !isScreenFilterEnabled(), math.Floor(scale) == scale:
|
||||
case !screenFilterEnabled.Load(), math.Floor(scale) == scale:
|
||||
op := &DrawImageOptions{}
|
||||
op.GeoM = geoM
|
||||
g.screen.DrawImage(g.offscreen, op)
|
||||
|
22
go.mod
22
go.mod
@ -1,28 +1,28 @@
|
||||
module github.com/hajimehoshi/ebiten/v2
|
||||
|
||||
go 1.18
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/ebitengine/gomobile v0.0.0-20240329170434-1771503ff0a8
|
||||
github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895
|
||||
github.com/ebitengine/hideconsole v1.0.0
|
||||
github.com/ebitengine/oto/v3 v3.3.0-alpha.1
|
||||
github.com/ebitengine/purego v0.8.0-alpha.1
|
||||
github.com/ebitengine/purego v0.8.0-alpha.2
|
||||
github.com/gen2brain/mpeg v0.3.2-0.20240412154320-a2ac4fc8a46f
|
||||
github.com/go-text/typesetting v0.1.1-0.20240402181327-ced1d6822703
|
||||
github.com/hajimehoshi/bitmapfont/v3 v3.0.0
|
||||
github.com/go-text/typesetting v0.1.1
|
||||
github.com/hajimehoshi/bitmapfont/v3 v3.2.0-alpha.1
|
||||
github.com/hajimehoshi/go-mp3 v0.3.4
|
||||
github.com/jakecoffman/cp v1.2.1
|
||||
github.com/jezek/xgb v1.1.1
|
||||
github.com/jfreymuth/oggvorbis v1.0.5
|
||||
github.com/kisielk/errcheck v1.7.0
|
||||
golang.org/x/image v0.15.0
|
||||
golang.org/x/sync v0.6.0
|
||||
golang.org/x/sys v0.18.0
|
||||
golang.org/x/text v0.14.0
|
||||
golang.org/x/tools v0.19.0
|
||||
golang.org/x/image v0.16.0
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/sys v0.20.0
|
||||
golang.org/x/text v0.15.0
|
||||
golang.org/x/tools v0.21.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/jfreymuth/vorbis v1.0.2 // indirect
|
||||
golang.org/x/mod v0.16.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
)
|
||||
|
53
go.sum
53
go.sum
@ -1,18 +1,18 @@
|
||||
github.com/ebitengine/gomobile v0.0.0-20240329170434-1771503ff0a8 h1:5e8X7WEdOWrjrKvgaWF6PRnDvJicfrkEnwAkWtMN74g=
|
||||
github.com/ebitengine/gomobile v0.0.0-20240329170434-1771503ff0a8/go.mod h1:tWboRRNagZwwwis4QIgEFG1ZNFwBJ3LAhSLAXAAxobQ=
|
||||
github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 h1:48bCqKTuD7Z0UovDfvpCn7wZ0GUZ+yosIteNDthn3FU=
|
||||
github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895/go.mod h1:XZdLv05c5hOZm3fM2NlJ92FyEZjnslcMcNRrhxs8+8M=
|
||||
github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE=
|
||||
github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A=
|
||||
github.com/ebitengine/oto/v3 v3.3.0-alpha.1 h1:J2nBmQwPLKc4+yLObytq1jKNydI96l6EjZfgefiqGbk=
|
||||
github.com/ebitengine/oto/v3 v3.3.0-alpha.1/go.mod h1:T2/VV0UWG97GEEf4kORMU2nCneYT/YmwSTxPutSVaUg=
|
||||
github.com/ebitengine/purego v0.8.0-alpha.1 h1:52AgJTNaQRi7YtOtdJl4hkxNWhAGMxuDuDjOVIp5Ojk=
|
||||
github.com/ebitengine/purego v0.8.0-alpha.1/go.mod h1:y8L+ZRLphbdPW2xs41fur/KaW57yTzrFsqsclHyHrTM=
|
||||
github.com/ebitengine/purego v0.8.0-alpha.2 h1:+Kyr9n4eXAGMzhtWJxfdQ7AzGn0+6ZWihfCCxul3Dso=
|
||||
github.com/ebitengine/purego v0.8.0-alpha.2/go.mod h1:w5fARo4H5UrAgQTz0yqDfZ6bjstTQwUFmO+TN+nHlWE=
|
||||
github.com/gen2brain/mpeg v0.3.2-0.20240412154320-a2ac4fc8a46f h1:ysqRe+lvUiL0dH5XzkH0Bz68bFMPJ4f5Si4L/HD9SGk=
|
||||
github.com/gen2brain/mpeg v0.3.2-0.20240412154320-a2ac4fc8a46f/go.mod h1:i/ebyRRv/IoHixuZ9bElZnXbmfoUVPGQpdsJ4sVuX38=
|
||||
github.com/go-text/typesetting v0.1.1-0.20240402181327-ced1d6822703 h1:AqtMl9yw7r319Ah4W2afQm3Ql+PEsQKHds18tGvKhog=
|
||||
github.com/go-text/typesetting v0.1.1-0.20240402181327-ced1d6822703/go.mod h1:2+owI/sxa73XA581LAzVuEBZ3WEEV2pXeDswCH/3i1I=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66 h1:GUrm65PQPlhFSKjLPGOZNPNxLCybjzjYBzjfoBGaDUY=
|
||||
github.com/hajimehoshi/bitmapfont/v3 v3.0.0 h1:r2+6gYK38nfztS/et50gHAswb9hXgxXECYgE8Nczmi4=
|
||||
github.com/hajimehoshi/bitmapfont/v3 v3.0.0/go.mod h1:+CxxG+uMmgU4mI2poq944i3uZ6UYFfAkj9V6WqmuvZA=
|
||||
github.com/go-text/typesetting v0.1.1 h1:bGAesCuo85nXnEN5LmFMVGAGpGkCPtHrZLi//qD7EJo=
|
||||
github.com/go-text/typesetting v0.1.1/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04 h1:zBx+p/W2aQYtNuyZNcTfinWvXBQwYtDfme051PR/lAY=
|
||||
github.com/hajimehoshi/bitmapfont/v3 v3.2.0-alpha.1 h1:GLoMNCbvXOd39tFkqk9w/MI0xSLJaDzEOOl8mT1ILtI=
|
||||
github.com/hajimehoshi/bitmapfont/v3 v3.2.0-alpha.1/go.mod h1:VVaVK/4HpV1MHWswCl5miFOuLoRVyIplB3qEJxZK2OA=
|
||||
github.com/hajimehoshi/go-mp3 v0.3.4 h1:NUP7pBYH8OguP4diaTZ9wJbUbk3tC0KlfzsEpWmYj68=
|
||||
github.com/hajimehoshi/go-mp3 v0.3.4/go.mod h1:fRtZraRFcWb0pu7ok0LqyFhCUrPeMsGRSVop0eemFmo=
|
||||
github.com/hajimehoshi/oto/v2 v2.3.1/go.mod h1:seWLbgHH7AyUMYKfKYT9pg7PhUu9/SisyJvNTT+ASQo=
|
||||
@ -26,43 +26,50 @@ github.com/jfreymuth/vorbis v1.0.2 h1:m1xH6+ZI4thH927pgKD8JOH4eaGRm18rEE9/0WKjvN
|
||||
github.com/jfreymuth/vorbis v1.0.2/go.mod h1:DoftRo4AznKnShRl1GxiTFCseHr4zR9BN3TWXyuzrqQ=
|
||||
github.com/kisielk/errcheck v1.7.0 h1:+SbscKmWJ5mOK/bO1zS60F5I9WwZDWOfRsC4RwfwRV0=
|
||||
github.com/kisielk/errcheck v1.7.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw=
|
||||
golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
37
image.go
37
image.go
@ -262,7 +262,7 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
|
||||
})
|
||||
}
|
||||
|
||||
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillAll, canSkipMipmap(geoM, filter), false)
|
||||
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRuleFillAll, canSkipMipmap(geoM, filter), false)
|
||||
}
|
||||
|
||||
// Vertex represents a vertex passed to DrawTriangles.
|
||||
@ -310,17 +310,36 @@ const (
|
||||
// FillRule is the rule whether an overlapped region is rendered with DrawTriangles(Shader).
|
||||
type FillRule int
|
||||
|
||||
const (
|
||||
// FillRuleFillAll indicates all the triangles are rendered regardless of overlaps.
|
||||
FillRuleFillAll FillRule = FillRule(graphicsdriver.FillRuleFillAll)
|
||||
|
||||
// FillRuleNonZero means that triangles are rendered based on the non-zero rule.
|
||||
// If and only if the number of overlaps is not 0, the region is rendered.
|
||||
FillRuleNonZero FillRule = FillRule(graphicsdriver.FillRuleNonZero)
|
||||
|
||||
// FillRuleEvenOdd means that triangles are rendered based on the even-odd rule.
|
||||
// If and only if the number of overlaps is odd, the region is rendered.
|
||||
FillRuleEvenOdd FillRule = FillRule(graphicsdriver.FillRuleEvenOdd)
|
||||
)
|
||||
|
||||
const (
|
||||
// FillAll indicates all the triangles are rendered regardless of overlaps.
|
||||
FillAll FillRule = FillRule(graphicsdriver.FillAll)
|
||||
//
|
||||
// Deprecated: as of v2.8. Use FillRuleFillAll instead.
|
||||
FillAll = FillRuleFillAll
|
||||
|
||||
// NonZero means that triangles are rendered based on the non-zero rule.
|
||||
// If and only if the number of overlaps is not 0, the region is rendered.
|
||||
NonZero FillRule = FillRule(graphicsdriver.NonZero)
|
||||
//
|
||||
// Deprecated: as of v2.8. Use FillRuleNonZero instead.
|
||||
NonZero = FillRuleNonZero
|
||||
|
||||
// EvenOdd means that triangles are rendered based on the even-odd rule.
|
||||
// If and only if the number of overlaps is odd, the region is rendered.
|
||||
EvenOdd FillRule = FillRule(graphicsdriver.EvenOdd)
|
||||
//
|
||||
// Deprecated: as of v2.8. Use FillRuleEvenOdd instead.
|
||||
EvenOdd = FillRuleEvenOdd
|
||||
)
|
||||
|
||||
// ColorScaleMode is the mode of color scales in vertices.
|
||||
@ -371,11 +390,11 @@ type DrawTrianglesOptions struct {
|
||||
|
||||
// FillRule indicates the rule how an overlapped region is rendered.
|
||||
//
|
||||
// The rules NonZero and EvenOdd are useful when you want to render a complex polygon.
|
||||
// The rules FillRuleNonZero and FillRuleEvenOdd are useful when you want to render a complex polygon.
|
||||
// A complex polygon is a non-convex polygon like a concave polygon, a polygon with holes, or a self-intersecting polygon.
|
||||
// See examples/vector for actual usages.
|
||||
//
|
||||
// The default (zero) value is FillAll.
|
||||
// The default (zero) value is FillRuleFillAll.
|
||||
FillRule FillRule
|
||||
|
||||
// AntiAlias indicates whether the rendering uses anti-alias or not.
|
||||
@ -547,11 +566,11 @@ type DrawTrianglesShaderOptions struct {
|
||||
|
||||
// FillRule indicates the rule how an overlapped region is rendered.
|
||||
//
|
||||
// The rules NonZero and EvenOdd are useful when you want to render a complex polygon.
|
||||
// The rules FillRuleNonZero and FillRuleEvenOdd are useful when you want to render a complex polygon.
|
||||
// A complex polygon is a non-convex polygon like a concave polygon, a polygon with holes, or a self-intersecting polygon.
|
||||
// See examples/vector for actual usages.
|
||||
//
|
||||
// The default (zero) value is FillAll.
|
||||
// The default (zero) value is FillRuleFillAll.
|
||||
FillRule FillRule
|
||||
|
||||
// AntiAlias indicates whether the rendering uses anti-alias or not.
|
||||
@ -949,7 +968,7 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR
|
||||
i.tmpUniforms = i.tmpUniforms[:0]
|
||||
i.tmpUniforms = shader.appendUniforms(i.tmpUniforms, options.Uniforms)
|
||||
|
||||
i.image.DrawTriangles(imgs, vs, is, blend, i.adjustedBounds(), srcRegions, shader.shader, i.tmpUniforms, graphicsdriver.FillAll, true, false)
|
||||
i.image.DrawTriangles(imgs, vs, is, blend, i.adjustedBounds(), srcRegions, shader.shader, i.tmpUniforms, graphicsdriver.FillRuleFillAll, true, false)
|
||||
}
|
||||
|
||||
// SubImage returns an image representing the portion of the image p visible through r.
|
||||
|
@ -2696,7 +2696,7 @@ func TestImageEvenOdd(t *testing.T) {
|
||||
// Draw all the vertices once. The even-odd rule is applied for all the vertices once.
|
||||
dst := ebiten.NewImage(16, 16)
|
||||
op := &ebiten.DrawTrianglesOptions{
|
||||
FillRule: ebiten.EvenOdd,
|
||||
FillRule: ebiten.FillRuleEvenOdd,
|
||||
}
|
||||
dst.DrawTriangles(append(append(vs0, vs1...), vs2...), append(append(is0, is1...), is2...), emptySubImage, op)
|
||||
for j := 0; j < 16; j++ {
|
||||
@ -2794,15 +2794,15 @@ func TestImageEvenOdd(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestImageFillRule(t *testing.T) {
|
||||
for _, fillRule := range []ebiten.FillRule{ebiten.FillAll, ebiten.NonZero, ebiten.EvenOdd} {
|
||||
for _, fillRule := range []ebiten.FillRule{ebiten.FillRuleFillAll, ebiten.FillRuleNonZero, ebiten.FillRuleEvenOdd} {
|
||||
fillRule := fillRule
|
||||
var name string
|
||||
switch fillRule {
|
||||
case ebiten.FillAll:
|
||||
case ebiten.FillRuleFillAll:
|
||||
name = "FillAll"
|
||||
case ebiten.NonZero:
|
||||
case ebiten.FillRuleNonZero:
|
||||
name = "NonZero"
|
||||
case ebiten.EvenOdd:
|
||||
case ebiten.FillRuleEvenOdd:
|
||||
name = "EvenOdd"
|
||||
}
|
||||
t.Run(name, func(t *testing.T) {
|
||||
@ -2885,11 +2885,11 @@ func TestImageFillRule(t *testing.T) {
|
||||
var want color.RGBA
|
||||
switch {
|
||||
case 2 <= i && i < 7 && 2 <= j && j < 7:
|
||||
if fillRule != ebiten.EvenOdd {
|
||||
if fillRule != ebiten.FillRuleEvenOdd {
|
||||
want = color.RGBA{G: 0xff, A: 0xff}
|
||||
}
|
||||
case 9 <= i && i < 14 && 9 <= j && j < 14:
|
||||
if fillRule == ebiten.FillAll {
|
||||
if fillRule == ebiten.FillRuleFillAll {
|
||||
want = color.RGBA{B: 0xff, A: 0xff}
|
||||
}
|
||||
case 1 <= i && i < 15 && 1 <= j && j < 15:
|
||||
@ -2922,11 +2922,11 @@ func TestImageFillRule(t *testing.T) {
|
||||
var want color.RGBA
|
||||
switch {
|
||||
case 3 <= i && i < 8 && 3 <= j && j < 8:
|
||||
if fillRule != ebiten.EvenOdd {
|
||||
if fillRule != ebiten.FillRuleEvenOdd {
|
||||
want = color.RGBA{G: 0xff, A: 0xff}
|
||||
}
|
||||
case 10 <= i && i < 15 && 10 <= j && j < 15:
|
||||
if fillRule == ebiten.FillAll {
|
||||
if fillRule == ebiten.FillRuleFillAll {
|
||||
want = color.RGBA{B: 0xff, A: 0xff}
|
||||
}
|
||||
case 2 <= i && i < 16 && 2 <= j && j < 16:
|
||||
@ -3726,7 +3726,7 @@ func TestImageTooManyConstantBuffersInDirectX(t *testing.T) {
|
||||
dst0 := ebiten.NewImage(16, 16)
|
||||
dst1 := ebiten.NewImage(16, 16)
|
||||
op := &ebiten.DrawTrianglesOptions{
|
||||
FillRule: ebiten.EvenOdd,
|
||||
FillRule: ebiten.FillRuleEvenOdd,
|
||||
}
|
||||
for i := 0; i < 100; i++ {
|
||||
dst0.DrawTriangles(vs, is, src, op)
|
||||
|
@ -14,16 +14,12 @@
|
||||
|
||||
package atlas
|
||||
|
||||
import (
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
)
|
||||
|
||||
const (
|
||||
BaseCountToPutOnSourceBackend = baseCountToPutOnSourceBackend
|
||||
)
|
||||
|
||||
func PutImagesOnSourceBackendForTesting(graphicsDriver graphicsdriver.Graphics) {
|
||||
putImagesOnSourceBackend(graphicsDriver)
|
||||
func PutImagesOnSourceBackendForTesting() {
|
||||
putImagesOnSourceBackend()
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -36,13 +36,6 @@ var (
|
||||
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
|
||||
@ -81,7 +74,7 @@ func flushDeferred() {
|
||||
// Actual time duration is increased in an exponential way for each usage as a rendering target.
|
||||
const baseCountToPutOnSourceBackend = 10
|
||||
|
||||
func putImagesOnSourceBackend(graphicsDriver graphicsdriver.Graphics) {
|
||||
func putImagesOnSourceBackend() {
|
||||
// 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.
|
||||
@ -97,7 +90,7 @@ func putImagesOnSourceBackend(graphicsDriver graphicsdriver.Graphics) {
|
||||
i.usedAsSourceCount++
|
||||
}
|
||||
if int64(i.usedAsSourceCount) >= int64(baseCountToPutOnSourceBackend*(1<<uint(min(i.usedAsDestinationCount, 31)))) {
|
||||
i.putOnSourceBackend(graphicsDriver)
|
||||
i.putOnSourceBackend()
|
||||
i.usedAsSourceCount = 0
|
||||
}
|
||||
})
|
||||
@ -158,7 +151,7 @@ func (b *backend) extendIfNeeded(width, height int) {
|
||||
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.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader.ensureShader(), nil, graphicsdriver.FillAll)
|
||||
newImg.DrawTriangles(srcs, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader.ensureShader(), nil, graphicsdriver.FillRuleFillAll)
|
||||
b.image.Dispose()
|
||||
|
||||
b.image = newImg
|
||||
@ -182,7 +175,7 @@ func newClearedImage(width, height int, screen bool) *graphicscommand.Image {
|
||||
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.ShaderSrcImageCount]*graphicscommand.Image{}, vs, is, graphicsdriver.BlendClear, region, [graphics.ShaderSrcImageCount]image.Rectangle{}, clearShader.ensureShader(), nil, graphicsdriver.FillAll)
|
||||
i.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{}, vs, is, graphicsdriver.BlendClear, region, [graphics.ShaderSrcImageCount]image.Rectangle{}, clearShader.ensureShader(), nil, graphicsdriver.FillRuleFillAll)
|
||||
}
|
||||
|
||||
func (b *backend) clearPixels(region image.Rectangle) {
|
||||
@ -355,11 +348,11 @@ func (i *Image) ensureIsolatedFromSource(backends []*backend) {
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, i.width, i.height)
|
||||
|
||||
newI.drawTriangles([graphics.ShaderSrcImageCount]*Image{i}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
newI.drawTriangles([graphics.ShaderSrcImageCount]*Image{i}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
newI.moveTo(i)
|
||||
}
|
||||
|
||||
func (i *Image) putOnSourceBackend(graphicsDriver graphicsdriver.Graphics) {
|
||||
func (i *Image) putOnSourceBackend() {
|
||||
if i.backend == nil {
|
||||
i.allocate(nil, true)
|
||||
return
|
||||
@ -385,7 +378,7 @@ func (i *Image) putOnSourceBackend(graphicsDriver graphicsdriver.Graphics) {
|
||||
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.ShaderSrcImageCount]*Image{i}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
newI.drawTriangles([graphics.ShaderSrcImageCount]*Image{i}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
|
||||
newI.moveTo(i)
|
||||
i.usedAsSourceCount = 0
|
||||
@ -464,6 +457,7 @@ func (i *Image) drawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertice
|
||||
return
|
||||
}
|
||||
|
||||
// This slice is not escaped to the heap. This can be checked by `go build -gcflags=-m`.
|
||||
backends := make([]*backend, 0, len(srcs))
|
||||
for _, src := range srcs {
|
||||
if src == nil {
|
||||
@ -1072,7 +1066,7 @@ func BeginFrame(graphicsDriver graphicsdriver.Graphics) error {
|
||||
}
|
||||
|
||||
flushDeferred()
|
||||
putImagesOnSourceBackend(graphicsDriver)
|
||||
putImagesOnSourceBackend()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ func TestEnsureIsolatedFromSourceBackend(t *testing.T) {
|
||||
vs := quadVertices(size/2, size/2, size/4, size/4, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, size, size)
|
||||
img4.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img3}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
img4.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img3}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
if got, want := img4.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -113,7 +113,7 @@ func TestEnsureIsolatedFromSourceBackend(t *testing.T) {
|
||||
// img5 is not allocated now, but is allocated at DrawTriangles.
|
||||
vs = quadVertices(0, 0, size/2, size/2, 1)
|
||||
dr = image.Rect(0, 0, size/2, size/2)
|
||||
img3.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img5}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
img3.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img5}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
if got, want := img3.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -147,7 +147,7 @@ func TestEnsureIsolatedFromSourceBackend(t *testing.T) {
|
||||
// Check further drawing doesn't cause panic.
|
||||
// This bug was fixed by 03dcd948.
|
||||
vs = quadVertices(0, 0, size/2, size/2, 1)
|
||||
img4.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img3}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
img4.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img3}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
}
|
||||
|
||||
func TestReputOnSourceBackend(t *testing.T) {
|
||||
@ -191,7 +191,7 @@ func TestReputOnSourceBackend(t *testing.T) {
|
||||
// Render onto img1. The count should not matter.
|
||||
for i := 0; i < 5; i++ {
|
||||
vs := quadVertices(size, size, 0, 0, 1)
|
||||
img1.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
img1.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
if got, want := img1.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -201,21 +201,21 @@ func TestReputOnSourceBackend(t *testing.T) {
|
||||
// Use the doubled count since img1 was on a texture atlas and became an isolated image once.
|
||||
// Then, img1 requires longer time to recover to be on a texture atlas again.
|
||||
for i := 0; i < atlas.BaseCountToPutOnSourceBackend*2; i++ {
|
||||
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting())
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
vs := quadVertices(size, size, 0, 0, 1)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
if got, want := img1.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
}
|
||||
// Finally, img1 is on a source backend.
|
||||
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting())
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
vs := quadVertices(size, size, 0, 0, 1)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
if got, want := img1.IsOnSourceBackendForTesting(), true; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting())
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
|
||||
pix = make([]byte, 4*size*size)
|
||||
ok, err := img1.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix, image.Rect(0, 0, size, size))
|
||||
@ -240,7 +240,7 @@ func TestReputOnSourceBackend(t *testing.T) {
|
||||
}
|
||||
|
||||
vs = quadVertices(size, size, 0, 0, 1)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
if got, want := img1.IsOnSourceBackendForTesting(), true; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -270,7 +270,7 @@ func TestReputOnSourceBackend(t *testing.T) {
|
||||
// Use img1 as a render target again. The count should not matter.
|
||||
for i := 0; i < 5; i++ {
|
||||
vs := quadVertices(size, size, 0, 0, 1)
|
||||
img1.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
img1.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
if got, want := img1.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -279,28 +279,28 @@ func TestReputOnSourceBackend(t *testing.T) {
|
||||
// Use img1 as a render source, but call WritePixels.
|
||||
// Now use 4x count as img1 became an isolated image again.
|
||||
for i := 0; i < atlas.BaseCountToPutOnSourceBackend*4; i++ {
|
||||
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting())
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
img1.WritePixels(make([]byte, 4*size*size), image.Rect(0, 0, size, size))
|
||||
vs := quadVertices(size, size, 0, 0, 1)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
if got, want := img1.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
}
|
||||
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting())
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
|
||||
// img1 is not on an atlas due to WritePixels.
|
||||
vs = quadVertices(size, size, 0, 0, 1)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
if got, want := img1.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
|
||||
// Use img3 as a render source. As img3 is volatile, img3 is never on an atlas.
|
||||
for i := 0; i < atlas.BaseCountToPutOnSourceBackend*2; i++ {
|
||||
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting())
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
vs := quadVertices(size, size, 0, 0, 1)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img3}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
img0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{img3}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
if got, want := img3.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -403,7 +403,7 @@ func TestWritePixelsAfterDrawTriangles(t *testing.T) {
|
||||
vs := quadVertices(w, h, 0, 0, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
dst.WritePixels(pix, image.Rect(0, 0, w, h))
|
||||
|
||||
pix = make([]byte, 4*w*h)
|
||||
@ -450,7 +450,7 @@ func TestSmallImages(t *testing.T) {
|
||||
vs := quadVertices(w, h, 0, 0, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
|
||||
pix = make([]byte, 4*w*h)
|
||||
ok, err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix, image.Rect(0, 0, w, h))
|
||||
@ -497,7 +497,7 @@ func TestLongImages(t *testing.T) {
|
||||
vs := quadVertices(w, h, 0, 0, scale)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, dstW, dstH)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
|
||||
pix = make([]byte, 4*dstW*dstH)
|
||||
ok, err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix, image.Rect(0, 0, dstW, dstH))
|
||||
@ -613,16 +613,16 @@ func TestDeallocatedAndReputOnSourceBackend(t *testing.T) {
|
||||
vs := quadVertices(size, size, 0, 0, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, size, size)
|
||||
src.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
src.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
if got, want := src.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
|
||||
// Use src as a render source.
|
||||
for i := 0; i < atlas.BaseCountToPutOnSourceBackend/2; i++ {
|
||||
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting())
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
vs := quadVertices(size, size, 0, 0, 1)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
if got, want := src.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -632,7 +632,7 @@ func TestDeallocatedAndReputOnSourceBackend(t *testing.T) {
|
||||
src.Deallocate()
|
||||
|
||||
// Confirm that PutImagesOnSourceBackendForTesting doesn't panic.
|
||||
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting())
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
}
|
||||
|
||||
// Issue #1456
|
||||
@ -656,7 +656,7 @@ func TestImageIsNotReputOnSourceBackendWithoutUsingAsSource(t *testing.T) {
|
||||
// Call DrawTriangles multiple times.
|
||||
// The number of DrawTriangles doesn't matter as long as these are called in one frame.
|
||||
for i := 0; i < 2; i++ {
|
||||
src2.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
src2.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
}
|
||||
if got, want := src2.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
@ -665,7 +665,7 @@ func TestImageIsNotReputOnSourceBackendWithoutUsingAsSource(t *testing.T) {
|
||||
// Update the count without using src2 as a rendering source.
|
||||
// This should not affect whether src2 is on an atlas or not.
|
||||
for i := 0; i < atlas.BaseCountToPutOnSourceBackend; i++ {
|
||||
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting())
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
if got, want := src2.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -673,15 +673,15 @@ func TestImageIsNotReputOnSourceBackendWithoutUsingAsSource(t *testing.T) {
|
||||
|
||||
// Update the count with using src2 as a rendering source.
|
||||
for i := 0; i < atlas.BaseCountToPutOnSourceBackend; i++ {
|
||||
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting())
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
vs := quadVertices(size, size, 0, 0, 1)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src2}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
if got, want := src2.IsOnSourceBackendForTesting(), false; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting())
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
if got, want := src2.IsOnSourceBackendForTesting(), true; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
@ -801,14 +801,14 @@ func TestDestinationCountOverflow(t *testing.T) {
|
||||
|
||||
// Use dst0 as a destination for a while.
|
||||
for i := 0; i < 31; i++ {
|
||||
dst0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
dst0.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting())
|
||||
}
|
||||
|
||||
// Use dst0 as a source for a while.
|
||||
// As dst0 is used as a destination too many times (31 is a maximum), dst0's backend should never be a source backend.
|
||||
for i := 0; i < 100; i++ {
|
||||
dst1.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{dst0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
dst1.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{dst0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting())
|
||||
if dst0.IsOnSourceBackendForTesting() {
|
||||
t.Errorf("dst0 cannot be on a source backend: %d", i)
|
||||
@ -834,17 +834,17 @@ func TestIteratingImagesToPutOnSourceBackend(t *testing.T) {
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
for _, img := range srcs {
|
||||
img.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
img.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
}
|
||||
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting())
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
|
||||
// Use srcs as sources. This will register an image to imagesToPutOnSourceBackend.
|
||||
// Check iterating the registered image works correctly.
|
||||
for i := 0; i < 100; i++ {
|
||||
for _, src := range srcs {
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
}
|
||||
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting())
|
||||
atlas.PutImagesOnSourceBackendForTesting()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,7 @@ var (
|
||||
func init() {
|
||||
var wg errgroup.Group
|
||||
wg.Go(func() error {
|
||||
ir, err := graphics.CompileShader([]byte(builtinshader.Shader(builtinshader.FilterNearest, builtinshader.AddressUnsafe, false)))
|
||||
ir, err := graphics.CompileShader([]byte(builtinshader.ShaderSource(builtinshader.FilterNearest, builtinshader.AddressUnsafe, false)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("atlas: compiling the nearest shader failed: %w", err)
|
||||
}
|
||||
@ -96,7 +96,7 @@ func init() {
|
||||
return nil
|
||||
})
|
||||
wg.Go(func() error {
|
||||
ir, err := graphics.CompileShader([]byte(builtinshader.Shader(builtinshader.FilterLinear, builtinshader.AddressUnsafe, false)))
|
||||
ir, err := graphics.CompileShader([]byte(builtinshader.ShaderSource(builtinshader.FilterLinear, builtinshader.AddressUnsafe, false)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("atlas: compiling the linear shader failed: %w", err)
|
||||
}
|
||||
@ -104,13 +104,7 @@ func init() {
|
||||
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)
|
||||
}`))
|
||||
ir, err := graphics.CompileShader([]byte(builtinshader.ClearShaderSource))
|
||||
if err != nil {
|
||||
return fmt.Errorf("atlas: compiling the clear shader failed: %w", err)
|
||||
}
|
||||
|
@ -37,12 +37,12 @@ func TestShaderFillTwice(t *testing.T) {
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
g := ui.Get().GraphicsDriverForTesting()
|
||||
s0 := atlas.NewShader(etesting.ShaderProgramFill(0xff, 0xff, 0xff, 0xff))
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s0, nil, graphicsdriver.FillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s0, nil, graphicsdriver.FillRuleFillAll)
|
||||
|
||||
// Vertices must be recreated (#1755)
|
||||
vs = quadVertices(w, h, 0, 0, 1)
|
||||
s1 := atlas.NewShader(etesting.ShaderProgramFill(0x80, 0x80, 0x80, 0xff))
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s1, nil, graphicsdriver.FillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s1, nil, graphicsdriver.FillRuleFillAll)
|
||||
|
||||
pix := make([]byte, 4*w*h)
|
||||
ok, err := dst.ReadPixels(g, pix, image.Rect(0, 0, w, h))
|
||||
@ -69,11 +69,11 @@ func TestImageDrawTwice(t *testing.T) {
|
||||
vs := quadVertices(w, h, 0, 0, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
|
||||
// Vertices must be recreated (#1755)
|
||||
vs = quadVertices(w, h, 0, 0, 1)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
|
||||
pix := make([]byte, 4*w*h)
|
||||
ok, err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix, image.Rect(0, 0, w, h))
|
||||
@ -97,7 +97,7 @@ func TestGCShader(t *testing.T) {
|
||||
vs := quadVertices(w, h, 0, 0, 1)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillRuleFillAll)
|
||||
|
||||
// Ensure other objects are GCed, as GC appends deferred functions for collected objects.
|
||||
ensureGC()
|
||||
|
@ -337,7 +337,7 @@ func (i *Image) syncPixelsIfNeeded() {
|
||||
srcs := [graphics.ShaderSrcImageCount]*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.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
i.img.DrawTriangles(srcs, vs, is, blend, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
|
||||
// TODO: Use clear if Go 1.21 is available.
|
||||
for pos := range i.dotsBuffer {
|
||||
|
@ -56,7 +56,7 @@ func TestUnsyncedPixels(t *testing.T) {
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, 16, 16)
|
||||
sr := [graphics.ShaderSrcImageCount]image.Rectangle{image.Rect(0, 0, 16, 16)}
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*buffered.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, sr, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*buffered.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, sr, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
|
||||
// Check the result is correct.
|
||||
var got [4]byte
|
||||
|
@ -123,10 +123,10 @@ func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
|
||||
|
||||
`))
|
||||
|
||||
// Shader returns the built-in shader based on the given parameters.
|
||||
// ShaderSource returns the built-in shader source based on the given parameters.
|
||||
//
|
||||
// The returned shader always uses a color matrix so far.
|
||||
func Shader(filter Filter, address Address, useColorM bool) []byte {
|
||||
func ShaderSource(filter Filter, address Address, useColorM bool) []byte {
|
||||
shadersM.Lock()
|
||||
defer shadersM.Unlock()
|
||||
|
||||
@ -165,3 +165,45 @@ func Shader(filter Filter, address Address, useColorM bool) []byte {
|
||||
shaders[filter][address][c] = b
|
||||
return b
|
||||
}
|
||||
|
||||
var ScreenShaderSource = []byte(`//kage:unit pixels
|
||||
|
||||
package main
|
||||
|
||||
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
|
||||
// Blend source colors in a square region, which size is 1/scale.
|
||||
scale := imageDstSize()/imageSrc0Size()
|
||||
pos := srcPos
|
||||
p0 := pos - 1/2.0/scale
|
||||
p1 := pos + 1/2.0/scale
|
||||
|
||||
// Texels must be in the source rect, so it is not necessary to check.
|
||||
c0 := imageSrc0UnsafeAt(p0)
|
||||
c1 := imageSrc0UnsafeAt(vec2(p1.x, p0.y))
|
||||
c2 := imageSrc0UnsafeAt(vec2(p0.x, p1.y))
|
||||
c3 := imageSrc0UnsafeAt(p1)
|
||||
|
||||
// p is the p1 value in one pixel assuming that the pixel's upper-left is (0, 0) and the lower-right is (1, 1).
|
||||
rate := clamp(fract(p1)*scale, 0, 1)
|
||||
return mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y)
|
||||
}
|
||||
`)
|
||||
|
||||
var ClearShaderSource = []byte(`//kage:unit pixels
|
||||
|
||||
package main
|
||||
|
||||
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
|
||||
return vec4(0)
|
||||
}
|
||||
`)
|
||||
|
||||
func AppendShaderSources(sources [][]byte) [][]byte {
|
||||
for filter := Filter(0); filter < FilterCount; filter++ {
|
||||
for address := Address(0); address < AddressCount; address++ {
|
||||
sources = append(sources, ShaderSource(filter, address, false), ShaderSource(filter, address, true))
|
||||
}
|
||||
}
|
||||
sources = append(sources, ScreenShaderSource, ClearShaderSource)
|
||||
return sources
|
||||
}
|
||||
|
@ -152,6 +152,14 @@ func (g *nativeGamepadsImpl) addDevice(device _IOHIDDeviceRef, gamepads *gamepad
|
||||
return
|
||||
}
|
||||
|
||||
elements := _IOHIDDeviceCopyMatchingElements(device, 0, kIOHIDOptionsTypeNone)
|
||||
// It is reportedly possible for this to fail on macOS 13 Ventura
|
||||
// if the application does not have input monitoring permissions
|
||||
if elements == 0 {
|
||||
return
|
||||
}
|
||||
defer _CFRelease(_CFTypeRef(elements))
|
||||
|
||||
name := "Unknown"
|
||||
if prop := _IOHIDDeviceGetProperty(device, _CFStringCreateWithCString(kCFAllocatorDefault, kIOHIDProductKey, kCFStringEncodingUTF8)); prop != 0 {
|
||||
var cstr [256]byte
|
||||
@ -189,9 +197,6 @@ func (g *nativeGamepadsImpl) addDevice(device _IOHIDDeviceRef, gamepads *gamepad
|
||||
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,
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ type nativeGamepadsDesktop struct {
|
||||
enumObjectsCallback uintptr
|
||||
|
||||
nativeWindow windows.HWND
|
||||
deviceChanged int32
|
||||
deviceChanged atomic.Bool
|
||||
err error
|
||||
}
|
||||
|
||||
@ -537,11 +537,11 @@ func (g *nativeGamepadsDesktop) update(gamepads *gamepads) error {
|
||||
g.origWndProc = h
|
||||
}
|
||||
|
||||
if atomic.LoadInt32(&g.deviceChanged) != 0 {
|
||||
if g.deviceChanged.Load() {
|
||||
if err := g.detectConnection(gamepads); err != nil {
|
||||
g.err = err
|
||||
}
|
||||
atomic.StoreInt32(&g.deviceChanged, 0)
|
||||
g.deviceChanged.Store(false)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -550,7 +550,7 @@ func (g *nativeGamepadsDesktop) update(gamepads *gamepads) error {
|
||||
func (g *nativeGamepadsDesktop) wndProc(hWnd uintptr, uMsg uint32, wParam, lParam uintptr) uintptr {
|
||||
switch uMsg {
|
||||
case _WM_DEVICECHANGE:
|
||||
atomic.StoreInt32(&g.deviceChanged, 1)
|
||||
g.deviceChanged.Store(true)
|
||||
}
|
||||
return _CallWindowProcW(g.origWndProc, hWnd, uMsg, wParam, lParam)
|
||||
}
|
||||
@ -794,6 +794,16 @@ func (g *nativeGamepadDesktop) hatState(hat int) int {
|
||||
if g.xinputState.Gamepad.wButtons&_XINPUT_GAMEPAD_DPAD_LEFT != 0 {
|
||||
v |= hatLeft
|
||||
}
|
||||
|
||||
// Treat invalid combinations as neither being pressed
|
||||
// while preserving what data can be preserved
|
||||
if (v&hatRight) != 0 && (v&hatLeft) != 0 {
|
||||
v &^= hatRight | hatLeft
|
||||
}
|
||||
if (v&hatUp) != 0 && (v&hatDown) != 0 {
|
||||
v &^= hatUp | hatDown
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
|
@ -140,13 +140,6 @@ func (*nativeGamepadsImpl) openGamepad(gamepads *gamepads, path string) (err err
|
||||
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
|
||||
|
@ -1,33 +0,0 @@
|
||||
// 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 !android && !ios && !js && !microsoftgdk && !nintendosdk && !playstation5
|
||||
|
||||
package gamepaddb
|
||||
|
||||
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(additionalGLFWGamepads); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
@ -25,8 +25,21 @@ import (
|
||||
//go:embed gamecontrollerdb_windows.txt
|
||||
var controllerBytes []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(controllerBytes); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := Update(additionalGLFWGamepads); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -39,40 +39,20 @@ const (
|
||||
platformIOS
|
||||
)
|
||||
|
||||
var currentPlatform platform
|
||||
|
||||
func init() {
|
||||
if runtime.GOOS == "windows" {
|
||||
currentPlatform = platformWindows
|
||||
return
|
||||
}
|
||||
|
||||
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" {
|
||||
currentPlatform = platformUnix
|
||||
return
|
||||
}
|
||||
|
||||
if runtime.GOOS == "android" {
|
||||
currentPlatform = platformAndroid
|
||||
return
|
||||
}
|
||||
|
||||
if runtime.GOOS == "ios" {
|
||||
currentPlatform = platformIOS
|
||||
return
|
||||
}
|
||||
|
||||
if runtime.GOOS == "darwin" {
|
||||
currentPlatform = platformMacOS
|
||||
return
|
||||
func currentPlatform() platform {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
return platformWindows
|
||||
case "aix", "dragonfly", "freebsd", "hurd", "illumos", "linux", "netbsd", "openbsd", "solaris":
|
||||
return platformUnix
|
||||
case "android":
|
||||
return platformAndroid
|
||||
case "ios":
|
||||
return platformIOS
|
||||
case "darwin":
|
||||
return platformMacOS
|
||||
default:
|
||||
return platformUnknown
|
||||
}
|
||||
}
|
||||
|
||||
@ -336,7 +316,7 @@ func buttonMappings(id string) map[StandardButton]mapping {
|
||||
if m, ok := gamepadButtonMappings[id]; ok {
|
||||
return m
|
||||
}
|
||||
if currentPlatform == platformAndroid {
|
||||
if currentPlatform() == platformAndroid {
|
||||
if addAndroidDefaultMappings(id) {
|
||||
return gamepadButtonMappings[id]
|
||||
}
|
||||
@ -348,7 +328,7 @@ func axisMappings(id string) map[StandardAxis]mapping {
|
||||
if m, ok := gamepadAxisMappings[id]; ok {
|
||||
return m
|
||||
}
|
||||
if currentPlatform == platformAndroid {
|
||||
if currentPlatform() == platformAndroid {
|
||||
if addAndroidDefaultMappings(id) {
|
||||
return gamepadAxisMappings[id]
|
||||
}
|
||||
@ -544,7 +524,7 @@ func Update(mappingData []byte) error {
|
||||
|
||||
for s.Scan() {
|
||||
line := s.Text()
|
||||
id, name, buttons, axes, err := parseLine(line, currentPlatform)
|
||||
id, name, buttons, axes, err := parseLine(line, currentPlatform())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
package gamepaddb_test
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
|
||||
@ -65,3 +66,17 @@ func TestUpdate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGLFWGamepadMappings(t *testing.T) {
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skip("the current platform doesn't use GLFW gamepad mappings")
|
||||
}
|
||||
|
||||
const id = "78696e70757401000000000000000000"
|
||||
if got, want := gamepaddb.HasStandardLayoutMapping(id), true; got != want {
|
||||
t.Errorf("got: %v, want: %v", got, want)
|
||||
}
|
||||
if got, want := gamepaddb.Name(id), "XInput Gamepad (GLFW)"; got != want {
|
||||
t.Errorf("got: %q, want: %q", got, want)
|
||||
}
|
||||
}
|
||||
|
@ -66,11 +66,27 @@ import (
|
||||
//go:embed gamecontrollerdb_{{.FileNameSuffix}}.txt
|
||||
var controllerBytes []byte
|
||||
|
||||
{{if .HasGLFWGamepads}}
|
||||
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,
|
||||
` + "`" + `)
|
||||
{{end}}
|
||||
|
||||
func init() {
|
||||
if err := Update(controllerBytes); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}`
|
||||
}{{if .HasGLFWGamepads}}
|
||||
if err := Update(additionalGLFWGamepads); err != nil {
|
||||
panic(err)
|
||||
}{{end}}
|
||||
}
|
||||
`
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
@ -86,12 +102,14 @@ func run() error {
|
||||
type gamePadPlatform struct {
|
||||
filenameSuffix string
|
||||
buildConstraints string
|
||||
hasGLFWGamepads bool
|
||||
}
|
||||
|
||||
platforms := map[string]gamePadPlatform{
|
||||
"Windows": {
|
||||
filenameSuffix: "windows",
|
||||
buildConstraints: "//go:build !microsoftgdk",
|
||||
hasGLFWGamepads: true,
|
||||
},
|
||||
"Mac OS X": {
|
||||
filenameSuffix: "macos_darwin",
|
||||
@ -142,11 +160,13 @@ func run() error {
|
||||
DoNotEdit string
|
||||
BuildConstraints string
|
||||
FileNameSuffix string
|
||||
HasGLFWGamepads bool
|
||||
}{
|
||||
License: license,
|
||||
DoNotEdit: doNotEdit,
|
||||
BuildConstraints: platform.buildConstraints,
|
||||
FileNameSuffix: platform.filenameSuffix,
|
||||
HasGLFWGamepads: platform.hasGLFWGamepads,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -100,6 +100,7 @@ const (
|
||||
_MAPVK_VSC_TO_VK = 1
|
||||
_MONITOR_DEFAULTTONEAREST = 0x00000002
|
||||
_MOUSE_MOVE_ABSOLUTE = 0x01
|
||||
_MOUSE_VIRTUAL_DESKTOP = 0x02
|
||||
_MSGFLT_ALLOW = 1
|
||||
_OCR_CROSS = 32515
|
||||
_OCR_HAND = 32649
|
||||
@ -121,6 +122,7 @@ const (
|
||||
_PFD_SUPPORT_OPENGL = 0x00000020
|
||||
_PFD_TYPE_RGBA = 0
|
||||
_QS_ALLEVENTS = _QS_INPUT | _QS_POSTMESSAGE | _QS_TIMER | _QS_PAINT | _QS_HOTKEY
|
||||
_QS_ALLINPUT = _QS_INPUT | _QS_POSTMESSAGE | _QS_TIMER | _QS_PAINT | _QS_HOTKEY | _QS_SENDMESSAGE
|
||||
_QS_HOTKEY = 0x0080
|
||||
_QS_INPUT = _QS_MOUSE | _QS_KEY | _QS_RAWINPUT
|
||||
_QS_KEY = 0x0001
|
||||
@ -130,6 +132,7 @@ const (
|
||||
_QS_PAINT = 0x0020
|
||||
_QS_POSTMESSAGE = 0x0008
|
||||
_QS_RAWINPUT = 0x0400
|
||||
_QS_SENDMESSAGE = 0x0040
|
||||
_QS_TIMER = 0x0010
|
||||
_RID_INPUT = 0x10000003
|
||||
_RIDEV_REMOVE = 0x00000001
|
||||
@ -139,11 +142,18 @@ const (
|
||||
_SIZE_MAXIMIZED = 2
|
||||
_SIZE_MINIMIZED = 1
|
||||
_SIZE_RESTORED = 0
|
||||
_SM_CXCURSOR = 13
|
||||
_SM_CXICON = 11
|
||||
_SM_CXSCREEN = 0
|
||||
_SM_CXSMICON = 49
|
||||
_SM_CYCAPTION = 4
|
||||
_SM_CYCURSOR = 14
|
||||
_SM_CYICON = 12
|
||||
_SM_CYSCREEN = 1
|
||||
_SM_CYSMICON = 50
|
||||
_SM_CXVIRTUALSCREEN = 78
|
||||
_SM_CYVIRTUALSCREEN = 79
|
||||
_SM_REMOTESESSION = 0x1000
|
||||
_SPI_GETFOREGROUNDLOCKTIMEOUT = 0x2000
|
||||
_SPI_GETMOUSETRAILS = 94
|
||||
_SPI_SETFOREGROUNDLOCKTIMEOUT = 0x2001
|
||||
@ -753,9 +763,11 @@ var (
|
||||
procChangeWindowMessageFilterEx = user32.NewProc("ChangeWindowMessageFilterEx")
|
||||
procClientToScreen = user32.NewProc("ClientToScreen")
|
||||
procClipCursor = user32.NewProc("ClipCursor")
|
||||
procCreateCursor = user32.NewProc("CreateCursor")
|
||||
procCreateIconIndirect = user32.NewProc("CreateIconIndirect")
|
||||
procCreateWindowExW = user32.NewProc("CreateWindowExW")
|
||||
procDefWindowProcW = user32.NewProc("DefWindowProcW")
|
||||
procDestroyCursor = user32.NewProc("DestroyCursor")
|
||||
procDestroyIcon = user32.NewProc("DestroyIcon")
|
||||
procDestroyWindow = user32.NewProc("DestroyWindow")
|
||||
procDispatchMessageW = user32.NewProc("DispatchMessageW")
|
||||
@ -913,6 +925,26 @@ func _ClipCursor(lpRect *_RECT) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func _CreateCursor(hInst _HINSTANCE, xHotSpot int32, yHotSpot int32, nWidth int32, nHeight int32, pvANDPlane, pvXORPlane []byte) (_HCURSOR, error) {
|
||||
var andPlane *byte
|
||||
if len(pvANDPlane) > 0 {
|
||||
andPlane = &pvANDPlane[0]
|
||||
}
|
||||
var xorPlane *byte
|
||||
if len(pvXORPlane) > 0 {
|
||||
xorPlane = &pvXORPlane[0]
|
||||
}
|
||||
|
||||
r, _, e := procCreateCursor.Call(uintptr(hInst), uintptr(xHotSpot), uintptr(yHotSpot), uintptr(nWidth), uintptr(nHeight), uintptr(unsafe.Pointer(andPlane)), uintptr(unsafe.Pointer(xorPlane)))
|
||||
runtime.KeepAlive(pvANDPlane)
|
||||
runtime.KeepAlive(pvXORPlane)
|
||||
|
||||
if _HCURSOR(r) == 0 && !errors.Is(e, windows.ERROR_SUCCESS) {
|
||||
return 0, fmt.Errorf("glfw: CreateCursor failed: %w", e)
|
||||
}
|
||||
return _HCURSOR(r), nil
|
||||
}
|
||||
|
||||
func _CreateBitmap(nWidth int32, nHeight int32, nPlanes uint32, nBitCount uint32, lpBits unsafe.Pointer) (_HBITMAP, error) {
|
||||
r, _, e := procCreateBitmap.Call(uintptr(nWidth), uintptr(nHeight), uintptr(nPlanes), uintptr(nBitCount), uintptr(lpBits))
|
||||
if _HBITMAP(r) == 0 {
|
||||
@ -984,6 +1016,14 @@ func _DefWindowProcW(hWnd windows.HWND, uMsg uint32, wParam _WPARAM, lParam _LPA
|
||||
return _LRESULT(r)
|
||||
}
|
||||
|
||||
func _DestroyCursor(hCursor _HCURSOR) error {
|
||||
r, _, e := procDestroyCursor.Call(uintptr(hCursor))
|
||||
if int32(r) == 0 && !errors.Is(e, windows.ERROR_SUCCESS) {
|
||||
return fmt.Errorf("glfw: DestroyCursor failed: %w", e)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func _DestroyIcon(hIcon _HICON) error {
|
||||
r, _, e := procDestroyIcon.Call(uintptr(hIcon))
|
||||
if int32(r) == 0 && !errors.Is(e, windows.ERROR_SUCCESS) {
|
||||
|
@ -227,7 +227,7 @@ static void createKeyTables(void)
|
||||
_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[0x69] = GLFW_KEY_PRINT_SCREEN;
|
||||
_glfw.ns.keycodes[0x6B] = GLFW_KEY_F14;
|
||||
_glfw.ns.keycodes[0x71] = GLFW_KEY_F15;
|
||||
_glfw.ns.keycodes[0x6A] = GLFW_KEY_F16;
|
||||
@ -420,7 +420,6 @@ static GLFWbool initializeTIS(void)
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)notification
|
||||
{
|
||||
_glfw.ns.finishedLaunching = GLFW_TRUE;
|
||||
_glfwPlatformPostEmptyEvent();
|
||||
|
||||
// In case we are unbundled, make us a proper UI application
|
||||
@ -455,9 +454,6 @@ int _glfwPlatformInit(void)
|
||||
toTarget:_glfw.ns.helper
|
||||
withObject:nil];
|
||||
|
||||
if (NSApp)
|
||||
_glfw.ns.finishedLaunching = GLFW_TRUE;
|
||||
|
||||
[NSApplication sharedApplication];
|
||||
|
||||
_glfw.ns.delegate = [[GLFWApplicationDelegate alloc] init];
|
||||
@ -509,6 +505,10 @@ int _glfwPlatformInit(void)
|
||||
_glfwInitTimerNS();
|
||||
|
||||
_glfwPollMonitorsNS();
|
||||
|
||||
if (![[NSRunningApplication currentApplication] isFinishedLaunching])
|
||||
[NSApp run];
|
||||
|
||||
return GLFW_TRUE;
|
||||
|
||||
} // autoreleasepool
|
||||
|
@ -109,7 +109,6 @@ typedef struct _GLFWlibraryNS
|
||||
{
|
||||
CGEventSourceRef eventSource;
|
||||
id delegate;
|
||||
GLFWbool finishedLaunching;
|
||||
GLFWbool cursorHidden;
|
||||
TISInputSourceRef inputSource;
|
||||
id unicodeData;
|
||||
|
@ -285,10 +285,15 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
|
||||
- (void)windowDidChangeOcclusionState:(NSNotification* )notification
|
||||
{
|
||||
if ([window->ns.object occlusionState] & NSWindowOcclusionStateVisible)
|
||||
window->ns.occluded = GLFW_FALSE;
|
||||
else
|
||||
window->ns.occluded = GLFW_TRUE;
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1090
|
||||
if ([window->ns.object respondsToSelector:@selector(occlusionState)])
|
||||
{
|
||||
if ([window->ns.object occlusionState] & NSWindowOcclusionStateVisible)
|
||||
window->ns.occluded = GLFW_FALSE;
|
||||
else
|
||||
window->ns.occluded = GLFW_TRUE;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
@ -878,9 +883,6 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
{
|
||||
@autoreleasepool {
|
||||
|
||||
if (!_glfw.ns.finishedLaunching)
|
||||
[NSApp run];
|
||||
|
||||
if (!createNativeWindow(window, wndconfig, fbconfig))
|
||||
return GLFW_FALSE;
|
||||
|
||||
@ -1239,7 +1241,7 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
||||
|
||||
if (window->monitor)
|
||||
{
|
||||
styleMask &= ~(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable);
|
||||
styleMask &= ~(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable);
|
||||
styleMask |= NSWindowStyleMaskBorderless;
|
||||
}
|
||||
else
|
||||
@ -1472,9 +1474,6 @@ void _glfwPlatformPollEvents(void)
|
||||
{
|
||||
@autoreleasepool {
|
||||
|
||||
if (!_glfw.ns.finishedLaunching)
|
||||
[NSApp run];
|
||||
|
||||
for (;;)
|
||||
{
|
||||
NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
|
||||
@ -1494,9 +1493,6 @@ void _glfwPlatformWaitEvents(void)
|
||||
{
|
||||
@autoreleasepool {
|
||||
|
||||
if (!_glfw.ns.finishedLaunching)
|
||||
[NSApp run];
|
||||
|
||||
// I wanted to pass NO to dequeue:, and rely on PollEvents to
|
||||
// dequeue and send. For reasons not at all clear to me, passing
|
||||
// NO to dequeue: causes this method never to return.
|
||||
@ -1515,9 +1511,6 @@ void _glfwPlatformWaitEventsTimeout(double timeout)
|
||||
{
|
||||
@autoreleasepool {
|
||||
|
||||
if (!_glfw.ns.finishedLaunching)
|
||||
[NSApp run];
|
||||
|
||||
NSDate* date = [NSDate dateWithTimeIntervalSinceNow:timeout];
|
||||
NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
|
||||
untilDate:date
|
||||
@ -1535,9 +1528,6 @@ void _glfwPlatformPostEmptyEvent(void)
|
||||
{
|
||||
@autoreleasepool {
|
||||
|
||||
if (!_glfw.ns.finishedLaunching)
|
||||
[NSApp run];
|
||||
|
||||
NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
|
||||
location:NSMakePoint(0, 0)
|
||||
modifierFlags:0
|
||||
@ -1616,14 +1606,15 @@ const char* _glfwPlatformGetScancodeName(int scancode)
|
||||
{
|
||||
@autoreleasepool {
|
||||
|
||||
if (scancode < 0 || scancode > 0xff ||
|
||||
_glfw.ns.keycodes[scancode] == GLFW_KEY_UNKNOWN)
|
||||
if (scancode < 0 || scancode > 0xff)
|
||||
{
|
||||
_glfwInputError(GLFW_INVALID_VALUE, "Invalid scancode %i", scancode);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const int key = _glfw.ns.keycodes[scancode];
|
||||
if (key == GLFW_KEY_UNKNOWN)
|
||||
return NULL;
|
||||
|
||||
UInt32 deadKeyState = 0;
|
||||
UniChar characters[4];
|
||||
|
@ -26,16 +26,6 @@
|
||||
//
|
||||
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)
|
||||
@ -56,6 +46,23 @@ GLFWbool _glfwIsValidContextConfig(const _GLFWctxconfig* ctxconfig)
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
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 != ctxconfig->share->context.source)
|
||||
{
|
||||
_glfwInputError(GLFW_INVALID_ENUM,
|
||||
"Context creation APIs do not match between contexts");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctxconfig->client == GLFW_OPENGL_API)
|
||||
{
|
||||
if ((ctxconfig->major < 1 || ctxconfig->minor < 0) ||
|
||||
@ -334,6 +341,8 @@ GLFWbool _glfwRefreshContextAttribs(_GLFWwindow* window,
|
||||
|
||||
previous = _glfwPlatformGetTls(&_glfw.contextSlot);
|
||||
glfwMakeContextCurrent((GLFWwindow*) window);
|
||||
if (_glfwPlatformGetTls(&_glfw.contextSlot) != window)
|
||||
return GLFW_FALSE;
|
||||
|
||||
window->context.GetIntegerv = (PFNGLGETINTEGERVPROC)
|
||||
window->context.getProcAddress("glGetIntegerv");
|
||||
|
@ -17,12 +17,6 @@ import (
|
||||
)
|
||||
|
||||
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 {
|
||||
@ -35,6 +29,15 @@ func checkValidContextConfig(ctxconfig *ctxconfig) error {
|
||||
return fmt.Errorf("glfw: invalid client API 0x%08X: %w", ctxconfig.client, InvalidEnum)
|
||||
}
|
||||
|
||||
if ctxconfig.share != nil {
|
||||
if ctxconfig.client == NoAPI || ctxconfig.share.context.client == NoAPI {
|
||||
return NoWindowContext
|
||||
}
|
||||
if ctxconfig.source != ctxconfig.share.context.source {
|
||||
return fmt.Errorf("glfw: context creation APIs do not match between contexts: %w", InvalidEnum)
|
||||
}
|
||||
}
|
||||
|
||||
if ctxconfig.client == OpenGLAPI {
|
||||
if (ctxconfig.major < 1 || ctxconfig.minor < 0) ||
|
||||
(ctxconfig.major == 1 && ctxconfig.minor > 5) ||
|
||||
@ -249,11 +252,11 @@ func (w *Window) refreshContextAttribs(ctxconfig *ctxconfig) (ferr error) {
|
||||
w.context.source = ctxconfig.source
|
||||
w.context.client = OpenGLAPI
|
||||
|
||||
p, err := _glfw.contextSlot.get()
|
||||
p1, err := _glfw.contextSlot.get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
previous := (*Window)(unsafe.Pointer(p))
|
||||
previous := (*Window)(unsafe.Pointer(p1))
|
||||
defer func() {
|
||||
err := previous.MakeContextCurrent()
|
||||
if ferr == nil {
|
||||
@ -264,6 +267,14 @@ func (w *Window) refreshContextAttribs(ctxconfig *ctxconfig) (ferr error) {
|
||||
return err
|
||||
}
|
||||
|
||||
p2, err := _glfw.contextSlot.get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (*Window)(unsafe.Pointer(p2)) != w {
|
||||
return nil
|
||||
}
|
||||
|
||||
getIntegerv := w.context.getProcAddress("glGetIntegerv")
|
||||
getString := w.context.getProcAddress("glGetString")
|
||||
if getIntegerv == 0 || getString == 0 {
|
||||
|
@ -66,13 +66,30 @@ static int getEGLConfigAttrib(EGLConfig config, int attrib)
|
||||
// Return the EGLConfig most closely matching the specified hints
|
||||
//
|
||||
static GLFWbool chooseEGLConfig(const _GLFWctxconfig* ctxconfig,
|
||||
const _GLFWfbconfig* desired,
|
||||
const _GLFWfbconfig* fbconfig,
|
||||
EGLConfig* result)
|
||||
{
|
||||
EGLConfig* nativeConfigs;
|
||||
_GLFWfbconfig* usableConfigs;
|
||||
const _GLFWfbconfig* closest;
|
||||
int i, nativeCount, usableCount;
|
||||
int i, nativeCount, usableCount, apiBit;
|
||||
GLFWbool wrongApiAvailable = GLFW_FALSE;
|
||||
|
||||
if (ctxconfig->client == GLFW_OPENGL_ES_API)
|
||||
{
|
||||
if (ctxconfig->major == 1)
|
||||
apiBit = EGL_OPENGL_ES_BIT;
|
||||
else
|
||||
apiBit = EGL_OPENGL_ES2_BIT;
|
||||
}
|
||||
else
|
||||
apiBit = EGL_OPENGL_BIT;
|
||||
|
||||
if (fbconfig->stereo)
|
||||
{
|
||||
_glfwInputError(GLFW_FORMAT_UNAVAILABLE, "EGL: Stereo rendering not supported");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
eglGetConfigs(_glfw.egl.display, NULL, 0, &nativeCount);
|
||||
if (!nativeCount)
|
||||
@ -109,7 +126,7 @@ static GLFWbool chooseEGLConfig(const _GLFWctxconfig* ctxconfig,
|
||||
if (!vi.visualid)
|
||||
continue;
|
||||
|
||||
if (desired->transparent)
|
||||
if (fbconfig->transparent)
|
||||
{
|
||||
int count;
|
||||
XVisualInfo* vis =
|
||||
@ -123,23 +140,10 @@ static GLFWbool chooseEGLConfig(const _GLFWctxconfig* ctxconfig,
|
||||
}
|
||||
#endif // _GLFW_X11
|
||||
|
||||
if (ctxconfig->client == GLFW_OPENGL_ES_API)
|
||||
if (!(getEGLConfigAttrib(n, EGL_RENDERABLE_TYPE) & apiBit))
|
||||
{
|
||||
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;
|
||||
wrongApiAvailable = GLFW_TRUE;
|
||||
continue;
|
||||
}
|
||||
|
||||
u->redBits = getEGLConfigAttrib(n, EGL_RED_SIZE);
|
||||
@ -151,15 +155,44 @@ static GLFWbool chooseEGLConfig(const _GLFWctxconfig* ctxconfig,
|
||||
u->stencilBits = getEGLConfigAttrib(n, EGL_STENCIL_SIZE);
|
||||
|
||||
u->samples = getEGLConfigAttrib(n, EGL_SAMPLES);
|
||||
u->doublebuffer = desired->doublebuffer;
|
||||
u->doublebuffer = fbconfig->doublebuffer;
|
||||
|
||||
u->handle = (uintptr_t) n;
|
||||
usableCount++;
|
||||
}
|
||||
|
||||
closest = _glfwChooseFBConfig(desired, usableConfigs, usableCount);
|
||||
closest = _glfwChooseFBConfig(fbconfig, usableConfigs, usableCount);
|
||||
if (closest)
|
||||
*result = (EGLConfig) closest->handle;
|
||||
else
|
||||
{
|
||||
if (wrongApiAvailable)
|
||||
{
|
||||
if (ctxconfig->client == GLFW_OPENGL_ES_API)
|
||||
{
|
||||
if (ctxconfig->major == 1)
|
||||
{
|
||||
_glfwInputError(GLFW_API_UNAVAILABLE,
|
||||
"EGL: Failed to find support for OpenGL ES 1.x");
|
||||
}
|
||||
else
|
||||
{
|
||||
_glfwInputError(GLFW_API_UNAVAILABLE,
|
||||
"EGL: Failed to find support for OpenGL ES 2 or later");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_glfwInputError(GLFW_API_UNAVAILABLE,
|
||||
"EGL: Failed to find support for OpenGL");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_glfwInputError(GLFW_FORMAT_UNAVAILABLE,
|
||||
"EGL: Failed to find a suitable EGLConfig");
|
||||
}
|
||||
}
|
||||
|
||||
free(nativeConfigs);
|
||||
free(usableConfigs);
|
||||
@ -231,6 +264,7 @@ static int extensionSupportedEGL(const char* extension)
|
||||
static GLFWglproc getProcAddressEGL(const char* procname)
|
||||
{
|
||||
_GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot);
|
||||
assert(window != NULL);
|
||||
|
||||
if (window->context.egl.client)
|
||||
{
|
||||
@ -454,11 +488,7 @@ GLFWbool _glfwCreateContextEGL(_GLFWwindow* window,
|
||||
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)
|
||||
{
|
||||
@ -515,18 +545,18 @@ GLFWbool _glfwCreateContextEGL(_GLFWwindow* window,
|
||||
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 (ctxconfig->noerror)
|
||||
{
|
||||
if (_glfw.egl.KHR_create_context_no_error)
|
||||
setAttrib(EGL_CONTEXT_OPENGL_NO_ERROR_KHR, GLFW_TRUE);
|
||||
}
|
||||
|
||||
if (mask)
|
||||
setAttrib(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, mask);
|
||||
|
||||
@ -578,9 +608,6 @@ GLFWbool _glfwCreateContextEGL(_GLFWwindow* window,
|
||||
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 =
|
||||
@ -640,6 +667,7 @@ GLFWbool _glfwCreateContextEGL(_GLFWwindow* window,
|
||||
#elif defined(__OpenBSD__) || defined(__NetBSD__)
|
||||
"libGL.so",
|
||||
#else
|
||||
"libOpenGL.so.0",
|
||||
"libGL.so.1",
|
||||
#endif
|
||||
NULL
|
||||
@ -702,11 +730,7 @@ GLFWbool _glfwChooseVisualEGL(const _GLFWwndconfig* wndconfig,
|
||||
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);
|
||||
|
@ -249,7 +249,7 @@ extern "C" {
|
||||
* release is made that does not contain any API changes.
|
||||
* @ingroup init
|
||||
*/
|
||||
#define GLFW_VERSION_REVISION 8
|
||||
#define GLFW_VERSION_REVISION 10
|
||||
/*! @} */
|
||||
|
||||
/*! @brief One.
|
||||
@ -296,8 +296,12 @@ extern "C" {
|
||||
#define GLFW_REPEAT 2
|
||||
/*! @} */
|
||||
|
||||
/*! @defgroup keys Keyboard keys
|
||||
* @brief Keyboard key IDs.
|
||||
/*! @ingroup input
|
||||
*/
|
||||
#define GLFW_KEY_UNKNOWN -1
|
||||
|
||||
/*! @defgroup keys Keyboard key tokens
|
||||
* @brief Keyboard key tokens.
|
||||
*
|
||||
* See [key input](@ref input_key) for how these are used.
|
||||
*
|
||||
@ -320,8 +324,6 @@ extern "C" {
|
||||
* @{
|
||||
*/
|
||||
|
||||
/* The unknown key */
|
||||
#define GLFW_KEY_UNKNOWN -1
|
||||
|
||||
/* Printable keys */
|
||||
#define GLFW_KEY_SPACE 32
|
||||
@ -1590,6 +1592,14 @@ typedef struct GLFWimage
|
||||
* bundle, if present. This can be disabled with the @ref
|
||||
* GLFW_COCOA_CHDIR_RESOURCES init hint.
|
||||
*
|
||||
* @remark @macos This function will create the main menu and dock icon for the
|
||||
* application. If GLFW finds a `MainMenu.nib` it is loaded and assumed to
|
||||
* contain a menu bar. Otherwise a minimal menu bar is created manually with
|
||||
* common commands like Hide, Quit and About. The About entry opens a minimal
|
||||
* about dialog with information from the application's bundle. The menu bar
|
||||
* and dock icon can be disabled entirely with the @ref GLFW_COCOA_MENUBAR init
|
||||
* hint.
|
||||
*
|
||||
* @remark @x11 This function will set the `LC_CTYPE` category of the
|
||||
* application locale according to the current environment if that category is
|
||||
* still "C". This is because the "C" locale breaks Unicode text input.
|
||||
@ -2410,8 +2420,8 @@ GLFWAPI void glfwWindowHintString(int hint, const char* value);
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
|
||||
* GLFW_INVALID_ENUM, @ref GLFW_INVALID_VALUE, @ref GLFW_API_UNAVAILABLE, @ref
|
||||
* GLFW_VERSION_UNAVAILABLE, @ref GLFW_FORMAT_UNAVAILABLE and @ref
|
||||
* GLFW_PLATFORM_ERROR.
|
||||
* GLFW_VERSION_UNAVAILABLE, @ref GLFW_FORMAT_UNAVAILABLE, @ref
|
||||
* GLFW_NO_WINDOW_CONTEXT and @ref GLFW_PLATFORM_ERROR.
|
||||
*
|
||||
* @remark @win32 Window creation will fail if the Microsoft GDI software
|
||||
* OpenGL implementation is the only one available.
|
||||
@ -2437,13 +2447,6 @@ GLFWAPI void glfwWindowHintString(int hint, const char* value);
|
||||
* [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/)
|
||||
* in the Mac Developer Library.
|
||||
*
|
||||
* @remark @macos The first time a window is created the menu bar is created.
|
||||
* If GLFW finds a `MainMenu.nib` it is loaded and assumed to contain a menu
|
||||
* bar. Otherwise a minimal menu bar is created manually with common commands
|
||||
* like Hide, Quit and About. The About entry opens a minimal about dialog
|
||||
* with information from the application's bundle. Menu bar creation can be
|
||||
* disabled entirely with the @ref GLFW_COCOA_MENUBAR init hint.
|
||||
*
|
||||
* @remark @macos On OS X 10.10 and later the window frame will not be rendered
|
||||
* at full resolution on Retina displays unless the
|
||||
* [GLFW_COCOA_RETINA_FRAMEBUFFER](@ref GLFW_COCOA_RETINA_FRAMEBUFFER_hint)
|
||||
@ -3360,11 +3363,15 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* window, int attrib);
|
||||
* @param[in] value `GLFW_TRUE` or `GLFW_FALSE`.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
|
||||
* GLFW_INVALID_ENUM, @ref GLFW_INVALID_VALUE and @ref GLFW_PLATFORM_ERROR.
|
||||
* GLFW_INVALID_ENUM, @ref GLFW_INVALID_VALUE, @ref GLFW_PLATFORM_ERROR.
|
||||
*
|
||||
* @remark Calling @ref glfwGetWindowAttrib will always return the latest
|
||||
* value, even if that value is ignored by the current mode of the window.
|
||||
*
|
||||
* @remark @wayland The [GLFW_FLOATING](@ref GLFW_FLOATING_attrib) window
|
||||
* attribute is not supported. Setting this will emit @ref
|
||||
* GLFW_PLATFORM_ERROR.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
* @sa @ref window_attribs
|
||||
@ -4039,8 +4046,8 @@ GLFWAPI int glfwRawMouseMotionSupported(void);
|
||||
* @param[in] scancode The scancode of the key to query.
|
||||
* @return The UTF-8 encoded, layout-specific name of the key, or `NULL`.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
||||
* GLFW_PLATFORM_ERROR.
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
|
||||
* GLFW_INVALID_VALUE, @ref GLFW_INVALID_ENUM and @ref GLFW_PLATFORM_ERROR.
|
||||
*
|
||||
* @remark The contents of the returned string may change when a keyboard
|
||||
* layout change event is received.
|
||||
@ -4062,15 +4069,18 @@ GLFWAPI const char* glfwGetKeyName(int key, int scancode);
|
||||
*
|
||||
* This function returns the platform-specific scancode of the specified key.
|
||||
*
|
||||
* If the key is `GLFW_KEY_UNKNOWN` or does not exist on the keyboard this
|
||||
* method will return `-1`.
|
||||
* If the specified [key token](@ref keys) corresponds to a physical key not
|
||||
* supported on the current platform then this method will return `-1`.
|
||||
* Calling this function with anything other than a key token will return `-1`
|
||||
* and generate a @ref GLFW_INVALID_ENUM error.
|
||||
*
|
||||
* @param[in] key Any [named key](@ref keys).
|
||||
* @return The platform-specific scancode for the key, or `-1` if an
|
||||
* [error](@ref error_handling) occurred.
|
||||
* @param[in] key Any [key token](@ref keys).
|
||||
* @return The platform-specific scancode for the key, or `-1` if the key is
|
||||
* not supported on the current platform or an [error](@ref error_handling)
|
||||
* occurred.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
|
||||
* GLFW_INVALID_ENUM and @ref GLFW_PLATFORM_ERROR.
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
||||
* GLFW_INVALID_ENUM.
|
||||
*
|
||||
* @thread_safety This function may be called from any thread.
|
||||
*
|
||||
@ -4354,10 +4364,9 @@ GLFWAPI void glfwSetCursor(GLFWwindow* window, GLFWcursor* cursor);
|
||||
* [character callback](@ref glfwSetCharCallback) instead.
|
||||
*
|
||||
* When a window loses input 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 focus
|
||||
* loss event has been processed, i.e. after the
|
||||
* [window focus callback](@ref glfwSetWindowFocusCallback) has been called.
|
||||
* events for all pressed keys with associated key tokens. You can tell these
|
||||
* events from user-generated events by the fact that the synthetic ones are
|
||||
* generated after the focus loss event has been processed, i.e. after the
|
||||
*
|
||||
* The scancode of a key is specific to that platform or sometimes even to that
|
||||
* machine. Scancodes are intended to allow users to bind keys that don't have
|
||||
@ -4708,12 +4717,15 @@ GLFWAPI const char* glfwGetClipboardString(GLFWwindow* window);
|
||||
* thread.
|
||||
*
|
||||
* This function makes the OpenGL or OpenGL ES context of the specified window
|
||||
* current on the calling thread. A context must only be made current on
|
||||
* a single thread at a time and each thread can have only a single current
|
||||
* context at a time.
|
||||
* current on the calling thread. It can also detach the current context from
|
||||
* the calling thread without making a new one current by passing in `NULL`.
|
||||
*
|
||||
* When moving a context between threads, you must make it non-current on the
|
||||
* old thread before making it current on the new one.
|
||||
* A context must only be made current on a single thread at a time and each
|
||||
* thread can have only a single current context at a time. Making a context
|
||||
* current detaches any previously current context on the calling thread.
|
||||
*
|
||||
* When moving a context between threads, you must detach it (make it
|
||||
* non-current) on the old thread before making it current on the new one.
|
||||
*
|
||||
* By default, making a context non-current implicitly forces a pipeline flush.
|
||||
* On machines that support `GL_KHR_context_flush_control`, you can control
|
||||
@ -4728,6 +4740,10 @@ GLFWAPI const char* glfwGetClipboardString(GLFWwindow* window);
|
||||
* @param[in] window The window whose context to make current, or `NULL` to
|
||||
* detach the current context.
|
||||
*
|
||||
* @remarks If the previously current context was created via a different
|
||||
* context creation API than the one passed to this function, GLFW will still
|
||||
* detach the previous one from its API before making the new one current.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
|
||||
* GLFW_NO_WINDOW_CONTEXT and @ref GLFW_PLATFORM_ERROR.
|
||||
*
|
||||
|
@ -99,7 +99,9 @@ extern "C" {
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
#include <objc/objc.h>
|
||||
#endif
|
||||
#elif defined(GLFW_EXPOSE_NATIVE_X11) || defined(GLFW_EXPOSE_NATIVE_GLX)
|
||||
#endif
|
||||
|
||||
#if defined(GLFW_EXPOSE_NATIVE_X11) || defined(GLFW_EXPOSE_NATIVE_GLX)
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/extensions/Xrandr.h>
|
||||
#endif
|
||||
|
@ -168,6 +168,7 @@ static void swapBuffersGLX(_GLFWwindow* window)
|
||||
static void swapIntervalGLX(int interval)
|
||||
{
|
||||
_GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot);
|
||||
assert(window != NULL);
|
||||
|
||||
if (_glfw.glx.EXT_swap_control)
|
||||
{
|
||||
@ -204,7 +205,10 @@ static GLFWglproc getProcAddressGLX(const char* procname)
|
||||
else if (_glfw.glx.GetProcAddressARB)
|
||||
return _glfw.glx.GetProcAddressARB((const GLubyte*) procname);
|
||||
else
|
||||
{
|
||||
// NOTE: glvnd provides GLX 1.4, so this can only happen with libGL
|
||||
return _glfw_dlsym(_glfw.glx.handle, procname);
|
||||
}
|
||||
}
|
||||
|
||||
static void destroyContextGLX(_GLFWwindow* window)
|
||||
|
@ -107,7 +107,6 @@ typedef struct _GLFWlibraryGLX
|
||||
int eventBase;
|
||||
int errorBase;
|
||||
|
||||
// dlopen handle for libGL.so.1
|
||||
void* handle;
|
||||
|
||||
// GLX 1.3 functions
|
||||
|
@ -280,6 +280,12 @@ GLFWAPI const char* glfwGetKeyName(int key, int scancode)
|
||||
|
||||
if (key != GLFW_KEY_UNKNOWN)
|
||||
{
|
||||
if (key < GLFW_KEY_SPACE || key > GLFW_KEY_LAST)
|
||||
{
|
||||
_glfwInputError(GLFW_INVALID_ENUM, "Invalid key %i", key);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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))
|
||||
@ -295,12 +301,12 @@ GLFWAPI const char* glfwGetKeyName(int key, int scancode)
|
||||
|
||||
GLFWAPI int glfwGetKeyScancode(int key)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(-1);
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(0);
|
||||
|
||||
if (key < GLFW_KEY_SPACE || key > GLFW_KEY_LAST)
|
||||
{
|
||||
_glfwInputError(GLFW_INVALID_ENUM, "Invalid key %i", key);
|
||||
return GLFW_RELEASE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return _glfwPlatformGetKeyScancode(key);
|
||||
|
@ -246,6 +246,9 @@ func GetKeyName(key Key, scancode int) (string, error) {
|
||||
}
|
||||
|
||||
if key != KeyUnknown {
|
||||
if key < KeySpace || key > KeyLast {
|
||||
return "", fmt.Errorf("glfw: invalid key %d: %w", key, InvalidEnum)
|
||||
}
|
||||
if key != KeyKPEqual && (key < KeyKP0 || key > KeyKPAdd) && (key < KeyApostrophe || key > KeyWorld2) {
|
||||
return "", nil
|
||||
}
|
||||
|
@ -57,11 +57,10 @@ static void swapIntervalNSGL(int interval)
|
||||
@autoreleasepool {
|
||||
|
||||
_GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot);
|
||||
if (window)
|
||||
{
|
||||
[window->context.nsgl.object setValues:&interval
|
||||
forParameter:NSOpenGLContextParameterSwapInterval];
|
||||
}
|
||||
assert(window != NULL);
|
||||
|
||||
[window->context.nsgl.object setValues:&interval
|
||||
forParameter:NSOpenGLContextParameterSwapInterval];
|
||||
|
||||
} // autoreleasepool
|
||||
}
|
||||
@ -138,7 +137,7 @@ GLFWbool _glfwCreateContextNSGL(_GLFWwindow* window,
|
||||
if (ctxconfig->client == GLFW_OPENGL_ES_API)
|
||||
{
|
||||
_glfwInputError(GLFW_API_UNAVAILABLE,
|
||||
"NSGL: OpenGL ES is not available on macOS");
|
||||
"NSGL: OpenGL ES is not available via NSGL");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
|
@ -5,13 +5,12 @@
|
||||
|
||||
//go:build darwin || freebsd || linux || netbsd || openbsd
|
||||
|
||||
#include "internal_unix.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "internal_unix.h"
|
||||
|
||||
|
||||
static void makeContextCurrentOSMesa(_GLFWwindow* window)
|
||||
{
|
||||
if (window)
|
||||
|
@ -19,8 +19,9 @@ func _glfwPlatformCreateTls(tls *C._GLFWtls) C.GLFWbool {
|
||||
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"))
|
||||
errstr := C.CString("POSIX: Failed to create context TLS")
|
||||
defer C.free(unsafe.Pointer(errstr))
|
||||
_glfwInputError(int32(PlatformError), errstr)
|
||||
return False
|
||||
}
|
||||
tls.posix.allocated = True
|
||||
@ -58,7 +59,9 @@ func _glfwPlatformCreateMutex(mutex *C._GLFWmutex) C.GLFWbool {
|
||||
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"))
|
||||
errstr := C.CString("POSIX: Failed to create mutex")
|
||||
defer C.free(unsafe.Pointer(errstr))
|
||||
_glfwInputError(int32(PlatformError), errstr)
|
||||
return False
|
||||
}
|
||||
mutex.posix.allocated = True
|
||||
|
@ -53,10 +53,23 @@ func (w *Window) choosePixelFormat(ctxconfig *ctxconfig, fbconfig_ *fbconfig) (i
|
||||
var nativeCount int32
|
||||
var attribs []int32
|
||||
|
||||
c, err := _DescribePixelFormat(w.context.platform.dc, 1, uint32(unsafe.Sizeof(_PIXELFORMATDESCRIPTOR{})), nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
nativeCount = c
|
||||
|
||||
if _glfw.platformContext.ARB_pixel_format {
|
||||
// NOTE: In a Parallels VM WGL_ARB_pixel_format returns fewer pixel formats than
|
||||
// DescribePixelFormat, violating the guarantees of the extension spec
|
||||
// HACK: Iterate through the minimum of both counts
|
||||
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
|
||||
var extensionCount int32
|
||||
if err := wglGetPixelFormatAttribivARB(w.context.platform.dc, 1, 0, 1, &attrib, &extensionCount); err != nil {
|
||||
return 0, fmt.Errorf("glfw: WGL: failed to retrieve pixel format attribute: %w", err)
|
||||
}
|
||||
if nativeCount > extensionCount {
|
||||
nativeCount = extensionCount
|
||||
}
|
||||
|
||||
attribs = append(attribs,
|
||||
@ -96,12 +109,6 @@ func (w *Window) choosePixelFormat(ctxconfig *ctxconfig, fbconfig_ *fbconfig) (i
|
||||
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)
|
||||
|
@ -239,6 +239,55 @@ func createHelperWindow() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func createBlankCursor() error {
|
||||
// HACK: Create a transparent cursor as using the NULL cursor breaks
|
||||
// using SetCursorPos when connected over RDP
|
||||
cursorWidth, err := _GetSystemMetrics(_SM_CXCURSOR)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cursorHeight, err := _GetSystemMetrics(_SM_CYCURSOR)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
andMask := make([]byte, cursorWidth*cursorHeight/8)
|
||||
for i := range andMask {
|
||||
andMask[i] = 0xff
|
||||
}
|
||||
xorMask := make([]byte, cursorWidth*cursorHeight/8)
|
||||
|
||||
// Cursor creation might fail, but that's fine as we get NULL in that case,
|
||||
// which serves as an acceptable fallback blank cursor (other than on RDP)
|
||||
c, _ := _CreateCursor(0, 0, 0, cursorWidth, cursorHeight, andMask, xorMask)
|
||||
_glfw.platformWindow.blankCursor = c
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initRemoteSession() error {
|
||||
if microsoftgdk.IsXbox() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if the current progress was started with Remote Desktop.
|
||||
r, err := _GetSystemMetrics(_SM_REMOTESESSION)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_glfw.platformWindow.isRemoteSession = r > 0
|
||||
|
||||
// With Remote desktop, we need to create a blank cursor because of the cursor is Set to nil
|
||||
// if cannot be moved to center in capture mode. If not Remote Desktop platformWindow.blankCursor stays nil
|
||||
// and will perform has before (normal).
|
||||
if _glfw.platformWindow.isRemoteSession {
|
||||
if err := createBlankCursor(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func platformInit() error {
|
||||
// Changing the foreground lock timeout was removed from the original code.
|
||||
// See https://github.com/glfw/glfw/commit/58b48a3a00d9c2a5ca10cc23069a71d8773cc7a4
|
||||
@ -293,6 +342,10 @@ func platformInit() error {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Some hacks are needed to support Remote Desktop...
|
||||
if err := initRemoteSession(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := pollMonitorsWin32(); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -301,6 +354,12 @@ func platformInit() error {
|
||||
}
|
||||
|
||||
func platformTerminate() error {
|
||||
if _glfw.platformWindow.blankCursor != 0 {
|
||||
if err := _DestroyCursor(_glfw.platformWindow.blankCursor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _glfw.platformWindow.deviceNotificationHandle != 0 {
|
||||
if err := _UnregisterDeviceNotification(_glfw.platformWindow.deviceNotificationHandle); err != nil {
|
||||
return err
|
||||
|
@ -66,12 +66,18 @@ type platformLibraryWindowState struct {
|
||||
scancodes [KeyLast + 1]int
|
||||
keynames [KeyLast + 1]string
|
||||
|
||||
// Where to place the cursor when re-enabled
|
||||
// restoreCursorPosX and restoreCursorPosY indicates where to place the cursor when re-enabled
|
||||
restoreCursorPosX float64
|
||||
restoreCursorPosY float64
|
||||
|
||||
// The window whose disabled cursor mode is active
|
||||
// disabledCursorWindow is the window whose disabled cursor mode is active
|
||||
disabledCursorWindow *Window
|
||||
// capturedCursorWindow is the window the cursor is captured in
|
||||
capturedCursorWindow *Window
|
||||
rawInput []byte
|
||||
mouseTrailSize uint32
|
||||
// isRemoteSession indicates if the process was started behind Remote Destop
|
||||
isRemoteSession bool
|
||||
// blankCursor is an invisible cursor, needed for special cases (see WM_INPUT handler)
|
||||
blankCursor _HCURSOR
|
||||
}
|
||||
|
@ -127,48 +127,29 @@ func createIcon(image *Image, xhot, yhot int, icon bool) (_HICON, error) {
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
func getFullWindowSize(style uint32, exStyle uint32, contentWidth, contentHeight int, dpi uint32) (fullWidth, fullHeight int, err error) {
|
||||
if microsoftgdk.IsXbox() {
|
||||
return contentWidth, contentHeight, nil
|
||||
}
|
||||
func (w *Window) applyAspectRatio(edge int, area *_RECT) error {
|
||||
var frame _RECT
|
||||
|
||||
ratio := float32(w.numer) / float32(w.denom)
|
||||
style := w.getWindowStyle()
|
||||
exStyle := w.getWindowExStyle()
|
||||
|
||||
rect := _RECT{
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: int32(contentWidth),
|
||||
bottom: int32(contentHeight),
|
||||
}
|
||||
if winver.IsWindows10AnniversaryUpdateOrGreater() {
|
||||
if err := _AdjustWindowRectExForDpi(&rect, style, false, exStyle, dpi); err != nil {
|
||||
return 0, 0, err
|
||||
if err := _AdjustWindowRectExForDpi(&frame, style, false, exStyle, _GetDpiForWindow(w.platform.handle)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := _AdjustWindowRectEx(&rect, style, false, exStyle); err != nil {
|
||||
return 0, 0, err
|
||||
if err := _AdjustWindowRectEx(&frame, style, false, exStyle); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return int(rect.right - rect.left), int(rect.bottom - rect.top), nil
|
||||
}
|
||||
|
||||
func (w *Window) applyAspectRatio(edge int, area *_RECT) error {
|
||||
ratio := float32(w.numer) / float32(w.denom)
|
||||
|
||||
var dpi uint32 = _USER_DEFAULT_SCREEN_DPI
|
||||
if winver.IsWindows10AnniversaryUpdateOrGreater() {
|
||||
dpi = _GetDpiForWindow(w.platform.handle)
|
||||
}
|
||||
|
||||
xoff, yoff, err := getFullWindowSize(w.getWindowStyle(), w.getWindowExStyle(), 0, 0, dpi)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if edge == _WMSZ_LEFT || edge == _WMSZ_BOTTOMLEFT || edge == _WMSZ_RIGHT || edge == _WMSZ_BOTTOMRIGHT {
|
||||
area.bottom = area.top + int32(yoff) + int32(float32(area.right-area.left-int32(xoff))/ratio)
|
||||
area.bottom = area.top + int32(frame.bottom-frame.top) + int32(float32(area.right-area.left-int32(frame.right-frame.left))/ratio)
|
||||
} else if edge == _WMSZ_TOPLEFT || edge == _WMSZ_TOPRIGHT {
|
||||
area.top = area.bottom - int32(yoff) - int32(float32(area.right-area.left-int32(xoff))/ratio)
|
||||
area.top = area.bottom - int32(frame.bottom-frame.top) - int32(float32(area.right-area.left-int32(frame.right-frame.left))/ratio)
|
||||
} else if edge == _WMSZ_TOP || edge == _WMSZ_BOTTOM {
|
||||
area.right = area.left + int32(xoff) + int32(float32(area.bottom-area.top-int32(yoff))*ratio)
|
||||
area.right = area.left + int32(frame.right-frame.left) + int32(float32(area.bottom-area.top-int32(frame.bottom-frame.top))*ratio)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -186,7 +167,10 @@ func (w *Window) updateCursorImage() error {
|
||||
_SetCursor(cursor)
|
||||
}
|
||||
} else {
|
||||
_SetCursor(0)
|
||||
// Connected via Remote Desktop, nil cursor will present SetCursorPos the move the cursor.
|
||||
// using a blank cursor fix that.
|
||||
// When not via Remote Desktop, platformWindow.blankCursor should be nil.
|
||||
_SetCursor(_glfw.platformWindow.blankCursor)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -214,26 +198,27 @@ func (w *Window) clientToScreen(rect _RECT) (_RECT, error) {
|
||||
return rect, nil
|
||||
}
|
||||
|
||||
func updateClipRect(window *Window) error {
|
||||
if window != nil {
|
||||
clipRect, err := _GetClientRect(window.platform.handle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clipRect, err = window.clientToScreen(clipRect)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := _ClipCursor(&clipRect); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := _ClipCursor(nil); err != nil {
|
||||
return err
|
||||
}
|
||||
func captureCursor(window *Window) error {
|
||||
clipRect, err := _GetClientRect(window.platform.handle)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clipRect, err = window.clientToScreen(clipRect)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := _ClipCursor(&clipRect); err != nil {
|
||||
return err
|
||||
}
|
||||
_glfw.platformWindow.capturedCursorWindow = window
|
||||
return nil
|
||||
}
|
||||
|
||||
func releaseCursor() error {
|
||||
if err := _ClipCursor(nil); err != nil {
|
||||
return err
|
||||
}
|
||||
_glfw.platformWindow.capturedCursorWindow = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -274,7 +259,7 @@ func (w *Window) disableCursor() error {
|
||||
if err := w.centerCursorInContentArea(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := updateClipRect(w); err != nil {
|
||||
if err := captureCursor(w); err != nil {
|
||||
return err
|
||||
}
|
||||
if w.rawMouseMotion {
|
||||
@ -292,7 +277,7 @@ func (w *Window) enableCursor() error {
|
||||
}
|
||||
}
|
||||
_glfw.platformWindow.disabledCursorWindow = nil
|
||||
if err := updateClipRect(nil); err != nil {
|
||||
if err := releaseCursor(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.platformSetCursorPos(_glfw.platformWindow.restoreCursorPosX, _glfw.platformWindow.restoreCursorPosY); err != nil {
|
||||
@ -925,8 +910,46 @@ func windowProc(hWnd windows.HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM)
|
||||
var dx, dy int
|
||||
data := (*_RAWINPUT)(unsafe.Pointer(&_glfw.platformWindow.rawInput[0]))
|
||||
if data.mouse.usFlags&_MOUSE_MOVE_ABSOLUTE != 0 {
|
||||
dx = int(data.mouse.lLastX) - window.platform.lastCursorPosX
|
||||
dy = int(data.mouse.lLastY) - window.platform.lastCursorPosY
|
||||
if _glfw.platformWindow.isRemoteSession {
|
||||
// Remote Desktop Mode
|
||||
// As per https://github.com/Microsoft/DirectXTK/commit/ef56b63f3739381e451f7a5a5bd2c9779d2a7555
|
||||
// MOUSE_MOVE_ABSOLUTE is a range from 0 through 65535, based on the screen size.
|
||||
// Apparently, absolute mode only occurs over RDP though.
|
||||
var smx int32 = _SM_CXSCREEN
|
||||
var smy int32 = _SM_CYSCREEN
|
||||
if data.mouse.usFlags&_MOUSE_VIRTUAL_DESKTOP != 0 {
|
||||
smx = _SM_CXVIRTUALSCREEN
|
||||
smy = _SM_CYVIRTUALSCREEN
|
||||
}
|
||||
|
||||
width, err := _GetSystemMetrics(smx)
|
||||
if err != nil {
|
||||
_glfw.errors = append(_glfw.errors, err)
|
||||
return 0
|
||||
}
|
||||
height, err := _GetSystemMetrics(smy)
|
||||
if err != nil {
|
||||
_glfw.errors = append(_glfw.errors, err)
|
||||
return 0
|
||||
}
|
||||
|
||||
pos := _POINT{
|
||||
x: int32(float64(data.mouse.lLastX) / 65535.0 * float64(width)),
|
||||
y: int32(float64(data.mouse.lLastY) / 65535.0 * float64(height)),
|
||||
}
|
||||
if err := _ScreenToClient(window.platform.handle, &pos); err != nil {
|
||||
_glfw.errors = append(_glfw.errors, err)
|
||||
return 0
|
||||
}
|
||||
|
||||
dx = int(pos.x) - window.platform.lastCursorPosX
|
||||
dy = int(pos.y) - window.platform.lastCursorPosY
|
||||
} else {
|
||||
// Normal mode
|
||||
// We should have the right absolute coords in data.mouse
|
||||
dx = int(data.mouse.lLastX) - window.platform.lastCursorPosX
|
||||
dy = int(data.mouse.lLastY) - window.platform.lastCursorPosY
|
||||
}
|
||||
} else {
|
||||
dx = int(data.mouse.lLastX)
|
||||
dy = int(data.mouse.lLastY)
|
||||
@ -986,8 +1009,8 @@ func windowProc(hWnd windows.HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM)
|
||||
iconified := wParam == _SIZE_MINIMIZED
|
||||
maximized := wParam == _SIZE_MAXIMIZED || (window.platform.maximized && wParam != _SIZE_RESTORED)
|
||||
|
||||
if _glfw.platformWindow.disabledCursorWindow == window {
|
||||
if err := updateClipRect(window); err != nil {
|
||||
if _glfw.platformWindow.capturedCursorWindow == window {
|
||||
if err := captureCursor(window); err != nil {
|
||||
_glfw.errors = append(_glfw.errors, err)
|
||||
return 0
|
||||
}
|
||||
@ -1032,8 +1055,8 @@ func windowProc(hWnd windows.HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM)
|
||||
return 0
|
||||
|
||||
case _WM_MOVE:
|
||||
if _glfw.platformWindow.disabledCursorWindow == window {
|
||||
if err := updateClipRect(window); err != nil {
|
||||
if _glfw.platformWindow.capturedCursorWindow == window {
|
||||
if err := captureCursor(window); err != nil {
|
||||
_glfw.errors = append(_glfw.errors, err)
|
||||
return 0
|
||||
}
|
||||
@ -1056,31 +1079,35 @@ func windowProc(hWnd windows.HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM)
|
||||
return 1
|
||||
|
||||
case _WM_GETMINMAXINFO:
|
||||
var dpi uint32 = _USER_DEFAULT_SCREEN_DPI
|
||||
var frame _RECT
|
||||
mmi := (*_MINMAXINFO)(unsafe.Pointer(lParam))
|
||||
style := window.getWindowStyle()
|
||||
exStyle := window.getWindowExStyle()
|
||||
|
||||
if window.monitor != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if winver.IsWindows10AnniversaryUpdateOrGreater() {
|
||||
dpi = _GetDpiForWindow(window.platform.handle)
|
||||
}
|
||||
|
||||
xoff, yoff, err := getFullWindowSize(window.getWindowStyle(), window.getWindowExStyle(), 0, 0, dpi)
|
||||
if err != nil {
|
||||
_glfw.errors = append(_glfw.errors, err)
|
||||
return 0
|
||||
if err := _AdjustWindowRectExForDpi(&frame, style, false, exStyle, _GetDpiForWindow(window.platform.handle)); err != nil {
|
||||
_glfw.errors = append(_glfw.errors, err)
|
||||
return 0
|
||||
}
|
||||
} else {
|
||||
if err := _AdjustWindowRectEx(&frame, style, false, exStyle); err != nil {
|
||||
_glfw.errors = append(_glfw.errors, err)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
if window.minwidth != DontCare && window.minheight != DontCare {
|
||||
mmi.ptMinTrackSize.x = int32(window.minwidth + xoff)
|
||||
mmi.ptMinTrackSize.y = int32(window.minheight + yoff)
|
||||
mmi.ptMinTrackSize.x = int32(window.minwidth) + (frame.right - frame.left)
|
||||
mmi.ptMinTrackSize.y = int32(window.minheight) + (frame.bottom - frame.top)
|
||||
}
|
||||
|
||||
if window.maxwidth != DontCare && window.maxheight != DontCare {
|
||||
mmi.ptMaxTrackSize.x = int32(window.maxwidth + xoff)
|
||||
mmi.ptMaxTrackSize.y = int32(window.maxheight + yoff)
|
||||
mmi.ptMaxTrackSize.x = int32(window.maxwidth) + (frame.right - frame.left)
|
||||
mmi.ptMaxTrackSize.y = int32(window.maxheight) + (frame.bottom - frame.top)
|
||||
}
|
||||
|
||||
if !window.decorated {
|
||||
@ -1205,7 +1232,7 @@ func (w *Window) createNativeWindow(wndconfig *wndconfig, fbconfig *fbconfig) er
|
||||
style := w.getWindowStyle()
|
||||
exStyle := w.getWindowExStyle()
|
||||
|
||||
var xpos, ypos, fullWidth, fullHeight int32
|
||||
var frameX, frameY, frameWidth, frameHeight int32
|
||||
if w.monitor != nil {
|
||||
mi, ok := _GetMonitorInfoW(w.monitor.platform.handle)
|
||||
if !ok {
|
||||
@ -1214,27 +1241,29 @@ func (w *Window) createNativeWindow(wndconfig *wndconfig, fbconfig *fbconfig) er
|
||||
// NOTE: This window placement is temporary and approximate, as the
|
||||
// correct position and size cannot be known until the monitor
|
||||
// video mode has been picked in _glfwSetVideoModeWin32
|
||||
xpos = mi.rcMonitor.left
|
||||
ypos = mi.rcMonitor.top
|
||||
fullWidth = mi.rcMonitor.right - mi.rcMonitor.left
|
||||
fullHeight = mi.rcMonitor.bottom - mi.rcMonitor.top
|
||||
frameX = mi.rcMonitor.left
|
||||
frameY = mi.rcMonitor.top
|
||||
frameWidth = mi.rcMonitor.right - mi.rcMonitor.left
|
||||
frameHeight = mi.rcMonitor.bottom - mi.rcMonitor.top
|
||||
} else {
|
||||
xpos = _CW_USEDEFAULT
|
||||
ypos = _CW_USEDEFAULT
|
||||
rect := _RECT{0, 0, int32(wndconfig.width), int32(wndconfig.height)}
|
||||
|
||||
w.platform.maximized = wndconfig.maximized
|
||||
if wndconfig.maximized {
|
||||
style |= _WS_MAXIMIZE
|
||||
}
|
||||
|
||||
w, h, err := getFullWindowSize(style, exStyle, wndconfig.width, wndconfig.height, _USER_DEFAULT_SCREEN_DPI)
|
||||
if err != nil {
|
||||
if err := _AdjustWindowRectEx(&rect, style, false, exStyle); err != nil {
|
||||
return err
|
||||
}
|
||||
fullWidth, fullHeight = int32(w), int32(h)
|
||||
|
||||
frameX = _CW_USEDEFAULT
|
||||
frameY = _CW_USEDEFAULT
|
||||
frameWidth = rect.right - rect.left
|
||||
frameHeight = rect.bottom - rect.top
|
||||
}
|
||||
|
||||
h, err := _CreateWindowExW(exStyle, _GLFW_WNDCLASSNAME, wndconfig.title, style, xpos, ypos, fullWidth, fullHeight,
|
||||
h, err := _CreateWindowExW(exStyle, _GLFW_WNDCLASSNAME, wndconfig.title, style, frameX, frameY, frameWidth, frameHeight,
|
||||
0, // No parent window
|
||||
0, // No window menu
|
||||
_glfw.platformWindow.instance, unsafe.Pointer(wndconfig))
|
||||
@ -1459,7 +1488,15 @@ func (w *Window) platformDestroyWindow() error {
|
||||
}
|
||||
|
||||
if _glfw.platformWindow.disabledCursorWindow == w {
|
||||
_glfw.platformWindow.disabledCursorWindow = nil
|
||||
if err := w.enableCursor(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _glfw.platformWindow.capturedCursorWindow == w {
|
||||
if err := releaseCursor(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if w.platform.handle != 0 {
|
||||
@ -2163,6 +2200,7 @@ func platformPollEvents() error {
|
||||
|
||||
// NOTE: Re-center the cursor only if it has moved since the last call,
|
||||
// to avoid breaking glfwWaitEvents with WM_MOUSEMOVE
|
||||
// The re-center is required in order to prevent the mouse cursor stopping at the edges of the screen.
|
||||
if window.platform.lastCursorPosX != width/2 || window.platform.lastCursorPosY != height/2 {
|
||||
if err := window.platformSetCursorPos(float64(width/2), float64(height/2)); err != nil {
|
||||
return err
|
||||
@ -2184,7 +2222,7 @@ func platformWaitEvents() error {
|
||||
}
|
||||
|
||||
func platformWaitEventsTimeout(timeout float64) error {
|
||||
if _, err := _MsgWaitForMultipleObjects(0, nil, false, uint32(timeout*1e3), _QS_ALLEVENTS); err != nil {
|
||||
if _, err := _MsgWaitForMultipleObjects(0, nil, false, uint32(timeout*1e3), _QS_ALLINPUT); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := platformPollEvents(); err != nil {
|
||||
@ -2235,20 +2273,48 @@ func (w *Window) platformSetCursorPos(xpos, ypos float64) error {
|
||||
}
|
||||
|
||||
func (w *Window) platformSetCursorMode(mode int) error {
|
||||
if mode == CursorDisabled {
|
||||
if w.platformWindowFocused() {
|
||||
if err := w.disableCursor(); err != nil {
|
||||
if w.platformWindowFocused() {
|
||||
if mode == CursorDisabled {
|
||||
xpos, ypos, err := w.platformGetCursorPos()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_glfw.platformWindow.restoreCursorPosX = xpos
|
||||
_glfw.platformWindow.restoreCursorPosY = ypos
|
||||
if err := w.centerCursorInContentArea(); err != nil {
|
||||
return err
|
||||
}
|
||||
if w.rawMouseMotion {
|
||||
if err := w.enableRawMouseMotion(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if _glfw.platformWindow.disabledCursorWindow == w {
|
||||
if w.rawMouseMotion {
|
||||
if err := w.disableRawMouseMotion(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if mode == CursorDisabled {
|
||||
if err := captureCursor(w); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := releaseCursor(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if _glfw.platformWindow.disabledCursorWindow == w {
|
||||
if err := w.enableCursor(); err != nil {
|
||||
return err
|
||||
if mode == CursorDisabled {
|
||||
_glfw.platformWindow.disabledCursorWindow = w
|
||||
} else if _glfw.platformWindow.disabledCursorWindow == w {
|
||||
_glfw.platformWindow.disabledCursorWindow = nil
|
||||
if err := w.platformSetCursorPos(_glfw.platformWindow.restoreCursorPosX, _glfw.platformWindow.restoreCursorPosY); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
in, err := w.cursorInContentArea()
|
||||
@ -2265,10 +2331,14 @@ func (w *Window) platformSetCursorMode(mode int) error {
|
||||
}
|
||||
|
||||
func platformGetScancodeName(scancode int) (string, error) {
|
||||
if scancode < 0 || scancode > (_KF_EXTENDED|0xff) || _glfw.platformWindow.keycodes[scancode] == KeyUnknown {
|
||||
if scancode < 0 || scancode > (_KF_EXTENDED|0xff) {
|
||||
return "", fmt.Errorf("glwfwin: invalid scancode %d: %w", scancode, InvalidValue)
|
||||
}
|
||||
return _glfw.platformWindow.keynames[_glfw.platformWindow.keycodes[scancode]], nil
|
||||
key := _glfw.platformWindow.keycodes[scancode]
|
||||
if key == KeyUnknown {
|
||||
return "", nil
|
||||
}
|
||||
return _glfw.platformWindow.keynames[key], nil
|
||||
}
|
||||
|
||||
func platformGetKeyScancode(key Key) int {
|
||||
|
@ -676,7 +676,7 @@ GLFWAPI float glfwGetWindowOpacity(GLFWwindow* handle)
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
assert(window != NULL);
|
||||
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(1.f);
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(0.f);
|
||||
return _glfwPlatformGetWindowOpacity(window);
|
||||
}
|
||||
|
||||
|
@ -357,6 +357,11 @@ static void updateNormalHints(_GLFWwindow* window, int width, int height)
|
||||
{
|
||||
XSizeHints* hints = XAllocSizeHints();
|
||||
|
||||
long supplied;
|
||||
XGetWMNormalHints(_glfw.x11.display, window->x11.handle, hints, &supplied);
|
||||
|
||||
hints->flags &= ~(PMinSize | PMaxSize | PAspect);
|
||||
|
||||
if (!window->monitor)
|
||||
{
|
||||
if (window->resizable)
|
||||
@ -393,9 +398,6 @@ static void updateNormalHints(_GLFWwindow* window, int width, int height)
|
||||
}
|
||||
}
|
||||
|
||||
hints->flags |= PWinGravity;
|
||||
hints->win_gravity = StaticGravity;
|
||||
|
||||
XSetWMNormalHints(_glfw.x11.display, window->x11.handle, hints);
|
||||
XFree(hints);
|
||||
|
||||
@ -561,6 +563,25 @@ static void updateCursorImage(_GLFWwindow* window)
|
||||
}
|
||||
}
|
||||
|
||||
// Grabs the cursor and confines it to the window
|
||||
//
|
||||
static void captureCursor(_GLFWwindow* window)
|
||||
{
|
||||
XGrabPointer(_glfw.x11.display, window->x11.handle, True,
|
||||
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
|
||||
GrabModeAsync, GrabModeAsync,
|
||||
window->x11.handle,
|
||||
None,
|
||||
CurrentTime);
|
||||
}
|
||||
|
||||
// Ungrabs the cursor
|
||||
//
|
||||
static void releaseCursor(void)
|
||||
{
|
||||
XUngrabPointer(_glfw.x11.display, CurrentTime);
|
||||
}
|
||||
|
||||
// Enable XI2 raw mouse motion events
|
||||
//
|
||||
static void enableRawMouseMotion(_GLFWwindow* window)
|
||||
@ -603,12 +624,7 @@ static void disableCursor(_GLFWwindow* window)
|
||||
&_glfw.x11.restoreCursorPosY);
|
||||
updateCursorImage(window);
|
||||
_glfwCenterCursorInContentArea(window);
|
||||
XGrabPointer(_glfw.x11.display, window->x11.handle, True,
|
||||
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
|
||||
GrabModeAsync, GrabModeAsync,
|
||||
window->x11.handle,
|
||||
_glfw.x11.hiddenCursorHandle,
|
||||
CurrentTime);
|
||||
captureCursor(window);
|
||||
}
|
||||
|
||||
// Exit disabled cursor mode for the specified window
|
||||
@ -619,7 +635,7 @@ static void enableCursor(_GLFWwindow* window)
|
||||
disableRawMouseMotion(window);
|
||||
|
||||
_glfw.x11.disabledCursorWindow = NULL;
|
||||
XUngrabPointer(_glfw.x11.display, CurrentTime);
|
||||
releaseCursor();
|
||||
_glfwPlatformSetCursorPos(window,
|
||||
_glfw.x11.restoreCursorPosX,
|
||||
_glfw.x11.restoreCursorPosY);
|
||||
@ -764,7 +780,28 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
|
||||
XFree(hints);
|
||||
}
|
||||
|
||||
updateNormalHints(window, width, height);
|
||||
// Set ICCCM WM_NORMAL_HINTS property
|
||||
{
|
||||
XSizeHints* hints = XAllocSizeHints();
|
||||
if (!hints)
|
||||
{
|
||||
_glfwInputError(GLFW_OUT_OF_MEMORY, "X11: Failed to allocate size hints");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
if (!wndconfig->resizable)
|
||||
{
|
||||
hints->flags |= (PMinSize | PMaxSize);
|
||||
hints->min_width = hints->max_width = width;
|
||||
hints->min_height = hints->max_height = height;
|
||||
}
|
||||
|
||||
hints->flags |= PWinGravity;
|
||||
hints->win_gravity = StaticGravity;
|
||||
|
||||
XSetWMNormalHints(_glfw.x11.display, window->x11.handle, hints);
|
||||
XFree(hints);
|
||||
}
|
||||
|
||||
// Set ICCCM WM_CLASS property
|
||||
{
|
||||
@ -1563,6 +1600,9 @@ static void processEvent(XEvent *event)
|
||||
if (event->xconfigure.width != window->x11.width ||
|
||||
event->xconfigure.height != window->x11.height)
|
||||
{
|
||||
window->x11.width = event->xconfigure.width;
|
||||
window->x11.height = event->xconfigure.height;
|
||||
|
||||
_glfwInputFramebufferSize(window,
|
||||
event->xconfigure.width,
|
||||
event->xconfigure.height);
|
||||
@ -1570,9 +1610,6 @@ static void processEvent(XEvent *event)
|
||||
_glfwInputWindowSize(window,
|
||||
event->xconfigure.width,
|
||||
event->xconfigure.height);
|
||||
|
||||
window->x11.width = event->xconfigure.width;
|
||||
window->x11.height = event->xconfigure.height;
|
||||
}
|
||||
|
||||
int xpos = event->xconfigure.x;
|
||||
@ -1600,9 +1637,10 @@ static void processEvent(XEvent *event)
|
||||
|
||||
if (xpos != window->x11.xpos || ypos != window->x11.ypos)
|
||||
{
|
||||
_glfwInputWindowPos(window, xpos, ypos);
|
||||
window->x11.xpos = xpos;
|
||||
window->x11.ypos = ypos;
|
||||
|
||||
_glfwInputWindowPos(window, xpos, ypos);
|
||||
}
|
||||
|
||||
return;
|
||||
@ -2085,7 +2123,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
void _glfwPlatformDestroyWindow(_GLFWwindow* window)
|
||||
{
|
||||
if (_glfw.x11.disabledCursorWindow == window)
|
||||
_glfw.x11.disabledCursorWindow = NULL;
|
||||
enableCursor(window);
|
||||
|
||||
if (window->monitor)
|
||||
releaseMonitor(window);
|
||||
@ -2891,16 +2929,40 @@ void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y)
|
||||
|
||||
void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
|
||||
{
|
||||
if (mode == GLFW_CURSOR_DISABLED)
|
||||
if (_glfwPlatformWindowFocused(window))
|
||||
{
|
||||
if (_glfwPlatformWindowFocused(window))
|
||||
disableCursor(window);
|
||||
}
|
||||
else if (_glfw.x11.disabledCursorWindow == window)
|
||||
enableCursor(window);
|
||||
else
|
||||
updateCursorImage(window);
|
||||
if (mode == GLFW_CURSOR_DISABLED)
|
||||
{
|
||||
_glfwPlatformGetCursorPos(window,
|
||||
&_glfw.x11.restoreCursorPosX,
|
||||
&_glfw.x11.restoreCursorPosY);
|
||||
_glfwCenterCursorInContentArea(window);
|
||||
if (window->rawMouseMotion)
|
||||
enableRawMouseMotion(window);
|
||||
}
|
||||
else if (_glfw.x11.disabledCursorWindow == window)
|
||||
{
|
||||
if (window->rawMouseMotion)
|
||||
disableRawMouseMotion(window);
|
||||
}
|
||||
|
||||
if (mode == GLFW_CURSOR_DISABLED)
|
||||
captureCursor(window);
|
||||
else
|
||||
releaseCursor();
|
||||
|
||||
if (mode == GLFW_CURSOR_DISABLED)
|
||||
_glfw.x11.disabledCursorWindow = window;
|
||||
else if (_glfw.x11.disabledCursorWindow == window)
|
||||
{
|
||||
_glfw.x11.disabledCursorWindow = NULL;
|
||||
_glfwPlatformSetCursorPos(window,
|
||||
_glfw.x11.restoreCursorPosX,
|
||||
_glfw.x11.restoreCursorPosY);
|
||||
}
|
||||
}
|
||||
|
||||
updateCursorImage(window);
|
||||
XFlush(_glfw.x11.display);
|
||||
}
|
||||
|
||||
@ -2909,14 +2971,15 @@ const char* _glfwPlatformGetScancodeName(int scancode)
|
||||
if (!_glfw.x11.xkb.available)
|
||||
return NULL;
|
||||
|
||||
if (scancode < 0 || scancode > 0xff ||
|
||||
_glfw.x11.keycodes[scancode] == GLFW_KEY_UNKNOWN)
|
||||
if (scancode < 0 || scancode > 0xff)
|
||||
{
|
||||
_glfwInputError(GLFW_INVALID_VALUE, "Invalid scancode %i", scancode);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const int key = _glfw.x11.keycodes[scancode];
|
||||
if (key == GLFW_KEY_UNKNOWN)
|
||||
return NULL;
|
||||
const KeySym keysym = XkbKeycodeToKeysym(_glfw.x11.display,
|
||||
scancode, _glfw.x11.xkb.group, 0);
|
||||
if (keysym == NoSymbol)
|
||||
|
@ -161,8 +161,8 @@ func __vertex(dstPos vec2, srcPos vec2, color vec4) (vec4, vec2, vec4) {
|
||||
return shaderSuffix, nil
|
||||
}
|
||||
|
||||
func CompileShader(src []byte) (*shaderir.Program, error) {
|
||||
unit, err := shader.ParseCompilerDirectives(src)
|
||||
func completeShaderSource(fragmentSrc []byte) ([]byte, error) {
|
||||
unit, err := shader.ParseCompilerDirectives(fragmentSrc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -172,14 +172,23 @@ func CompileShader(src []byte) (*shaderir.Program, error) {
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
buf.Write(src)
|
||||
buf.Write(fragmentSrc)
|
||||
buf.WriteString(suffix)
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func CompileShader(fragmentSrc []byte) (*shaderir.Program, error) {
|
||||
src, err := completeShaderSource(fragmentSrc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
const (
|
||||
vert = "__vertex"
|
||||
frag = "Fragment"
|
||||
)
|
||||
ir, err := shader.Compile(buf.Bytes(), vert, frag, ShaderSrcImageCount)
|
||||
ir, err := shader.Compile(src, vert, frag, ShaderSrcImageCount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -193,3 +202,11 @@ func CompileShader(src []byte) (*shaderir.Program, error) {
|
||||
|
||||
return ir, nil
|
||||
}
|
||||
|
||||
func CalcSourceHash(fragmentSrc []byte) (shaderir.SourceHash, error) {
|
||||
src, err := completeShaderSource(fragmentSrc)
|
||||
if err != nil {
|
||||
return shaderir.SourceHash{}, err
|
||||
}
|
||||
return shaderir.CalcSourceHash(src), nil
|
||||
}
|
||||
|
@ -182,7 +182,7 @@ func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dsts [graphics.S
|
||||
if c.fillRule != fillRule {
|
||||
return false
|
||||
}
|
||||
if c.fillRule != graphicsdriver.FillAll && mightOverlapDstRegions(c.vertices, vertices) {
|
||||
if c.fillRule != graphicsdriver.FillRuleFillAll && mightOverlapDstRegions(c.vertices, vertices) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
@ -43,14 +43,14 @@ const (
|
||||
maxVertexFloatCount = MaxVertexCount * graphics.VertexFloatCount
|
||||
)
|
||||
|
||||
var vsyncEnabled int32 = 1
|
||||
var vsyncEnabled atomic.Bool
|
||||
|
||||
func init() {
|
||||
vsyncEnabled.Store(true)
|
||||
}
|
||||
|
||||
func SetVsyncEnabled(enabled bool, graphicsDriver graphicsdriver.Graphics) {
|
||||
if enabled {
|
||||
atomic.StoreInt32(&vsyncEnabled, 1)
|
||||
} else {
|
||||
atomic.StoreInt32(&vsyncEnabled, 0)
|
||||
}
|
||||
vsyncEnabled.Store(enabled)
|
||||
|
||||
runOnRenderThread(func() {
|
||||
graphicsDriver.SetVsyncEnabled(enabled)
|
||||
@ -185,7 +185,7 @@ func (q *commandQueue) Flush(graphicsDriver graphicsdriver.Graphics, endFrame bo
|
||||
|
||||
var sync bool
|
||||
// Disable asynchronous rendering when vsync is on, as this causes a rendering delay (#2822).
|
||||
if endFrame && atomic.LoadInt32(&vsyncEnabled) != 0 {
|
||||
if endFrame && vsyncEnabled.Load() {
|
||||
sync = true
|
||||
}
|
||||
if !sync {
|
||||
|
@ -31,7 +31,7 @@ import (
|
||||
var nearestFilterShader *graphicscommand.Shader
|
||||
|
||||
func init() {
|
||||
ir, err := graphics.CompileShader([]byte(builtinshader.Shader(builtinshader.FilterNearest, builtinshader.AddressUnsafe, false)))
|
||||
ir, err := graphics.CompileShader([]byte(builtinshader.ShaderSource(builtinshader.FilterNearest, builtinshader.AddressUnsafe, false)))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("graphicscommand: compiling the nearest shader failed: %v", err))
|
||||
}
|
||||
@ -59,7 +59,7 @@ func TestClear(t *testing.T) {
|
||||
vs := quadVertices(w/2, h/2)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{src}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, nearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{src}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, nearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
|
||||
pix := make([]byte, 4*w*h)
|
||||
if err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), []graphicsdriver.PixelsArgs{
|
||||
@ -90,8 +90,8 @@ func TestWritePixelsPartAfterDrawTriangles(t *testing.T) {
|
||||
vs := quadVertices(w/2, h/2)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{clr}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, nearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, nearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{clr}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, nearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, nearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
bs := graphics.NewManagedBytes(4, func(bs []byte) {
|
||||
for i := range bs {
|
||||
bs[i] = 0
|
||||
@ -109,11 +109,11 @@ func TestShader(t *testing.T) {
|
||||
vs := quadVertices(w, h)
|
||||
is := graphics.QuadIndices()
|
||||
dr := image.Rect(0, 0, w, h)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{clr}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, nearestFilterShader, nil, graphicsdriver.FillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{clr}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, nearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
|
||||
|
||||
g := ui.Get().GraphicsDriverForTesting()
|
||||
s := graphicscommand.NewShader(etesting.ShaderProgramFill(0xff, 0, 0, 0xff))
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillAll)
|
||||
dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillRuleFillAll)
|
||||
|
||||
pix := make([]byte, 4*w*h)
|
||||
if err := dst.ReadPixels(g, []graphicsdriver.PixelsArgs{
|
||||
|
@ -71,7 +71,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
procD3DCompile *windows.LazyProc
|
||||
procD3DCompile *windows.LazyProc
|
||||
procD3DCreateBlob *windows.LazyProc
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -93,6 +94,7 @@ func init() {
|
||||
}
|
||||
|
||||
procD3DCompile = d3dcompiler.NewProc("D3DCompile")
|
||||
procD3DCreateBlob = d3dcompiler.NewProc("D3DCreateBlob")
|
||||
}
|
||||
|
||||
func isD3DCompilerDLLAvailable() bool {
|
||||
@ -135,6 +137,19 @@ func _D3DCompile(srcData []byte, sourceName string, pDefines []_D3D_SHADER_MACRO
|
||||
return code, nil
|
||||
}
|
||||
|
||||
func _D3DCreateBlob(size uint) (*_ID3DBlob, error) {
|
||||
if !isD3DCompilerDLLAvailable() {
|
||||
return nil, fmt.Errorf("directx: d3dcompiler_*.dll is missing in this environment")
|
||||
}
|
||||
|
||||
var blob *_ID3DBlob
|
||||
r, _, _ := procD3DCreateBlob.Call(uintptr(size), uintptr(unsafe.Pointer(&blob)))
|
||||
if uint32(r) != uint32(windows.S_OK) {
|
||||
return nil, fmt.Errorf("directx: D3DCreateBlob failed: %w", handleError(windows.Handle(uint32(r))))
|
||||
}
|
||||
return blob, nil
|
||||
}
|
||||
|
||||
type _D3D_SHADER_MACRO struct {
|
||||
Name *byte
|
||||
Definition *byte
|
||||
|
@ -482,8 +482,7 @@ func (g *graphics11) MaxImageSize() int {
|
||||
}
|
||||
|
||||
func (g *graphics11) NewShader(program *shaderir.Program) (graphicsdriver.Shader, error) {
|
||||
vs, ps, offsets := hlsl.Compile(program)
|
||||
vsh, psh, err := compileShader(vs, ps)
|
||||
vsh, psh, err := compileShader(program)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -492,7 +491,7 @@ func (g *graphics11) NewShader(program *shaderir.Program) (graphicsdriver.Shader
|
||||
graphics: g,
|
||||
id: g.genNextShaderID(),
|
||||
uniformTypes: program.Uniforms,
|
||||
uniformOffsets: offsets,
|
||||
uniformOffsets: hlsl.CalcUniformMemoryOffsets(program),
|
||||
vertexShaderBlob: vsh,
|
||||
pixelShaderBlob: psh,
|
||||
}
|
||||
@ -629,7 +628,7 @@ func (g *graphics11) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics
|
||||
}
|
||||
|
||||
g.deviceContext.RSSetViewports([]_D3D11_VIEWPORT{vp})
|
||||
if err := g.setAsRenderTargets(dsts[:targetCount], fillRule != graphicsdriver.FillAll); err != nil {
|
||||
if err := g.setAsRenderTargets(dsts[:targetCount], fillRule != graphicsdriver.FillRuleFillAll); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -639,7 +638,7 @@ func (g *graphics11) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics
|
||||
return err
|
||||
}
|
||||
|
||||
if fillRule == graphicsdriver.FillAll {
|
||||
if fillRule == graphicsdriver.FillRuleFillAll {
|
||||
bs, err := g.blendState(blend, noStencil)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -664,9 +663,9 @@ func (g *graphics11) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics
|
||||
})
|
||||
|
||||
switch fillRule {
|
||||
case graphicsdriver.FillAll:
|
||||
case graphicsdriver.FillRuleFillAll:
|
||||
g.deviceContext.DrawIndexed(uint32(dstRegion.IndexCount), uint32(indexOffset), 0)
|
||||
case graphicsdriver.NonZero:
|
||||
case graphicsdriver.FillRuleNonZero:
|
||||
bs, err := g.blendState(blend, incrementStencil)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -678,7 +677,7 @@ func (g *graphics11) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics
|
||||
}
|
||||
g.deviceContext.OMSetDepthStencilState(dss, 0)
|
||||
g.deviceContext.DrawIndexed(uint32(dstRegion.IndexCount), uint32(indexOffset), 0)
|
||||
case graphicsdriver.EvenOdd:
|
||||
case graphicsdriver.FillRuleEvenOdd:
|
||||
bs, err := g.blendState(blend, invertStencil)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -692,7 +691,7 @@ func (g *graphics11) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics
|
||||
g.deviceContext.DrawIndexed(uint32(dstRegion.IndexCount), uint32(indexOffset), 0)
|
||||
}
|
||||
|
||||
if fillRule != graphicsdriver.FillAll {
|
||||
if fillRule != graphicsdriver.FillRuleFillAll {
|
||||
bs, err := g.blendState(blend, drawWithStencil)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1093,8 +1093,7 @@ func (g *graphics12) MaxImageSize() int {
|
||||
}
|
||||
|
||||
func (g *graphics12) NewShader(program *shaderir.Program) (graphicsdriver.Shader, error) {
|
||||
vs, ps, offsets := hlsl.Compile(program)
|
||||
vsh, psh, err := compileShader(vs, ps)
|
||||
vsh, psh, err := compileShader(program)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1103,7 +1102,7 @@ func (g *graphics12) NewShader(program *shaderir.Program) (graphicsdriver.Shader
|
||||
graphics: g,
|
||||
id: g.genNextShaderID(),
|
||||
uniformTypes: program.Uniforms,
|
||||
uniformOffsets: offsets,
|
||||
uniformOffsets: hlsl.CalcUniformMemoryOffsets(program),
|
||||
vertexShader: vsh,
|
||||
pixelShader: psh,
|
||||
}
|
||||
@ -1197,7 +1196,7 @@ func (g *graphics12) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics
|
||||
|
||||
// Release constant buffers when too many ones will be created.
|
||||
numPipelines := 1
|
||||
if fillRule != graphicsdriver.FillAll {
|
||||
if fillRule != graphicsdriver.FillRuleFillAll {
|
||||
numPipelines = 2
|
||||
}
|
||||
if len(g.pipelineStates.constantBuffers[g.frameIndex])+numPipelines > numDescriptorsPerFrame {
|
||||
@ -1264,7 +1263,7 @@ func (g *graphics12) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics
|
||||
targetCount = graphics.ShaderDstImageCount
|
||||
}
|
||||
|
||||
if err := g.setAsRenderTargets(dsts[:targetCount], fillRule != graphicsdriver.FillAll); err != nil {
|
||||
if err := g.setAsRenderTargets(dsts[:targetCount], fillRule != graphicsdriver.FillRuleFillAll); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -289,7 +289,7 @@ func (p *pipelineStates) drawTriangles(device *_ID3D12Device, commandList *_ID3D
|
||||
}
|
||||
commandList.SetGraphicsRootDescriptorTable(2, sh)
|
||||
|
||||
if fillRule == graphicsdriver.FillAll {
|
||||
if fillRule == graphicsdriver.FillRuleFillAll {
|
||||
s, err := shader.pipelineState(blend, noStencil, screen)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -307,16 +307,16 @@ func (p *pipelineStates) drawTriangles(device *_ID3D12Device, commandList *_ID3D
|
||||
},
|
||||
})
|
||||
switch fillRule {
|
||||
case graphicsdriver.FillAll:
|
||||
case graphicsdriver.FillRuleFillAll:
|
||||
commandList.DrawIndexedInstanced(uint32(dstRegion.IndexCount), 1, uint32(indexOffset), 0, 0)
|
||||
case graphicsdriver.NonZero:
|
||||
case graphicsdriver.FillRuleNonZero:
|
||||
s, err := shader.pipelineState(blend, incrementStencil, screen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
commandList.SetPipelineState(s)
|
||||
commandList.DrawIndexedInstanced(uint32(dstRegion.IndexCount), 1, uint32(indexOffset), 0, 0)
|
||||
case graphicsdriver.EvenOdd:
|
||||
case graphicsdriver.FillRuleEvenOdd:
|
||||
s, err := shader.pipelineState(blend, invertStencil, screen)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -325,7 +325,7 @@ func (p *pipelineStates) drawTriangles(device *_ID3D12Device, commandList *_ID3D
|
||||
commandList.DrawIndexedInstanced(uint32(dstRegion.IndexCount), 1, uint32(indexOffset), 0, 0)
|
||||
}
|
||||
|
||||
if fillRule != graphicsdriver.FillAll {
|
||||
if fillRule != graphicsdriver.FillRuleFillAll {
|
||||
s, err := shader.pipelineState(blend, drawWithStencil, screen)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -16,18 +16,67 @@ package directx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/shaderir/hlsl"
|
||||
)
|
||||
|
||||
const (
|
||||
VertexShaderProfile = "vs_4_0"
|
||||
PixelShaderProfile = "ps_4_0"
|
||||
|
||||
VertexShaderEntryPoint = "VSMain"
|
||||
PixelShaderEntryPoint = "PSMain"
|
||||
)
|
||||
|
||||
type fxcPair struct {
|
||||
vertex []byte
|
||||
pixel []byte
|
||||
}
|
||||
|
||||
type precompiledFXCs struct {
|
||||
binaries map[shaderir.SourceHash]fxcPair
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
func (c *precompiledFXCs) put(hash shaderir.SourceHash, vertex, pixel []byte) {
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
|
||||
if c.binaries == nil {
|
||||
c.binaries = map[shaderir.SourceHash]fxcPair{}
|
||||
}
|
||||
if _, ok := c.binaries[hash]; ok {
|
||||
panic(fmt.Sprintf("directx: the precompiled library for the hash %s is already registered", hash.String()))
|
||||
}
|
||||
c.binaries[hash] = fxcPair{
|
||||
vertex: vertex,
|
||||
pixel: pixel,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *precompiledFXCs) get(hash shaderir.SourceHash) ([]byte, []byte) {
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
|
||||
f := c.binaries[hash]
|
||||
return f.vertex, f.pixel
|
||||
}
|
||||
|
||||
var thePrecompiledFXCs precompiledFXCs
|
||||
|
||||
func RegisterPrecompiledFXCs(source []byte, vertex, pixel []byte) {
|
||||
thePrecompiledFXCs.put(shaderir.CalcSourceHash(source), vertex, pixel)
|
||||
}
|
||||
|
||||
var vertexShaderCache = map[string]*_ID3DBlob{}
|
||||
|
||||
func compileShader(vs, ps string) (vsh, psh *_ID3DBlob, ferr error) {
|
||||
var flag uint32 = uint32(_D3DCOMPILE_OPTIMIZATION_LEVEL3)
|
||||
|
||||
func compileShader(program *shaderir.Program) (vsh, psh *_ID3DBlob, ferr error) {
|
||||
defer func() {
|
||||
if ferr == nil {
|
||||
return
|
||||
@ -40,6 +89,22 @@ func compileShader(vs, ps string) (vsh, psh *_ID3DBlob, ferr error) {
|
||||
}
|
||||
}()
|
||||
|
||||
if vshBin, pshBin := thePrecompiledFXCs.get(program.SourceHash); vshBin != nil && pshBin != nil {
|
||||
var err error
|
||||
if vsh, err = _D3DCreateBlob(uint(len(vshBin))); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if psh, err = _D3DCreateBlob(uint(len(pshBin))); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
copy(unsafe.Slice((*byte)(vsh.GetBufferPointer()), vsh.GetBufferSize()), vshBin)
|
||||
copy(unsafe.Slice((*byte)(psh.GetBufferPointer()), psh.GetBufferSize()), pshBin)
|
||||
return vsh, psh, nil
|
||||
}
|
||||
|
||||
vs, ps := hlsl.Compile(program)
|
||||
var flag uint32 = uint32(_D3DCOMPILE_OPTIMIZATION_LEVEL3)
|
||||
|
||||
var wg errgroup.Group
|
||||
|
||||
// Vertex shaders are likely the same. If so, reuse the same _ID3DBlob.
|
||||
@ -56,7 +121,7 @@ func compileShader(vs, ps string) (vsh, psh *_ID3DBlob, ferr error) {
|
||||
}
|
||||
}()
|
||||
wg.Go(func() error {
|
||||
v, err := _D3DCompile([]byte(vs), "shader", nil, nil, "VSMain", "vs_4_0", flag, 0)
|
||||
v, err := _D3DCompile([]byte(vs), "shader", nil, nil, VertexShaderEntryPoint, VertexShaderProfile, flag, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("directx: D3DCompile for VSMain failed, original source: %s, %w", vs, err)
|
||||
}
|
||||
@ -65,7 +130,7 @@ func compileShader(vs, ps string) (vsh, psh *_ID3DBlob, ferr error) {
|
||||
})
|
||||
}
|
||||
wg.Go(func() error {
|
||||
p, err := _D3DCompile([]byte(ps), "shader", nil, nil, "PSMain", "ps_4_0", flag, 0)
|
||||
p, err := _D3DCompile([]byte(ps), "shader", nil, nil, PixelShaderEntryPoint, PixelShaderProfile, flag, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("directx: D3DCompile for PSMain failed, original source: %s, %w", ps, err)
|
||||
}
|
||||
@ -77,7 +142,7 @@ func compileShader(vs, ps string) (vsh, psh *_ID3DBlob, ferr error) {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return
|
||||
return vsh, psh, nil
|
||||
}
|
||||
|
||||
func constantBufferSize(uniformTypes []shaderir.Type, uniformOffsets []int) int {
|
||||
|
@ -30,19 +30,19 @@ type DstRegion struct {
|
||||
type FillRule int
|
||||
|
||||
const (
|
||||
FillAll FillRule = iota
|
||||
NonZero
|
||||
EvenOdd
|
||||
FillRuleFillAll FillRule = iota
|
||||
FillRuleNonZero
|
||||
FillRuleEvenOdd
|
||||
)
|
||||
|
||||
func (f FillRule) String() string {
|
||||
switch f {
|
||||
case FillAll:
|
||||
return "FillAll"
|
||||
case NonZero:
|
||||
return "NonZero"
|
||||
case EvenOdd:
|
||||
return "EvenOdd"
|
||||
case FillRuleFillAll:
|
||||
return "FillRuleFillAll"
|
||||
case FillRuleNonZero:
|
||||
return "FillRuleNonZero"
|
||||
case FillRuleEvenOdd:
|
||||
return "FillRuleEvenOdd"
|
||||
default:
|
||||
return fmt.Sprintf("FillRule(%d)", f)
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ import (
|
||||
// Layer is an object that manages image-based content and
|
||||
// allows you to perform animations on that content.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/calayer.
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/calayer?language=objc.
|
||||
type Layer interface {
|
||||
// Layer returns the underlying CALayer * pointer.
|
||||
Layer() unsafe.Pointer
|
||||
@ -43,15 +43,15 @@ type Layer interface {
|
||||
|
||||
// MetalLayer is a Core Animation Metal layer, a layer that manages a pool of Metal drawables.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer.
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer?language=objc.
|
||||
type MetalLayer struct {
|
||||
metalLayer objc.ID
|
||||
}
|
||||
|
||||
// MakeMetalLayer creates a new Core Animation Metal layer.
|
||||
// NewMetalLayer creates a new Core Animation Metal layer.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer.
|
||||
func MakeMetalLayer() (MetalLayer, error) {
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer?language=objc.
|
||||
func NewMetalLayer() (MetalLayer, error) {
|
||||
coreGraphics, err := purego.Dlopen("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics", purego.RTLD_LAZY|purego.RTLD_GLOBAL)
|
||||
if err != nil {
|
||||
return MetalLayer{}, err
|
||||
@ -88,14 +88,14 @@ func (ml MetalLayer) Layer() unsafe.Pointer {
|
||||
|
||||
// PixelFormat returns the pixel format of textures for rendering layer content.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat.
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat?language=objc.
|
||||
func (ml MetalLayer) PixelFormat() mtl.PixelFormat {
|
||||
return mtl.PixelFormat(ml.metalLayer.Send(objc.RegisterName("pixelFormat")))
|
||||
}
|
||||
|
||||
// SetDevice sets the Metal device responsible for the layer's drawable resources.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478163-device.
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478163-device?language=objc.
|
||||
func (ml MetalLayer) SetDevice(device mtl.Device) {
|
||||
ml.metalLayer.Send(objc.RegisterName("setDevice:"), uintptr(device.Device()))
|
||||
}
|
||||
@ -111,7 +111,7 @@ func (ml MetalLayer) SetOpaque(opaque bool) {
|
||||
// PixelFormatRGBA16Float, PixelFormatBGRA10XR, or PixelFormatBGRA10XRSRGB.
|
||||
// SetPixelFormat panics for other values.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat.
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat?language=objc.
|
||||
func (ml MetalLayer) SetPixelFormat(pf mtl.PixelFormat) {
|
||||
switch pf {
|
||||
case mtl.PixelFormatRGBA8UNorm, mtl.PixelFormatRGBA8UNormSRGB, mtl.PixelFormatBGRA8UNorm, mtl.PixelFormatBGRA8UNormSRGB, mtl.PixelFormatStencil8:
|
||||
@ -126,7 +126,7 @@ func (ml MetalLayer) SetPixelFormat(pf mtl.PixelFormat) {
|
||||
//
|
||||
// It can set to 2 or 3 only. SetMaximumDrawableCount panics for other values.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2938720-maximumdrawablecount.
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2938720-maximumdrawablecount?language=objc.
|
||||
func (ml MetalLayer) SetMaximumDrawableCount(count int) {
|
||||
if count < 2 || count > 3 {
|
||||
panic(errors.New(fmt.Sprintf("failed trying to set maximumDrawableCount to %d outside of the valid range of [2, 3]", count)))
|
||||
@ -137,7 +137,7 @@ func (ml MetalLayer) SetMaximumDrawableCount(count int) {
|
||||
// SetDisplaySyncEnabled controls whether the Metal layer and its drawables
|
||||
// are synchronized with the display's refresh rate.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2887087-displaysyncenabled.
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2887087-displaysyncenabled?language=objc.
|
||||
func (ml MetalLayer) SetDisplaySyncEnabled(enabled bool) {
|
||||
if runtime.GOOS == "ios" {
|
||||
return
|
||||
@ -147,7 +147,7 @@ func (ml MetalLayer) SetDisplaySyncEnabled(enabled bool) {
|
||||
|
||||
// SetDrawableSize sets the size, in pixels, of textures for rendering layer content.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478174-drawablesize.
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478174-drawablesize?language=objc.
|
||||
func (ml MetalLayer) SetDrawableSize(width, height int) {
|
||||
// TODO: once objc supports calling functions with struct arguments replace this with just a ID.Send call
|
||||
var sel_setDrawableSize = objc.RegisterName("setDrawableSize:")
|
||||
@ -161,7 +161,7 @@ func (ml MetalLayer) SetDrawableSize(width, height int) {
|
||||
|
||||
// NextDrawable returns a Metal drawable.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478172-nextdrawable.
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478172-nextdrawable?language=objc.
|
||||
func (ml MetalLayer) NextDrawable() (MetalDrawable, error) {
|
||||
md := ml.metalLayer.Send(objc.RegisterName("nextDrawable"))
|
||||
if md == 0 {
|
||||
@ -172,28 +172,28 @@ func (ml MetalLayer) NextDrawable() (MetalDrawable, error) {
|
||||
|
||||
// PresentsWithTransaction returns a Boolean value that determines whether the layer presents its content using a Core Animation transaction.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction?language=objc
|
||||
func (ml MetalLayer) PresentsWithTransaction() bool {
|
||||
return ml.metalLayer.Send(objc.RegisterName("presentsWithTransaction")) != 0
|
||||
}
|
||||
|
||||
// SetPresentsWithTransaction sets a Boolean value that determines whether the layer presents its content using a Core Animation transaction.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction?language=objc
|
||||
func (ml MetalLayer) SetPresentsWithTransaction(presentsWithTransaction bool) {
|
||||
ml.metalLayer.Send(objc.RegisterName("setPresentsWithTransaction:"), presentsWithTransaction)
|
||||
}
|
||||
|
||||
// SetFramebufferOnly sets a Boolean value that determines whether the layer’s textures are used only for rendering.
|
||||
//
|
||||
// https://developer.apple.com/documentation/quartzcore/cametallayer/1478168-framebufferonly
|
||||
// https://developer.apple.com/documentation/quartzcore/cametallayer/1478168-framebufferonly?language=objc
|
||||
func (ml MetalLayer) SetFramebufferOnly(framebufferOnly bool) {
|
||||
ml.metalLayer.Send(objc.RegisterName("setFramebufferOnly:"), framebufferOnly)
|
||||
}
|
||||
|
||||
// MetalDrawable is a displayable resource that can be rendered or written to by Metal.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable.
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable?language=objc.
|
||||
type MetalDrawable struct {
|
||||
metalDrawable objc.ID
|
||||
}
|
||||
@ -205,14 +205,14 @@ func (md MetalDrawable) Drawable() unsafe.Pointer {
|
||||
|
||||
// Texture returns a Metal texture object representing the drawable object's content.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable/1478159-texture.
|
||||
// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable/1478159-texture?language=objc.
|
||||
func (md MetalDrawable) Texture() mtl.Texture {
|
||||
return mtl.NewTexture(md.metalDrawable.Send(objc.RegisterName("texture")))
|
||||
}
|
||||
|
||||
// Present presents the drawable onscreen as soon as possible.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldrawable/1470284-present.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldrawable/1470284-present?language=objc.
|
||||
func (md MetalDrawable) Present() {
|
||||
md.metalDrawable.Send(objc.RegisterName("present"))
|
||||
}
|
||||
|
@ -184,7 +184,7 @@ func (g *Graphics) gcBuffers() {
|
||||
|
||||
func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer {
|
||||
if g.cb == (mtl.CommandBuffer{}) {
|
||||
g.cb = g.cq.MakeCommandBuffer()
|
||||
g.cb = g.cq.CommandBuffer()
|
||||
}
|
||||
|
||||
var newBuf mtl.Buffer
|
||||
@ -197,7 +197,7 @@ func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer {
|
||||
}
|
||||
|
||||
if newBuf == (mtl.Buffer{}) {
|
||||
newBuf = g.view.getMTLDevice().MakeBufferWithLength(pow2(length), resourceStorageMode)
|
||||
newBuf = g.view.getMTLDevice().NewBufferWithLength(pow2(length), resourceStorageMode)
|
||||
}
|
||||
|
||||
if g.buffers == nil {
|
||||
@ -286,7 +286,7 @@ func (g *Graphics) NewImage(width, height int) (graphicsdriver.Image, error) {
|
||||
StorageMode: storageMode,
|
||||
Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget,
|
||||
}
|
||||
t := g.view.getMTLDevice().MakeTexture(td)
|
||||
t := g.view.getMTLDevice().NewTextureWithDescriptor(td)
|
||||
i := &Image{
|
||||
id: g.genNextImageID(),
|
||||
graphics: g,
|
||||
@ -397,7 +397,7 @@ func (g *Graphics) Initialize() error {
|
||||
}
|
||||
|
||||
// The stencil reference value is always 0 (default).
|
||||
g.dsss[noStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
|
||||
g.dsss[noStencil] = g.view.getMTLDevice().NewDepthStencilStateWithDescriptor(mtl.DepthStencilDescriptor{
|
||||
BackFaceStencil: mtl.StencilDescriptor{
|
||||
StencilFailureOperation: mtl.StencilOperationKeep,
|
||||
DepthFailureOperation: mtl.StencilOperationKeep,
|
||||
@ -411,7 +411,7 @@ func (g *Graphics) Initialize() error {
|
||||
StencilCompareFunction: mtl.CompareFunctionAlways,
|
||||
},
|
||||
})
|
||||
g.dsss[incrementStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
|
||||
g.dsss[incrementStencil] = g.view.getMTLDevice().NewDepthStencilStateWithDescriptor(mtl.DepthStencilDescriptor{
|
||||
BackFaceStencil: mtl.StencilDescriptor{
|
||||
StencilFailureOperation: mtl.StencilOperationKeep,
|
||||
DepthFailureOperation: mtl.StencilOperationKeep,
|
||||
@ -425,7 +425,7 @@ func (g *Graphics) Initialize() error {
|
||||
StencilCompareFunction: mtl.CompareFunctionAlways,
|
||||
},
|
||||
})
|
||||
g.dsss[invertStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
|
||||
g.dsss[invertStencil] = g.view.getMTLDevice().NewDepthStencilStateWithDescriptor(mtl.DepthStencilDescriptor{
|
||||
BackFaceStencil: mtl.StencilDescriptor{
|
||||
StencilFailureOperation: mtl.StencilOperationKeep,
|
||||
DepthFailureOperation: mtl.StencilOperationKeep,
|
||||
@ -439,7 +439,7 @@ func (g *Graphics) Initialize() error {
|
||||
StencilCompareFunction: mtl.CompareFunctionAlways,
|
||||
},
|
||||
})
|
||||
g.dsss[drawWithStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{
|
||||
g.dsss[drawWithStencil] = g.view.getMTLDevice().NewDepthStencilStateWithDescriptor(mtl.DepthStencilDescriptor{
|
||||
BackFaceStencil: mtl.StencilDescriptor{
|
||||
StencilFailureOperation: mtl.StencilOperationKeep,
|
||||
DepthFailureOperation: mtl.StencilOperationKeep,
|
||||
@ -454,7 +454,7 @@ func (g *Graphics) Initialize() error {
|
||||
},
|
||||
})
|
||||
|
||||
g.cq = g.view.getMTLDevice().MakeCommandQueue()
|
||||
g.cq = g.view.getMTLDevice().NewCommandQueue()
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -471,7 +471,7 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
|
||||
// When preparing a stencil buffer, flush the current render command encoder
|
||||
// to make sure the stencil buffer is cleared when loading.
|
||||
// TODO: What about clearing the stencil buffer by vertices?
|
||||
if g.lastDst != dst || g.lastFillRule != fillRule || fillRule != graphicsdriver.FillAll {
|
||||
if g.lastDst != dst || g.lastFillRule != fillRule || fillRule != graphicsdriver.FillRuleFillAll {
|
||||
g.flushRenderCommandEncoderIfNeeded()
|
||||
}
|
||||
g.lastDst = dst
|
||||
@ -497,7 +497,7 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
|
||||
rpd.ColorAttachments[0].Texture = t
|
||||
rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{}
|
||||
|
||||
if fillRule != graphicsdriver.FillAll {
|
||||
if fillRule != graphicsdriver.FillRuleFillAll {
|
||||
dst.ensureStencil()
|
||||
rpd.StencilAttachment.LoadAction = mtl.LoadActionClear
|
||||
rpd.StencilAttachment.StoreAction = mtl.StoreActionDontCare
|
||||
@ -505,9 +505,9 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
|
||||
}
|
||||
|
||||
if g.cb == (mtl.CommandBuffer{}) {
|
||||
g.cb = g.cq.MakeCommandBuffer()
|
||||
g.cb = g.cq.CommandBuffer()
|
||||
}
|
||||
g.rce = g.cb.MakeRenderCommandEncoder(rpd)
|
||||
g.rce = g.cb.RenderCommandEncoderWithDescriptor(rpd)
|
||||
}
|
||||
|
||||
w, h := dst.internalSize()
|
||||
@ -544,26 +544,26 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
|
||||
drawWithStencilRpss mtl.RenderPipelineState
|
||||
)
|
||||
switch fillRule {
|
||||
case graphicsdriver.FillAll:
|
||||
case graphicsdriver.FillRuleFillAll:
|
||||
s, err := shader.RenderPipelineState(&g.view, blend, noStencil, dst.screen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
noStencilRpss = s
|
||||
case graphicsdriver.NonZero:
|
||||
case graphicsdriver.FillRuleNonZero:
|
||||
s, err := shader.RenderPipelineState(&g.view, blend, incrementStencil, dst.screen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
incrementStencilRpss = s
|
||||
case graphicsdriver.EvenOdd:
|
||||
case graphicsdriver.FillRuleEvenOdd:
|
||||
s, err := shader.RenderPipelineState(&g.view, blend, invertStencil, dst.screen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
invertStencilRpss = s
|
||||
}
|
||||
if fillRule != graphicsdriver.FillAll {
|
||||
if fillRule != graphicsdriver.FillRuleFillAll {
|
||||
s, err := shader.RenderPipelineState(&g.view, blend, drawWithStencil, dst.screen)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -580,20 +580,20 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
|
||||
})
|
||||
|
||||
switch fillRule {
|
||||
case graphicsdriver.FillAll:
|
||||
case graphicsdriver.FillRuleFillAll:
|
||||
g.rce.SetDepthStencilState(g.dsss[noStencil])
|
||||
g.rce.SetRenderPipelineState(noStencilRpss)
|
||||
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0))))
|
||||
case graphicsdriver.NonZero:
|
||||
case graphicsdriver.FillRuleNonZero:
|
||||
g.rce.SetDepthStencilState(g.dsss[incrementStencil])
|
||||
g.rce.SetRenderPipelineState(incrementStencilRpss)
|
||||
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0))))
|
||||
case graphicsdriver.EvenOdd:
|
||||
case graphicsdriver.FillRuleEvenOdd:
|
||||
g.rce.SetDepthStencilState(g.dsss[invertStencil])
|
||||
g.rce.SetRenderPipelineState(invertStencilRpss)
|
||||
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0))))
|
||||
}
|
||||
if fillRule != graphicsdriver.FillAll {
|
||||
if fillRule != graphicsdriver.FillRuleFillAll {
|
||||
g.rce.SetDepthStencilState(g.dsss[drawWithStencil])
|
||||
g.rce.SetRenderPipelineState(drawWithStencilRpss)
|
||||
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0))))
|
||||
@ -810,8 +810,8 @@ func (i *Image) syncTexture() {
|
||||
panic("metal: command buffer must be empty at syncTexture: flushIfNeeded is not called yet?")
|
||||
}
|
||||
|
||||
cb := i.graphics.cq.MakeCommandBuffer()
|
||||
bce := cb.MakeBlitCommandEncoder()
|
||||
cb := i.graphics.cq.CommandBuffer()
|
||||
bce := cb.BlitCommandEncoder()
|
||||
bce.SynchronizeTexture(i.texture, 0, 0)
|
||||
bce.EndEncoding()
|
||||
|
||||
@ -859,7 +859,7 @@ func (i *Image) WritePixels(args []graphicsdriver.PixelsArgs) error {
|
||||
StorageMode: storageMode,
|
||||
Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget,
|
||||
}
|
||||
t := g.view.getMTLDevice().MakeTexture(td)
|
||||
t := g.view.getMTLDevice().NewTextureWithDescriptor(td)
|
||||
g.tmpTextures = append(g.tmpTextures, t)
|
||||
|
||||
for _, a := range args {
|
||||
@ -870,9 +870,9 @@ func (i *Image) WritePixels(args []graphicsdriver.PixelsArgs) error {
|
||||
}
|
||||
|
||||
if g.cb == (mtl.CommandBuffer{}) {
|
||||
g.cb = i.graphics.cq.MakeCommandBuffer()
|
||||
g.cb = i.graphics.cq.CommandBuffer()
|
||||
}
|
||||
bce := g.cb.MakeBlitCommandEncoder()
|
||||
bce := g.cb.BlitCommandEncoder()
|
||||
for _, a := range args {
|
||||
so := mtl.Origin{X: a.Region.Min.X - region.Min.X, Y: a.Region.Min.Y - region.Min.Y, Z: 0}
|
||||
ss := mtl.Size{Width: a.Region.Dx(), Height: a.Region.Dy(), Depth: 1}
|
||||
@ -914,5 +914,5 @@ func (i *Image) ensureStencil() {
|
||||
StorageMode: mtl.StorageModePrivate,
|
||||
Usage: mtl.TextureUsageRenderTarget,
|
||||
}
|
||||
i.stencil = i.graphics.view.getMTLDevice().MakeTexture(td)
|
||||
i.stencil = i.graphics.view.getMTLDevice().NewTextureWithDescriptor(td)
|
||||
}
|
||||
|
39
internal/graphicsdriver/metal/mtl/dispatch_darwin.go
Normal file
39
internal/graphicsdriver/metal/mtl/dispatch_darwin.go
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2024 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 mtl
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/ebitengine/purego"
|
||||
)
|
||||
|
||||
var libSystem uintptr
|
||||
|
||||
var (
|
||||
dispatchDataCreate func(buffer unsafe.Pointer, size uint, queue uintptr, destructor uintptr) uintptr
|
||||
dispatchRelease func(obj uintptr)
|
||||
)
|
||||
|
||||
func init() {
|
||||
lib, err := purego.Dlopen("/usr/lib/libSystem.B.dylib", purego.RTLD_LAZY|purego.RTLD_GLOBAL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
libSystem = lib
|
||||
|
||||
purego.RegisterLibFunc(&dispatchDataCreate, libSystem, "dispatch_data_create")
|
||||
purego.RegisterLibFunc(&dispatchRelease, libSystem, "dispatch_release")
|
||||
}
|
@ -112,15 +112,15 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) {
|
||||
return in.color;
|
||||
}
|
||||
`
|
||||
lib, err := device.MakeLibrary(source, mtl.CompileOptions{})
|
||||
lib, err := device.NewLibraryWithSource(source, mtl.CompileOptions{})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
vs, err := lib.MakeFunction("VertexShader")
|
||||
vs, err := lib.NewFunctionWithName("VertexShader")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
fs, err := lib.MakeFunction("FragmentShader")
|
||||
fs, err := lib.NewFunctionWithName("FragmentShader")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
@ -129,7 +129,7 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) {
|
||||
rpld.FragmentFunction = fs
|
||||
rpld.ColorAttachments[0].PixelFormat = mtl.PixelFormatRGBA8UNorm
|
||||
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskAll
|
||||
rps, err := device.MakeRenderPipelineState(rpld)
|
||||
rps, err := device.NewRenderPipelineStateWithDescriptor(rpld)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
@ -144,7 +144,7 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) {
|
||||
{f32.Vec4{-0.75, -0.75, 0, 1}, f32.Vec4{1, 1, 1, 1}},
|
||||
{f32.Vec4{+0.75, -0.75, 0, 1}, f32.Vec4{0, 0, 0, 1}},
|
||||
}
|
||||
vertexBuffer := device.MakeBufferWithBytes(unsafe.Pointer(&vertexData[0]), unsafe.Sizeof(vertexData), mtl.ResourceStorageModeManaged)
|
||||
vertexBuffer := device.NewBufferWithBytes(unsafe.Pointer(&vertexData[0]), unsafe.Sizeof(vertexData), mtl.ResourceStorageModeManaged)
|
||||
|
||||
// Create an output texture to render into.
|
||||
td := mtl.TextureDescriptor{
|
||||
@ -154,10 +154,10 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) {
|
||||
Height: 20,
|
||||
StorageMode: mtl.StorageModeManaged,
|
||||
}
|
||||
texture := device.MakeTexture(td)
|
||||
texture := device.NewTextureWithDescriptor(td)
|
||||
|
||||
cq := device.MakeCommandQueue()
|
||||
cb := cq.MakeCommandBuffer()
|
||||
cq := device.NewCommandQueue()
|
||||
cb := cq.CommandBuffer()
|
||||
|
||||
// Encode all render commands.
|
||||
var rpd mtl.RenderPassDescriptor
|
||||
@ -165,14 +165,14 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) {
|
||||
rpd.ColorAttachments[0].StoreAction = mtl.StoreActionStore
|
||||
rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{Red: 0, Green: 0, Blue: 0, Alpha: 1}
|
||||
rpd.ColorAttachments[0].Texture = texture
|
||||
rce := cb.MakeRenderCommandEncoder(rpd)
|
||||
rce := cb.RenderCommandEncoderWithDescriptor(rpd)
|
||||
rce.SetRenderPipelineState(rps)
|
||||
rce.SetVertexBuffer(vertexBuffer, 0, 0)
|
||||
rce.DrawPrimitives(mtl.PrimitiveTypeTriangle, 0, 3)
|
||||
rce.EndEncoding()
|
||||
|
||||
// Encode all blit commands.
|
||||
bce := cb.MakeBlitCommandEncoder()
|
||||
bce := cb.BlitCommandEncoder()
|
||||
bce.Synchronize(texture)
|
||||
bce.EndEncoding()
|
||||
|
||||
|
@ -36,7 +36,7 @@ import (
|
||||
|
||||
// GPUFamily represents the functionality for families of GPUs.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlgpufamily
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlgpufamily?language=objc.
|
||||
type GPUFamily int
|
||||
|
||||
const (
|
||||
@ -54,7 +54,7 @@ const (
|
||||
|
||||
// FeatureSet defines a specific platform, hardware, and software configuration.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlfeatureset.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlfeatureset?language=objc.
|
||||
type FeatureSet uint16
|
||||
|
||||
const (
|
||||
@ -92,7 +92,7 @@ const (
|
||||
// TextureType defines The dimension of each image, including whether multiple images are arranged into an array or
|
||||
// a cube.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtltexturetype
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtltexturetype?language=objc.
|
||||
type TextureType uint16
|
||||
|
||||
const (
|
||||
@ -102,7 +102,7 @@ const (
|
||||
// PixelFormat defines data formats that describe the organization
|
||||
// and characteristics of individual pixels in a texture.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlpixelformat.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlpixelformat?language=objc.
|
||||
type PixelFormat uint16
|
||||
|
||||
// The data formats that describe the organization and characteristics
|
||||
@ -117,7 +117,7 @@ const (
|
||||
|
||||
// PrimitiveType defines geometric primitive types for drawing commands.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlprimitivetype.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlprimitivetype?language=objc.
|
||||
type PrimitiveType uint8
|
||||
|
||||
// Geometric primitive types for drawing commands.
|
||||
@ -132,7 +132,7 @@ const (
|
||||
// LoadAction defines actions performed at the start of a rendering pass
|
||||
// for a render command encoder.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlloadaction.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlloadaction?language=objc.
|
||||
type LoadAction uint8
|
||||
|
||||
// Actions performed at the start of a rendering pass for a render command encoder.
|
||||
@ -145,7 +145,7 @@ const (
|
||||
// StoreAction defines actions performed at the end of a rendering pass
|
||||
// for a render command encoder.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlstoreaction.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlstoreaction?language=objc.
|
||||
type StoreAction uint8
|
||||
|
||||
// Actions performed at the end of a rendering pass for a render command encoder.
|
||||
@ -160,7 +160,7 @@ const (
|
||||
|
||||
// StorageMode defines the memory location and access permissions of a resource.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode?language=objc.
|
||||
type StorageMode uint8
|
||||
|
||||
const (
|
||||
@ -189,7 +189,7 @@ const (
|
||||
// ResourceOptions defines optional arguments used to create
|
||||
// and influence behavior of buffer and texture objects.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlresourceoptions.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlresourceoptions?language=objc.
|
||||
type ResourceOptions uint16
|
||||
|
||||
const (
|
||||
@ -237,7 +237,7 @@ const (
|
||||
|
||||
// CPUCacheMode is the CPU cache mode that defines the CPU mapping of a resource.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcpucachemode.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcpucachemode?language=objc.
|
||||
type CPUCacheMode uint8
|
||||
|
||||
const (
|
||||
@ -252,7 +252,7 @@ const (
|
||||
|
||||
// IndexType is the index type for an index buffer that references vertices of geometric primitives.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode?language=objc
|
||||
type IndexType uint8
|
||||
|
||||
const (
|
||||
@ -358,7 +358,7 @@ const (
|
||||
// Resource represents a memory allocation for storing specialized data
|
||||
// that is accessible to the GPU.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlresource.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlresource?language=objc.
|
||||
type Resource interface {
|
||||
// resource returns the underlying id<MTLResource> pointer.
|
||||
resource() unsafe.Pointer
|
||||
@ -366,7 +366,7 @@ type Resource interface {
|
||||
|
||||
// RenderPipelineDescriptor configures new RenderPipelineState objects.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinedescriptor.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinedescriptor?language=objc.
|
||||
type RenderPipelineDescriptor struct {
|
||||
// VertexFunction is a programmable function that processes individual vertices in a rendering pass.
|
||||
VertexFunction Function
|
||||
@ -384,7 +384,7 @@ type RenderPipelineDescriptor struct {
|
||||
// RenderPipelineColorAttachmentDescriptor describes a color render target that specifies
|
||||
// the color configuration and color operations associated with a render pipeline.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinecolorattachmentdescriptor.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinecolorattachmentdescriptor?language=objc.
|
||||
type RenderPipelineColorAttachmentDescriptor struct {
|
||||
// PixelFormat is the pixel format of the color attachment's texture.
|
||||
PixelFormat PixelFormat
|
||||
@ -404,7 +404,7 @@ type RenderPipelineColorAttachmentDescriptor struct {
|
||||
// RenderPassDescriptor describes a group of render targets that serve as
|
||||
// the output destination for pixels generated by a render pass.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassdescriptor.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassdescriptor?language=objc.
|
||||
type RenderPassDescriptor struct {
|
||||
// ColorAttachments is array of state information for attachments that store color data.
|
||||
ColorAttachments [1]RenderPassColorAttachmentDescriptor
|
||||
@ -416,7 +416,7 @@ type RenderPassDescriptor struct {
|
||||
// RenderPassColorAttachmentDescriptor describes a color render target that serves
|
||||
// as the output destination for color pixels generated by a render pass.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpasscolorattachmentdescriptor.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpasscolorattachmentdescriptor?language=objc.
|
||||
type RenderPassColorAttachmentDescriptor struct {
|
||||
RenderPassAttachmentDescriptor
|
||||
ClearColor ClearColor
|
||||
@ -425,7 +425,7 @@ type RenderPassColorAttachmentDescriptor struct {
|
||||
// RenderPassStencilAttachment describes a stencil render target that serves as the output
|
||||
// destination for stencil pixels generated by a render pass.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassstencilattachmentdescriptor
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassstencilattachmentdescriptor?language=objc.
|
||||
type RenderPassStencilAttachment struct {
|
||||
RenderPassAttachmentDescriptor
|
||||
}
|
||||
@ -433,7 +433,7 @@ type RenderPassStencilAttachment struct {
|
||||
// RenderPassAttachmentDescriptor describes a render target that serves
|
||||
// as the output destination for pixels generated by a render pass.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassattachmentdescriptor.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassattachmentdescriptor?language=objc.
|
||||
type RenderPassAttachmentDescriptor struct {
|
||||
LoadAction LoadAction
|
||||
StoreAction StoreAction
|
||||
@ -442,14 +442,14 @@ type RenderPassAttachmentDescriptor struct {
|
||||
|
||||
// ClearColor is an RGBA value used for a color pixel.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlclearcolor.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlclearcolor?language=objc.
|
||||
type ClearColor struct {
|
||||
Red, Green, Blue, Alpha float64
|
||||
}
|
||||
|
||||
// TextureDescriptor configures new Texture objects.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtltexturedescriptor.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtltexturedescriptor?language=objc.
|
||||
type TextureDescriptor struct {
|
||||
TextureType TextureType
|
||||
PixelFormat PixelFormat
|
||||
@ -462,7 +462,7 @@ type TextureDescriptor struct {
|
||||
// Device is abstract representation of the GPU that
|
||||
// serves as the primary interface for a Metal app.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice?language=objc.
|
||||
type Device struct {
|
||||
device objc.ID
|
||||
|
||||
@ -493,6 +493,7 @@ var (
|
||||
sel_supportsFeatureSet = objc.RegisterName("supportsFeatureSet:")
|
||||
sel_newCommandQueue = objc.RegisterName("newCommandQueue")
|
||||
sel_newLibraryWithSource_options_error = objc.RegisterName("newLibraryWithSource:options:error:")
|
||||
sel_newLibraryWithData_error = objc.RegisterName("newLibraryWithData:error:")
|
||||
sel_release = objc.RegisterName("release")
|
||||
sel_retain = objc.RegisterName("retain")
|
||||
sel_new = objc.RegisterName("new")
|
||||
@ -567,7 +568,7 @@ var (
|
||||
|
||||
// CreateSystemDefaultDevice returns the preferred system default Metal device.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/1433401-mtlcreatesystemdefaultdevice.
|
||||
// Reference: https://developer.apple.com/documentation/metal/1433401-mtlcreatesystemdefaultdevice?language=objc.
|
||||
func CreateSystemDefaultDevice() (Device, error) {
|
||||
metal, err := purego.Dlopen("/System/Library/Frameworks/Metal.framework/Metal", purego.RTLD_LAZY|purego.RTLD_GLOBAL)
|
||||
if err != nil {
|
||||
@ -607,37 +608,36 @@ func (d Device) Device() unsafe.Pointer { return *(*unsafe.Pointer)(unsafe.Point
|
||||
|
||||
// RespondsToSelector returns a Boolean value that indicates whether the receiver implements or inherits a method that can respond to a specified message.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418583-respondstoselector
|
||||
// Reference: https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418583-respondstoselector?language=objc.
|
||||
func (d Device) RespondsToSelector(sel objc.SEL) bool {
|
||||
return d.device.Send(sel_respondsToSelector, sel) != 0
|
||||
}
|
||||
|
||||
// SupportsFamily returns a Boolean value that indicates whether the GPU device supports the feature set of a specific GPU family.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/3143473-supportsfamily
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/3143473-supportsfamily?language=objc.
|
||||
func (d Device) SupportsFamily(gpuFamily GPUFamily) bool {
|
||||
return d.device.Send(sel_supportsFamily, uintptr(gpuFamily)) != 0
|
||||
}
|
||||
|
||||
// SupportsFeatureSet reports whether device d supports feature set fs.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433418-supportsfeatureset.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433418-supportsfeatureset?language=objc.
|
||||
func (d Device) SupportsFeatureSet(fs FeatureSet) bool {
|
||||
return d.device.Send(sel_supportsFeatureSet, uintptr(fs)) != 0
|
||||
}
|
||||
|
||||
// MakeCommandQueue creates a serial command submission queue.
|
||||
// NewCommandQueue creates a queue you use to submit rendering and computation commands to a GPU.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433388-makecommandqueue.
|
||||
func (d Device) MakeCommandQueue() CommandQueue {
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433388-newcommandqueue?language=objc.
|
||||
func (d Device) NewCommandQueue() CommandQueue {
|
||||
return CommandQueue{d.device.Send(sel_newCommandQueue)}
|
||||
}
|
||||
|
||||
// MakeLibrary creates a new library that contains
|
||||
// the functions stored in the specified source string.
|
||||
// NewLibraryWithSource synchronously creates a Metal library instance by compiling the functions in a source string.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433431-makelibrary.
|
||||
func (d Device) MakeLibrary(source string, opt CompileOptions) (Library, error) {
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433431-newlibrarywithsource?language=objc.
|
||||
func (d Device) NewLibraryWithSource(source string, opt CompileOptions) (Library, error) {
|
||||
var err cocoa.NSError
|
||||
l := d.device.Send(
|
||||
sel_newLibraryWithSource_options_error,
|
||||
@ -652,10 +652,31 @@ func (d Device) MakeLibrary(source string, opt CompileOptions) (Library, error)
|
||||
return Library{l}, nil
|
||||
}
|
||||
|
||||
// MakeRenderPipelineState creates a render pipeline state object.
|
||||
// NewLibraryWithData Creates a Metal library instance that contains the functions in a precompiled Metal library.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433369-makerenderpipelinestate.
|
||||
func (d Device) MakeRenderPipelineState(rpd RenderPipelineDescriptor) (RenderPipelineState, error) {
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433391-newlibrarywithdata?language=objc.
|
||||
func (d Device) NewLibraryWithData(buffer []byte) (Library, error) {
|
||||
defer runtime.KeepAlive(buffer)
|
||||
|
||||
data := dispatchDataCreate(unsafe.Pointer(&buffer[0]), uint(len(buffer)), 0, 0)
|
||||
defer dispatchRelease(data)
|
||||
|
||||
var err cocoa.NSError
|
||||
l := d.device.Send(
|
||||
sel_newLibraryWithData_error,
|
||||
data,
|
||||
unsafe.Pointer(&err),
|
||||
)
|
||||
if l == 0 {
|
||||
return Library{}, errors.New(cocoa.NSString{ID: err.Send(sel_localizedDescription)}.String())
|
||||
}
|
||||
return Library{l}, nil
|
||||
}
|
||||
|
||||
// NewRenderPipelineStateWithDescriptor synchronously creates a render pipeline state.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433369-newrenderpipelinestatewithdescri?language=objc.
|
||||
func (d Device) NewRenderPipelineStateWithDescriptor(rpd RenderPipelineDescriptor) (RenderPipelineState, error) {
|
||||
renderPipelineDescriptor := objc.ID(class_MTLRenderPipelineDescriptor).Send(sel_new)
|
||||
renderPipelineDescriptor.Send(sel_setVertexFunction, rpd.VertexFunction.function)
|
||||
renderPipelineDescriptor.Send(sel_setFragmentFunction, rpd.FragmentFunction.function)
|
||||
@ -683,26 +704,24 @@ func (d Device) MakeRenderPipelineState(rpd RenderPipelineDescriptor) (RenderPip
|
||||
return RenderPipelineState{renderPipelineState}, nil
|
||||
}
|
||||
|
||||
// MakeBufferWithBytes allocates a new buffer of a given length
|
||||
// and initializes its contents by copying existing data into it.
|
||||
// NewBufferWithBytes allocates a new buffer of a given length and initializes its contents by copying existing data into it.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433429-makebuffer.
|
||||
func (d Device) MakeBufferWithBytes(bytes unsafe.Pointer, length uintptr, opt ResourceOptions) Buffer {
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433429-newbufferwithbytes?language=objc.
|
||||
func (d Device) NewBufferWithBytes(bytes unsafe.Pointer, length uintptr, opt ResourceOptions) Buffer {
|
||||
return Buffer{d.device.Send(sel_newBufferWithBytes_length_options, bytes, length, uintptr(opt))}
|
||||
}
|
||||
|
||||
// MakeBufferWithLength allocates a new zero-filled buffer of a given length.
|
||||
// NewBufferWithLength allocates a new zero-filled buffer of a given length.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433375-newbufferwithlength
|
||||
func (d Device) MakeBufferWithLength(length uintptr, opt ResourceOptions) Buffer {
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433375-newbufferwithlength?language=objc.
|
||||
func (d Device) NewBufferWithLength(length uintptr, opt ResourceOptions) Buffer {
|
||||
return Buffer{d.device.Send(sel_newBufferWithLength_options, length, uintptr(opt))}
|
||||
}
|
||||
|
||||
// MakeTexture creates a texture object with privately owned storage
|
||||
// that contains texture state.
|
||||
// NewTextureWithDescriptor creates a new texture instance.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433425-maketexture.
|
||||
func (d Device) MakeTexture(td TextureDescriptor) Texture {
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433425-newtexturewithdescriptor?language=objc.
|
||||
func (d Device) NewTextureWithDescriptor(td TextureDescriptor) Texture {
|
||||
textureDescriptor := objc.ID(class_MTLTextureDescriptor).Send(sel_new)
|
||||
textureDescriptor.Send(sel_setTextureType, uintptr(td.TextureType))
|
||||
textureDescriptor.Send(sel_setPixelFormat, uintptr(td.PixelFormat))
|
||||
@ -717,10 +736,10 @@ func (d Device) MakeTexture(td TextureDescriptor) Texture {
|
||||
}
|
||||
}
|
||||
|
||||
// MakeDepthStencilState creates a new object that contains depth and stencil test state.
|
||||
// NewDepthStencilStateWithDescriptor creates a depth-stencil state instance.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433412-makedepthstencilstate
|
||||
func (d Device) MakeDepthStencilState(dsd DepthStencilDescriptor) DepthStencilState {
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433412-newdepthstencilstatewithdescript?language=objc.
|
||||
func (d Device) NewDepthStencilStateWithDescriptor(dsd DepthStencilDescriptor) DepthStencilState {
|
||||
depthStencilDescriptor := objc.ID(class_MTLDepthStencilDescriptor).Send(sel_new)
|
||||
backFaceStencil := depthStencilDescriptor.Send(sel_backFaceStencil)
|
||||
backFaceStencil.Send(sel_setStencilFailureOperation, uintptr(dsd.BackFaceStencil.StencilFailureOperation))
|
||||
@ -742,14 +761,14 @@ func (d Device) MakeDepthStencilState(dsd DepthStencilDescriptor) DepthStencilSt
|
||||
// CompileOptions specifies optional compilation settings for
|
||||
// the graphics or compute functions within a library.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcompileoptions.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcompileoptions?language=objc.
|
||||
type CompileOptions struct {
|
||||
// TODO.
|
||||
}
|
||||
|
||||
// Drawable is a displayable resource that can be rendered or written to.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldrawable.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldrawable?language=objc.
|
||||
type Drawable interface {
|
||||
// Drawable returns the underlying id<MTLDrawable> pointer.
|
||||
Drawable() unsafe.Pointer
|
||||
@ -758,7 +777,7 @@ type Drawable interface {
|
||||
// CommandQueue is a queue that organizes the order
|
||||
// in which command buffers are executed by the GPU.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue?language=objc.
|
||||
type CommandQueue struct {
|
||||
commandQueue objc.ID
|
||||
}
|
||||
@ -767,17 +786,17 @@ func (cq CommandQueue) Release() {
|
||||
cq.commandQueue.Send(sel_release)
|
||||
}
|
||||
|
||||
// MakeCommandBuffer creates a command buffer.
|
||||
// CommandBuffer returns a command buffer from the command queue that maintains strong references to resources.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue/1508686-makecommandbuffer.
|
||||
func (cq CommandQueue) MakeCommandBuffer() CommandBuffer {
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue/1508686-commandbuffer?language=objc.
|
||||
func (cq CommandQueue) CommandBuffer() CommandBuffer {
|
||||
return CommandBuffer{cq.commandQueue.Send(sel_commandBuffer)}
|
||||
}
|
||||
|
||||
// CommandBuffer is a container that stores encoded commands
|
||||
// that are committed to and executed by the GPU.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer?language=objc.
|
||||
type CommandBuffer struct {
|
||||
commandBuffer objc.ID
|
||||
}
|
||||
@ -792,44 +811,43 @@ func (cb CommandBuffer) Release() {
|
||||
|
||||
// Status returns the current stage in the lifetime of the command buffer.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443048-status
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443048-status?language=objc.
|
||||
func (cb CommandBuffer) Status() CommandBufferStatus {
|
||||
return CommandBufferStatus(cb.commandBuffer.Send(sel_status))
|
||||
}
|
||||
|
||||
// PresentDrawable registers a drawable presentation to occur as soon as possible.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443029-presentdrawable.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443029-presentdrawable?language=objc.
|
||||
func (cb CommandBuffer) PresentDrawable(d Drawable) {
|
||||
cb.commandBuffer.Send(sel_presentDrawable, d.Drawable())
|
||||
}
|
||||
|
||||
// Commit commits this command buffer for execution as soon as possible.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443003-commit.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443003-commit?language=objc.
|
||||
func (cb CommandBuffer) Commit() {
|
||||
cb.commandBuffer.Send(sel_commit)
|
||||
}
|
||||
|
||||
// WaitUntilCompleted waits for the execution of this command buffer to complete.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443039-waituntilcompleted.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443039-waituntilcompleted?language=objc.
|
||||
func (cb CommandBuffer) WaitUntilCompleted() {
|
||||
cb.commandBuffer.Send(sel_waitUntilCompleted)
|
||||
}
|
||||
|
||||
// WaitUntilScheduled blocks execution of the current thread until the command buffer is scheduled.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443036-waituntilscheduled.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443036-waituntilscheduled?language=objc.
|
||||
func (cb CommandBuffer) WaitUntilScheduled() {
|
||||
cb.commandBuffer.Send(sel_waitUntilScheduled)
|
||||
}
|
||||
|
||||
// MakeRenderCommandEncoder creates an encoder object that can
|
||||
// encode graphics rendering commands into this command buffer.
|
||||
// RenderCommandEncoderWithDescriptor creates a render command encoder from a descriptor.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1442999-makerendercommandencoder.
|
||||
func (cb CommandBuffer) MakeRenderCommandEncoder(rpd RenderPassDescriptor) RenderCommandEncoder {
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1442999-rendercommandencoderwithdescript?language=objc.
|
||||
func (cb CommandBuffer) RenderCommandEncoderWithDescriptor(rpd RenderPassDescriptor) RenderCommandEncoder {
|
||||
var renderPassDescriptor = objc.ID(class_MTLRenderPassDescriptor).Send(sel_new)
|
||||
var colorAttachments0 = renderPassDescriptor.Send(sel_colorAttachments).Send(sel_objectAtIndexedSubscript, 0)
|
||||
colorAttachments0.Send(sel_setLoadAction, int(rpd.ColorAttachments[0].LoadAction))
|
||||
@ -850,11 +868,11 @@ func (cb CommandBuffer) MakeRenderCommandEncoder(rpd RenderPassDescriptor) Rende
|
||||
return RenderCommandEncoder{CommandEncoder{rce}}
|
||||
}
|
||||
|
||||
// MakeBlitCommandEncoder creates an encoder object that can encode
|
||||
// BlitCommandEncoder creates an encoder object that can encode
|
||||
// memory operation (blit) commands into this command buffer.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443001-makeblitcommandencoder.
|
||||
func (cb CommandBuffer) MakeBlitCommandEncoder() BlitCommandEncoder {
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443001-makeblitcommandencoder?language=objc.
|
||||
func (cb CommandBuffer) BlitCommandEncoder() BlitCommandEncoder {
|
||||
ce := cb.commandBuffer.Send(sel_blitCommandEncoder)
|
||||
return BlitCommandEncoder{CommandEncoder{ce}}
|
||||
}
|
||||
@ -862,14 +880,14 @@ func (cb CommandBuffer) MakeBlitCommandEncoder() BlitCommandEncoder {
|
||||
// CommandEncoder is an encoder that writes sequential GPU commands
|
||||
// into a command buffer.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandencoder.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443001-blitcommandencoder?language=objc.
|
||||
type CommandEncoder struct {
|
||||
commandEncoder objc.ID
|
||||
}
|
||||
|
||||
// EndEncoding declares that all command generation from this encoder is completed.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandencoder/1458038-endencoding.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlcommandencoder/1458038-endencoding?language=objc.
|
||||
func (ce CommandEncoder) EndEncoding() {
|
||||
ce.commandEncoder.Send(sel_endEncoding)
|
||||
}
|
||||
@ -877,7 +895,7 @@ func (ce CommandEncoder) EndEncoding() {
|
||||
// RenderCommandEncoder is an encoder that specifies graphics-rendering commands
|
||||
// and executes graphics functions.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder?language=objc.
|
||||
type RenderCommandEncoder struct {
|
||||
CommandEncoder
|
||||
}
|
||||
@ -888,7 +906,7 @@ func (rce RenderCommandEncoder) Release() {
|
||||
|
||||
// SetRenderPipelineState sets the current render pipeline state object.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515811-setrenderpipelinestate.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515811-setrenderpipelinestate?language=objc.
|
||||
func (rce RenderCommandEncoder) SetRenderPipelineState(rps RenderPipelineState) {
|
||||
rce.commandEncoder.Send(sel_setRenderPipelineState, rps.renderPipelineState)
|
||||
}
|
||||
@ -903,7 +921,7 @@ func (rce RenderCommandEncoder) SetViewport(viewport Viewport) {
|
||||
|
||||
// SetScissorRect sets the scissor rectangle for a fragment scissor test.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515583-setscissorrect
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515583-setscissorrect?language=objc.
|
||||
func (rce RenderCommandEncoder) SetScissorRect(scissorRect ScissorRect) {
|
||||
inv := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:{MTLScissorRect=qqqq}"))
|
||||
inv.SetTarget(rce.commandEncoder)
|
||||
@ -915,14 +933,14 @@ func (rce RenderCommandEncoder) SetScissorRect(scissorRect ScissorRect) {
|
||||
// SetVertexBuffer sets a buffer for the vertex shader function at an index
|
||||
// in the buffer argument table with an offset that specifies the start of the data.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515829-setvertexbuffer.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515829-setvertexbuffer?language=objc.
|
||||
func (rce RenderCommandEncoder) SetVertexBuffer(buf Buffer, offset, index int) {
|
||||
rce.commandEncoder.Send(sel_setVertexBuffer_offset_atIndex, buf.buffer, offset, index)
|
||||
}
|
||||
|
||||
// SetVertexBytes sets a block of data for the vertex function.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515846-setvertexbytes.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515846-setvertexbytes?language=objc.
|
||||
func (rce RenderCommandEncoder) SetVertexBytes(bytes unsafe.Pointer, length uintptr, index int) {
|
||||
rce.commandEncoder.Send(sel_setVertexBytes_length_atIndex, bytes, length, index)
|
||||
}
|
||||
@ -933,7 +951,7 @@ func (rce RenderCommandEncoder) SetFragmentBytes(bytes unsafe.Pointer, length ui
|
||||
|
||||
// SetFragmentTexture sets a texture for the fragment function at an index in the texture argument table.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515390-setfragmenttexture
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515390-setfragmenttexture?language=objc.
|
||||
func (rce RenderCommandEncoder) SetFragmentTexture(texture Texture, index int) {
|
||||
rce.commandEncoder.Send(sel_setFragmentTexture_atIndex, texture.texture, index)
|
||||
}
|
||||
@ -944,7 +962,7 @@ func (rce RenderCommandEncoder) SetBlendColor(red, green, blue, alpha float32) {
|
||||
|
||||
// SetDepthStencilState sets the depth and stencil test state.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516119-setdepthstencilstate
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516119-setdepthstencilstate?language=objc.
|
||||
func (rce RenderCommandEncoder) SetDepthStencilState(depthStencilState DepthStencilState) {
|
||||
rce.commandEncoder.Send(sel_setDepthStencilState, depthStencilState.depthStencilState)
|
||||
}
|
||||
@ -952,7 +970,7 @@ func (rce RenderCommandEncoder) SetDepthStencilState(depthStencilState DepthSten
|
||||
// DrawPrimitives renders one instance of primitives using vertex data
|
||||
// in contiguous array elements.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516326-drawprimitives.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516326-drawprimitives?language=objc.
|
||||
func (rce RenderCommandEncoder) DrawPrimitives(typ PrimitiveType, vertexStart, vertexCount int) {
|
||||
rce.commandEncoder.Send(sel_drawPrimitives_vertexStart_vertexCount, uintptr(typ), vertexStart, vertexCount)
|
||||
}
|
||||
@ -969,7 +987,7 @@ func (rce RenderCommandEncoder) DrawIndexedPrimitives(typ PrimitiveType, indexCo
|
||||
// BlitCommandEncoder is an encoder that specifies resource copy
|
||||
// and resource synchronization commands.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder?language=objc.
|
||||
type BlitCommandEncoder struct {
|
||||
CommandEncoder
|
||||
}
|
||||
@ -977,7 +995,7 @@ type BlitCommandEncoder struct {
|
||||
// Synchronize flushes any copy of the specified resource from its corresponding
|
||||
// Device caches and, if needed, invalidates any CPU caches.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400775-synchronize.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400775-synchronize?language=objc.
|
||||
func (bce BlitCommandEncoder) Synchronize(resource Resource) {
|
||||
if runtime.GOOS == "ios" {
|
||||
return
|
||||
@ -985,6 +1003,9 @@ func (bce BlitCommandEncoder) Synchronize(resource Resource) {
|
||||
bce.commandEncoder.Send(sel_synchronizeResource, resource.resource())
|
||||
}
|
||||
|
||||
// SynchronizeTexture encodes a command that synchronizes a part of the CPU’s copy of a texture so that it matches the GPU’s copy.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400757-synchronizetexture?language=objc.
|
||||
func (bce BlitCommandEncoder) SynchronizeTexture(texture Texture, slice int, level int) {
|
||||
if runtime.GOOS == "ios" {
|
||||
return
|
||||
@ -992,6 +1013,9 @@ func (bce BlitCommandEncoder) SynchronizeTexture(texture Texture, slice int, lev
|
||||
bce.commandEncoder.Send(sel_synchronizeTexture_slice_level, texture.texture, slice, level)
|
||||
}
|
||||
|
||||
// CopyFromTexture encodes a command that copies image data from a texture’s slice into another slice.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400754-copyfromtexture?language=objc.
|
||||
func (bce BlitCommandEncoder) CopyFromTexture(sourceTexture Texture, sourceSlice int, sourceLevel int, sourceOrigin Origin, sourceSize Size, destinationTexture Texture, destinationSlice int, destinationLevel int, destinationOrigin Origin) {
|
||||
inv := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:@QQ{MTLOrigin=qqq}{MTLSize=qqq}@QQ{MTLOrigin=qqq}"))
|
||||
inv.SetTarget(bce.commandEncoder)
|
||||
@ -1010,15 +1034,15 @@ func (bce BlitCommandEncoder) CopyFromTexture(sourceTexture Texture, sourceSlice
|
||||
|
||||
// Library is a collection of compiled graphics or compute functions.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtllibrary.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtllibrary?language=objc.
|
||||
type Library struct {
|
||||
library objc.ID
|
||||
}
|
||||
|
||||
// MakeFunction returns a pre-compiled, non-specialized function.
|
||||
// NewFunctionWithName returns a pre-compiled, non-specialized function.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtllibrary/1515524-makefunction.
|
||||
func (l Library) MakeFunction(name string) (Function, error) {
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtllibrary/1515524-newfunctionwithname?language=objc.
|
||||
func (l Library) NewFunctionWithName(name string) (Function, error) {
|
||||
f := l.library.Send(sel_newFunctionWithName,
|
||||
cocoa.NSString_alloc().InitWithUTF8String(name).ID,
|
||||
)
|
||||
@ -1028,10 +1052,14 @@ func (l Library) MakeFunction(name string) (Function, error) {
|
||||
return Function{f}, nil
|
||||
}
|
||||
|
||||
func (l Library) Release() {
|
||||
l.library.Send(sel_release)
|
||||
}
|
||||
|
||||
// Texture is a memory allocation for storing formatted
|
||||
// image data that is accessible to the GPU.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtltexture.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtltexture?language=objc.
|
||||
type Texture struct {
|
||||
texture objc.ID
|
||||
}
|
||||
@ -1042,7 +1070,9 @@ func NewTexture(texture objc.ID) Texture {
|
||||
}
|
||||
|
||||
// resource implements the Resource interface.
|
||||
func (t Texture) resource() unsafe.Pointer { return *(*unsafe.Pointer)(unsafe.Pointer(&t.texture)) }
|
||||
func (t Texture) resource() unsafe.Pointer {
|
||||
return *(*unsafe.Pointer)(unsafe.Pointer(&t.texture))
|
||||
}
|
||||
|
||||
func (t Texture) Release() {
|
||||
t.texture.Send(sel_release)
|
||||
@ -1051,7 +1081,7 @@ func (t Texture) Release() {
|
||||
// GetBytes copies a block of pixels from the storage allocation of texture
|
||||
// slice zero into system memory at a specified address.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515751-getbytes.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515751-getbytes?language=objc.
|
||||
func (t Texture) GetBytes(pixelBytes *byte, bytesPerRow uintptr, region Region, level int) {
|
||||
inv := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:^vQ{MTLRegion={MTLOrigin=qqq}{MTLSize=qqq}}Q"))
|
||||
inv.SetTarget(t.texture)
|
||||
@ -1065,7 +1095,7 @@ func (t Texture) GetBytes(pixelBytes *byte, bytesPerRow uintptr, region Region,
|
||||
|
||||
// ReplaceRegion copies a block of pixels from the caller's pointer into the storage allocation for slice 0 of a texture.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515464-replaceregion
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515464-replaceregion?language=objc.
|
||||
func (t Texture) ReplaceRegion(region Region, level int, pixelBytes unsafe.Pointer, bytesPerRow int) {
|
||||
inv := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:{MTLRegion={MTLOrigin=qqq}{MTLSize=qqq}}Q^vQ"))
|
||||
inv.SetTarget(t.texture)
|
||||
@ -1079,14 +1109,14 @@ func (t Texture) ReplaceRegion(region Region, level int, pixelBytes unsafe.Point
|
||||
|
||||
// Width is the width of the texture image for the base level mipmap, in pixels.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515339-width
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515339-width?language=objc.
|
||||
func (t Texture) Width() int {
|
||||
return int(t.texture.Send(sel_width))
|
||||
}
|
||||
|
||||
// Height is the height of the texture image for the base level mipmap, in pixels.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515938-height
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515938-height?language=objc.
|
||||
func (t Texture) Height() int {
|
||||
return int(t.texture.Send(sel_height))
|
||||
}
|
||||
@ -1094,13 +1124,19 @@ func (t Texture) Height() int {
|
||||
// Buffer is a memory allocation for storing unformatted data
|
||||
// that is accessible to the GPU.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlbuffer.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlbuffer?language=objc.
|
||||
type Buffer struct {
|
||||
buffer objc.ID
|
||||
}
|
||||
|
||||
func (b Buffer) resource() unsafe.Pointer { return *(*unsafe.Pointer)(unsafe.Pointer(&b.buffer)) }
|
||||
// resource implements the Resource interface.
|
||||
func (b Buffer) resource() unsafe.Pointer {
|
||||
return *(*unsafe.Pointer)(unsafe.Pointer(&b.buffer))
|
||||
}
|
||||
|
||||
// Length returns the logical size of the buffer, in bytes.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlbuffer/1515373-length?language=objc.
|
||||
func (b Buffer) Length() uintptr {
|
||||
return uintptr(b.buffer.Send(sel_length))
|
||||
}
|
||||
@ -1121,13 +1157,9 @@ func (b Buffer) Release() {
|
||||
b.buffer.Send(sel_release)
|
||||
}
|
||||
|
||||
func (b Buffer) Native() unsafe.Pointer {
|
||||
return *(*unsafe.Pointer)(unsafe.Pointer(&b.buffer))
|
||||
}
|
||||
|
||||
// Function represents a programmable graphics or compute function executed by the GPU.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlfunction.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlfunction?language=objc.
|
||||
type Function struct {
|
||||
function objc.ID
|
||||
}
|
||||
@ -1139,7 +1171,7 @@ func (f Function) Release() {
|
||||
// RenderPipelineState contains the graphics functions
|
||||
// and configuration state used in a render pass.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinestate.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinestate?language=objc.
|
||||
type RenderPipelineState struct {
|
||||
renderPipelineState objc.ID
|
||||
}
|
||||
@ -1151,7 +1183,7 @@ func (r RenderPipelineState) Release() {
|
||||
// Region is a rectangular block of pixels in an image or texture,
|
||||
// defined by its upper-left corner and its size.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlregion.
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlregion?language=objc.
|
||||
type Region struct {
|
||||
Origin Origin // The location of the upper-left corner of the block.
|
||||
Size Size // The size of the block.
|
||||
@ -1160,25 +1192,36 @@ type Region struct {
|
||||
// Origin represents the location of a pixel in an image or texture relative
|
||||
// to the upper-left corner, whose coordinates are (0, 0).
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlorigin.
|
||||
type Origin struct{ X, Y, Z int }
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlorigin?language=objc.
|
||||
type Origin struct {
|
||||
X int
|
||||
Y int
|
||||
Z int
|
||||
}
|
||||
|
||||
// Size represents the set of dimensions that declare the size of an object,
|
||||
// such as an image, texture, threadgroup, or grid.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlsize.
|
||||
type Size struct{ Width, Height, Depth int }
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlsize?language=objc.
|
||||
type Size struct {
|
||||
Width int
|
||||
Height int
|
||||
Depth int
|
||||
}
|
||||
|
||||
// RegionMake2D returns a 2D, rectangular region for image or texture data.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/1515675-mtlregionmake2d.
|
||||
// Reference: https://developer.apple.com/documentation/metal/1515675-mtlregionmake2d?language=objc.
|
||||
func RegionMake2D(x, y, width, height int) Region {
|
||||
return Region{
|
||||
Origin: Origin{x, y, 0},
|
||||
Size: Size{width, height, 1},
|
||||
Origin: Origin{X: x, Y: y, Z: 0},
|
||||
Size: Size{Width: width, Height: height, Depth: 1},
|
||||
}
|
||||
}
|
||||
|
||||
// Viewport is a 3D rectangular region for the viewport clipping.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlviewport?language=objc.
|
||||
type Viewport struct {
|
||||
OriginX float64
|
||||
OriginY float64
|
||||
@ -1188,9 +1231,9 @@ type Viewport struct {
|
||||
ZFar float64
|
||||
}
|
||||
|
||||
// ScissorRect represents a rectangle for the scissor fragment test.
|
||||
// ScissorRect is a rectangle for the scissor fragment test.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlscissorrect
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlscissorrect?language=objc.
|
||||
type ScissorRect struct {
|
||||
X int
|
||||
Y int
|
||||
@ -1200,7 +1243,7 @@ type ScissorRect struct {
|
||||
|
||||
// DepthStencilState is a depth and stencil state object that specifies the depth and stencil configuration and operations used in a render pass.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldepthstencilstate
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldepthstencilstate?language=objc.
|
||||
type DepthStencilState struct {
|
||||
depthStencilState objc.ID
|
||||
}
|
||||
@ -1211,7 +1254,7 @@ func (d DepthStencilState) Release() {
|
||||
|
||||
// DepthStencilDescriptor is an object that configures new MTLDepthStencilState objects.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldepthstencildescriptor
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtldepthstencildescriptor?language=objc.
|
||||
type DepthStencilDescriptor struct {
|
||||
// BackFaceStencil is the stencil descriptor for back-facing primitives.
|
||||
BackFaceStencil StencilDescriptor
|
||||
@ -1222,7 +1265,7 @@ type DepthStencilDescriptor struct {
|
||||
|
||||
// StencilDescriptor is an object that defines the front-facing or back-facing stencil operations of a depth and stencil state object.
|
||||
//
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlstencildescriptor
|
||||
// Reference: https://developer.apple.com/documentation/metal/mtlstencildescriptor?language=objc.
|
||||
type StencilDescriptor struct {
|
||||
// StencilFailureOperation is the operation that is performed to update the values in the stencil attachment when the stencil test fails.
|
||||
StencilFailureOperation StencilOperation
|
||||
|
@ -56,15 +56,15 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) {
|
||||
return in.color;
|
||||
}
|
||||
`
|
||||
lib, err := device.MakeLibrary(source, mtl.CompileOptions{})
|
||||
lib, err := device.NewLibraryWithSource(source, mtl.CompileOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
vs, err := lib.MakeFunction("VertexShader")
|
||||
vs, err := lib.NewFunctionWithName("VertexShader")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fs, err := lib.MakeFunction("FragmentShader")
|
||||
fs, err := lib.NewFunctionWithName("FragmentShader")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -72,7 +72,7 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) {
|
||||
rpld.VertexFunction = vs
|
||||
rpld.FragmentFunction = fs
|
||||
rpld.ColorAttachments[0].PixelFormat = mtl.PixelFormatRGBA8UNorm
|
||||
rps, err := device.MakeRenderPipelineState(rpld)
|
||||
rps, err := device.NewRenderPipelineStateWithDescriptor(rpld)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -87,7 +87,7 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) {
|
||||
{f32.Vec4{-0.75, -0.75, 0, 1}, f32.Vec4{0, 1, 0, 1}},
|
||||
{f32.Vec4{+0.75, -0.75, 0, 1}, f32.Vec4{0, 0, 1, 1}},
|
||||
}
|
||||
vertexBuffer := device.MakeBufferWithBytes(unsafe.Pointer(&vertexData[0]), unsafe.Sizeof(vertexData), mtl.ResourceStorageModeManaged)
|
||||
vertexBuffer := device.NewBufferWithBytes(unsafe.Pointer(&vertexData[0]), unsafe.Sizeof(vertexData), mtl.ResourceStorageModeManaged)
|
||||
|
||||
// Create an output texture to render into.
|
||||
td := mtl.TextureDescriptor{
|
||||
@ -97,10 +97,10 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) {
|
||||
Height: 512,
|
||||
StorageMode: mtl.StorageModeManaged,
|
||||
}
|
||||
texture := device.MakeTexture(td)
|
||||
texture := device.NewTextureWithDescriptor(td)
|
||||
|
||||
cq := device.MakeCommandQueue()
|
||||
cb := cq.MakeCommandBuffer()
|
||||
cq := device.NewCommandQueue()
|
||||
cb := cq.CommandBuffer()
|
||||
|
||||
// Encode all render commands.
|
||||
var rpd mtl.RenderPassDescriptor
|
||||
@ -108,14 +108,14 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) {
|
||||
rpd.ColorAttachments[0].StoreAction = mtl.StoreActionStore
|
||||
rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{Red: 0.35, Green: 0.65, Blue: 0.85, Alpha: 1}
|
||||
rpd.ColorAttachments[0].Texture = texture
|
||||
rce := cb.MakeRenderCommandEncoder(rpd)
|
||||
rce := cb.RenderCommandEncoderWithDescriptor(rpd)
|
||||
rce.SetRenderPipelineState(rps)
|
||||
rce.SetVertexBuffer(vertexBuffer, 0, 0)
|
||||
rce.DrawPrimitives(mtl.PrimitiveTypeTriangle, 0, 3)
|
||||
rce.EndEncoding()
|
||||
|
||||
// Encode all blit commands.
|
||||
bce := cb.MakeBlitCommandEncoder()
|
||||
bce := cb.BlitCommandEncoder()
|
||||
bce.Synchronize(texture)
|
||||
bce.EndEncoding()
|
||||
|
||||
|
@ -16,6 +16,7 @@ package metal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl"
|
||||
@ -23,6 +24,37 @@ import (
|
||||
"github.com/hajimehoshi/ebiten/v2/internal/shaderir/msl"
|
||||
)
|
||||
|
||||
type precompiledLibraries struct {
|
||||
binaries map[shaderir.SourceHash][]byte
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
func (c *precompiledLibraries) put(hash shaderir.SourceHash, bin []byte) {
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
|
||||
if c.binaries == nil {
|
||||
c.binaries = map[shaderir.SourceHash][]byte{}
|
||||
}
|
||||
if _, ok := c.binaries[hash]; ok {
|
||||
panic(fmt.Sprintf("metal: the precompiled library for the hash %s is already registered", hash.String()))
|
||||
}
|
||||
c.binaries[hash] = bin
|
||||
}
|
||||
|
||||
func (c *precompiledLibraries) get(hash shaderir.SourceHash) []byte {
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
|
||||
return c.binaries[hash]
|
||||
}
|
||||
|
||||
var thePrecompiledLibraries precompiledLibraries
|
||||
|
||||
func RegisterPrecompiledLibrary(source []byte, bin []byte) {
|
||||
thePrecompiledLibraries.put(shaderir.CalcSourceHash(source), bin)
|
||||
}
|
||||
|
||||
type shaderRpsKey struct {
|
||||
blend graphicsdriver.Blend
|
||||
stencilMode stencilMode
|
||||
@ -33,9 +65,12 @@ type Shader struct {
|
||||
id graphicsdriver.ShaderID
|
||||
|
||||
ir *shaderir.Program
|
||||
lib mtl.Library
|
||||
fs mtl.Function
|
||||
vs mtl.Function
|
||||
rpss map[shaderRpsKey]mtl.RenderPipelineState
|
||||
|
||||
libraryPrecompiled bool
|
||||
}
|
||||
|
||||
func newShader(device mtl.Device, id graphicsdriver.ShaderID, program *shaderir.Program) (*Shader, error) {
|
||||
@ -60,21 +95,42 @@ func (s *Shader) Dispose() {
|
||||
}
|
||||
s.vs.Release()
|
||||
s.fs.Release()
|
||||
// Do not release s.lib if this is precompiled. This is a shared precompiled library.
|
||||
if !s.libraryPrecompiled {
|
||||
s.lib.Release()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Shader) init(device mtl.Device) error {
|
||||
src := msl.Compile(s.ir)
|
||||
lib, err := device.MakeLibrary(src, mtl.CompileOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("metal: device.MakeLibrary failed: %w, source: %s", err, src)
|
||||
var src string
|
||||
if libBin := thePrecompiledLibraries.get(s.ir.SourceHash); len(libBin) > 0 {
|
||||
lib, err := device.NewLibraryWithData(libBin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.lib = lib
|
||||
} else {
|
||||
src = msl.Compile(s.ir)
|
||||
lib, err := device.NewLibraryWithSource(src, mtl.CompileOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("metal: device.MakeLibrary failed: %w, source: %s", err, src)
|
||||
}
|
||||
s.lib = lib
|
||||
}
|
||||
vs, err := lib.MakeFunction(msl.VertexName)
|
||||
|
||||
vs, err := s.lib.NewFunctionWithName(msl.VertexName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("metal: lib.MakeFunction for vertex failed: %w, source: %s", err, src)
|
||||
if src != "" {
|
||||
return fmt.Errorf("metal: lib.MakeFunction for vertex failed: %w, source: %s", err, src)
|
||||
}
|
||||
return fmt.Errorf("metal: lib.MakeFunction for vertex failed: %w", err)
|
||||
}
|
||||
fs, err := lib.MakeFunction(msl.FragmentName)
|
||||
fs, err := s.lib.NewFunctionWithName(msl.FragmentName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("metal: lib.MakeFunction for fragment failed: %w, source: %s", err, src)
|
||||
if src != "" {
|
||||
return fmt.Errorf("metal: lib.MakeFunction for fragment failed: %w, source: %s", err, src)
|
||||
}
|
||||
return fmt.Errorf("metal: lib.MakeFunction for fragment failed: %w", err)
|
||||
}
|
||||
s.fs = fs
|
||||
s.vs = vs
|
||||
@ -120,7 +176,7 @@ func (s *Shader) RenderPipelineState(view *view, blend graphicsdriver.Blend, ste
|
||||
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskNone
|
||||
}
|
||||
|
||||
rps, err := view.getMTLDevice().MakeRenderPipelineState(rpld)
|
||||
rps, err := view.getMTLDevice().NewRenderPipelineStateWithDescriptor(rpld)
|
||||
if err != nil {
|
||||
return mtl.RenderPipelineState{}, err
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ func (v *view) colorPixelFormat() mtl.PixelFormat {
|
||||
func (v *view) initialize(device mtl.Device) error {
|
||||
v.device = device
|
||||
|
||||
ml, err := ca.MakeMetalLayer()
|
||||
ml, err := ca.NewMetalLayer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
// SPDX-FileCopyrightText: 2014 Eric Woroshow
|
||||
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors
|
||||
|
||||
//go:build !darwin && !js && !windows && !playstation5
|
||||
//go:build nintendosdk
|
||||
|
||||
package gl
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build darwin || windows
|
||||
//go:build (darwin || freebsd || linux || netbsd || openbsd || windows) && !nintendosdk && !playstation5
|
||||
|
||||
package gl
|
||||
|
||||
|
@ -16,38 +16,18 @@
|
||||
|
||||
package gl
|
||||
|
||||
// #cgo LDFLAGS: -ldl
|
||||
//
|
||||
// #include <dlfcn.h>
|
||||
// #include <stdlib.h>
|
||||
//
|
||||
// static void* getProcAddressGL(void* libGL, const char* name) {
|
||||
// static void*(*glXGetProcAddress)(const char*);
|
||||
// if (!glXGetProcAddress) {
|
||||
// glXGetProcAddress = dlsym(libGL, "glXGetProcAddress");
|
||||
// if (!glXGetProcAddress) {
|
||||
// glXGetProcAddress = dlsym(libGL, "glXGetProcAddressARB");
|
||||
// }
|
||||
// }
|
||||
// return glXGetProcAddress(name);
|
||||
// }
|
||||
//
|
||||
// static void* getProcAddressGLES(void* libGLES, const char* name) {
|
||||
// return dlsym(libGLES, name);
|
||||
// }
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ebitengine/purego"
|
||||
)
|
||||
|
||||
var (
|
||||
libGL unsafe.Pointer
|
||||
libGLES unsafe.Pointer
|
||||
libGL uintptr
|
||||
libGLES uintptr
|
||||
)
|
||||
|
||||
func (c *defaultContext) init() error {
|
||||
@ -68,11 +48,12 @@ func (c *defaultContext) init() error {
|
||||
// Try OpenGL first. OpenGL is preferable as this doesn't cause context losses.
|
||||
if !preferES {
|
||||
// Usually libGL.so or libGL.so.1 is used. libGL.so.2 might exist only on NetBSD.
|
||||
// TODO: Should "libOpenGL.so.0" [1] and "libGLX.so.0" [2] be added? These were added as of GLFW 3.3.9.
|
||||
// [1] https://github.com/glfw/glfw/commit/55aad3c37b67f17279378db52da0a3ab81bbf26d
|
||||
// [2] https://github.com/glfw/glfw/commit/c18851f52ec9704eb06464058a600845ec1eada1
|
||||
for _, name := range []string{"libGL.so", "libGL.so.2", "libGL.so.1", "libGL.so.0"} {
|
||||
cname := C.CString(name)
|
||||
lib := C.dlopen(cname, C.RTLD_LAZY|C.RTLD_GLOBAL)
|
||||
C.free(unsafe.Pointer(cname))
|
||||
if lib != nil {
|
||||
lib, err := purego.Dlopen(name, purego.RTLD_LAZY|purego.RTLD_GLOBAL)
|
||||
if err == nil {
|
||||
libGL = lib
|
||||
return nil
|
||||
}
|
||||
@ -81,10 +62,8 @@ func (c *defaultContext) init() error {
|
||||
|
||||
// Try OpenGL ES.
|
||||
for _, name := range []string{"libGLESv2.so", "libGLESv2.so.2", "libGLESv2.so.1", "libGLESv2.so.0"} {
|
||||
cname := C.CString(name)
|
||||
lib := C.dlopen(cname, C.RTLD_LAZY|C.RTLD_GLOBAL)
|
||||
C.free(unsafe.Pointer(cname))
|
||||
if lib != nil {
|
||||
lib, err := purego.Dlopen(name, purego.RTLD_LAZY|purego.RTLD_GLOBAL)
|
||||
if err == nil {
|
||||
libGLES = lib
|
||||
c.isES = true
|
||||
return nil
|
||||
@ -96,19 +75,32 @@ func (c *defaultContext) init() error {
|
||||
|
||||
func (c *defaultContext) getProcAddress(name string) (uintptr, error) {
|
||||
if c.isES {
|
||||
return getProcAddressGLES(name), nil
|
||||
return getProcAddressGLES(name)
|
||||
}
|
||||
return getProcAddressGL(name), nil
|
||||
return getProcAddressGL(name)
|
||||
}
|
||||
|
||||
func getProcAddressGL(name string) uintptr {
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
return uintptr(C.getProcAddressGL(libGL, cname))
|
||||
var glXGetProcAddress func(name string) uintptr
|
||||
|
||||
func getProcAddressGL(name string) (uintptr, error) {
|
||||
if glXGetProcAddress == nil {
|
||||
if _, err := purego.Dlsym(libGL, "glXGetProcAddress"); err == nil {
|
||||
purego.RegisterLibFunc(&glXGetProcAddress, libGL, "glXGetProcAddress")
|
||||
} else if _, err := purego.Dlsym(libGL, "glXGetProcAddressARB"); err == nil {
|
||||
purego.RegisterLibFunc(&glXGetProcAddress, libGL, "glXGetProcAddressARB")
|
||||
}
|
||||
}
|
||||
if glXGetProcAddress == nil {
|
||||
return 0, fmt.Errorf("gl: failed to find glXGetProcAddress or glXGetProcAddressARB in libGL.so")
|
||||
}
|
||||
|
||||
return glXGetProcAddress(name), nil
|
||||
}
|
||||
|
||||
func getProcAddressGLES(name string) uintptr {
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
return uintptr(C.getProcAddressGLES(libGLES, cname))
|
||||
func getProcAddressGLES(name string) (uintptr, error) {
|
||||
proc, err := purego.Dlsym(libGLES, name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return proc, nil
|
||||
}
|
||||
|
@ -329,7 +329,7 @@ func (g *Graphics) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphicsdr
|
||||
}
|
||||
g.uniformVars = g.uniformVars[:0]
|
||||
|
||||
if fillRule != graphicsdriver.FillAll {
|
||||
if fillRule != graphicsdriver.FillRuleFillAll {
|
||||
if err := dsts[firstTarget].ensureStencilBuffer(framebufferNative(f)); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -345,7 +345,7 @@ func (g *Graphics) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphicsdr
|
||||
)
|
||||
|
||||
switch fillRule {
|
||||
case graphicsdriver.NonZero:
|
||||
case graphicsdriver.FillRuleNonZero:
|
||||
g.context.ctx.Clear(gl.STENCIL_BUFFER_BIT)
|
||||
g.context.ctx.StencilFunc(gl.ALWAYS, 0x00, 0xff)
|
||||
g.context.ctx.StencilOpSeparate(gl.FRONT, gl.KEEP, gl.KEEP, gl.INCR_WRAP)
|
||||
@ -353,7 +353,7 @@ func (g *Graphics) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphicsdr
|
||||
g.context.ctx.ColorMask(false, false, false, false)
|
||||
|
||||
g.context.ctx.DrawElements(gl.TRIANGLES, int32(dstRegion.IndexCount), gl.UNSIGNED_INT, indexOffset*int(unsafe.Sizeof(uint32(0))))
|
||||
case graphicsdriver.EvenOdd:
|
||||
case graphicsdriver.FillRuleEvenOdd:
|
||||
g.context.ctx.Clear(gl.STENCIL_BUFFER_BIT)
|
||||
g.context.ctx.StencilFunc(gl.ALWAYS, 0x00, 0xff)
|
||||
g.context.ctx.StencilOpSeparate(gl.FRONT_AND_BACK, gl.KEEP, gl.KEEP, gl.INVERT)
|
||||
@ -361,7 +361,7 @@ func (g *Graphics) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphicsdr
|
||||
|
||||
g.context.ctx.DrawElements(gl.TRIANGLES, int32(dstRegion.IndexCount), gl.UNSIGNED_INT, indexOffset*int(unsafe.Sizeof(uint32(0))))
|
||||
}
|
||||
if fillRule != graphicsdriver.FillAll {
|
||||
if fillRule != graphicsdriver.FillRuleFillAll {
|
||||
g.context.ctx.StencilFunc(gl.NOTEQUAL, 0x00, 0xff)
|
||||
g.context.ctx.StencilOpSeparate(gl.FRONT_AND_BACK, gl.KEEP, gl.KEEP, gl.KEEP)
|
||||
g.context.ctx.ColorMask(true, true, true, true)
|
||||
@ -370,7 +370,7 @@ func (g *Graphics) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphicsdr
|
||||
indexOffset += dstRegion.IndexCount
|
||||
}
|
||||
|
||||
if fillRule != graphicsdriver.FillAll {
|
||||
if fillRule != graphicsdriver.FillRuleFillAll {
|
||||
g.context.ctx.Disable(gl.STENCIL_TEST)
|
||||
}
|
||||
|
||||
|
23
internal/graphicsdriver/playstation5/shader_paystation5.go
Normal file
23
internal/graphicsdriver/playstation5/shader_paystation5.go
Normal file
@ -0,0 +1,23 @@
|
||||
// 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 playstation5
|
||||
|
||||
// This is a separated pure Go file so that the `shaderprecomp` package can use this without Cgo.
|
||||
|
||||
package playstation5
|
||||
|
||||
func RegisterPrecompiledShaders(source []byte, vertex, pixel []byte) {
|
||||
// TODO: Implement this.
|
||||
}
|
@ -262,7 +262,7 @@ func (m *Mipmap) level(level int) *buffered.Image {
|
||||
s := buffered.NewImage(w2, h2, m.imageType)
|
||||
|
||||
dstRegion := image.Rect(0, 0, w2, h2)
|
||||
s.DrawTriangles([graphics.ShaderSrcImageCount]*buffered.Image{src}, vs, is, graphicsdriver.BlendCopy, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{}, shader, nil, graphicsdriver.FillAll)
|
||||
s.DrawTriangles([graphics.ShaderSrcImageCount]*buffered.Image{src}, vs, is, graphicsdriver.BlendCopy, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{}, shader, nil, graphicsdriver.FillRuleFillAll)
|
||||
m.setImg(level, s)
|
||||
|
||||
return m.imgs[level]
|
||||
|
@ -23,13 +23,13 @@ import (
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
exec "golang.org/x/sys/execabs"
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
|
@ -20,14 +20,13 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
exec "golang.org/x/sys/execabs"
|
||||
)
|
||||
|
||||
func isWSL() (bool, error) {
|
||||
|
4
internal/processtest/testdata/issue2138.go
vendored
4
internal/processtest/testdata/issue2138.go
vendored
@ -59,7 +59,7 @@ func (g *Game) Update() error {
|
||||
}
|
||||
|
||||
func (g *Game) Draw(screen *ebiten.Image) {
|
||||
// Before the fix, some complex renderings with EvenOdd might cause a DirectX error like this (#2138):
|
||||
// Before the fix, some complex renderings with FillRuleEvenOdd might cause a DirectX error like this (#2138):
|
||||
// panic: directx: IDXGISwapChain4::Present failed: HRESULT(2289696773)
|
||||
|
||||
screen.DrawImage(debugCircleImage, nil)
|
||||
@ -74,7 +74,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
|
||||
p.Arc(100, 100, 6, 0, 2*math.Pi, vector.Clockwise)
|
||||
filling, indicies := p.AppendVerticesAndIndicesForFilling(nil, nil)
|
||||
screen.DrawTriangles(filling, indicies, whiteTextureImage, &ebiten.DrawTrianglesOptions{
|
||||
FillRule: ebiten.EvenOdd,
|
||||
FillRule: ebiten.FillRuleEvenOdd,
|
||||
})
|
||||
}
|
||||
|
||||
|
74
internal/processtest/testdata/shader.go
vendored
Normal file
74
internal/processtest/testdata/shader.go
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
// 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 ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
// This test confirms that deallocation of a shader works correctly.
|
||||
|
||||
type Game struct {
|
||||
count int
|
||||
|
||||
img *ebiten.Image
|
||||
}
|
||||
|
||||
func (g *Game) Update() error {
|
||||
if g.img == nil {
|
||||
g.img = ebiten.NewImage(1, 1)
|
||||
}
|
||||
|
||||
g.count++
|
||||
|
||||
s, err := ebiten.NewShader([]byte(fmt.Sprintf(`//kage:unit pixels
|
||||
|
||||
package main
|
||||
|
||||
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
|
||||
return vec4(%d/255.0)
|
||||
}
|
||||
`, g.count)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Use the shader to ensure that the shader is actually allocated.
|
||||
g.img.DrawRectShader(1, 1, s, nil)
|
||||
|
||||
s.Deallocate()
|
||||
|
||||
if g.count == 60 {
|
||||
return ebiten.Termination
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Game) Draw(screen *ebiten.Image) {
|
||||
}
|
||||
|
||||
func (g *Game) Layout(w, h int) (int, int) {
|
||||
return 320, 240
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := ebiten.RunGame(&Game{}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
@ -199,7 +199,7 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
||||
return nil, nil, nil, false
|
||||
}
|
||||
for _, expr := range es {
|
||||
if expr.Type == shaderir.FunctionExpr {
|
||||
if expr.Type == shaderir.FunctionExpr || expr.Type == shaderir.BuiltinFuncExpr {
|
||||
cs.addError(e.Pos(), fmt.Sprintf("function name cannot be an argument: %s", e.Fun))
|
||||
return nil, nil, nil, false
|
||||
}
|
||||
|
@ -202,6 +202,7 @@ func Compile(src []byte, vertexEntry, fragmentEntry string, textureCount int) (*
|
||||
fragmentEntry: fragmentEntry,
|
||||
unit: unit,
|
||||
}
|
||||
s.ir.SourceHash = shaderir.CalcSourceHash(src)
|
||||
s.global.ir = &shaderir.Block{}
|
||||
s.parse(f)
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user