Merge with main - fixed conflicts

This commit is contained in:
Zyko 2024-06-09 17:35:03 +02:00
commit 6a00b8680a
124 changed files with 2627 additions and 1028 deletions

View File

@ -7,7 +7,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, macos-latest, windows-latest] 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 }} name: Test with Go ${{ matrix.go }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
env: env:
@ -44,9 +44,10 @@ jobs:
- name: Install wasmbrowsertest - name: Install wasmbrowsertest
run: | 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' || '' }} 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 - name: Prepare ebitenmobile test
run: | run: |
@ -77,9 +78,6 @@ jobs:
go list ./... | grep -v -x -F -f .github/workflows/govetblock_windows.txt | xargs go vet go list ./... | grep -v -x -F -f .github/workflows/govetblock_windows.txt | xargs go vet
- name: go vet (vettool) - 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: | run: |
go install ./internal/vettools go install ./internal/vettools
go vet -vettool=$(which vettools)${{ runner.os == 'Windows' && '.exe' || '' }} -v ./... go vet -vettool=$(which vettools)${{ runner.os == 'Windows' && '.exe' || '' }} -v ./...
@ -144,8 +142,6 @@ jobs:
run: | run: |
sudo dpkg --add-architecture i386 sudo dpkg --add-architecture i386
sudo apt-get update 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 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 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 ./... 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 ./... env GOARCH=386 EBITENGINE_DIRECTX=version=12 go test -shuffle=on -v ./...
- name: go test (Wasm) - name: go test (Wasm)
if: ${{ runner.os != 'macOS' && runner.os != 'Windows' }}
run: | 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 - name: Install ebitenmobile
run: | run: |

5
.gitignore vendored
View File

@ -7,3 +7,8 @@
.vscode .vscode
go.work go.work
go.work.sum go.work.sum
*.fxc
!dummy.fxc
*.metallib
!dummy.metallib

129
README.md
View File

@ -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. 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/). [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.
```

View File

@ -31,6 +31,7 @@ import (
type Stream struct { type Stream struct {
orig *mp3.Decoder orig *mp3.Decoder
resampling *convert.Resampling resampling *convert.Resampling
sampleRate int
} }
// Read is implementation of io.Reader's Read. // Read is implementation of io.Reader's Read.
@ -57,6 +58,11 @@ func (s *Stream) Length() int64 {
return s.orig.Length() 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 decodes an MP3 source and returns a decoded stream.
// //
// DecodeWithoutResampling returns error when decoding fails or IO error happens. // DecodeWithoutResampling returns error when decoding fails or IO error happens.
@ -73,6 +79,7 @@ func DecodeWithoutResampling(src io.Reader) (*Stream, error) {
s := &Stream{ s := &Stream{
orig: d, orig: d,
resampling: nil, resampling: nil,
sampleRate: d.SampleRate(),
} }
return s, nil 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. // A Stream doesn't close src even if src implements io.Closer.
// Closing the source is src owner's responsibility. // 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) { func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) {
d, err := mp3.NewDecoder(src) d, err := mp3.NewDecoder(src)
if err != nil { if err != nil {
@ -100,6 +110,7 @@ func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) {
s := &Stream{ s := &Stream{
orig: d, orig: d,
resampling: r, resampling: r,
sampleRate: sampleRate,
} }
return s, nil return s, nil
} }

View File

@ -27,50 +27,48 @@ import (
// Stream is a decoded audio stream. // Stream is a decoded audio stream.
type Stream struct { type Stream struct {
decoded io.ReadSeeker readSeeker io.ReadSeeker
size int64 length int64
sampleRate int
} }
// Read is implementation of io.Reader's Read. // Read is implementation of io.Reader's Read.
func (s *Stream) Read(p []byte) (int, error) { 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. // Seek is implementation of io.Seeker's Seek.
// //
// Note that Seek can take long since decoding is a relatively heavy task. // Note that Seek can take long since decoding is a relatively heavy task.
func (s *Stream) Seek(offset int64, whence int) (int64, error) { 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. // Length returns the size of decoded stream in bytes.
// //
// If the source is not io.Seeker, Length returns 0. // If the source is not io.Seeker, Length returns 0.
func (s *Stream) Length() int64 { func (s *Stream) Length() int64 {
return s.size return s.length
} }
type decoder interface { // SampleRate returns the sample rate of the decoded stream.
Read([]float32) (int, error) func (s *Stream) SampleRate() int {
SetPosition(int64) error return s.sampleRate
Length() int64
Channels() int
SampleRate() int
} }
type decoded struct { type i16Stream struct {
totalBytes int totalBytes int
posInBytes int posInBytes int
decoder decoder vorbisReader *oggvorbis.Reader
decoderr io.Reader i16Reader io.Reader
} }
func (d *decoded) Read(b []byte) (int, error) { func (s *i16Stream) Read(b []byte) (int, error) {
if d.decoderr == nil { if s.i16Reader == nil {
d.decoderr = convert.NewReaderFromFloat32Reader(d.decoder) s.i16Reader = convert.NewReaderFromFloat32Reader(s.vorbisReader)
} }
l := d.totalBytes - d.posInBytes l := s.totalBytes - s.posInBytes
if l > len(b) { if l > len(b) {
l = len(b) l = len(b)
} }
@ -79,7 +77,7 @@ func (d *decoded) Read(b []byte) (int, error) {
} }
retry: retry:
n, err := d.decoderr.Read(b[:l]) n, err := s.i16Reader.Read(b[:l])
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return 0, err return 0, err
} }
@ -88,59 +86,63 @@ retry:
goto retry goto retry
} }
d.posInBytes += n s.posInBytes += n
if d.posInBytes == d.totalBytes || err == io.EOF { if s.posInBytes == s.totalBytes || err == io.EOF {
return n, io.EOF return n, io.EOF
} }
return n, nil 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) next := int64(0)
switch whence { switch whence {
case io.SeekStart: case io.SeekStart:
next = offset next = offset
case io.SeekCurrent: case io.SeekCurrent:
next = int64(d.posInBytes) + offset next = int64(s.posInBytes) + offset
case io.SeekEnd: case io.SeekEnd:
next = int64(d.totalBytes) + offset next = int64(s.totalBytes) + offset
} }
// pos should be always even // pos should be always even
next = next / 2 * 2 next = next / 2 * 2
d.posInBytes = int(next) s.posInBytes = int(next)
if err := d.decoder.SetPosition(next / int64(d.decoder.Channels()) / 2); err != nil { if err := s.vorbisReader.SetPosition(next / int64(s.vorbisReader.Channels()) / 2); err != nil {
return 0, err return 0, err
} }
d.decoderr = nil s.i16Reader = nil
return next, nil return next, nil
} }
func (d *decoded) Length() int64 { func (s *i16Stream) Length() int64 {
return int64(d.totalBytes) return int64(s.totalBytes)
} }
// decode accepts an ogg stream and returns a decorded stream. // 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) r, err := oggvorbis.NewReader(in)
if err != nil { if err != nil {
return nil, 0, 0, err 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. // TODO: r.Length() returns 0 when the format is unknown.
// Should we check that? // Should we check that?
totalBytes: int(r.Length()) * r.Channels() * 2, // 2 means 16bit per sample. totalBytes: int(r.Length()) * r.Channels() * 2, // 2 means 16bit per sample.
posInBytes: 0, posInBytes: 0,
decoder: r, vorbisReader: r,
} }
if _, ok := in.(io.Seeker); ok { 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 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 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. // 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. // A Stream doesn't close src even if src implements io.Closer.
// Closing the source is src owner's responsibility. // Closing the source is src owner's responsibility.
func DecodeWithoutResampling(src io.Reader) (*Stream, error) { func DecodeWithoutResampling(src io.Reader) (*Stream, error) {
decoded, channelCount, _, err := decode(src) i16Stream, channelCount, sampleRate, err := decode(src)
if err != nil { if err != nil {
return nil, err 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 = i16Stream
} length := i16Stream.Length()
var s io.ReadSeeker = decoded
size := decoded.Length()
if channelCount == 1 { if channelCount == 1 {
s = convert.NewStereo16(s, true, false) s = convert.NewStereo16(s, true, false)
size *= 2 length *= 2
} }
stream := &Stream{ stream := &Stream{
decoded: s, readSeeker: s,
size: size, length: length,
sampleRate: sampleRate,
} }
return stream, nil 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. // A Stream doesn't close src even if src implements io.Closer.
// Closing the source is src owner's responsibility. // 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) { func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) {
decoded, channelCount, origSampleRate, err := decode(src) i16Stream, channelCount, origSampleRate, err := decode(src)
if err != nil { if err != nil {
return nil, err 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 = i16Stream
} length := i16Stream.Length()
var s io.ReadSeeker = decoded
size := decoded.Length()
if channelCount == 1 { if channelCount == 1 {
s = convert.NewStereo16(s, true, false) s = convert.NewStereo16(s, true, false)
size *= 2 length *= 2
} }
if origSampleRate != sampleRate { if origSampleRate != sampleRate {
r := convert.NewResampling(s, size, origSampleRate, sampleRate) r := convert.NewResampling(s, length, origSampleRate, sampleRate)
s = r 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 return stream, nil
} }

View File

@ -50,16 +50,16 @@ func TestMono(t *testing.T) {
} }
// Stream decoded by audio/vorbis.DecodeWithSampleRate() is always 16bit stereo. // Stream decoded by audio/vorbis.DecodeWithSampleRate() is always 16bit stereo.
got := s.Length()
// On the other hand, the original vorbis package is monoral. // On the other hand, the original vorbis package is monoral.
// As Length() represents the number of samples, // As Length() represents the number of samples,
// this needs to be doubled by 2 (= bytes in 16bits). // this needs to be doubled by 2 (= bytes in 16bits).
want := r.Length() * 2 * 2 if got, want := s.Length(), r.Length()*2*2; got != want {
if got != want {
t.Errorf("s.Length(): got: %d, want: %d", 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) { func TestTooShort(t *testing.T) {
@ -70,11 +70,13 @@ func TestTooShort(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
got := s.Length() if got, want := s.Length(), int64(79424); got != want {
want := int64(79424)
if got != want {
t.Errorf("s.Length(): got: %d, want: %d", 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 { type reader struct {
@ -93,9 +95,11 @@ func TestNonSeeker(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
got := s.Length() if got, want := s.Length(), int64(0); got != want {
want := int64(0)
if got != want {
t.Errorf("s.Length(): got: %d, want: %d", 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)
}
} }

View File

@ -26,8 +26,9 @@ import (
// Stream is a decoded audio stream. // Stream is a decoded audio stream.
type Stream struct { type Stream struct {
inner io.ReadSeeker inner io.ReadSeeker
size int64 size int64
sampleRate int
} }
// Read is implementation of io.Reader's Read. // Read is implementation of io.Reader's Read.
@ -49,6 +50,11 @@ func (s *Stream) Length() int64 {
return s.size return s.size
} }
// SampleRate returns the sample rate of the decoded stream.
func (s *Stream) SampleRate() int {
return s.sampleRate
}
type stream struct { type stream struct {
src io.Reader src io.Reader
headerSize int64 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. // A Stream doesn't close src even if src implements io.Closer.
// Closing the source is src owner's responsibility. // Closing the source is src owner's responsibility.
func DecodeWithoutResampling(src io.Reader) (*Stream, error) { func DecodeWithoutResampling(src io.Reader) (*Stream, error) {
s, _, err := decode(src) s, err := decode(src)
if err != nil { if err != nil {
return nil, err 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. // A Stream doesn't close src even if src implements io.Closer.
// Closing the source is src owner's responsibility. // 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) { func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) {
s, origSampleRate, err := decode(src) s, err := decode(src)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if sampleRate == origSampleRate { if sampleRate == s.sampleRate {
return s, nil return s, nil
} }
r := convert.NewResampling(s.inner, s.size, origSampleRate, sampleRate) r := convert.NewResampling(s.inner, s.size, s.sampleRate, sampleRate)
return &Stream{ return &Stream{
inner: r, inner: r,
size: r.Length(), size: r.Length(),
sampleRate: sampleRate,
}, nil }, nil
} }
func decode(src io.Reader) (*Stream, int, error) { func decode(src io.Reader) (*Stream, error) {
buf := make([]byte, 12) buf := make([]byte, 12)
n, err := io.ReadFull(src, buf) n, err := io.ReadFull(src, buf)
if n != len(buf) { if n != len(buf) {
return nil, 0, fmt.Errorf("wav: invalid header") return nil, fmt.Errorf("wav: invalid header")
} }
if err != nil { if err != nil {
return nil, 0, err return nil, err
} }
if !bytes.Equal(buf[0:4], []byte("RIFF")) { 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")) { 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 // Read chunks
@ -178,10 +188,10 @@ chunks:
buf := make([]byte, 8) buf := make([]byte, 8)
n, err := io.ReadFull(src, buf) n, err := io.ReadFull(src, buf)
if n != len(buf) { if n != len(buf) {
return nil, 0, fmt.Errorf("wav: invalid header") return nil, fmt.Errorf("wav: invalid header")
} }
if err != nil { if err != nil {
return nil, 0, err return nil, err
} }
headerSize += 8 headerSize += 8
size := int64(buf[4]) | int64(buf[5])<<8 | int64(buf[6])<<16 | int64(buf[7])<<24 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 ")): case bytes.Equal(buf[0:4], []byte("fmt ")):
// Size of 'fmt' header is usually 16, but can be more than 16. // Size of 'fmt' header is usually 16, but can be more than 16.
if size < 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) buf := make([]byte, size)
n, err := io.ReadFull(src, buf) n, err := io.ReadFull(src, buf)
if n != len(buf) { if n != len(buf) {
return nil, 0, fmt.Errorf("wav: invalid header") return nil, fmt.Errorf("wav: invalid header")
} }
if err != nil { if err != nil {
return nil, 0, err return nil, err
} }
format := int(buf[0]) | int(buf[1])<<8 format := int(buf[0]) | int(buf[1])<<8
if format != 1 { 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 channelCount := int(buf[2]) | int(buf[3])<<8
switch channelCount { switch channelCount {
@ -210,11 +220,11 @@ chunks:
case 2: case 2:
mono = false mono = false
default: 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 bitsPerSample = int(buf[14]) | int(buf[15])<<8
if bitsPerSample != 8 && bitsPerSample != 16 { 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 sampleRate = int(buf[4]) | int(buf[5])<<8 | int(buf[6])<<16 | int(buf[7])<<24
headerSize += size headerSize += size
@ -225,10 +235,10 @@ chunks:
buf := make([]byte, size) buf := make([]byte, size)
n, err := io.ReadFull(src, buf) n, err := io.ReadFull(src, buf)
if n != len(buf) { if n != len(buf) {
return nil, 0, fmt.Errorf("wav: invalid header") return nil, fmt.Errorf("wav: invalid header")
} }
if err != nil { if err != nil {
return nil, 0, err return nil, err
} }
headerSize += size headerSize += size
} }
@ -249,7 +259,11 @@ chunks:
dataSize *= 2 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. // Decode decodes WAV (RIFF) data to playable stream.

View File

@ -23,10 +23,10 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
exec "golang.org/x/sys/execabs"
"golang.org/x/tools/go/packages" "golang.org/x/tools/go/packages"
) )

View File

@ -18,13 +18,13 @@ import (
_ "embed" _ "embed"
"fmt" "fmt"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"runtime/debug" "runtime/debug"
// Add a dependency on gomobile in order to get the version via debug.ReadBuildInfo(). // Add a dependency on gomobile in order to get the version via debug.ReadBuildInfo().
_ "github.com/ebitengine/gomobile/geom" _ "github.com/ebitengine/gomobile/geom"
exec "golang.org/x/sys/execabs"
) )
//go:embed gobind.go //go:embed gobind.go

View File

@ -27,12 +27,12 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"text/template" "text/template"
"unicode" "unicode"
exec "golang.org/x/sys/execabs"
"golang.org/x/tools/go/packages" "golang.org/x/tools/go/packages"
) )

View File

@ -179,7 +179,7 @@ func builtinShader(filter builtinshader.Filter, address builtinshader.Address) *
return s return s
} }
src := builtinshader.Shader(filter, address, true) src := builtinshader.ShaderSource(filter, address, true)
s, err := ebiten.NewShader(src) s, err := ebiten.NewShader(src)
if err != nil { if err != nil {
panic(fmt.Sprintf("colorm: NewShader for a built-in shader failed: %v", err)) panic(fmt.Sprintf("colorm: NewShader for a built-in shader failed: %v", err))

View File

@ -72,11 +72,11 @@ type DrawTrianglesOptions struct {
// FillRule indicates the rule how an overlapped region is rendered. // 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. // 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. // See examples/vector for actual usages.
// //
// The default (zero) value is ebiten.FillAll. // The default (zero) value is ebiten.FillRuleFillAll.
FillRule ebiten.FillRule FillRule ebiten.FillRule
// AntiAlias indicates whether the rendering uses anti-alias or not. // AntiAlias indicates whether the rendering uses anti-alias or not.

32
ebiten_test.go Normal file
View 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)
}
}

View File

@ -40,6 +40,7 @@ func OpenFile(path string) (ReadSeekCloser, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer res.Body.Close()
body, err := io.ReadAll(res.Body) body, err := io.ReadAll(res.Body)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -171,7 +171,7 @@ func NewPlayer(game *Game, audioContext *audio.Context, musicType musicType) (*P
player.audioPlayer.Play() player.audioPlayer.Play()
go func() { go func() {
s, err := wav.DecodeWithSampleRate(sampleRate, bytes.NewReader(raudio.Jab_wav)) s, err := wav.DecodeWithoutResampling(bytes.NewReader(raudio.Jab_wav))
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
return return

View 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)
}

View File

@ -0,0 +1 @@
This is a dummy .fxc file to trick Go's embed package.

View 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
}

View 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

View 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)
}
}

View File

@ -0,0 +1 @@
This is a dummy .metallib file to trick Go's embed package.

View 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
}

View 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

View 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
}

View 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
}

View 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
}

View File

@ -141,14 +141,14 @@ func drawEbitenText(screen *ebiten.Image, x, y int, aa bool, line bool) {
op := &ebiten.DrawTrianglesOptions{} op := &ebiten.DrawTrianglesOptions{}
op.AntiAlias = aa op.AntiAlias = aa
// For strokes (AppendVerticesAndIndicesForStroke), FillAll and NonZero work. // For strokes (AppendVerticesAndIndicesForStroke), FillRuleFillAll and FillRuleNonZero work.
// //
// For filling (AppendVerticesAndIndicesForFilling), NonZero and EvenOdd work. // For filling (AppendVerticesAndIndicesForFilling), FillRuleNonZero and FillRuleEvenOdd work.
// NonZero and EvenOdd differ when rendering a complex polygons with self-intersections and/or holes. // 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 . // 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. // For simplicity, this example always uses FillRuleNonZero, whichever strokes or filling is done.
op.FillRule = ebiten.NonZero op.FillRule = ebiten.FillRuleNonZero
screen.DrawTriangles(vs, is, whiteSubImage, op) 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 := &ebiten.DrawTrianglesOptions{}
op.AntiAlias = aa op.AntiAlias = aa
op.FillRule = ebiten.NonZero op.FillRule = ebiten.FillRuleNonZero
screen.DrawTriangles(vs, is, whiteSubImage, op) 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 := &ebiten.DrawTrianglesOptions{}
op.AntiAlias = aa op.AntiAlias = aa
op.FillRule = ebiten.NonZero op.FillRule = ebiten.FillRuleNonZero
screen.DrawTriangles(vs, is, whiteSubImage, op) 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 := &ebiten.DrawTrianglesOptions{}
op.AntiAlias = aa op.AntiAlias = aa
op.FillRule = ebiten.NonZero op.FillRule = ebiten.FillRuleNonZero
screen.DrawTriangles(vs, is, whiteSubImage, op) screen.DrawTriangles(vs, is, whiteSubImage, op)
} }

View File

@ -21,44 +21,14 @@ import (
"sync/atomic" "sync/atomic"
"github.com/hajimehoshi/ebiten/v2/internal/atlas" "github.com/hajimehoshi/ebiten/v2/internal/atlas"
"github.com/hajimehoshi/ebiten/v2/internal/builtinshader"
"github.com/hajimehoshi/ebiten/v2/internal/ui" "github.com/hajimehoshi/ebiten/v2/internal/ui"
) )
const screenShaderSrc = `//kage:unit pixels var screenFilterEnabled atomic.Bool
package main func init() {
screenFilterEnabled.Store(true)
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
// Blend source colors in a square region, which size is 1/scale.
scale := imageDstSize()/imageSrc0Size()
pos := srcPos
p0 := pos - 1/2.0/scale
p1 := pos + 1/2.0/scale
// Texels must be in the source rect, so it is not necessary to check.
c0 := imageSrc0UnsafeAt(p0)
c1 := imageSrc0UnsafeAt(vec2(p1.x, p0.y))
c2 := imageSrc0UnsafeAt(vec2(p0.x, p1.y))
c3 := imageSrc0UnsafeAt(p1)
// p is the p1 value in one pixel assuming that the pixel's upper-left is (0, 0) and the lower-right is (1, 1).
rate := clamp(fract(p1)*scale, 0, 1)
return mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y)
}
`
var screenFilterEnabled = int32(1)
func isScreenFilterEnabled() bool {
return atomic.LoadInt32(&screenFilterEnabled) != 0
}
func setScreenFilterEnabled(enabled bool) {
v := int32(0)
if enabled {
v = 1
}
atomic.StoreInt32(&screenFilterEnabled, v)
} }
type gameForUI struct { type gameForUI struct {
@ -76,7 +46,7 @@ func newGameForUI(game Game, transparent bool) *gameForUI {
transparent: transparent, transparent: transparent,
} }
s, err := NewShader([]byte(screenShaderSrc)) s, err := NewShader(builtinshader.ScreenShaderSource)
if err != nil { if err != nil {
panic(fmt.Sprintf("ebiten: compiling the screen shader failed: %v", err)) panic(fmt.Sprintf("ebiten: compiling the screen shader failed: %v", err))
} }
@ -167,7 +137,7 @@ func (g *gameForUI) DrawFinalScreen(scale, offsetX, offsetY float64) {
} }
switch { switch {
case !isScreenFilterEnabled(), math.Floor(scale) == scale: case !screenFilterEnabled.Load(), math.Floor(scale) == scale:
op := &DrawImageOptions{} op := &DrawImageOptions{}
op.GeoM = geoM op.GeoM = geoM
g.screen.DrawImage(g.offscreen, op) g.screen.DrawImage(g.offscreen, op)

22
go.mod
View File

@ -1,28 +1,28 @@
module github.com/hajimehoshi/ebiten/v2 module github.com/hajimehoshi/ebiten/v2
go 1.18 go 1.19
require ( 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/hideconsole v1.0.0
github.com/ebitengine/oto/v3 v3.3.0-alpha.1 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/gen2brain/mpeg v0.3.2-0.20240412154320-a2ac4fc8a46f
github.com/go-text/typesetting v0.1.1-0.20240402181327-ced1d6822703 github.com/go-text/typesetting v0.1.1
github.com/hajimehoshi/bitmapfont/v3 v3.0.0 github.com/hajimehoshi/bitmapfont/v3 v3.2.0-alpha.1
github.com/hajimehoshi/go-mp3 v0.3.4 github.com/hajimehoshi/go-mp3 v0.3.4
github.com/jakecoffman/cp v1.2.1 github.com/jakecoffman/cp v1.2.1
github.com/jezek/xgb v1.1.1 github.com/jezek/xgb v1.1.1
github.com/jfreymuth/oggvorbis v1.0.5 github.com/jfreymuth/oggvorbis v1.0.5
github.com/kisielk/errcheck v1.7.0 github.com/kisielk/errcheck v1.7.0
golang.org/x/image v0.15.0 golang.org/x/image v0.16.0
golang.org/x/sync v0.6.0 golang.org/x/sync v0.7.0
golang.org/x/sys v0.18.0 golang.org/x/sys v0.20.0
golang.org/x/text v0.14.0 golang.org/x/text v0.15.0
golang.org/x/tools v0.19.0 golang.org/x/tools v0.21.0
) )
require ( require (
github.com/jfreymuth/vorbis v1.0.2 // indirect 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
View File

@ -1,18 +1,18 @@
github.com/ebitengine/gomobile v0.0.0-20240329170434-1771503ff0a8 h1:5e8X7WEdOWrjrKvgaWF6PRnDvJicfrkEnwAkWtMN74g= github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 h1:48bCqKTuD7Z0UovDfvpCn7wZ0GUZ+yosIteNDthn3FU=
github.com/ebitengine/gomobile v0.0.0-20240329170434-1771503ff0a8/go.mod h1:tWboRRNagZwwwis4QIgEFG1ZNFwBJ3LAhSLAXAAxobQ= 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 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE=
github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A= 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 h1:J2nBmQwPLKc4+yLObytq1jKNydI96l6EjZfgefiqGbk=
github.com/ebitengine/oto/v3 v3.3.0-alpha.1/go.mod h1:T2/VV0UWG97GEEf4kORMU2nCneYT/YmwSTxPutSVaUg= 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.2 h1:+Kyr9n4eXAGMzhtWJxfdQ7AzGn0+6ZWihfCCxul3Dso=
github.com/ebitengine/purego v0.8.0-alpha.1/go.mod h1:y8L+ZRLphbdPW2xs41fur/KaW57yTzrFsqsclHyHrTM= 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 h1:ysqRe+lvUiL0dH5XzkH0Bz68bFMPJ4f5Si4L/HD9SGk=
github.com/gen2brain/mpeg v0.3.2-0.20240412154320-a2ac4fc8a46f/go.mod h1:i/ebyRRv/IoHixuZ9bElZnXbmfoUVPGQpdsJ4sVuX38= 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 h1:bGAesCuo85nXnEN5LmFMVGAGpGkCPtHrZLi//qD7EJo=
github.com/go-text/typesetting v0.1.1-0.20240402181327-ced1d6822703/go.mod h1:2+owI/sxa73XA581LAzVuEBZ3WEEV2pXeDswCH/3i1I= github.com/go-text/typesetting v0.1.1/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI=
github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66 h1:GUrm65PQPlhFSKjLPGOZNPNxLCybjzjYBzjfoBGaDUY= github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04 h1:zBx+p/W2aQYtNuyZNcTfinWvXBQwYtDfme051PR/lAY=
github.com/hajimehoshi/bitmapfont/v3 v3.0.0 h1:r2+6gYK38nfztS/et50gHAswb9hXgxXECYgE8Nczmi4= github.com/hajimehoshi/bitmapfont/v3 v3.2.0-alpha.1 h1:GLoMNCbvXOd39tFkqk9w/MI0xSLJaDzEOOl8mT1ILtI=
github.com/hajimehoshi/bitmapfont/v3 v3.0.0/go.mod h1:+CxxG+uMmgU4mI2poq944i3uZ6UYFfAkj9V6WqmuvZA= 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 h1:NUP7pBYH8OguP4diaTZ9wJbUbk3tC0KlfzsEpWmYj68=
github.com/hajimehoshi/go-mp3 v0.3.4/go.mod h1:fRtZraRFcWb0pu7ok0LqyFhCUrPeMsGRSVop0eemFmo= 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= 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/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 h1:+SbscKmWJ5mOK/bO1zS60F5I9WwZDWOfRsC4RwfwRV0=
github.com/kisielk/errcheck v1.7.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= 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= 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-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/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.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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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-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-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.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-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.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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-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-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-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-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-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.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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-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.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.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.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.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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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-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.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.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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= 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= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -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. // 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). // FillRule is the rule whether an overlapped region is rendered with DrawTriangles(Shader).
type FillRule int 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 ( const (
// FillAll indicates all the triangles are rendered regardless of overlaps. // 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. // 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. // 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. // 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. // 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. // 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. // 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. // 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. // See examples/vector for actual usages.
// //
// The default (zero) value is FillAll. // The default (zero) value is FillRuleFillAll.
FillRule FillRule FillRule FillRule
// AntiAlias indicates whether the rendering uses anti-alias or not. // 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. // 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. // 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. // See examples/vector for actual usages.
// //
// The default (zero) value is FillAll. // The default (zero) value is FillRuleFillAll.
FillRule FillRule FillRule FillRule
// AntiAlias indicates whether the rendering uses anti-alias or not. // 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 = i.tmpUniforms[:0]
i.tmpUniforms = shader.appendUniforms(i.tmpUniforms, options.Uniforms) 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. // SubImage returns an image representing the portion of the image p visible through r.

View File

@ -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. // Draw all the vertices once. The even-odd rule is applied for all the vertices once.
dst := ebiten.NewImage(16, 16) dst := ebiten.NewImage(16, 16)
op := &ebiten.DrawTrianglesOptions{ op := &ebiten.DrawTrianglesOptions{
FillRule: ebiten.EvenOdd, FillRule: ebiten.FillRuleEvenOdd,
} }
dst.DrawTriangles(append(append(vs0, vs1...), vs2...), append(append(is0, is1...), is2...), emptySubImage, op) dst.DrawTriangles(append(append(vs0, vs1...), vs2...), append(append(is0, is1...), is2...), emptySubImage, op)
for j := 0; j < 16; j++ { for j := 0; j < 16; j++ {
@ -2794,15 +2794,15 @@ func TestImageEvenOdd(t *testing.T) {
} }
func TestImageFillRule(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 fillRule := fillRule
var name string var name string
switch fillRule { switch fillRule {
case ebiten.FillAll: case ebiten.FillRuleFillAll:
name = "FillAll" name = "FillAll"
case ebiten.NonZero: case ebiten.FillRuleNonZero:
name = "NonZero" name = "NonZero"
case ebiten.EvenOdd: case ebiten.FillRuleEvenOdd:
name = "EvenOdd" name = "EvenOdd"
} }
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
@ -2885,11 +2885,11 @@ func TestImageFillRule(t *testing.T) {
var want color.RGBA var want color.RGBA
switch { switch {
case 2 <= i && i < 7 && 2 <= j && j < 7: case 2 <= i && i < 7 && 2 <= j && j < 7:
if fillRule != ebiten.EvenOdd { if fillRule != ebiten.FillRuleEvenOdd {
want = color.RGBA{G: 0xff, A: 0xff} want = color.RGBA{G: 0xff, A: 0xff}
} }
case 9 <= i && i < 14 && 9 <= j && j < 14: case 9 <= i && i < 14 && 9 <= j && j < 14:
if fillRule == ebiten.FillAll { if fillRule == ebiten.FillRuleFillAll {
want = color.RGBA{B: 0xff, A: 0xff} want = color.RGBA{B: 0xff, A: 0xff}
} }
case 1 <= i && i < 15 && 1 <= j && j < 15: case 1 <= i && i < 15 && 1 <= j && j < 15:
@ -2922,11 +2922,11 @@ func TestImageFillRule(t *testing.T) {
var want color.RGBA var want color.RGBA
switch { switch {
case 3 <= i && i < 8 && 3 <= j && j < 8: case 3 <= i && i < 8 && 3 <= j && j < 8:
if fillRule != ebiten.EvenOdd { if fillRule != ebiten.FillRuleEvenOdd {
want = color.RGBA{G: 0xff, A: 0xff} want = color.RGBA{G: 0xff, A: 0xff}
} }
case 10 <= i && i < 15 && 10 <= j && j < 15: case 10 <= i && i < 15 && 10 <= j && j < 15:
if fillRule == ebiten.FillAll { if fillRule == ebiten.FillRuleFillAll {
want = color.RGBA{B: 0xff, A: 0xff} want = color.RGBA{B: 0xff, A: 0xff}
} }
case 2 <= i && i < 16 && 2 <= j && j < 16: case 2 <= i && i < 16 && 2 <= j && j < 16:
@ -3726,7 +3726,7 @@ func TestImageTooManyConstantBuffersInDirectX(t *testing.T) {
dst0 := ebiten.NewImage(16, 16) dst0 := ebiten.NewImage(16, 16)
dst1 := ebiten.NewImage(16, 16) dst1 := ebiten.NewImage(16, 16)
op := &ebiten.DrawTrianglesOptions{ op := &ebiten.DrawTrianglesOptions{
FillRule: ebiten.EvenOdd, FillRule: ebiten.FillRuleEvenOdd,
} }
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
dst0.DrawTriangles(vs, is, src, op) dst0.DrawTriangles(vs, is, src, op)

View File

@ -14,16 +14,12 @@
package atlas package atlas
import (
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
)
const ( const (
BaseCountToPutOnSourceBackend = baseCountToPutOnSourceBackend BaseCountToPutOnSourceBackend = baseCountToPutOnSourceBackend
) )
func PutImagesOnSourceBackendForTesting(graphicsDriver graphicsdriver.Graphics) { func PutImagesOnSourceBackendForTesting() {
putImagesOnSourceBackend(graphicsDriver) putImagesOnSourceBackend()
} }
var ( var (

View File

@ -36,13 +36,6 @@ var (
maxSize = 0 maxSize = 0
) )
func max(a, b int) int {
if a > b {
return a
}
return b
}
func min(a, b int) int { func min(a, b int) int {
if a < b { if a < b {
return a return a
@ -81,7 +74,7 @@ func flushDeferred() {
// Actual time duration is increased in an exponential way for each usage as a rendering target. // Actual time duration is increased in an exponential way for each usage as a rendering target.
const baseCountToPutOnSourceBackend = 10 const baseCountToPutOnSourceBackend = 10
func putImagesOnSourceBackend(graphicsDriver graphicsdriver.Graphics) { func putImagesOnSourceBackend() {
// The counter usedAsDestinationCount is updated at most once per frame (#2676). // The counter usedAsDestinationCount is updated at most once per frame (#2676).
imagesUsedAsDestination.forEach(func(i *Image) { imagesUsedAsDestination.forEach(func(i *Image) {
// This counter is not updated when the backend is created in this frame. // This counter is not updated when the backend is created in this frame.
@ -97,7 +90,7 @@ func putImagesOnSourceBackend(graphicsDriver graphicsdriver.Graphics) {
i.usedAsSourceCount++ i.usedAsSourceCount++
} }
if int64(i.usedAsSourceCount) >= int64(baseCountToPutOnSourceBackend*(1<<uint(min(i.usedAsDestinationCount, 31)))) { if int64(i.usedAsSourceCount) >= int64(baseCountToPutOnSourceBackend*(1<<uint(min(i.usedAsDestinationCount, 31)))) {
i.putOnSourceBackend(graphicsDriver) i.putOnSourceBackend()
i.usedAsSourceCount = 0 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) vs := quadVertices(0, 0, float32(sw), float32(sh), 0, 0, float32(sw), float32(sh), 1, 1, 1, 1)
is := graphics.QuadIndices() is := graphics.QuadIndices()
dr := image.Rect(0, 0, sw, sh) 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.Dispose()
b.image = newImg b.image = newImg
@ -182,7 +175,7 @@ func newClearedImage(width, height int, screen bool) *graphicscommand.Image {
func clearImage(i *graphicscommand.Image, region image.Rectangle) { 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) 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() 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) { func (b *backend) clearPixels(region image.Rectangle) {
@ -355,11 +348,11 @@ func (i *Image) ensureIsolatedFromSource(backends []*backend) {
is := graphics.QuadIndices() is := graphics.QuadIndices()
dr := image.Rect(0, 0, i.width, i.height) 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) newI.moveTo(i)
} }
func (i *Image) putOnSourceBackend(graphicsDriver graphicsdriver.Graphics) { func (i *Image) putOnSourceBackend() {
if i.backend == nil { if i.backend == nil {
i.allocate(nil, true) i.allocate(nil, true)
return 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) graphics.QuadVertices(vs, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
is := graphics.QuadIndices() is := graphics.QuadIndices()
dr := image.Rect(0, 0, i.width, i.height) 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) newI.moveTo(i)
i.usedAsSourceCount = 0 i.usedAsSourceCount = 0
@ -464,6 +457,7 @@ func (i *Image) drawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertice
return return
} }
// This slice is not escaped to the heap. This can be checked by `go build -gcflags=-m`.
backends := make([]*backend, 0, len(srcs)) backends := make([]*backend, 0, len(srcs))
for _, src := range srcs { for _, src := range srcs {
if src == nil { if src == nil {
@ -1072,7 +1066,7 @@ func BeginFrame(graphicsDriver graphicsdriver.Graphics) error {
} }
flushDeferred() flushDeferred()
putImagesOnSourceBackend(graphicsDriver) putImagesOnSourceBackend()
return nil return nil
} }

View File

@ -105,7 +105,7 @@ func TestEnsureIsolatedFromSourceBackend(t *testing.T) {
vs := quadVertices(size/2, size/2, size/4, size/4, 1) vs := quadVertices(size/2, size/2, size/4, size/4, 1)
is := graphics.QuadIndices() is := graphics.QuadIndices()
dr := image.Rect(0, 0, size, size) 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 { if got, want := img4.IsOnSourceBackendForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", 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. // img5 is not allocated now, but is allocated at DrawTriangles.
vs = quadVertices(0, 0, size/2, size/2, 1) vs = quadVertices(0, 0, size/2, size/2, 1)
dr = image.Rect(0, 0, size/2, size/2) 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 { if got, want := img3.IsOnSourceBackendForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", 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. // Check further drawing doesn't cause panic.
// This bug was fixed by 03dcd948. // This bug was fixed by 03dcd948.
vs = quadVertices(0, 0, size/2, size/2, 1) 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) { func TestReputOnSourceBackend(t *testing.T) {
@ -191,7 +191,7 @@ func TestReputOnSourceBackend(t *testing.T) {
// Render onto img1. The count should not matter. // Render onto img1. The count should not matter.
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
vs := quadVertices(size, size, 0, 0, 1) 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 { if got, want := img1.IsOnSourceBackendForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", 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. // 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. // Then, img1 requires longer time to recover to be on a texture atlas again.
for i := 0; i < atlas.BaseCountToPutOnSourceBackend*2; i++ { for i := 0; i < atlas.BaseCountToPutOnSourceBackend*2; i++ {
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting()) atlas.PutImagesOnSourceBackendForTesting()
vs := quadVertices(size, size, 0, 0, 1) 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 { if got, want := img1.IsOnSourceBackendForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
} }
// Finally, img1 is on a source backend. // Finally, img1 is on a source backend.
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting()) atlas.PutImagesOnSourceBackendForTesting()
vs := quadVertices(size, size, 0, 0, 1) 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 { if got, want := img1.IsOnSourceBackendForTesting(), true; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting()) atlas.PutImagesOnSourceBackendForTesting()
pix = make([]byte, 4*size*size) pix = make([]byte, 4*size*size)
ok, err := img1.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix, image.Rect(0, 0, 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) 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 { if got, want := img1.IsOnSourceBackendForTesting(), true; got != want {
t.Errorf("got: %v, want: %v", 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. // Use img1 as a render target again. The count should not matter.
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
vs := quadVertices(size, size, 0, 0, 1) 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 { if got, want := img1.IsOnSourceBackendForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", 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. // Use img1 as a render source, but call WritePixels.
// Now use 4x count as img1 became an isolated image again. // Now use 4x count as img1 became an isolated image again.
for i := 0; i < atlas.BaseCountToPutOnSourceBackend*4; i++ { 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)) img1.WritePixels(make([]byte, 4*size*size), image.Rect(0, 0, size, size))
vs := quadVertices(size, size, 0, 0, 1) 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 { if got, want := img1.IsOnSourceBackendForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", 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. // img1 is not on an atlas due to WritePixels.
vs = quadVertices(size, size, 0, 0, 1) 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 { if got, want := img1.IsOnSourceBackendForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", 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. // Use img3 as a render source. As img3 is volatile, img3 is never on an atlas.
for i := 0; i < atlas.BaseCountToPutOnSourceBackend*2; i++ { for i := 0; i < atlas.BaseCountToPutOnSourceBackend*2; i++ {
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting()) atlas.PutImagesOnSourceBackendForTesting()
vs := quadVertices(size, size, 0, 0, 1) 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 { if got, want := img3.IsOnSourceBackendForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", 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) vs := quadVertices(w, h, 0, 0, 1)
is := graphics.QuadIndices() is := graphics.QuadIndices()
dr := image.Rect(0, 0, w, h) 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)) dst.WritePixels(pix, image.Rect(0, 0, w, h))
pix = make([]byte, 4*w*h) pix = make([]byte, 4*w*h)
@ -450,7 +450,7 @@ func TestSmallImages(t *testing.T) {
vs := quadVertices(w, h, 0, 0, 1) vs := quadVertices(w, h, 0, 0, 1)
is := graphics.QuadIndices() is := graphics.QuadIndices()
dr := image.Rect(0, 0, w, h) 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) pix = make([]byte, 4*w*h)
ok, err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix, image.Rect(0, 0, 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) vs := quadVertices(w, h, 0, 0, scale)
is := graphics.QuadIndices() is := graphics.QuadIndices()
dr := image.Rect(0, 0, dstW, dstH) 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) pix = make([]byte, 4*dstW*dstH)
ok, err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix, image.Rect(0, 0, 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) vs := quadVertices(size, size, 0, 0, 1)
is := graphics.QuadIndices() is := graphics.QuadIndices()
dr := image.Rect(0, 0, size, size) 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 { if got, want := src.IsOnSourceBackendForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
// Use src as a render source. // Use src as a render source.
for i := 0; i < atlas.BaseCountToPutOnSourceBackend/2; i++ { for i := 0; i < atlas.BaseCountToPutOnSourceBackend/2; i++ {
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting()) atlas.PutImagesOnSourceBackendForTesting()
vs := quadVertices(size, size, 0, 0, 1) 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 { if got, want := src.IsOnSourceBackendForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", got, want) t.Errorf("got: %v, want: %v", got, want)
} }
@ -632,7 +632,7 @@ func TestDeallocatedAndReputOnSourceBackend(t *testing.T) {
src.Deallocate() src.Deallocate()
// Confirm that PutImagesOnSourceBackendForTesting doesn't panic. // Confirm that PutImagesOnSourceBackendForTesting doesn't panic.
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting()) atlas.PutImagesOnSourceBackendForTesting()
} }
// Issue #1456 // Issue #1456
@ -656,7 +656,7 @@ func TestImageIsNotReputOnSourceBackendWithoutUsingAsSource(t *testing.T) {
// Call DrawTriangles multiple times. // Call DrawTriangles multiple times.
// The number of DrawTriangles doesn't matter as long as these are called in one frame. // The number of DrawTriangles doesn't matter as long as these are called in one frame.
for i := 0; i < 2; i++ { 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 { if got, want := src2.IsOnSourceBackendForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", 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. // Update the count without using src2 as a rendering source.
// This should not affect whether src2 is on an atlas or not. // This should not affect whether src2 is on an atlas or not.
for i := 0; i < atlas.BaseCountToPutOnSourceBackend; i++ { for i := 0; i < atlas.BaseCountToPutOnSourceBackend; i++ {
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting()) atlas.PutImagesOnSourceBackendForTesting()
if got, want := src2.IsOnSourceBackendForTesting(), false; got != want { if got, want := src2.IsOnSourceBackendForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", 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. // Update the count with using src2 as a rendering source.
for i := 0; i < atlas.BaseCountToPutOnSourceBackend; i++ { for i := 0; i < atlas.BaseCountToPutOnSourceBackend; i++ {
atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting()) atlas.PutImagesOnSourceBackendForTesting()
vs := quadVertices(size, size, 0, 0, 1) 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 { if got, want := src2.IsOnSourceBackendForTesting(), false; got != want {
t.Errorf("got: %v, want: %v", 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 { if got, want := src2.IsOnSourceBackendForTesting(), true; got != want {
t.Errorf("got: %v, want: %v", 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. // Use dst0 as a destination for a while.
for i := 0; i < 31; i++ { 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()) atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting())
} }
// Use dst0 as a source for a while. // 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. // 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++ { 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()) atlas.PutImagesOnSourceBackendForTesting(ui.Get().GraphicsDriverForTesting())
if dst0.IsOnSourceBackendForTesting() { if dst0.IsOnSourceBackendForTesting() {
t.Errorf("dst0 cannot be on a source backend: %d", i) t.Errorf("dst0 cannot be on a source backend: %d", i)
@ -834,17 +834,17 @@ func TestIteratingImagesToPutOnSourceBackend(t *testing.T) {
is := graphics.QuadIndices() is := graphics.QuadIndices()
dr := image.Rect(0, 0, w, h) dr := image.Rect(0, 0, w, h)
for _, img := range srcs { 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. // Use srcs as sources. This will register an image to imagesToPutOnSourceBackend.
// Check iterating the registered image works correctly. // Check iterating the registered image works correctly.
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
for _, src := range srcs { 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()
} }
} }

View File

@ -88,7 +88,7 @@ var (
func init() { func init() {
var wg errgroup.Group var wg errgroup.Group
wg.Go(func() error { 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 { if err != nil {
return fmt.Errorf("atlas: compiling the nearest shader failed: %w", err) return fmt.Errorf("atlas: compiling the nearest shader failed: %w", err)
} }
@ -96,7 +96,7 @@ func init() {
return nil return nil
}) })
wg.Go(func() error { 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 { if err != nil {
return fmt.Errorf("atlas: compiling the linear shader failed: %w", err) return fmt.Errorf("atlas: compiling the linear shader failed: %w", err)
} }
@ -104,13 +104,7 @@ func init() {
return nil return nil
}) })
wg.Go(func() error { wg.Go(func() error {
ir, err := graphics.CompileShader([]byte(`//kage:unit pixels ir, err := graphics.CompileShader([]byte(builtinshader.ClearShaderSource))
package main
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
return vec4(0)
}`))
if err != nil { if err != nil {
return fmt.Errorf("atlas: compiling the clear shader failed: %w", err) return fmt.Errorf("atlas: compiling the clear shader failed: %w", err)
} }

View File

@ -37,12 +37,12 @@ func TestShaderFillTwice(t *testing.T) {
dr := image.Rect(0, 0, w, h) dr := image.Rect(0, 0, w, h)
g := ui.Get().GraphicsDriverForTesting() g := ui.Get().GraphicsDriverForTesting()
s0 := atlas.NewShader(etesting.ShaderProgramFill(0xff, 0xff, 0xff, 0xff)) 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) // Vertices must be recreated (#1755)
vs = quadVertices(w, h, 0, 0, 1) vs = quadVertices(w, h, 0, 0, 1)
s1 := atlas.NewShader(etesting.ShaderProgramFill(0x80, 0x80, 0x80, 0xff)) 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) pix := make([]byte, 4*w*h)
ok, err := dst.ReadPixels(g, pix, image.Rect(0, 0, 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) vs := quadVertices(w, h, 0, 0, 1)
is := graphics.QuadIndices() is := graphics.QuadIndices()
dr := image.Rect(0, 0, w, h) 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) // Vertices must be recreated (#1755)
vs = quadVertices(w, h, 0, 0, 1) 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) pix := make([]byte, 4*w*h)
ok, err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix, image.Rect(0, 0, 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) vs := quadVertices(w, h, 0, 0, 1)
is := graphics.QuadIndices() is := graphics.QuadIndices()
dr := image.Rect(0, 0, w, h) 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. // Ensure other objects are GCed, as GC appends deferred functions for collected objects.
ensureGC() ensureGC()

View File

@ -337,7 +337,7 @@ func (i *Image) syncPixelsIfNeeded() {
srcs := [graphics.ShaderSrcImageCount]*atlas.Image{whiteImage.img} srcs := [graphics.ShaderSrcImageCount]*atlas.Image{whiteImage.img}
dr := image.Rect(0, 0, i.width, i.height) dr := image.Rect(0, 0, i.width, i.height)
blend := graphicsdriver.BlendCopy 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. // TODO: Use clear if Go 1.21 is available.
for pos := range i.dotsBuffer { for pos := range i.dotsBuffer {

View File

@ -56,7 +56,7 @@ func TestUnsyncedPixels(t *testing.T) {
is := graphics.QuadIndices() is := graphics.QuadIndices()
dr := image.Rect(0, 0, 16, 16) dr := image.Rect(0, 0, 16, 16)
sr := [graphics.ShaderSrcImageCount]image.Rectangle{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. // Check the result is correct.
var got [4]byte var got [4]byte

View File

@ -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. // 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() shadersM.Lock()
defer shadersM.Unlock() defer shadersM.Unlock()
@ -165,3 +165,45 @@ func Shader(filter Filter, address Address, useColorM bool) []byte {
shaders[filter][address][c] = b shaders[filter][address][c] = b
return 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
}

View File

@ -152,6 +152,14 @@ func (g *nativeGamepadsImpl) addDevice(device _IOHIDDeviceRef, gamepads *gamepad
return 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" name := "Unknown"
if prop := _IOHIDDeviceGetProperty(device, _CFStringCreateWithCString(kCFAllocatorDefault, kIOHIDProductKey, kCFStringEncodingUTF8)); prop != 0 { if prop := _IOHIDDeviceGetProperty(device, _CFStringCreateWithCString(kCFAllocatorDefault, kIOHIDProductKey, kCFStringEncodingUTF8)); prop != 0 {
var cstr [256]byte 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]) 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{ n := &nativeGamepadImpl{
device: device, device: device,
} }

View File

@ -113,7 +113,7 @@ type nativeGamepadsDesktop struct {
enumObjectsCallback uintptr enumObjectsCallback uintptr
nativeWindow windows.HWND nativeWindow windows.HWND
deviceChanged int32 deviceChanged atomic.Bool
err error err error
} }
@ -537,11 +537,11 @@ func (g *nativeGamepadsDesktop) update(gamepads *gamepads) error {
g.origWndProc = h g.origWndProc = h
} }
if atomic.LoadInt32(&g.deviceChanged) != 0 { if g.deviceChanged.Load() {
if err := g.detectConnection(gamepads); err != nil { if err := g.detectConnection(gamepads); err != nil {
g.err = err g.err = err
} }
atomic.StoreInt32(&g.deviceChanged, 0) g.deviceChanged.Store(false)
} }
return nil 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 { func (g *nativeGamepadsDesktop) wndProc(hWnd uintptr, uMsg uint32, wParam, lParam uintptr) uintptr {
switch uMsg { switch uMsg {
case _WM_DEVICECHANGE: case _WM_DEVICECHANGE:
atomic.StoreInt32(&g.deviceChanged, 1) g.deviceChanged.Store(true)
} }
return _CallWindowProcW(g.origWndProc, hWnd, uMsg, wParam, lParam) 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 { if g.xinputState.Gamepad.wButtons&_XINPUT_GAMEPAD_DPAD_LEFT != 0 {
v |= hatLeft 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 return v
} }

View File

@ -140,13 +140,6 @@ func (*nativeGamepadsImpl) openGamepad(gamepads *gamepads, path string) (err err
return fmt.Errorf("gamepad: ioctl for an ID failed: %w", 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 !isBitSet(evBits, unix.EV_ABS) {
if err := unix.Close(fd); err != nil { if err := unix.Close(fd); err != nil {
return err return err

View File

@ -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)
}
}

View File

@ -25,8 +25,21 @@ import (
//go:embed gamecontrollerdb_windows.txt //go:embed gamecontrollerdb_windows.txt
var controllerBytes []byte 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() { func init() {
if err := Update(controllerBytes); err != nil { if err := Update(controllerBytes); err != nil {
panic(err) panic(err)
} }
if err := Update(additionalGLFWGamepads); err != nil {
panic(err)
}
} }

View File

@ -39,40 +39,20 @@ const (
platformIOS platformIOS
) )
var currentPlatform platform func currentPlatform() platform {
switch runtime.GOOS {
func init() { case "windows":
if runtime.GOOS == "windows" { return platformWindows
currentPlatform = platformWindows case "aix", "dragonfly", "freebsd", "hurd", "illumos", "linux", "netbsd", "openbsd", "solaris":
return return platformUnix
} case "android":
return platformAndroid
if runtime.GOOS == "aix" || case "ios":
runtime.GOOS == "dragonfly" || return platformIOS
runtime.GOOS == "freebsd" || case "darwin":
runtime.GOOS == "hurd" || return platformMacOS
runtime.GOOS == "illumos" || default:
runtime.GOOS == "linux" || return platformUnknown
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
} }
} }
@ -336,7 +316,7 @@ func buttonMappings(id string) map[StandardButton]mapping {
if m, ok := gamepadButtonMappings[id]; ok { if m, ok := gamepadButtonMappings[id]; ok {
return m return m
} }
if currentPlatform == platformAndroid { if currentPlatform() == platformAndroid {
if addAndroidDefaultMappings(id) { if addAndroidDefaultMappings(id) {
return gamepadButtonMappings[id] return gamepadButtonMappings[id]
} }
@ -348,7 +328,7 @@ func axisMappings(id string) map[StandardAxis]mapping {
if m, ok := gamepadAxisMappings[id]; ok { if m, ok := gamepadAxisMappings[id]; ok {
return m return m
} }
if currentPlatform == platformAndroid { if currentPlatform() == platformAndroid {
if addAndroidDefaultMappings(id) { if addAndroidDefaultMappings(id) {
return gamepadAxisMappings[id] return gamepadAxisMappings[id]
} }
@ -544,7 +524,7 @@ func Update(mappingData []byte) error {
for s.Scan() { for s.Scan() {
line := s.Text() line := s.Text()
id, name, buttons, axes, err := parseLine(line, currentPlatform) id, name, buttons, axes, err := parseLine(line, currentPlatform())
if err != nil { if err != nil {
return err return err
} }

View File

@ -15,6 +15,7 @@
package gamepaddb_test package gamepaddb_test
import ( import (
"runtime"
"testing" "testing"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb" "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)
}
}

View File

@ -66,11 +66,27 @@ import (
//go:embed gamecontrollerdb_{{.FileNameSuffix}}.txt //go:embed gamecontrollerdb_{{.FileNameSuffix}}.txt
var controllerBytes []byte 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() { func init() {
if err := Update(controllerBytes); err != nil { if err := Update(controllerBytes); err != nil {
panic(err) panic(err)
} }{{if .HasGLFWGamepads}}
}` if err := Update(additionalGLFWGamepads); err != nil {
panic(err)
}{{end}}
}
`
func main() { func main() {
if err := run(); err != nil { if err := run(); err != nil {
@ -86,12 +102,14 @@ func run() error {
type gamePadPlatform struct { type gamePadPlatform struct {
filenameSuffix string filenameSuffix string
buildConstraints string buildConstraints string
hasGLFWGamepads bool
} }
platforms := map[string]gamePadPlatform{ platforms := map[string]gamePadPlatform{
"Windows": { "Windows": {
filenameSuffix: "windows", filenameSuffix: "windows",
buildConstraints: "//go:build !microsoftgdk", buildConstraints: "//go:build !microsoftgdk",
hasGLFWGamepads: true,
}, },
"Mac OS X": { "Mac OS X": {
filenameSuffix: "macos_darwin", filenameSuffix: "macos_darwin",
@ -142,11 +160,13 @@ func run() error {
DoNotEdit string DoNotEdit string
BuildConstraints string BuildConstraints string
FileNameSuffix string FileNameSuffix string
HasGLFWGamepads bool
}{ }{
License: license, License: license,
DoNotEdit: doNotEdit, DoNotEdit: doNotEdit,
BuildConstraints: platform.buildConstraints, BuildConstraints: platform.buildConstraints,
FileNameSuffix: platform.filenameSuffix, FileNameSuffix: platform.filenameSuffix,
HasGLFWGamepads: platform.hasGLFWGamepads,
}); err != nil { }); err != nil {
return err return err
} }

View File

@ -100,6 +100,7 @@ const (
_MAPVK_VSC_TO_VK = 1 _MAPVK_VSC_TO_VK = 1
_MONITOR_DEFAULTTONEAREST = 0x00000002 _MONITOR_DEFAULTTONEAREST = 0x00000002
_MOUSE_MOVE_ABSOLUTE = 0x01 _MOUSE_MOVE_ABSOLUTE = 0x01
_MOUSE_VIRTUAL_DESKTOP = 0x02
_MSGFLT_ALLOW = 1 _MSGFLT_ALLOW = 1
_OCR_CROSS = 32515 _OCR_CROSS = 32515
_OCR_HAND = 32649 _OCR_HAND = 32649
@ -121,6 +122,7 @@ const (
_PFD_SUPPORT_OPENGL = 0x00000020 _PFD_SUPPORT_OPENGL = 0x00000020
_PFD_TYPE_RGBA = 0 _PFD_TYPE_RGBA = 0
_QS_ALLEVENTS = _QS_INPUT | _QS_POSTMESSAGE | _QS_TIMER | _QS_PAINT | _QS_HOTKEY _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_HOTKEY = 0x0080
_QS_INPUT = _QS_MOUSE | _QS_KEY | _QS_RAWINPUT _QS_INPUT = _QS_MOUSE | _QS_KEY | _QS_RAWINPUT
_QS_KEY = 0x0001 _QS_KEY = 0x0001
@ -130,6 +132,7 @@ const (
_QS_PAINT = 0x0020 _QS_PAINT = 0x0020
_QS_POSTMESSAGE = 0x0008 _QS_POSTMESSAGE = 0x0008
_QS_RAWINPUT = 0x0400 _QS_RAWINPUT = 0x0400
_QS_SENDMESSAGE = 0x0040
_QS_TIMER = 0x0010 _QS_TIMER = 0x0010
_RID_INPUT = 0x10000003 _RID_INPUT = 0x10000003
_RIDEV_REMOVE = 0x00000001 _RIDEV_REMOVE = 0x00000001
@ -139,11 +142,18 @@ const (
_SIZE_MAXIMIZED = 2 _SIZE_MAXIMIZED = 2
_SIZE_MINIMIZED = 1 _SIZE_MINIMIZED = 1
_SIZE_RESTORED = 0 _SIZE_RESTORED = 0
_SM_CXCURSOR = 13
_SM_CXICON = 11 _SM_CXICON = 11
_SM_CXSCREEN = 0
_SM_CXSMICON = 49 _SM_CXSMICON = 49
_SM_CYCAPTION = 4 _SM_CYCAPTION = 4
_SM_CYCURSOR = 14
_SM_CYICON = 12 _SM_CYICON = 12
_SM_CYSCREEN = 1
_SM_CYSMICON = 50 _SM_CYSMICON = 50
_SM_CXVIRTUALSCREEN = 78
_SM_CYVIRTUALSCREEN = 79
_SM_REMOTESESSION = 0x1000
_SPI_GETFOREGROUNDLOCKTIMEOUT = 0x2000 _SPI_GETFOREGROUNDLOCKTIMEOUT = 0x2000
_SPI_GETMOUSETRAILS = 94 _SPI_GETMOUSETRAILS = 94
_SPI_SETFOREGROUNDLOCKTIMEOUT = 0x2001 _SPI_SETFOREGROUNDLOCKTIMEOUT = 0x2001
@ -753,9 +763,11 @@ var (
procChangeWindowMessageFilterEx = user32.NewProc("ChangeWindowMessageFilterEx") procChangeWindowMessageFilterEx = user32.NewProc("ChangeWindowMessageFilterEx")
procClientToScreen = user32.NewProc("ClientToScreen") procClientToScreen = user32.NewProc("ClientToScreen")
procClipCursor = user32.NewProc("ClipCursor") procClipCursor = user32.NewProc("ClipCursor")
procCreateCursor = user32.NewProc("CreateCursor")
procCreateIconIndirect = user32.NewProc("CreateIconIndirect") procCreateIconIndirect = user32.NewProc("CreateIconIndirect")
procCreateWindowExW = user32.NewProc("CreateWindowExW") procCreateWindowExW = user32.NewProc("CreateWindowExW")
procDefWindowProcW = user32.NewProc("DefWindowProcW") procDefWindowProcW = user32.NewProc("DefWindowProcW")
procDestroyCursor = user32.NewProc("DestroyCursor")
procDestroyIcon = user32.NewProc("DestroyIcon") procDestroyIcon = user32.NewProc("DestroyIcon")
procDestroyWindow = user32.NewProc("DestroyWindow") procDestroyWindow = user32.NewProc("DestroyWindow")
procDispatchMessageW = user32.NewProc("DispatchMessageW") procDispatchMessageW = user32.NewProc("DispatchMessageW")
@ -913,6 +925,26 @@ func _ClipCursor(lpRect *_RECT) error {
return nil 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) { 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)) r, _, e := procCreateBitmap.Call(uintptr(nWidth), uintptr(nHeight), uintptr(nPlanes), uintptr(nBitCount), uintptr(lpBits))
if _HBITMAP(r) == 0 { if _HBITMAP(r) == 0 {
@ -984,6 +1016,14 @@ func _DefWindowProcW(hWnd windows.HWND, uMsg uint32, wParam _WPARAM, lParam _LPA
return _LRESULT(r) 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 { func _DestroyIcon(hIcon _HICON) error {
r, _, e := procDestroyIcon.Call(uintptr(hIcon)) r, _, e := procDestroyIcon.Call(uintptr(hIcon))
if int32(r) == 0 && !errors.Is(e, windows.ERROR_SUCCESS) { if int32(r) == 0 && !errors.Is(e, windows.ERROR_SUCCESS) {

View File

@ -227,7 +227,7 @@ static void createKeyTables(void)
_glfw.ns.keycodes[0x6D] = GLFW_KEY_F10; _glfw.ns.keycodes[0x6D] = GLFW_KEY_F10;
_glfw.ns.keycodes[0x67] = GLFW_KEY_F11; _glfw.ns.keycodes[0x67] = GLFW_KEY_F11;
_glfw.ns.keycodes[0x6F] = GLFW_KEY_F12; _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[0x6B] = GLFW_KEY_F14;
_glfw.ns.keycodes[0x71] = GLFW_KEY_F15; _glfw.ns.keycodes[0x71] = GLFW_KEY_F15;
_glfw.ns.keycodes[0x6A] = GLFW_KEY_F16; _glfw.ns.keycodes[0x6A] = GLFW_KEY_F16;
@ -420,7 +420,6 @@ static GLFWbool initializeTIS(void)
- (void)applicationDidFinishLaunching:(NSNotification *)notification - (void)applicationDidFinishLaunching:(NSNotification *)notification
{ {
_glfw.ns.finishedLaunching = GLFW_TRUE;
_glfwPlatformPostEmptyEvent(); _glfwPlatformPostEmptyEvent();
// In case we are unbundled, make us a proper UI application // In case we are unbundled, make us a proper UI application
@ -455,9 +454,6 @@ int _glfwPlatformInit(void)
toTarget:_glfw.ns.helper toTarget:_glfw.ns.helper
withObject:nil]; withObject:nil];
if (NSApp)
_glfw.ns.finishedLaunching = GLFW_TRUE;
[NSApplication sharedApplication]; [NSApplication sharedApplication];
_glfw.ns.delegate = [[GLFWApplicationDelegate alloc] init]; _glfw.ns.delegate = [[GLFWApplicationDelegate alloc] init];
@ -509,6 +505,10 @@ int _glfwPlatformInit(void)
_glfwInitTimerNS(); _glfwInitTimerNS();
_glfwPollMonitorsNS(); _glfwPollMonitorsNS();
if (![[NSRunningApplication currentApplication] isFinishedLaunching])
[NSApp run];
return GLFW_TRUE; return GLFW_TRUE;
} // autoreleasepool } // autoreleasepool

View File

@ -109,7 +109,6 @@ typedef struct _GLFWlibraryNS
{ {
CGEventSourceRef eventSource; CGEventSourceRef eventSource;
id delegate; id delegate;
GLFWbool finishedLaunching;
GLFWbool cursorHidden; GLFWbool cursorHidden;
TISInputSourceRef inputSource; TISInputSourceRef inputSource;
id unicodeData; id unicodeData;

View File

@ -285,10 +285,15 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
- (void)windowDidChangeOcclusionState:(NSNotification* )notification - (void)windowDidChangeOcclusionState:(NSNotification* )notification
{ {
if ([window->ns.object occlusionState] & NSWindowOcclusionStateVisible) #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1090
window->ns.occluded = GLFW_FALSE; if ([window->ns.object respondsToSelector:@selector(occlusionState)])
else {
window->ns.occluded = GLFW_TRUE; if ([window->ns.object occlusionState] & NSWindowOcclusionStateVisible)
window->ns.occluded = GLFW_FALSE;
else
window->ns.occluded = GLFW_TRUE;
}
#endif
} }
@end @end
@ -878,9 +883,6 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
{ {
@autoreleasepool { @autoreleasepool {
if (!_glfw.ns.finishedLaunching)
[NSApp run];
if (!createNativeWindow(window, wndconfig, fbconfig)) if (!createNativeWindow(window, wndconfig, fbconfig))
return GLFW_FALSE; return GLFW_FALSE;
@ -1239,7 +1241,7 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
if (window->monitor) if (window->monitor)
{ {
styleMask &= ~(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable); styleMask &= ~(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable);
styleMask |= NSWindowStyleMaskBorderless; styleMask |= NSWindowStyleMaskBorderless;
} }
else else
@ -1472,9 +1474,6 @@ void _glfwPlatformPollEvents(void)
{ {
@autoreleasepool { @autoreleasepool {
if (!_glfw.ns.finishedLaunching)
[NSApp run];
for (;;) for (;;)
{ {
NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
@ -1494,9 +1493,6 @@ void _glfwPlatformWaitEvents(void)
{ {
@autoreleasepool { @autoreleasepool {
if (!_glfw.ns.finishedLaunching)
[NSApp run];
// I wanted to pass NO to dequeue:, and rely on PollEvents to // I wanted to pass NO to dequeue:, and rely on PollEvents to
// dequeue and send. For reasons not at all clear to me, passing // dequeue and send. For reasons not at all clear to me, passing
// NO to dequeue: causes this method never to return. // NO to dequeue: causes this method never to return.
@ -1515,9 +1511,6 @@ void _glfwPlatformWaitEventsTimeout(double timeout)
{ {
@autoreleasepool { @autoreleasepool {
if (!_glfw.ns.finishedLaunching)
[NSApp run];
NSDate* date = [NSDate dateWithTimeIntervalSinceNow:timeout]; NSDate* date = [NSDate dateWithTimeIntervalSinceNow:timeout];
NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
untilDate:date untilDate:date
@ -1535,9 +1528,6 @@ void _glfwPlatformPostEmptyEvent(void)
{ {
@autoreleasepool { @autoreleasepool {
if (!_glfw.ns.finishedLaunching)
[NSApp run];
NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
location:NSMakePoint(0, 0) location:NSMakePoint(0, 0)
modifierFlags:0 modifierFlags:0
@ -1616,14 +1606,15 @@ const char* _glfwPlatformGetScancodeName(int scancode)
{ {
@autoreleasepool { @autoreleasepool {
if (scancode < 0 || scancode > 0xff || if (scancode < 0 || scancode > 0xff)
_glfw.ns.keycodes[scancode] == GLFW_KEY_UNKNOWN)
{ {
_glfwInputError(GLFW_INVALID_VALUE, "Invalid scancode %i", scancode); _glfwInputError(GLFW_INVALID_VALUE, "Invalid scancode %i", scancode);
return NULL; return NULL;
} }
const int key = _glfw.ns.keycodes[scancode]; const int key = _glfw.ns.keycodes[scancode];
if (key == GLFW_KEY_UNKNOWN)
return NULL;
UInt32 deadKeyState = 0; UInt32 deadKeyState = 0;
UniChar characters[4]; UniChar characters[4];

View File

@ -26,16 +26,6 @@
// //
GLFWbool _glfwIsValidContextConfig(const _GLFWctxconfig* ctxconfig) 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 && if (ctxconfig->source != GLFW_NATIVE_CONTEXT_API &&
ctxconfig->source != GLFW_EGL_CONTEXT_API && ctxconfig->source != GLFW_EGL_CONTEXT_API &&
ctxconfig->source != GLFW_OSMESA_CONTEXT_API) ctxconfig->source != GLFW_OSMESA_CONTEXT_API)
@ -56,6 +46,23 @@ GLFWbool _glfwIsValidContextConfig(const _GLFWctxconfig* ctxconfig)
return GLFW_FALSE; 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->client == GLFW_OPENGL_API)
{ {
if ((ctxconfig->major < 1 || ctxconfig->minor < 0) || if ((ctxconfig->major < 1 || ctxconfig->minor < 0) ||
@ -334,6 +341,8 @@ GLFWbool _glfwRefreshContextAttribs(_GLFWwindow* window,
previous = _glfwPlatformGetTls(&_glfw.contextSlot); previous = _glfwPlatformGetTls(&_glfw.contextSlot);
glfwMakeContextCurrent((GLFWwindow*) window); glfwMakeContextCurrent((GLFWwindow*) window);
if (_glfwPlatformGetTls(&_glfw.contextSlot) != window)
return GLFW_FALSE;
window->context.GetIntegerv = (PFNGLGETINTEGERVPROC) window->context.GetIntegerv = (PFNGLGETINTEGERVPROC)
window->context.getProcAddress("glGetIntegerv"); window->context.getProcAddress("glGetIntegerv");

View File

@ -17,12 +17,6 @@ import (
) )
func checkValidContextConfig(ctxconfig *ctxconfig) error { func checkValidContextConfig(ctxconfig *ctxconfig) error {
if ctxconfig.share != nil {
if ctxconfig.client == NoAPI || ctxconfig.share.context.client == NoAPI {
return NoWindowContext
}
}
if ctxconfig.source != NativeContextAPI && if ctxconfig.source != NativeContextAPI &&
ctxconfig.source != EGLContextAPI && ctxconfig.source != EGLContextAPI &&
ctxconfig.source != OSMesaContextAPI { 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) 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.client == OpenGLAPI {
if (ctxconfig.major < 1 || ctxconfig.minor < 0) || if (ctxconfig.major < 1 || ctxconfig.minor < 0) ||
(ctxconfig.major == 1 && ctxconfig.minor > 5) || (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.source = ctxconfig.source
w.context.client = OpenGLAPI w.context.client = OpenGLAPI
p, err := _glfw.contextSlot.get() p1, err := _glfw.contextSlot.get()
if err != nil { if err != nil {
return err return err
} }
previous := (*Window)(unsafe.Pointer(p)) previous := (*Window)(unsafe.Pointer(p1))
defer func() { defer func() {
err := previous.MakeContextCurrent() err := previous.MakeContextCurrent()
if ferr == nil { if ferr == nil {
@ -264,6 +267,14 @@ func (w *Window) refreshContextAttribs(ctxconfig *ctxconfig) (ferr error) {
return err 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") getIntegerv := w.context.getProcAddress("glGetIntegerv")
getString := w.context.getProcAddress("glGetString") getString := w.context.getProcAddress("glGetString")
if getIntegerv == 0 || getString == 0 { if getIntegerv == 0 || getString == 0 {

View File

@ -66,13 +66,30 @@ static int getEGLConfigAttrib(EGLConfig config, int attrib)
// Return the EGLConfig most closely matching the specified hints // Return the EGLConfig most closely matching the specified hints
// //
static GLFWbool chooseEGLConfig(const _GLFWctxconfig* ctxconfig, static GLFWbool chooseEGLConfig(const _GLFWctxconfig* ctxconfig,
const _GLFWfbconfig* desired, const _GLFWfbconfig* fbconfig,
EGLConfig* result) EGLConfig* result)
{ {
EGLConfig* nativeConfigs; EGLConfig* nativeConfigs;
_GLFWfbconfig* usableConfigs; _GLFWfbconfig* usableConfigs;
const _GLFWfbconfig* closest; 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); eglGetConfigs(_glfw.egl.display, NULL, 0, &nativeCount);
if (!nativeCount) if (!nativeCount)
@ -109,7 +126,7 @@ static GLFWbool chooseEGLConfig(const _GLFWctxconfig* ctxconfig,
if (!vi.visualid) if (!vi.visualid)
continue; continue;
if (desired->transparent) if (fbconfig->transparent)
{ {
int count; int count;
XVisualInfo* vis = XVisualInfo* vis =
@ -123,23 +140,10 @@ static GLFWbool chooseEGLConfig(const _GLFWctxconfig* ctxconfig,
} }
#endif // _GLFW_X11 #endif // _GLFW_X11
if (ctxconfig->client == GLFW_OPENGL_ES_API) if (!(getEGLConfigAttrib(n, EGL_RENDERABLE_TYPE) & apiBit))
{ {
if (ctxconfig->major == 1) wrongApiAvailable = GLFW_TRUE;
{ continue;
if (!(getEGLConfigAttrib(n, EGL_RENDERABLE_TYPE) & EGL_OPENGL_ES_BIT))
continue;
}
else
{
if (!(getEGLConfigAttrib(n, EGL_RENDERABLE_TYPE) & EGL_OPENGL_ES2_BIT))
continue;
}
}
else if (ctxconfig->client == GLFW_OPENGL_API)
{
if (!(getEGLConfigAttrib(n, EGL_RENDERABLE_TYPE) & EGL_OPENGL_BIT))
continue;
} }
u->redBits = getEGLConfigAttrib(n, EGL_RED_SIZE); u->redBits = getEGLConfigAttrib(n, EGL_RED_SIZE);
@ -151,15 +155,44 @@ static GLFWbool chooseEGLConfig(const _GLFWctxconfig* ctxconfig,
u->stencilBits = getEGLConfigAttrib(n, EGL_STENCIL_SIZE); u->stencilBits = getEGLConfigAttrib(n, EGL_STENCIL_SIZE);
u->samples = getEGLConfigAttrib(n, EGL_SAMPLES); u->samples = getEGLConfigAttrib(n, EGL_SAMPLES);
u->doublebuffer = desired->doublebuffer; u->doublebuffer = fbconfig->doublebuffer;
u->handle = (uintptr_t) n; u->handle = (uintptr_t) n;
usableCount++; usableCount++;
} }
closest = _glfwChooseFBConfig(desired, usableConfigs, usableCount); closest = _glfwChooseFBConfig(fbconfig, usableConfigs, usableCount);
if (closest) if (closest)
*result = (EGLConfig) closest->handle; *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(nativeConfigs);
free(usableConfigs); free(usableConfigs);
@ -231,6 +264,7 @@ static int extensionSupportedEGL(const char* extension)
static GLFWglproc getProcAddressEGL(const char* procname) static GLFWglproc getProcAddressEGL(const char* procname)
{ {
_GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot); _GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot);
assert(window != NULL);
if (window->context.egl.client) if (window->context.egl.client)
{ {
@ -454,11 +488,7 @@ GLFWbool _glfwCreateContextEGL(_GLFWwindow* window,
share = ctxconfig->share->context.egl.handle; share = ctxconfig->share->context.egl.handle;
if (!chooseEGLConfig(ctxconfig, fbconfig, &config)) if (!chooseEGLConfig(ctxconfig, fbconfig, &config))
{
_glfwInputError(GLFW_FORMAT_UNAVAILABLE,
"EGL: Failed to find a suitable EGLConfig");
return GLFW_FALSE; return GLFW_FALSE;
}
if (ctxconfig->client == GLFW_OPENGL_ES_API) if (ctxconfig->client == GLFW_OPENGL_ES_API)
{ {
@ -515,18 +545,18 @@ GLFWbool _glfwCreateContextEGL(_GLFWwindow* window,
flags |= EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR; flags |= EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR;
} }
if (ctxconfig->noerror)
{
if (_glfw.egl.KHR_create_context_no_error)
setAttrib(EGL_CONTEXT_OPENGL_NO_ERROR_KHR, GLFW_TRUE);
}
if (ctxconfig->major != 1 || ctxconfig->minor != 0) if (ctxconfig->major != 1 || ctxconfig->minor != 0)
{ {
setAttrib(EGL_CONTEXT_MAJOR_VERSION_KHR, ctxconfig->major); setAttrib(EGL_CONTEXT_MAJOR_VERSION_KHR, ctxconfig->major);
setAttrib(EGL_CONTEXT_MINOR_VERSION_KHR, ctxconfig->minor); 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) if (mask)
setAttrib(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, mask); setAttrib(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, mask);
@ -578,9 +608,6 @@ GLFWbool _glfwCreateContextEGL(_GLFWwindow* window,
if (!fbconfig->doublebuffer) if (!fbconfig->doublebuffer)
setAttrib(EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER); 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); setAttrib(EGL_NONE, EGL_NONE);
window->context.egl.surface = window->context.egl.surface =
@ -640,6 +667,7 @@ GLFWbool _glfwCreateContextEGL(_GLFWwindow* window,
#elif defined(__OpenBSD__) || defined(__NetBSD__) #elif defined(__OpenBSD__) || defined(__NetBSD__)
"libGL.so", "libGL.so",
#else #else
"libOpenGL.so.0",
"libGL.so.1", "libGL.so.1",
#endif #endif
NULL NULL
@ -702,11 +730,7 @@ GLFWbool _glfwChooseVisualEGL(const _GLFWwndconfig* wndconfig,
const long vimask = VisualScreenMask | VisualIDMask; const long vimask = VisualScreenMask | VisualIDMask;
if (!chooseEGLConfig(ctxconfig, fbconfig, &native)) if (!chooseEGLConfig(ctxconfig, fbconfig, &native))
{
_glfwInputError(GLFW_FORMAT_UNAVAILABLE,
"EGL: Failed to find a suitable EGLConfig");
return GLFW_FALSE; return GLFW_FALSE;
}
eglGetConfigAttrib(_glfw.egl.display, native, eglGetConfigAttrib(_glfw.egl.display, native,
EGL_NATIVE_VISUAL_ID, &visualID); EGL_NATIVE_VISUAL_ID, &visualID);

View File

@ -249,7 +249,7 @@ extern "C" {
* release is made that does not contain any API changes. * release is made that does not contain any API changes.
* @ingroup init * @ingroup init
*/ */
#define GLFW_VERSION_REVISION 8 #define GLFW_VERSION_REVISION 10
/*! @} */ /*! @} */
/*! @brief One. /*! @brief One.
@ -296,8 +296,12 @@ extern "C" {
#define GLFW_REPEAT 2 #define GLFW_REPEAT 2
/*! @} */ /*! @} */
/*! @defgroup keys Keyboard keys /*! @ingroup input
* @brief Keyboard key IDs. */
#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. * 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 */ /* Printable keys */
#define GLFW_KEY_SPACE 32 #define GLFW_KEY_SPACE 32
@ -1590,6 +1592,14 @@ typedef struct GLFWimage
* bundle, if present. This can be disabled with the @ref * bundle, if present. This can be disabled with the @ref
* GLFW_COCOA_CHDIR_RESOURCES init hint. * 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 * @remark @x11 This function will set the `LC_CTYPE` category of the
* application locale according to the current environment if that category is * application locale according to the current environment if that category is
* still "C". This is because the "C" locale breaks Unicode text input. * 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 * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
* GLFW_INVALID_ENUM, @ref GLFW_INVALID_VALUE, @ref GLFW_API_UNAVAILABLE, @ref * GLFW_INVALID_ENUM, @ref GLFW_INVALID_VALUE, @ref GLFW_API_UNAVAILABLE, @ref
* GLFW_VERSION_UNAVAILABLE, @ref GLFW_FORMAT_UNAVAILABLE and @ref * GLFW_VERSION_UNAVAILABLE, @ref GLFW_FORMAT_UNAVAILABLE, @ref
* GLFW_PLATFORM_ERROR. * GLFW_NO_WINDOW_CONTEXT and @ref GLFW_PLATFORM_ERROR.
* *
* @remark @win32 Window creation will fail if the Microsoft GDI software * @remark @win32 Window creation will fail if the Microsoft GDI software
* OpenGL implementation is the only one available. * 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/) * [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/)
* in the Mac Developer Library. * 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 * @remark @macos On OS X 10.10 and later the window frame will not be rendered
* at full resolution on Retina displays unless the * at full resolution on Retina displays unless the
* [GLFW_COCOA_RETINA_FRAMEBUFFER](@ref GLFW_COCOA_RETINA_FRAMEBUFFER_hint) * [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`. * @param[in] value `GLFW_TRUE` or `GLFW_FALSE`.
* *
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * @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 * @remark Calling @ref glfwGetWindowAttrib will always return the latest
* value, even if that value is ignored by the current mode of the window. * 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. * @thread_safety This function must only be called from the main thread.
* *
* @sa @ref window_attribs * @sa @ref window_attribs
@ -4039,8 +4046,8 @@ GLFWAPI int glfwRawMouseMotionSupported(void);
* @param[in] scancode The scancode of the key to query. * @param[in] scancode The scancode of the key to query.
* @return The UTF-8 encoded, layout-specific name of the key, or `NULL`. * @return The UTF-8 encoded, layout-specific name of the key, or `NULL`.
* *
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
* GLFW_PLATFORM_ERROR. * GLFW_INVALID_VALUE, @ref GLFW_INVALID_ENUM and @ref GLFW_PLATFORM_ERROR.
* *
* @remark The contents of the returned string may change when a keyboard * @remark The contents of the returned string may change when a keyboard
* layout change event is received. * 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. * 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 * If the specified [key token](@ref keys) corresponds to a physical key not
* method will return `-1`. * 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). * @param[in] key Any [key token](@ref keys).
* @return The platform-specific scancode for the key, or `-1` if an * @return The platform-specific scancode for the key, or `-1` if the key is
* [error](@ref error_handling) occurred. * not supported on the current platform or an [error](@ref error_handling)
* occurred.
* *
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_INVALID_ENUM and @ref GLFW_PLATFORM_ERROR. * GLFW_INVALID_ENUM.
* *
* @thread_safety This function may be called from any thread. * @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. * [character callback](@ref glfwSetCharCallback) instead.
* *
* When a window loses input focus, it will generate synthetic key release * 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 for all pressed keys with associated key tokens. You can tell these
* events by the fact that the synthetic ones are generated after the focus * events from user-generated events by the fact that the synthetic ones are
* loss event has been processed, i.e. after the * generated after the focus loss event has been processed, i.e. after the
* [window focus callback](@ref glfwSetWindowFocusCallback) has been called.
* *
* The scancode of a key is specific to that platform or sometimes even to that * 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 * 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. * thread.
* *
* This function makes the OpenGL or OpenGL ES context of the specified window * 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 * current on the calling thread. It can also detach the current context from
* a single thread at a time and each thread can have only a single current * the calling thread without making a new one current by passing in `NULL`.
* context at a time.
* *
* When moving a context between threads, you must make it non-current on the * A context must only be made current on a single thread at a time and each
* old thread before making it current on the new one. * 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. * By default, making a context non-current implicitly forces a pipeline flush.
* On machines that support `GL_KHR_context_flush_control`, you can control * 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 * @param[in] window The window whose context to make current, or `NULL` to
* detach the current context. * 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 * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
* GLFW_NO_WINDOW_CONTEXT and @ref GLFW_PLATFORM_ERROR. * GLFW_NO_WINDOW_CONTEXT and @ref GLFW_PLATFORM_ERROR.
* *

View File

@ -99,7 +99,9 @@ extern "C" {
#include <ApplicationServices/ApplicationServices.h> #include <ApplicationServices/ApplicationServices.h>
#include <objc/objc.h> #include <objc/objc.h>
#endif #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/Xlib.h>
#include <X11/extensions/Xrandr.h> #include <X11/extensions/Xrandr.h>
#endif #endif

View File

@ -168,6 +168,7 @@ static void swapBuffersGLX(_GLFWwindow* window)
static void swapIntervalGLX(int interval) static void swapIntervalGLX(int interval)
{ {
_GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot); _GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot);
assert(window != NULL);
if (_glfw.glx.EXT_swap_control) if (_glfw.glx.EXT_swap_control)
{ {
@ -204,7 +205,10 @@ static GLFWglproc getProcAddressGLX(const char* procname)
else if (_glfw.glx.GetProcAddressARB) else if (_glfw.glx.GetProcAddressARB)
return _glfw.glx.GetProcAddressARB((const GLubyte*) procname); return _glfw.glx.GetProcAddressARB((const GLubyte*) procname);
else else
{
// NOTE: glvnd provides GLX 1.4, so this can only happen with libGL
return _glfw_dlsym(_glfw.glx.handle, procname); return _glfw_dlsym(_glfw.glx.handle, procname);
}
} }
static void destroyContextGLX(_GLFWwindow* window) static void destroyContextGLX(_GLFWwindow* window)

View File

@ -107,7 +107,6 @@ typedef struct _GLFWlibraryGLX
int eventBase; int eventBase;
int errorBase; int errorBase;
// dlopen handle for libGL.so.1
void* handle; void* handle;
// GLX 1.3 functions // GLX 1.3 functions

View File

@ -280,6 +280,12 @@ GLFWAPI const char* glfwGetKeyName(int key, int scancode)
if (key != GLFW_KEY_UNKNOWN) 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 && if (key != GLFW_KEY_KP_EQUAL &&
(key < GLFW_KEY_KP_0 || key > GLFW_KEY_KP_ADD) && (key < GLFW_KEY_KP_0 || key > GLFW_KEY_KP_ADD) &&
(key < GLFW_KEY_APOSTROPHE || key > GLFW_KEY_WORLD_2)) (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) 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) if (key < GLFW_KEY_SPACE || key > GLFW_KEY_LAST)
{ {
_glfwInputError(GLFW_INVALID_ENUM, "Invalid key %i", key); _glfwInputError(GLFW_INVALID_ENUM, "Invalid key %i", key);
return GLFW_RELEASE; return -1;
} }
return _glfwPlatformGetKeyScancode(key); return _glfwPlatformGetKeyScancode(key);

View File

@ -246,6 +246,9 @@ func GetKeyName(key Key, scancode int) (string, error) {
} }
if key != KeyUnknown { 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) { if key != KeyKPEqual && (key < KeyKP0 || key > KeyKPAdd) && (key < KeyApostrophe || key > KeyWorld2) {
return "", nil return "", nil
} }

View File

@ -57,11 +57,10 @@ static void swapIntervalNSGL(int interval)
@autoreleasepool { @autoreleasepool {
_GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot); _GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot);
if (window) assert(window != NULL);
{
[window->context.nsgl.object setValues:&interval [window->context.nsgl.object setValues:&interval
forParameter:NSOpenGLContextParameterSwapInterval]; forParameter:NSOpenGLContextParameterSwapInterval];
}
} // autoreleasepool } // autoreleasepool
} }
@ -138,7 +137,7 @@ GLFWbool _glfwCreateContextNSGL(_GLFWwindow* window,
if (ctxconfig->client == GLFW_OPENGL_ES_API) if (ctxconfig->client == GLFW_OPENGL_ES_API)
{ {
_glfwInputError(GLFW_API_UNAVAILABLE, _glfwInputError(GLFW_API_UNAVAILABLE,
"NSGL: OpenGL ES is not available on macOS"); "NSGL: OpenGL ES is not available via NSGL");
return GLFW_FALSE; return GLFW_FALSE;
} }

View File

@ -5,13 +5,12 @@
//go:build darwin || freebsd || linux || netbsd || openbsd //go:build darwin || freebsd || linux || netbsd || openbsd
#include "internal_unix.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <assert.h> #include <assert.h>
#include "internal_unix.h"
static void makeContextCurrentOSMesa(_GLFWwindow* window) static void makeContextCurrentOSMesa(_GLFWwindow* window)
{ {
if (window) if (window)

View File

@ -19,8 +19,9 @@ func _glfwPlatformCreateTls(tls *C._GLFWtls) C.GLFWbool {
panic("glfw: TLS must not be allocated") panic("glfw: TLS must not be allocated")
} }
if pthread_key_create(&tls.posix.key, 0) != 0 { if pthread_key_create(&tls.posix.key, 0) != 0 {
_glfwInputError(int32(PlatformError), errstr := C.CString("POSIX: Failed to create context TLS")
C.CString("POSIX: Failed to create context TLS")) defer C.free(unsafe.Pointer(errstr))
_glfwInputError(int32(PlatformError), errstr)
return False return False
} }
tls.posix.allocated = True tls.posix.allocated = True
@ -58,7 +59,9 @@ func _glfwPlatformCreateMutex(mutex *C._GLFWmutex) C.GLFWbool {
panic("glfw: mutex must not be allocated") panic("glfw: mutex must not be allocated")
} }
if pthread_mutex_init(&mutex.posix.handle, nil) != 0 { 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 return False
} }
mutex.posix.allocated = True mutex.posix.allocated = True

View File

@ -53,10 +53,23 @@ func (w *Window) choosePixelFormat(ctxconfig *ctxconfig, fbconfig_ *fbconfig) (i
var nativeCount int32 var nativeCount int32
var attribs []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 { 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 var attrib int32 = _WGL_NUMBER_PIXEL_FORMATS_ARB
if err := wglGetPixelFormatAttribivARB(w.context.platform.dc, 1, 0, 1, &attrib, &nativeCount); err != nil { var extensionCount int32
return 0, err 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, attribs = append(attribs,
@ -96,12 +109,6 @@ func (w *Window) choosePixelFormat(ctxconfig *ctxconfig, fbconfig_ *fbconfig) (i
attribs = append(attribs, _WGL_COLORSPACE_EXT) 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) usableConfigs := make([]*fbconfig, 0, nativeCount)

View File

@ -239,6 +239,55 @@ func createHelperWindow() error {
return nil 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 { func platformInit() error {
// Changing the foreground lock timeout was removed from the original code. // Changing the foreground lock timeout was removed from the original code.
// See https://github.com/glfw/glfw/commit/58b48a3a00d9c2a5ca10cc23069a71d8773cc7a4 // See https://github.com/glfw/glfw/commit/58b48a3a00d9c2a5ca10cc23069a71d8773cc7a4
@ -293,6 +342,10 @@ func platformInit() error {
return err return err
} }
} else { } else {
// Some hacks are needed to support Remote Desktop...
if err := initRemoteSession(); err != nil {
return err
}
if err := pollMonitorsWin32(); err != nil { if err := pollMonitorsWin32(); err != nil {
return err return err
} }
@ -301,6 +354,12 @@ func platformInit() error {
} }
func platformTerminate() 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 _glfw.platformWindow.deviceNotificationHandle != 0 {
if err := _UnregisterDeviceNotification(_glfw.platformWindow.deviceNotificationHandle); err != nil { if err := _UnregisterDeviceNotification(_glfw.platformWindow.deviceNotificationHandle); err != nil {
return err return err

View File

@ -66,12 +66,18 @@ type platformLibraryWindowState struct {
scancodes [KeyLast + 1]int scancodes [KeyLast + 1]int
keynames [KeyLast + 1]string 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 restoreCursorPosX float64
restoreCursorPosY float64 restoreCursorPosY float64
// The window whose disabled cursor mode is active // disabledCursorWindow is the window whose disabled cursor mode is active
disabledCursorWindow *Window disabledCursorWindow *Window
// capturedCursorWindow is the window the cursor is captured in
capturedCursorWindow *Window
rawInput []byte rawInput []byte
mouseTrailSize uint32 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
} }

View File

@ -127,48 +127,29 @@ func createIcon(image *Image, xhot, yhot int, icon bool) (_HICON, error) {
return handle, nil return handle, nil
} }
func getFullWindowSize(style uint32, exStyle uint32, contentWidth, contentHeight int, dpi uint32) (fullWidth, fullHeight int, err error) { func (w *Window) applyAspectRatio(edge int, area *_RECT) error {
if microsoftgdk.IsXbox() { var frame _RECT
return contentWidth, contentHeight, nil
} 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 winver.IsWindows10AnniversaryUpdateOrGreater() {
if err := _AdjustWindowRectExForDpi(&rect, style, false, exStyle, dpi); err != nil { if err := _AdjustWindowRectExForDpi(&frame, style, false, exStyle, _GetDpiForWindow(w.platform.handle)); err != nil {
return 0, 0, err return err
} }
} else { } else {
if err := _AdjustWindowRectEx(&rect, style, false, exStyle); err != nil { if err := _AdjustWindowRectEx(&frame, style, false, exStyle); err != nil {
return 0, 0, err 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 { 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 { } 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 { } 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 return nil
@ -186,7 +167,10 @@ func (w *Window) updateCursorImage() error {
_SetCursor(cursor) _SetCursor(cursor)
} }
} else { } 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 return nil
} }
@ -214,26 +198,27 @@ func (w *Window) clientToScreen(rect _RECT) (_RECT, error) {
return rect, nil return rect, nil
} }
func updateClipRect(window *Window) error { func captureCursor(window *Window) error {
if window != nil { clipRect, err := _GetClientRect(window.platform.handle)
clipRect, err := _GetClientRect(window.platform.handle) if err != nil {
if err != nil { return err
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
}
} }
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 return nil
} }
@ -274,7 +259,7 @@ func (w *Window) disableCursor() error {
if err := w.centerCursorInContentArea(); err != nil { if err := w.centerCursorInContentArea(); err != nil {
return err return err
} }
if err := updateClipRect(w); err != nil { if err := captureCursor(w); err != nil {
return err return err
} }
if w.rawMouseMotion { if w.rawMouseMotion {
@ -292,7 +277,7 @@ func (w *Window) enableCursor() error {
} }
} }
_glfw.platformWindow.disabledCursorWindow = nil _glfw.platformWindow.disabledCursorWindow = nil
if err := updateClipRect(nil); err != nil { if err := releaseCursor(); err != nil {
return err return err
} }
if err := w.platformSetCursorPos(_glfw.platformWindow.restoreCursorPosX, _glfw.platformWindow.restoreCursorPosY); err != nil { 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 var dx, dy int
data := (*_RAWINPUT)(unsafe.Pointer(&_glfw.platformWindow.rawInput[0])) data := (*_RAWINPUT)(unsafe.Pointer(&_glfw.platformWindow.rawInput[0]))
if data.mouse.usFlags&_MOUSE_MOVE_ABSOLUTE != 0 { if data.mouse.usFlags&_MOUSE_MOVE_ABSOLUTE != 0 {
dx = int(data.mouse.lLastX) - window.platform.lastCursorPosX if _glfw.platformWindow.isRemoteSession {
dy = int(data.mouse.lLastY) - window.platform.lastCursorPosY // 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 { } else {
dx = int(data.mouse.lLastX) dx = int(data.mouse.lLastX)
dy = int(data.mouse.lLastY) dy = int(data.mouse.lLastY)
@ -986,8 +1009,8 @@ func windowProc(hWnd windows.HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM)
iconified := wParam == _SIZE_MINIMIZED iconified := wParam == _SIZE_MINIMIZED
maximized := wParam == _SIZE_MAXIMIZED || (window.platform.maximized && wParam != _SIZE_RESTORED) maximized := wParam == _SIZE_MAXIMIZED || (window.platform.maximized && wParam != _SIZE_RESTORED)
if _glfw.platformWindow.disabledCursorWindow == window { if _glfw.platformWindow.capturedCursorWindow == window {
if err := updateClipRect(window); err != nil { if err := captureCursor(window); err != nil {
_glfw.errors = append(_glfw.errors, err) _glfw.errors = append(_glfw.errors, err)
return 0 return 0
} }
@ -1032,8 +1055,8 @@ func windowProc(hWnd windows.HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM)
return 0 return 0
case _WM_MOVE: case _WM_MOVE:
if _glfw.platformWindow.disabledCursorWindow == window { if _glfw.platformWindow.capturedCursorWindow == window {
if err := updateClipRect(window); err != nil { if err := captureCursor(window); err != nil {
_glfw.errors = append(_glfw.errors, err) _glfw.errors = append(_glfw.errors, err)
return 0 return 0
} }
@ -1056,31 +1079,35 @@ func windowProc(hWnd windows.HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM)
return 1 return 1
case _WM_GETMINMAXINFO: case _WM_GETMINMAXINFO:
var dpi uint32 = _USER_DEFAULT_SCREEN_DPI var frame _RECT
mmi := (*_MINMAXINFO)(unsafe.Pointer(lParam)) mmi := (*_MINMAXINFO)(unsafe.Pointer(lParam))
style := window.getWindowStyle()
exStyle := window.getWindowExStyle()
if window.monitor != nil { if window.monitor != nil {
break break
} }
if winver.IsWindows10AnniversaryUpdateOrGreater() { if winver.IsWindows10AnniversaryUpdateOrGreater() {
dpi = _GetDpiForWindow(window.platform.handle) if err := _AdjustWindowRectExForDpi(&frame, style, false, exStyle, _GetDpiForWindow(window.platform.handle)); err != nil {
} _glfw.errors = append(_glfw.errors, err)
return 0
xoff, yoff, err := getFullWindowSize(window.getWindowStyle(), window.getWindowExStyle(), 0, 0, dpi) }
if err != nil { } else {
_glfw.errors = append(_glfw.errors, err) if err := _AdjustWindowRectEx(&frame, style, false, exStyle); err != nil {
return 0 _glfw.errors = append(_glfw.errors, err)
return 0
}
} }
if window.minwidth != DontCare && window.minheight != DontCare { if window.minwidth != DontCare && window.minheight != DontCare {
mmi.ptMinTrackSize.x = int32(window.minwidth + xoff) mmi.ptMinTrackSize.x = int32(window.minwidth) + (frame.right - frame.left)
mmi.ptMinTrackSize.y = int32(window.minheight + yoff) mmi.ptMinTrackSize.y = int32(window.minheight) + (frame.bottom - frame.top)
} }
if window.maxwidth != DontCare && window.maxheight != DontCare { if window.maxwidth != DontCare && window.maxheight != DontCare {
mmi.ptMaxTrackSize.x = int32(window.maxwidth + xoff) mmi.ptMaxTrackSize.x = int32(window.maxwidth) + (frame.right - frame.left)
mmi.ptMaxTrackSize.y = int32(window.maxheight + yoff) mmi.ptMaxTrackSize.y = int32(window.maxheight) + (frame.bottom - frame.top)
} }
if !window.decorated { if !window.decorated {
@ -1205,7 +1232,7 @@ func (w *Window) createNativeWindow(wndconfig *wndconfig, fbconfig *fbconfig) er
style := w.getWindowStyle() style := w.getWindowStyle()
exStyle := w.getWindowExStyle() exStyle := w.getWindowExStyle()
var xpos, ypos, fullWidth, fullHeight int32 var frameX, frameY, frameWidth, frameHeight int32
if w.monitor != nil { if w.monitor != nil {
mi, ok := _GetMonitorInfoW(w.monitor.platform.handle) mi, ok := _GetMonitorInfoW(w.monitor.platform.handle)
if !ok { 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 // NOTE: This window placement is temporary and approximate, as the
// correct position and size cannot be known until the monitor // correct position and size cannot be known until the monitor
// video mode has been picked in _glfwSetVideoModeWin32 // video mode has been picked in _glfwSetVideoModeWin32
xpos = mi.rcMonitor.left frameX = mi.rcMonitor.left
ypos = mi.rcMonitor.top frameY = mi.rcMonitor.top
fullWidth = mi.rcMonitor.right - mi.rcMonitor.left frameWidth = mi.rcMonitor.right - mi.rcMonitor.left
fullHeight = mi.rcMonitor.bottom - mi.rcMonitor.top frameHeight = mi.rcMonitor.bottom - mi.rcMonitor.top
} else { } else {
xpos = _CW_USEDEFAULT rect := _RECT{0, 0, int32(wndconfig.width), int32(wndconfig.height)}
ypos = _CW_USEDEFAULT
w.platform.maximized = wndconfig.maximized w.platform.maximized = wndconfig.maximized
if wndconfig.maximized { if wndconfig.maximized {
style |= _WS_MAXIMIZE style |= _WS_MAXIMIZE
} }
w, h, err := getFullWindowSize(style, exStyle, wndconfig.width, wndconfig.height, _USER_DEFAULT_SCREEN_DPI) if err := _AdjustWindowRectEx(&rect, style, false, exStyle); err != nil {
if err != nil {
return err 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 parent window
0, // No window menu 0, // No window menu
_glfw.platformWindow.instance, unsafe.Pointer(wndconfig)) _glfw.platformWindow.instance, unsafe.Pointer(wndconfig))
@ -1459,7 +1488,15 @@ func (w *Window) platformDestroyWindow() error {
} }
if _glfw.platformWindow.disabledCursorWindow == w { 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 { 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, // NOTE: Re-center the cursor only if it has moved since the last call,
// to avoid breaking glfwWaitEvents with WM_MOUSEMOVE // 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 window.platform.lastCursorPosX != width/2 || window.platform.lastCursorPosY != height/2 {
if err := window.platformSetCursorPos(float64(width/2), float64(height/2)); err != nil { if err := window.platformSetCursorPos(float64(width/2), float64(height/2)); err != nil {
return err return err
@ -2184,7 +2222,7 @@ func platformWaitEvents() error {
} }
func platformWaitEventsTimeout(timeout float64) 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 return err
} }
if err := platformPollEvents(); err != nil { if err := platformPollEvents(); err != nil {
@ -2235,20 +2273,48 @@ func (w *Window) platformSetCursorPos(xpos, ypos float64) error {
} }
func (w *Window) platformSetCursorMode(mode int) error { func (w *Window) platformSetCursorMode(mode int) error {
if mode == CursorDisabled { if w.platformWindowFocused() {
if w.platformWindowFocused() { if mode == CursorDisabled {
if err := w.disableCursor(); err != nil { 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 err
} }
} }
return nil
}
if _glfw.platformWindow.disabledCursorWindow == w { if mode == CursorDisabled {
if err := w.enableCursor(); err != nil { _glfw.platformWindow.disabledCursorWindow = w
return err } 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() in, err := w.cursorInContentArea()
@ -2265,10 +2331,14 @@ func (w *Window) platformSetCursorMode(mode int) error {
} }
func platformGetScancodeName(scancode int) (string, 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 "", 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 { func platformGetKeyScancode(key Key) int {

View File

@ -676,7 +676,7 @@ GLFWAPI float glfwGetWindowOpacity(GLFWwindow* handle)
_GLFWwindow* window = (_GLFWwindow*) handle; _GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL); assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(1.f); _GLFW_REQUIRE_INIT_OR_RETURN(0.f);
return _glfwPlatformGetWindowOpacity(window); return _glfwPlatformGetWindowOpacity(window);
} }

View File

@ -357,6 +357,11 @@ static void updateNormalHints(_GLFWwindow* window, int width, int height)
{ {
XSizeHints* hints = XAllocSizeHints(); XSizeHints* hints = XAllocSizeHints();
long supplied;
XGetWMNormalHints(_glfw.x11.display, window->x11.handle, hints, &supplied);
hints->flags &= ~(PMinSize | PMaxSize | PAspect);
if (!window->monitor) if (!window->monitor)
{ {
if (window->resizable) 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); XSetWMNormalHints(_glfw.x11.display, window->x11.handle, hints);
XFree(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 // Enable XI2 raw mouse motion events
// //
static void enableRawMouseMotion(_GLFWwindow* window) static void enableRawMouseMotion(_GLFWwindow* window)
@ -603,12 +624,7 @@ static void disableCursor(_GLFWwindow* window)
&_glfw.x11.restoreCursorPosY); &_glfw.x11.restoreCursorPosY);
updateCursorImage(window); updateCursorImage(window);
_glfwCenterCursorInContentArea(window); _glfwCenterCursorInContentArea(window);
XGrabPointer(_glfw.x11.display, window->x11.handle, True, captureCursor(window);
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
GrabModeAsync, GrabModeAsync,
window->x11.handle,
_glfw.x11.hiddenCursorHandle,
CurrentTime);
} }
// Exit disabled cursor mode for the specified window // Exit disabled cursor mode for the specified window
@ -619,7 +635,7 @@ static void enableCursor(_GLFWwindow* window)
disableRawMouseMotion(window); disableRawMouseMotion(window);
_glfw.x11.disabledCursorWindow = NULL; _glfw.x11.disabledCursorWindow = NULL;
XUngrabPointer(_glfw.x11.display, CurrentTime); releaseCursor();
_glfwPlatformSetCursorPos(window, _glfwPlatformSetCursorPos(window,
_glfw.x11.restoreCursorPosX, _glfw.x11.restoreCursorPosX,
_glfw.x11.restoreCursorPosY); _glfw.x11.restoreCursorPosY);
@ -764,7 +780,28 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
XFree(hints); 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 // Set ICCCM WM_CLASS property
{ {
@ -1563,6 +1600,9 @@ static void processEvent(XEvent *event)
if (event->xconfigure.width != window->x11.width || if (event->xconfigure.width != window->x11.width ||
event->xconfigure.height != window->x11.height) event->xconfigure.height != window->x11.height)
{ {
window->x11.width = event->xconfigure.width;
window->x11.height = event->xconfigure.height;
_glfwInputFramebufferSize(window, _glfwInputFramebufferSize(window,
event->xconfigure.width, event->xconfigure.width,
event->xconfigure.height); event->xconfigure.height);
@ -1570,9 +1610,6 @@ static void processEvent(XEvent *event)
_glfwInputWindowSize(window, _glfwInputWindowSize(window,
event->xconfigure.width, event->xconfigure.width,
event->xconfigure.height); event->xconfigure.height);
window->x11.width = event->xconfigure.width;
window->x11.height = event->xconfigure.height;
} }
int xpos = event->xconfigure.x; int xpos = event->xconfigure.x;
@ -1600,9 +1637,10 @@ static void processEvent(XEvent *event)
if (xpos != window->x11.xpos || ypos != window->x11.ypos) if (xpos != window->x11.xpos || ypos != window->x11.ypos)
{ {
_glfwInputWindowPos(window, xpos, ypos);
window->x11.xpos = xpos; window->x11.xpos = xpos;
window->x11.ypos = ypos; window->x11.ypos = ypos;
_glfwInputWindowPos(window, xpos, ypos);
} }
return; return;
@ -2085,7 +2123,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
void _glfwPlatformDestroyWindow(_GLFWwindow* window) void _glfwPlatformDestroyWindow(_GLFWwindow* window)
{ {
if (_glfw.x11.disabledCursorWindow == window) if (_glfw.x11.disabledCursorWindow == window)
_glfw.x11.disabledCursorWindow = NULL; enableCursor(window);
if (window->monitor) if (window->monitor)
releaseMonitor(window); releaseMonitor(window);
@ -2891,16 +2929,40 @@ void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y)
void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
{ {
if (mode == GLFW_CURSOR_DISABLED) if (_glfwPlatformWindowFocused(window))
{ {
if (_glfwPlatformWindowFocused(window)) if (mode == GLFW_CURSOR_DISABLED)
disableCursor(window); {
} _glfwPlatformGetCursorPos(window,
else if (_glfw.x11.disabledCursorWindow == window) &_glfw.x11.restoreCursorPosX,
enableCursor(window); &_glfw.x11.restoreCursorPosY);
else _glfwCenterCursorInContentArea(window);
updateCursorImage(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); XFlush(_glfw.x11.display);
} }
@ -2909,14 +2971,15 @@ const char* _glfwPlatformGetScancodeName(int scancode)
if (!_glfw.x11.xkb.available) if (!_glfw.x11.xkb.available)
return NULL; return NULL;
if (scancode < 0 || scancode > 0xff || if (scancode < 0 || scancode > 0xff)
_glfw.x11.keycodes[scancode] == GLFW_KEY_UNKNOWN)
{ {
_glfwInputError(GLFW_INVALID_VALUE, "Invalid scancode %i", scancode); _glfwInputError(GLFW_INVALID_VALUE, "Invalid scancode %i", scancode);
return NULL; return NULL;
} }
const int key = _glfw.x11.keycodes[scancode]; const int key = _glfw.x11.keycodes[scancode];
if (key == GLFW_KEY_UNKNOWN)
return NULL;
const KeySym keysym = XkbKeycodeToKeysym(_glfw.x11.display, const KeySym keysym = XkbKeycodeToKeysym(_glfw.x11.display,
scancode, _glfw.x11.xkb.group, 0); scancode, _glfw.x11.xkb.group, 0);
if (keysym == NoSymbol) if (keysym == NoSymbol)

View File

@ -161,8 +161,8 @@ func __vertex(dstPos vec2, srcPos vec2, color vec4) (vec4, vec2, vec4) {
return shaderSuffix, nil return shaderSuffix, nil
} }
func CompileShader(src []byte) (*shaderir.Program, error) { func completeShaderSource(fragmentSrc []byte) ([]byte, error) {
unit, err := shader.ParseCompilerDirectives(src) unit, err := shader.ParseCompilerDirectives(fragmentSrc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -172,14 +172,23 @@ func CompileShader(src []byte) (*shaderir.Program, error) {
} }
var buf bytes.Buffer var buf bytes.Buffer
buf.Write(src) buf.Write(fragmentSrc)
buf.WriteString(suffix) 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 ( const (
vert = "__vertex" vert = "__vertex"
frag = "Fragment" frag = "Fragment"
) )
ir, err := shader.Compile(buf.Bytes(), vert, frag, ShaderSrcImageCount) ir, err := shader.Compile(src, vert, frag, ShaderSrcImageCount)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -193,3 +202,11 @@ func CompileShader(src []byte) (*shaderir.Program, error) {
return ir, nil 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
}

View File

@ -182,7 +182,7 @@ func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dsts [graphics.S
if c.fillRule != fillRule { if c.fillRule != fillRule {
return false return false
} }
if c.fillRule != graphicsdriver.FillAll && mightOverlapDstRegions(c.vertices, vertices) { if c.fillRule != graphicsdriver.FillRuleFillAll && mightOverlapDstRegions(c.vertices, vertices) {
return false return false
} }
return true return true

View File

@ -43,14 +43,14 @@ const (
maxVertexFloatCount = MaxVertexCount * graphics.VertexFloatCount maxVertexFloatCount = MaxVertexCount * graphics.VertexFloatCount
) )
var vsyncEnabled int32 = 1 var vsyncEnabled atomic.Bool
func init() {
vsyncEnabled.Store(true)
}
func SetVsyncEnabled(enabled bool, graphicsDriver graphicsdriver.Graphics) { func SetVsyncEnabled(enabled bool, graphicsDriver graphicsdriver.Graphics) {
if enabled { vsyncEnabled.Store(enabled)
atomic.StoreInt32(&vsyncEnabled, 1)
} else {
atomic.StoreInt32(&vsyncEnabled, 0)
}
runOnRenderThread(func() { runOnRenderThread(func() {
graphicsDriver.SetVsyncEnabled(enabled) graphicsDriver.SetVsyncEnabled(enabled)
@ -185,7 +185,7 @@ func (q *commandQueue) Flush(graphicsDriver graphicsdriver.Graphics, endFrame bo
var sync bool var sync bool
// Disable asynchronous rendering when vsync is on, as this causes a rendering delay (#2822). // 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 sync = true
} }
if !sync { if !sync {

View File

@ -31,7 +31,7 @@ import (
var nearestFilterShader *graphicscommand.Shader var nearestFilterShader *graphicscommand.Shader
func init() { 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 { if err != nil {
panic(fmt.Sprintf("graphicscommand: compiling the nearest shader failed: %v", err)) 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) vs := quadVertices(w/2, h/2)
is := graphics.QuadIndices() is := graphics.QuadIndices()
dr := image.Rect(0, 0, w, h) 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) pix := make([]byte, 4*w*h)
if err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), []graphicsdriver.PixelsArgs{ if err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), []graphicsdriver.PixelsArgs{
@ -90,8 +90,8 @@ func TestWritePixelsPartAfterDrawTriangles(t *testing.T) {
vs := quadVertices(w/2, h/2) vs := quadVertices(w/2, h/2)
is := graphics.QuadIndices() is := graphics.QuadIndices()
dr := image.Rect(0, 0, w, h) 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)
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{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, nearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
bs := graphics.NewManagedBytes(4, func(bs []byte) { bs := graphics.NewManagedBytes(4, func(bs []byte) {
for i := range bs { for i := range bs {
bs[i] = 0 bs[i] = 0
@ -109,11 +109,11 @@ func TestShader(t *testing.T) {
vs := quadVertices(w, h) vs := quadVertices(w, h)
is := graphics.QuadIndices() is := graphics.QuadIndices()
dr := image.Rect(0, 0, w, h) 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() g := ui.Get().GraphicsDriverForTesting()
s := graphicscommand.NewShader(etesting.ShaderProgramFill(0xff, 0, 0, 0xff)) 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) pix := make([]byte, 4*w*h)
if err := dst.ReadPixels(g, []graphicsdriver.PixelsArgs{ if err := dst.ReadPixels(g, []graphicsdriver.PixelsArgs{

View File

@ -71,7 +71,8 @@ const (
) )
var ( var (
procD3DCompile *windows.LazyProc procD3DCompile *windows.LazyProc
procD3DCreateBlob *windows.LazyProc
) )
func init() { func init() {
@ -93,6 +94,7 @@ func init() {
} }
procD3DCompile = d3dcompiler.NewProc("D3DCompile") procD3DCompile = d3dcompiler.NewProc("D3DCompile")
procD3DCreateBlob = d3dcompiler.NewProc("D3DCreateBlob")
} }
func isD3DCompilerDLLAvailable() bool { func isD3DCompilerDLLAvailable() bool {
@ -135,6 +137,19 @@ func _D3DCompile(srcData []byte, sourceName string, pDefines []_D3D_SHADER_MACRO
return code, nil 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 { type _D3D_SHADER_MACRO struct {
Name *byte Name *byte
Definition *byte Definition *byte

View File

@ -482,8 +482,7 @@ func (g *graphics11) MaxImageSize() int {
} }
func (g *graphics11) NewShader(program *shaderir.Program) (graphicsdriver.Shader, error) { func (g *graphics11) NewShader(program *shaderir.Program) (graphicsdriver.Shader, error) {
vs, ps, offsets := hlsl.Compile(program) vsh, psh, err := compileShader(program)
vsh, psh, err := compileShader(vs, ps)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -492,7 +491,7 @@ func (g *graphics11) NewShader(program *shaderir.Program) (graphicsdriver.Shader
graphics: g, graphics: g,
id: g.genNextShaderID(), id: g.genNextShaderID(),
uniformTypes: program.Uniforms, uniformTypes: program.Uniforms,
uniformOffsets: offsets, uniformOffsets: hlsl.CalcUniformMemoryOffsets(program),
vertexShaderBlob: vsh, vertexShaderBlob: vsh,
pixelShaderBlob: psh, pixelShaderBlob: psh,
} }
@ -629,7 +628,7 @@ func (g *graphics11) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics
} }
g.deviceContext.RSSetViewports([]_D3D11_VIEWPORT{vp}) 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 return err
} }
@ -639,7 +638,7 @@ func (g *graphics11) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics
return err return err
} }
if fillRule == graphicsdriver.FillAll { if fillRule == graphicsdriver.FillRuleFillAll {
bs, err := g.blendState(blend, noStencil) bs, err := g.blendState(blend, noStencil)
if err != nil { if err != nil {
return err return err
@ -664,9 +663,9 @@ func (g *graphics11) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics
}) })
switch fillRule { switch fillRule {
case graphicsdriver.FillAll: case graphicsdriver.FillRuleFillAll:
g.deviceContext.DrawIndexed(uint32(dstRegion.IndexCount), uint32(indexOffset), 0) g.deviceContext.DrawIndexed(uint32(dstRegion.IndexCount), uint32(indexOffset), 0)
case graphicsdriver.NonZero: case graphicsdriver.FillRuleNonZero:
bs, err := g.blendState(blend, incrementStencil) bs, err := g.blendState(blend, incrementStencil)
if err != nil { if err != nil {
return err return err
@ -678,7 +677,7 @@ func (g *graphics11) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics
} }
g.deviceContext.OMSetDepthStencilState(dss, 0) g.deviceContext.OMSetDepthStencilState(dss, 0)
g.deviceContext.DrawIndexed(uint32(dstRegion.IndexCount), uint32(indexOffset), 0) g.deviceContext.DrawIndexed(uint32(dstRegion.IndexCount), uint32(indexOffset), 0)
case graphicsdriver.EvenOdd: case graphicsdriver.FillRuleEvenOdd:
bs, err := g.blendState(blend, invertStencil) bs, err := g.blendState(blend, invertStencil)
if err != nil { if err != nil {
return err return err
@ -692,7 +691,7 @@ func (g *graphics11) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics
g.deviceContext.DrawIndexed(uint32(dstRegion.IndexCount), uint32(indexOffset), 0) g.deviceContext.DrawIndexed(uint32(dstRegion.IndexCount), uint32(indexOffset), 0)
} }
if fillRule != graphicsdriver.FillAll { if fillRule != graphicsdriver.FillRuleFillAll {
bs, err := g.blendState(blend, drawWithStencil) bs, err := g.blendState(blend, drawWithStencil)
if err != nil { if err != nil {
return err return err

View File

@ -1093,8 +1093,7 @@ func (g *graphics12) MaxImageSize() int {
} }
func (g *graphics12) NewShader(program *shaderir.Program) (graphicsdriver.Shader, error) { func (g *graphics12) NewShader(program *shaderir.Program) (graphicsdriver.Shader, error) {
vs, ps, offsets := hlsl.Compile(program) vsh, psh, err := compileShader(program)
vsh, psh, err := compileShader(vs, ps)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1103,7 +1102,7 @@ func (g *graphics12) NewShader(program *shaderir.Program) (graphicsdriver.Shader
graphics: g, graphics: g,
id: g.genNextShaderID(), id: g.genNextShaderID(),
uniformTypes: program.Uniforms, uniformTypes: program.Uniforms,
uniformOffsets: offsets, uniformOffsets: hlsl.CalcUniformMemoryOffsets(program),
vertexShader: vsh, vertexShader: vsh,
pixelShader: psh, pixelShader: psh,
} }
@ -1197,7 +1196,7 @@ func (g *graphics12) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics
// Release constant buffers when too many ones will be created. // Release constant buffers when too many ones will be created.
numPipelines := 1 numPipelines := 1
if fillRule != graphicsdriver.FillAll { if fillRule != graphicsdriver.FillRuleFillAll {
numPipelines = 2 numPipelines = 2
} }
if len(g.pipelineStates.constantBuffers[g.frameIndex])+numPipelines > numDescriptorsPerFrame { if len(g.pipelineStates.constantBuffers[g.frameIndex])+numPipelines > numDescriptorsPerFrame {
@ -1264,7 +1263,7 @@ func (g *graphics12) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics
targetCount = graphics.ShaderDstImageCount 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 return err
} }

View File

@ -289,7 +289,7 @@ func (p *pipelineStates) drawTriangles(device *_ID3D12Device, commandList *_ID3D
} }
commandList.SetGraphicsRootDescriptorTable(2, sh) commandList.SetGraphicsRootDescriptorTable(2, sh)
if fillRule == graphicsdriver.FillAll { if fillRule == graphicsdriver.FillRuleFillAll {
s, err := shader.pipelineState(blend, noStencil, screen) s, err := shader.pipelineState(blend, noStencil, screen)
if err != nil { if err != nil {
return err return err
@ -307,16 +307,16 @@ func (p *pipelineStates) drawTriangles(device *_ID3D12Device, commandList *_ID3D
}, },
}) })
switch fillRule { switch fillRule {
case graphicsdriver.FillAll: case graphicsdriver.FillRuleFillAll:
commandList.DrawIndexedInstanced(uint32(dstRegion.IndexCount), 1, uint32(indexOffset), 0, 0) commandList.DrawIndexedInstanced(uint32(dstRegion.IndexCount), 1, uint32(indexOffset), 0, 0)
case graphicsdriver.NonZero: case graphicsdriver.FillRuleNonZero:
s, err := shader.pipelineState(blend, incrementStencil, screen) s, err := shader.pipelineState(blend, incrementStencil, screen)
if err != nil { if err != nil {
return err return err
} }
commandList.SetPipelineState(s) commandList.SetPipelineState(s)
commandList.DrawIndexedInstanced(uint32(dstRegion.IndexCount), 1, uint32(indexOffset), 0, 0) commandList.DrawIndexedInstanced(uint32(dstRegion.IndexCount), 1, uint32(indexOffset), 0, 0)
case graphicsdriver.EvenOdd: case graphicsdriver.FillRuleEvenOdd:
s, err := shader.pipelineState(blend, invertStencil, screen) s, err := shader.pipelineState(blend, invertStencil, screen)
if err != nil { if err != nil {
return err return err
@ -325,7 +325,7 @@ func (p *pipelineStates) drawTriangles(device *_ID3D12Device, commandList *_ID3D
commandList.DrawIndexedInstanced(uint32(dstRegion.IndexCount), 1, uint32(indexOffset), 0, 0) 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) s, err := shader.pipelineState(blend, drawWithStencil, screen)
if err != nil { if err != nil {
return err return err

View File

@ -16,18 +16,67 @@ package directx
import ( import (
"fmt" "fmt"
"sync"
"unsafe"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir" "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{} var vertexShaderCache = map[string]*_ID3DBlob{}
func compileShader(vs, ps string) (vsh, psh *_ID3DBlob, ferr error) { func compileShader(program *shaderir.Program) (vsh, psh *_ID3DBlob, ferr error) {
var flag uint32 = uint32(_D3DCOMPILE_OPTIMIZATION_LEVEL3)
defer func() { defer func() {
if ferr == nil { if ferr == nil {
return 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 var wg errgroup.Group
// Vertex shaders are likely the same. If so, reuse the same _ID3DBlob. // 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 { 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 { if err != nil {
return fmt.Errorf("directx: D3DCompile for VSMain failed, original source: %s, %w", vs, err) 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 { 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 { if err != nil {
return fmt.Errorf("directx: D3DCompile for PSMain failed, original source: %s, %w", ps, err) 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 nil, nil, err
} }
return return vsh, psh, nil
} }
func constantBufferSize(uniformTypes []shaderir.Type, uniformOffsets []int) int { func constantBufferSize(uniformTypes []shaderir.Type, uniformOffsets []int) int {

View File

@ -30,19 +30,19 @@ type DstRegion struct {
type FillRule int type FillRule int
const ( const (
FillAll FillRule = iota FillRuleFillAll FillRule = iota
NonZero FillRuleNonZero
EvenOdd FillRuleEvenOdd
) )
func (f FillRule) String() string { func (f FillRule) String() string {
switch f { switch f {
case FillAll: case FillRuleFillAll:
return "FillAll" return "FillRuleFillAll"
case NonZero: case FillRuleNonZero:
return "NonZero" return "FillRuleNonZero"
case EvenOdd: case FillRuleEvenOdd:
return "EvenOdd" return "FillRuleEvenOdd"
default: default:
return fmt.Sprintf("FillRule(%d)", f) return fmt.Sprintf("FillRule(%d)", f)
} }

View File

@ -35,7 +35,7 @@ import (
// Layer is an object that manages image-based content and // Layer is an object that manages image-based content and
// allows you to perform animations on that content. // 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 { type Layer interface {
// Layer returns the underlying CALayer * pointer. // Layer returns the underlying CALayer * pointer.
Layer() unsafe.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. // 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 { type MetalLayer struct {
metalLayer objc.ID 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. // Reference: https://developer.apple.com/documentation/quartzcore/cametallayer?language=objc.
func MakeMetalLayer() (MetalLayer, error) { func NewMetalLayer() (MetalLayer, error) {
coreGraphics, err := purego.Dlopen("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics", purego.RTLD_LAZY|purego.RTLD_GLOBAL) coreGraphics, err := purego.Dlopen("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics", purego.RTLD_LAZY|purego.RTLD_GLOBAL)
if err != nil { if err != nil {
return MetalLayer{}, err return MetalLayer{}, err
@ -88,14 +88,14 @@ func (ml MetalLayer) Layer() unsafe.Pointer {
// PixelFormat returns the pixel format of textures for rendering layer content. // 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 { func (ml MetalLayer) PixelFormat() mtl.PixelFormat {
return mtl.PixelFormat(ml.metalLayer.Send(objc.RegisterName("pixelFormat"))) return mtl.PixelFormat(ml.metalLayer.Send(objc.RegisterName("pixelFormat")))
} }
// SetDevice sets the Metal device responsible for the layer's drawable resources. // 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) { func (ml MetalLayer) SetDevice(device mtl.Device) {
ml.metalLayer.Send(objc.RegisterName("setDevice:"), uintptr(device.Device())) ml.metalLayer.Send(objc.RegisterName("setDevice:"), uintptr(device.Device()))
} }
@ -111,7 +111,7 @@ func (ml MetalLayer) SetOpaque(opaque bool) {
// PixelFormatRGBA16Float, PixelFormatBGRA10XR, or PixelFormatBGRA10XRSRGB. // PixelFormatRGBA16Float, PixelFormatBGRA10XR, or PixelFormatBGRA10XRSRGB.
// SetPixelFormat panics for other values. // 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) { func (ml MetalLayer) SetPixelFormat(pf mtl.PixelFormat) {
switch pf { switch pf {
case mtl.PixelFormatRGBA8UNorm, mtl.PixelFormatRGBA8UNormSRGB, mtl.PixelFormatBGRA8UNorm, mtl.PixelFormatBGRA8UNormSRGB, mtl.PixelFormatStencil8: 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. // 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) { func (ml MetalLayer) SetMaximumDrawableCount(count int) {
if count < 2 || count > 3 { 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))) 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 // SetDisplaySyncEnabled controls whether the Metal layer and its drawables
// are synchronized with the display's refresh rate. // 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) { func (ml MetalLayer) SetDisplaySyncEnabled(enabled bool) {
if runtime.GOOS == "ios" { if runtime.GOOS == "ios" {
return return
@ -147,7 +147,7 @@ func (ml MetalLayer) SetDisplaySyncEnabled(enabled bool) {
// SetDrawableSize sets the size, in pixels, of textures for rendering layer content. // 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) { func (ml MetalLayer) SetDrawableSize(width, height int) {
// TODO: once objc supports calling functions with struct arguments replace this with just a ID.Send call // TODO: once objc supports calling functions with struct arguments replace this with just a ID.Send call
var sel_setDrawableSize = objc.RegisterName("setDrawableSize:") var sel_setDrawableSize = objc.RegisterName("setDrawableSize:")
@ -161,7 +161,7 @@ func (ml MetalLayer) SetDrawableSize(width, height int) {
// NextDrawable returns a Metal drawable. // 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) { func (ml MetalLayer) NextDrawable() (MetalDrawable, error) {
md := ml.metalLayer.Send(objc.RegisterName("nextDrawable")) md := ml.metalLayer.Send(objc.RegisterName("nextDrawable"))
if md == 0 { 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. // 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 { func (ml MetalLayer) PresentsWithTransaction() bool {
return ml.metalLayer.Send(objc.RegisterName("presentsWithTransaction")) != 0 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. // 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) { func (ml MetalLayer) SetPresentsWithTransaction(presentsWithTransaction bool) {
ml.metalLayer.Send(objc.RegisterName("setPresentsWithTransaction:"), presentsWithTransaction) ml.metalLayer.Send(objc.RegisterName("setPresentsWithTransaction:"), presentsWithTransaction)
} }
// SetFramebufferOnly sets a Boolean value that determines whether the layers textures are used only for rendering. // SetFramebufferOnly sets a Boolean value that determines whether the layers 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) { func (ml MetalLayer) SetFramebufferOnly(framebufferOnly bool) {
ml.metalLayer.Send(objc.RegisterName("setFramebufferOnly:"), framebufferOnly) ml.metalLayer.Send(objc.RegisterName("setFramebufferOnly:"), framebufferOnly)
} }
// MetalDrawable is a displayable resource that can be rendered or written to by Metal. // 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 { type MetalDrawable struct {
metalDrawable objc.ID 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. // 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 { func (md MetalDrawable) Texture() mtl.Texture {
return mtl.NewTexture(md.metalDrawable.Send(objc.RegisterName("texture"))) return mtl.NewTexture(md.metalDrawable.Send(objc.RegisterName("texture")))
} }
// Present presents the drawable onscreen as soon as possible. // 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() { func (md MetalDrawable) Present() {
md.metalDrawable.Send(objc.RegisterName("present")) md.metalDrawable.Send(objc.RegisterName("present"))
} }

View File

@ -184,7 +184,7 @@ func (g *Graphics) gcBuffers() {
func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer { func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer {
if g.cb == (mtl.CommandBuffer{}) { if g.cb == (mtl.CommandBuffer{}) {
g.cb = g.cq.MakeCommandBuffer() g.cb = g.cq.CommandBuffer()
} }
var newBuf mtl.Buffer var newBuf mtl.Buffer
@ -197,7 +197,7 @@ func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer {
} }
if newBuf == (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 { if g.buffers == nil {
@ -286,7 +286,7 @@ func (g *Graphics) NewImage(width, height int) (graphicsdriver.Image, error) {
StorageMode: storageMode, StorageMode: storageMode,
Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget, Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget,
} }
t := g.view.getMTLDevice().MakeTexture(td) t := g.view.getMTLDevice().NewTextureWithDescriptor(td)
i := &Image{ i := &Image{
id: g.genNextImageID(), id: g.genNextImageID(),
graphics: g, graphics: g,
@ -397,7 +397,7 @@ func (g *Graphics) Initialize() error {
} }
// The stencil reference value is always 0 (default). // 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{ BackFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationKeep, StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: mtl.StencilOperationKeep, DepthFailureOperation: mtl.StencilOperationKeep,
@ -411,7 +411,7 @@ func (g *Graphics) Initialize() error {
StencilCompareFunction: mtl.CompareFunctionAlways, StencilCompareFunction: mtl.CompareFunctionAlways,
}, },
}) })
g.dsss[incrementStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{ g.dsss[incrementStencil] = g.view.getMTLDevice().NewDepthStencilStateWithDescriptor(mtl.DepthStencilDescriptor{
BackFaceStencil: mtl.StencilDescriptor{ BackFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationKeep, StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: mtl.StencilOperationKeep, DepthFailureOperation: mtl.StencilOperationKeep,
@ -425,7 +425,7 @@ func (g *Graphics) Initialize() error {
StencilCompareFunction: mtl.CompareFunctionAlways, StencilCompareFunction: mtl.CompareFunctionAlways,
}, },
}) })
g.dsss[invertStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{ g.dsss[invertStencil] = g.view.getMTLDevice().NewDepthStencilStateWithDescriptor(mtl.DepthStencilDescriptor{
BackFaceStencil: mtl.StencilDescriptor{ BackFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationKeep, StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: mtl.StencilOperationKeep, DepthFailureOperation: mtl.StencilOperationKeep,
@ -439,7 +439,7 @@ func (g *Graphics) Initialize() error {
StencilCompareFunction: mtl.CompareFunctionAlways, StencilCompareFunction: mtl.CompareFunctionAlways,
}, },
}) })
g.dsss[drawWithStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{ g.dsss[drawWithStencil] = g.view.getMTLDevice().NewDepthStencilStateWithDescriptor(mtl.DepthStencilDescriptor{
BackFaceStencil: mtl.StencilDescriptor{ BackFaceStencil: mtl.StencilDescriptor{
StencilFailureOperation: mtl.StencilOperationKeep, StencilFailureOperation: mtl.StencilOperationKeep,
DepthFailureOperation: 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 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 // When preparing a stencil buffer, flush the current render command encoder
// to make sure the stencil buffer is cleared when loading. // to make sure the stencil buffer is cleared when loading.
// TODO: What about clearing the stencil buffer by vertices? // 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.flushRenderCommandEncoderIfNeeded()
} }
g.lastDst = dst 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].Texture = t
rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{} rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{}
if fillRule != graphicsdriver.FillAll { if fillRule != graphicsdriver.FillRuleFillAll {
dst.ensureStencil() dst.ensureStencil()
rpd.StencilAttachment.LoadAction = mtl.LoadActionClear rpd.StencilAttachment.LoadAction = mtl.LoadActionClear
rpd.StencilAttachment.StoreAction = mtl.StoreActionDontCare rpd.StencilAttachment.StoreAction = mtl.StoreActionDontCare
@ -505,9 +505,9 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
} }
if g.cb == (mtl.CommandBuffer{}) { 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() w, h := dst.internalSize()
@ -544,26 +544,26 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
drawWithStencilRpss mtl.RenderPipelineState drawWithStencilRpss mtl.RenderPipelineState
) )
switch fillRule { switch fillRule {
case graphicsdriver.FillAll: case graphicsdriver.FillRuleFillAll:
s, err := shader.RenderPipelineState(&g.view, blend, noStencil, dst.screen) s, err := shader.RenderPipelineState(&g.view, blend, noStencil, dst.screen)
if err != nil { if err != nil {
return err return err
} }
noStencilRpss = s noStencilRpss = s
case graphicsdriver.NonZero: case graphicsdriver.FillRuleNonZero:
s, err := shader.RenderPipelineState(&g.view, blend, incrementStencil, dst.screen) s, err := shader.RenderPipelineState(&g.view, blend, incrementStencil, dst.screen)
if err != nil { if err != nil {
return err return err
} }
incrementStencilRpss = s incrementStencilRpss = s
case graphicsdriver.EvenOdd: case graphicsdriver.FillRuleEvenOdd:
s, err := shader.RenderPipelineState(&g.view, blend, invertStencil, dst.screen) s, err := shader.RenderPipelineState(&g.view, blend, invertStencil, dst.screen)
if err != nil { if err != nil {
return err return err
} }
invertStencilRpss = s invertStencilRpss = s
} }
if fillRule != graphicsdriver.FillAll { if fillRule != graphicsdriver.FillRuleFillAll {
s, err := shader.RenderPipelineState(&g.view, blend, drawWithStencil, dst.screen) s, err := shader.RenderPipelineState(&g.view, blend, drawWithStencil, dst.screen)
if err != nil { if err != nil {
return err return err
@ -580,20 +580,20 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
}) })
switch fillRule { switch fillRule {
case graphicsdriver.FillAll: case graphicsdriver.FillRuleFillAll:
g.rce.SetDepthStencilState(g.dsss[noStencil]) g.rce.SetDepthStencilState(g.dsss[noStencil])
g.rce.SetRenderPipelineState(noStencilRpss) g.rce.SetRenderPipelineState(noStencilRpss)
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0)))) 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.SetDepthStencilState(g.dsss[incrementStencil])
g.rce.SetRenderPipelineState(incrementStencilRpss) g.rce.SetRenderPipelineState(incrementStencilRpss)
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0)))) 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.SetDepthStencilState(g.dsss[invertStencil])
g.rce.SetRenderPipelineState(invertStencilRpss) g.rce.SetRenderPipelineState(invertStencilRpss)
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0)))) 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.SetDepthStencilState(g.dsss[drawWithStencil])
g.rce.SetRenderPipelineState(drawWithStencilRpss) g.rce.SetRenderPipelineState(drawWithStencilRpss)
g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0)))) 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?") panic("metal: command buffer must be empty at syncTexture: flushIfNeeded is not called yet?")
} }
cb := i.graphics.cq.MakeCommandBuffer() cb := i.graphics.cq.CommandBuffer()
bce := cb.MakeBlitCommandEncoder() bce := cb.BlitCommandEncoder()
bce.SynchronizeTexture(i.texture, 0, 0) bce.SynchronizeTexture(i.texture, 0, 0)
bce.EndEncoding() bce.EndEncoding()
@ -859,7 +859,7 @@ func (i *Image) WritePixels(args []graphicsdriver.PixelsArgs) error {
StorageMode: storageMode, StorageMode: storageMode,
Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget, Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget,
} }
t := g.view.getMTLDevice().MakeTexture(td) t := g.view.getMTLDevice().NewTextureWithDescriptor(td)
g.tmpTextures = append(g.tmpTextures, t) g.tmpTextures = append(g.tmpTextures, t)
for _, a := range args { for _, a := range args {
@ -870,9 +870,9 @@ func (i *Image) WritePixels(args []graphicsdriver.PixelsArgs) error {
} }
if g.cb == (mtl.CommandBuffer{}) { 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 { 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} 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} ss := mtl.Size{Width: a.Region.Dx(), Height: a.Region.Dy(), Depth: 1}
@ -914,5 +914,5 @@ func (i *Image) ensureStencil() {
StorageMode: mtl.StorageModePrivate, StorageMode: mtl.StorageModePrivate,
Usage: mtl.TextureUsageRenderTarget, Usage: mtl.TextureUsageRenderTarget,
} }
i.stencil = i.graphics.view.getMTLDevice().MakeTexture(td) i.stencil = i.graphics.view.getMTLDevice().NewTextureWithDescriptor(td)
} }

View 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")
}

View File

@ -112,15 +112,15 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) {
return in.color; return in.color;
} }
` `
lib, err := device.MakeLibrary(source, mtl.CompileOptions{}) lib, err := device.NewLibraryWithSource(source, mtl.CompileOptions{})
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
vs, err := lib.MakeFunction("VertexShader") vs, err := lib.NewFunctionWithName("VertexShader")
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
fs, err := lib.MakeFunction("FragmentShader") fs, err := lib.NewFunctionWithName("FragmentShader")
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
@ -129,7 +129,7 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) {
rpld.FragmentFunction = fs rpld.FragmentFunction = fs
rpld.ColorAttachments[0].PixelFormat = mtl.PixelFormatRGBA8UNorm rpld.ColorAttachments[0].PixelFormat = mtl.PixelFormatRGBA8UNorm
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskAll rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskAll
rps, err := device.MakeRenderPipelineState(rpld) rps, err := device.NewRenderPipelineStateWithDescriptor(rpld)
if err != nil { if err != nil {
log.Fatalln(err) 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{1, 1, 1, 1}},
{f32.Vec4{+0.75, -0.75, 0, 1}, f32.Vec4{0, 0, 0, 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. // Create an output texture to render into.
td := mtl.TextureDescriptor{ td := mtl.TextureDescriptor{
@ -154,10 +154,10 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) {
Height: 20, Height: 20,
StorageMode: mtl.StorageModeManaged, StorageMode: mtl.StorageModeManaged,
} }
texture := device.MakeTexture(td) texture := device.NewTextureWithDescriptor(td)
cq := device.MakeCommandQueue() cq := device.NewCommandQueue()
cb := cq.MakeCommandBuffer() cb := cq.CommandBuffer()
// Encode all render commands. // Encode all render commands.
var rpd mtl.RenderPassDescriptor var rpd mtl.RenderPassDescriptor
@ -165,14 +165,14 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) {
rpd.ColorAttachments[0].StoreAction = mtl.StoreActionStore rpd.ColorAttachments[0].StoreAction = mtl.StoreActionStore
rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{Red: 0, Green: 0, Blue: 0, Alpha: 1} rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{Red: 0, Green: 0, Blue: 0, Alpha: 1}
rpd.ColorAttachments[0].Texture = texture rpd.ColorAttachments[0].Texture = texture
rce := cb.MakeRenderCommandEncoder(rpd) rce := cb.RenderCommandEncoderWithDescriptor(rpd)
rce.SetRenderPipelineState(rps) rce.SetRenderPipelineState(rps)
rce.SetVertexBuffer(vertexBuffer, 0, 0) rce.SetVertexBuffer(vertexBuffer, 0, 0)
rce.DrawPrimitives(mtl.PrimitiveTypeTriangle, 0, 3) rce.DrawPrimitives(mtl.PrimitiveTypeTriangle, 0, 3)
rce.EndEncoding() rce.EndEncoding()
// Encode all blit commands. // Encode all blit commands.
bce := cb.MakeBlitCommandEncoder() bce := cb.BlitCommandEncoder()
bce.Synchronize(texture) bce.Synchronize(texture)
bce.EndEncoding() bce.EndEncoding()

View File

@ -36,7 +36,7 @@ import (
// GPUFamily represents the functionality for families of GPUs. // 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 type GPUFamily int
const ( const (
@ -54,7 +54,7 @@ const (
// FeatureSet defines a specific platform, hardware, and software configuration. // 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 type FeatureSet uint16
const ( const (
@ -92,7 +92,7 @@ const (
// TextureType defines The dimension of each image, including whether multiple images are arranged into an array or // TextureType defines The dimension of each image, including whether multiple images are arranged into an array or
// a cube. // a cube.
// //
// Reference: https://developer.apple.com/documentation/metal/mtltexturetype // Reference: https://developer.apple.com/documentation/metal/mtltexturetype?language=objc.
type TextureType uint16 type TextureType uint16
const ( const (
@ -102,7 +102,7 @@ const (
// PixelFormat defines data formats that describe the organization // PixelFormat defines data formats that describe the organization
// and characteristics of individual pixels in a texture. // 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 type PixelFormat uint16
// The data formats that describe the organization and characteristics // The data formats that describe the organization and characteristics
@ -117,7 +117,7 @@ const (
// PrimitiveType defines geometric primitive types for drawing commands. // 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 type PrimitiveType uint8
// Geometric primitive types for drawing commands. // Geometric primitive types for drawing commands.
@ -132,7 +132,7 @@ const (
// LoadAction defines actions performed at the start of a rendering pass // LoadAction defines actions performed at the start of a rendering pass
// for a render command encoder. // 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 type LoadAction uint8
// Actions performed at the start of a rendering pass for a render command encoder. // 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 // StoreAction defines actions performed at the end of a rendering pass
// for a render command encoder. // 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 type StoreAction uint8
// Actions performed at the end of a rendering pass for a render command encoder. // 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. // 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 type StorageMode uint8
const ( const (
@ -189,7 +189,7 @@ const (
// ResourceOptions defines optional arguments used to create // ResourceOptions defines optional arguments used to create
// and influence behavior of buffer and texture objects. // 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 type ResourceOptions uint16
const ( const (
@ -237,7 +237,7 @@ const (
// CPUCacheMode is the CPU cache mode that defines the CPU mapping of a resource. // 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 type CPUCacheMode uint8
const ( const (
@ -252,7 +252,7 @@ const (
// IndexType is the index type for an index buffer that references vertices of geometric primitives. // 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 type IndexType uint8
const ( const (
@ -358,7 +358,7 @@ const (
// Resource represents a memory allocation for storing specialized data // Resource represents a memory allocation for storing specialized data
// that is accessible to the GPU. // 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 { type Resource interface {
// resource returns the underlying id<MTLResource> pointer. // resource returns the underlying id<MTLResource> pointer.
resource() unsafe.Pointer resource() unsafe.Pointer
@ -366,7 +366,7 @@ type Resource interface {
// RenderPipelineDescriptor configures new RenderPipelineState objects. // 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 { type RenderPipelineDescriptor struct {
// VertexFunction is a programmable function that processes individual vertices in a rendering pass. // VertexFunction is a programmable function that processes individual vertices in a rendering pass.
VertexFunction Function VertexFunction Function
@ -384,7 +384,7 @@ type RenderPipelineDescriptor struct {
// RenderPipelineColorAttachmentDescriptor describes a color render target that specifies // RenderPipelineColorAttachmentDescriptor describes a color render target that specifies
// the color configuration and color operations associated with a render pipeline. // 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 { type RenderPipelineColorAttachmentDescriptor struct {
// PixelFormat is the pixel format of the color attachment's texture. // PixelFormat is the pixel format of the color attachment's texture.
PixelFormat PixelFormat PixelFormat PixelFormat
@ -404,7 +404,7 @@ type RenderPipelineColorAttachmentDescriptor struct {
// RenderPassDescriptor describes a group of render targets that serve as // RenderPassDescriptor describes a group of render targets that serve as
// the output destination for pixels generated by a render pass. // 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 { type RenderPassDescriptor struct {
// ColorAttachments is array of state information for attachments that store color data. // ColorAttachments is array of state information for attachments that store color data.
ColorAttachments [1]RenderPassColorAttachmentDescriptor ColorAttachments [1]RenderPassColorAttachmentDescriptor
@ -416,7 +416,7 @@ type RenderPassDescriptor struct {
// RenderPassColorAttachmentDescriptor describes a color render target that serves // RenderPassColorAttachmentDescriptor describes a color render target that serves
// as the output destination for color pixels generated by a render pass. // 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 { type RenderPassColorAttachmentDescriptor struct {
RenderPassAttachmentDescriptor RenderPassAttachmentDescriptor
ClearColor ClearColor ClearColor ClearColor
@ -425,7 +425,7 @@ type RenderPassColorAttachmentDescriptor struct {
// RenderPassStencilAttachment describes a stencil render target that serves as the output // RenderPassStencilAttachment describes a stencil render target that serves as the output
// destination for stencil pixels generated by a render pass. // 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 { type RenderPassStencilAttachment struct {
RenderPassAttachmentDescriptor RenderPassAttachmentDescriptor
} }
@ -433,7 +433,7 @@ type RenderPassStencilAttachment struct {
// RenderPassAttachmentDescriptor describes a render target that serves // RenderPassAttachmentDescriptor describes a render target that serves
// as the output destination for pixels generated by a render pass. // 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 { type RenderPassAttachmentDescriptor struct {
LoadAction LoadAction LoadAction LoadAction
StoreAction StoreAction StoreAction StoreAction
@ -442,14 +442,14 @@ type RenderPassAttachmentDescriptor struct {
// ClearColor is an RGBA value used for a color pixel. // 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 { type ClearColor struct {
Red, Green, Blue, Alpha float64 Red, Green, Blue, Alpha float64
} }
// TextureDescriptor configures new Texture objects. // 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 { type TextureDescriptor struct {
TextureType TextureType TextureType TextureType
PixelFormat PixelFormat PixelFormat PixelFormat
@ -462,7 +462,7 @@ type TextureDescriptor struct {
// Device is abstract representation of the GPU that // Device is abstract representation of the GPU that
// serves as the primary interface for a Metal app. // 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 { type Device struct {
device objc.ID device objc.ID
@ -493,6 +493,7 @@ var (
sel_supportsFeatureSet = objc.RegisterName("supportsFeatureSet:") sel_supportsFeatureSet = objc.RegisterName("supportsFeatureSet:")
sel_newCommandQueue = objc.RegisterName("newCommandQueue") sel_newCommandQueue = objc.RegisterName("newCommandQueue")
sel_newLibraryWithSource_options_error = objc.RegisterName("newLibraryWithSource:options:error:") sel_newLibraryWithSource_options_error = objc.RegisterName("newLibraryWithSource:options:error:")
sel_newLibraryWithData_error = objc.RegisterName("newLibraryWithData:error:")
sel_release = objc.RegisterName("release") sel_release = objc.RegisterName("release")
sel_retain = objc.RegisterName("retain") sel_retain = objc.RegisterName("retain")
sel_new = objc.RegisterName("new") sel_new = objc.RegisterName("new")
@ -567,7 +568,7 @@ var (
// CreateSystemDefaultDevice returns the preferred system default Metal device. // 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) { func CreateSystemDefaultDevice() (Device, error) {
metal, err := purego.Dlopen("/System/Library/Frameworks/Metal.framework/Metal", purego.RTLD_LAZY|purego.RTLD_GLOBAL) metal, err := purego.Dlopen("/System/Library/Frameworks/Metal.framework/Metal", purego.RTLD_LAZY|purego.RTLD_GLOBAL)
if err != nil { 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. // 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 { func (d Device) RespondsToSelector(sel objc.SEL) bool {
return d.device.Send(sel_respondsToSelector, sel) != 0 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. // 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 { func (d Device) SupportsFamily(gpuFamily GPUFamily) bool {
return d.device.Send(sel_supportsFamily, uintptr(gpuFamily)) != 0 return d.device.Send(sel_supportsFamily, uintptr(gpuFamily)) != 0
} }
// SupportsFeatureSet reports whether device d supports feature set fs. // 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 { func (d Device) SupportsFeatureSet(fs FeatureSet) bool {
return d.device.Send(sel_supportsFeatureSet, uintptr(fs)) != 0 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. // Reference: https://developer.apple.com/documentation/metal/mtldevice/1433388-newcommandqueue?language=objc.
func (d Device) MakeCommandQueue() CommandQueue { func (d Device) NewCommandQueue() CommandQueue {
return CommandQueue{d.device.Send(sel_newCommandQueue)} return CommandQueue{d.device.Send(sel_newCommandQueue)}
} }
// MakeLibrary creates a new library that contains // NewLibraryWithSource synchronously creates a Metal library instance by compiling the functions in a source string.
// the functions stored in the specified source string.
// //
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433431-makelibrary. // Reference: https://developer.apple.com/documentation/metal/mtldevice/1433431-newlibrarywithsource?language=objc.
func (d Device) MakeLibrary(source string, opt CompileOptions) (Library, error) { func (d Device) NewLibraryWithSource(source string, opt CompileOptions) (Library, error) {
var err cocoa.NSError var err cocoa.NSError
l := d.device.Send( l := d.device.Send(
sel_newLibraryWithSource_options_error, sel_newLibraryWithSource_options_error,
@ -652,10 +652,31 @@ func (d Device) MakeLibrary(source string, opt CompileOptions) (Library, error)
return Library{l}, nil 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. // Reference: https://developer.apple.com/documentation/metal/mtldevice/1433391-newlibrarywithdata?language=objc.
func (d Device) MakeRenderPipelineState(rpd RenderPipelineDescriptor) (RenderPipelineState, error) { 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 := objc.ID(class_MTLRenderPipelineDescriptor).Send(sel_new)
renderPipelineDescriptor.Send(sel_setVertexFunction, rpd.VertexFunction.function) renderPipelineDescriptor.Send(sel_setVertexFunction, rpd.VertexFunction.function)
renderPipelineDescriptor.Send(sel_setFragmentFunction, rpd.FragmentFunction.function) renderPipelineDescriptor.Send(sel_setFragmentFunction, rpd.FragmentFunction.function)
@ -683,26 +704,24 @@ func (d Device) MakeRenderPipelineState(rpd RenderPipelineDescriptor) (RenderPip
return RenderPipelineState{renderPipelineState}, nil return RenderPipelineState{renderPipelineState}, nil
} }
// MakeBufferWithBytes allocates a new buffer of a given length // NewBufferWithBytes allocates a new buffer of a given length and initializes its contents by copying existing data into it.
// and initializes its contents by copying existing data into it.
// //
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433429-makebuffer. // Reference: https://developer.apple.com/documentation/metal/mtldevice/1433429-newbufferwithbytes?language=objc.
func (d Device) MakeBufferWithBytes(bytes unsafe.Pointer, length uintptr, opt ResourceOptions) Buffer { 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))} 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 // Reference: https://developer.apple.com/documentation/metal/mtldevice/1433375-newbufferwithlength?language=objc.
func (d Device) MakeBufferWithLength(length uintptr, opt ResourceOptions) Buffer { func (d Device) NewBufferWithLength(length uintptr, opt ResourceOptions) Buffer {
return Buffer{d.device.Send(sel_newBufferWithLength_options, length, uintptr(opt))} return Buffer{d.device.Send(sel_newBufferWithLength_options, length, uintptr(opt))}
} }
// MakeTexture creates a texture object with privately owned storage // NewTextureWithDescriptor creates a new texture instance.
// that contains texture state.
// //
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433425-maketexture. // Reference: https://developer.apple.com/documentation/metal/mtldevice/1433425-newtexturewithdescriptor?language=objc.
func (d Device) MakeTexture(td TextureDescriptor) Texture { func (d Device) NewTextureWithDescriptor(td TextureDescriptor) Texture {
textureDescriptor := objc.ID(class_MTLTextureDescriptor).Send(sel_new) textureDescriptor := objc.ID(class_MTLTextureDescriptor).Send(sel_new)
textureDescriptor.Send(sel_setTextureType, uintptr(td.TextureType)) textureDescriptor.Send(sel_setTextureType, uintptr(td.TextureType))
textureDescriptor.Send(sel_setPixelFormat, uintptr(td.PixelFormat)) 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 // Reference: https://developer.apple.com/documentation/metal/mtldevice/1433412-newdepthstencilstatewithdescript?language=objc.
func (d Device) MakeDepthStencilState(dsd DepthStencilDescriptor) DepthStencilState { func (d Device) NewDepthStencilStateWithDescriptor(dsd DepthStencilDescriptor) DepthStencilState {
depthStencilDescriptor := objc.ID(class_MTLDepthStencilDescriptor).Send(sel_new) depthStencilDescriptor := objc.ID(class_MTLDepthStencilDescriptor).Send(sel_new)
backFaceStencil := depthStencilDescriptor.Send(sel_backFaceStencil) backFaceStencil := depthStencilDescriptor.Send(sel_backFaceStencil)
backFaceStencil.Send(sel_setStencilFailureOperation, uintptr(dsd.BackFaceStencil.StencilFailureOperation)) 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 // CompileOptions specifies optional compilation settings for
// the graphics or compute functions within a library. // 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 { type CompileOptions struct {
// TODO. // TODO.
} }
// Drawable is a displayable resource that can be rendered or written to. // 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 { type Drawable interface {
// Drawable returns the underlying id<MTLDrawable> pointer. // Drawable returns the underlying id<MTLDrawable> pointer.
Drawable() unsafe.Pointer Drawable() unsafe.Pointer
@ -758,7 +777,7 @@ type Drawable interface {
// CommandQueue is a queue that organizes the order // CommandQueue is a queue that organizes the order
// in which command buffers are executed by the GPU. // 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 { type CommandQueue struct {
commandQueue objc.ID commandQueue objc.ID
} }
@ -767,17 +786,17 @@ func (cq CommandQueue) Release() {
cq.commandQueue.Send(sel_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. // Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue/1508686-commandbuffer?language=objc.
func (cq CommandQueue) MakeCommandBuffer() CommandBuffer { func (cq CommandQueue) CommandBuffer() CommandBuffer {
return CommandBuffer{cq.commandQueue.Send(sel_commandBuffer)} return CommandBuffer{cq.commandQueue.Send(sel_commandBuffer)}
} }
// CommandBuffer is a container that stores encoded commands // CommandBuffer is a container that stores encoded commands
// that are committed to and executed by the GPU. // 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 { type CommandBuffer struct {
commandBuffer objc.ID commandBuffer objc.ID
} }
@ -792,44 +811,43 @@ func (cb CommandBuffer) Release() {
// Status returns the current stage in the lifetime of the command buffer. // 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 { func (cb CommandBuffer) Status() CommandBufferStatus {
return CommandBufferStatus(cb.commandBuffer.Send(sel_status)) return CommandBufferStatus(cb.commandBuffer.Send(sel_status))
} }
// PresentDrawable registers a drawable presentation to occur as soon as possible. // 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) { func (cb CommandBuffer) PresentDrawable(d Drawable) {
cb.commandBuffer.Send(sel_presentDrawable, d.Drawable()) cb.commandBuffer.Send(sel_presentDrawable, d.Drawable())
} }
// Commit commits this command buffer for execution as soon as possible. // 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() { func (cb CommandBuffer) Commit() {
cb.commandBuffer.Send(sel_commit) cb.commandBuffer.Send(sel_commit)
} }
// WaitUntilCompleted waits for the execution of this command buffer to complete. // 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() { func (cb CommandBuffer) WaitUntilCompleted() {
cb.commandBuffer.Send(sel_waitUntilCompleted) cb.commandBuffer.Send(sel_waitUntilCompleted)
} }
// WaitUntilScheduled blocks execution of the current thread until the command buffer is scheduled. // 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() { func (cb CommandBuffer) WaitUntilScheduled() {
cb.commandBuffer.Send(sel_waitUntilScheduled) cb.commandBuffer.Send(sel_waitUntilScheduled)
} }
// MakeRenderCommandEncoder creates an encoder object that can // RenderCommandEncoderWithDescriptor creates a render command encoder from a descriptor.
// encode graphics rendering commands into this command buffer.
// //
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1442999-makerendercommandencoder. // Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1442999-rendercommandencoderwithdescript?language=objc.
func (cb CommandBuffer) MakeRenderCommandEncoder(rpd RenderPassDescriptor) RenderCommandEncoder { func (cb CommandBuffer) RenderCommandEncoderWithDescriptor(rpd RenderPassDescriptor) RenderCommandEncoder {
var renderPassDescriptor = objc.ID(class_MTLRenderPassDescriptor).Send(sel_new) var renderPassDescriptor = objc.ID(class_MTLRenderPassDescriptor).Send(sel_new)
var colorAttachments0 = renderPassDescriptor.Send(sel_colorAttachments).Send(sel_objectAtIndexedSubscript, 0) var colorAttachments0 = renderPassDescriptor.Send(sel_colorAttachments).Send(sel_objectAtIndexedSubscript, 0)
colorAttachments0.Send(sel_setLoadAction, int(rpd.ColorAttachments[0].LoadAction)) colorAttachments0.Send(sel_setLoadAction, int(rpd.ColorAttachments[0].LoadAction))
@ -850,11 +868,11 @@ func (cb CommandBuffer) MakeRenderCommandEncoder(rpd RenderPassDescriptor) Rende
return RenderCommandEncoder{CommandEncoder{rce}} 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. // memory operation (blit) commands into this command buffer.
// //
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443001-makeblitcommandencoder. // Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443001-makeblitcommandencoder?language=objc.
func (cb CommandBuffer) MakeBlitCommandEncoder() BlitCommandEncoder { func (cb CommandBuffer) BlitCommandEncoder() BlitCommandEncoder {
ce := cb.commandBuffer.Send(sel_blitCommandEncoder) ce := cb.commandBuffer.Send(sel_blitCommandEncoder)
return BlitCommandEncoder{CommandEncoder{ce}} return BlitCommandEncoder{CommandEncoder{ce}}
} }
@ -862,14 +880,14 @@ func (cb CommandBuffer) MakeBlitCommandEncoder() BlitCommandEncoder {
// CommandEncoder is an encoder that writes sequential GPU commands // CommandEncoder is an encoder that writes sequential GPU commands
// into a command buffer. // 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 { type CommandEncoder struct {
commandEncoder objc.ID commandEncoder objc.ID
} }
// EndEncoding declares that all command generation from this encoder is completed. // 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() { func (ce CommandEncoder) EndEncoding() {
ce.commandEncoder.Send(sel_endEncoding) ce.commandEncoder.Send(sel_endEncoding)
} }
@ -877,7 +895,7 @@ func (ce CommandEncoder) EndEncoding() {
// RenderCommandEncoder is an encoder that specifies graphics-rendering commands // RenderCommandEncoder is an encoder that specifies graphics-rendering commands
// and executes graphics functions. // 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 { type RenderCommandEncoder struct {
CommandEncoder CommandEncoder
} }
@ -888,7 +906,7 @@ func (rce RenderCommandEncoder) Release() {
// SetRenderPipelineState sets the current render pipeline state object. // 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) { func (rce RenderCommandEncoder) SetRenderPipelineState(rps RenderPipelineState) {
rce.commandEncoder.Send(sel_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. // 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) { func (rce RenderCommandEncoder) SetScissorRect(scissorRect ScissorRect) {
inv := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:{MTLScissorRect=qqqq}")) inv := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:{MTLScissorRect=qqqq}"))
inv.SetTarget(rce.commandEncoder) 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 // 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. // 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) { func (rce RenderCommandEncoder) SetVertexBuffer(buf Buffer, offset, index int) {
rce.commandEncoder.Send(sel_setVertexBuffer_offset_atIndex, buf.buffer, offset, index) rce.commandEncoder.Send(sel_setVertexBuffer_offset_atIndex, buf.buffer, offset, index)
} }
// SetVertexBytes sets a block of data for the vertex function. // 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) { func (rce RenderCommandEncoder) SetVertexBytes(bytes unsafe.Pointer, length uintptr, index int) {
rce.commandEncoder.Send(sel_setVertexBytes_length_atIndex, bytes, length, index) 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. // 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) { func (rce RenderCommandEncoder) SetFragmentTexture(texture Texture, index int) {
rce.commandEncoder.Send(sel_setFragmentTexture_atIndex, texture.texture, index) 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. // 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) { func (rce RenderCommandEncoder) SetDepthStencilState(depthStencilState DepthStencilState) {
rce.commandEncoder.Send(sel_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 // DrawPrimitives renders one instance of primitives using vertex data
// in contiguous array elements. // 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) { func (rce RenderCommandEncoder) DrawPrimitives(typ PrimitiveType, vertexStart, vertexCount int) {
rce.commandEncoder.Send(sel_drawPrimitives_vertexStart_vertexCount, uintptr(typ), vertexStart, vertexCount) 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 // BlitCommandEncoder is an encoder that specifies resource copy
// and resource synchronization commands. // 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 { type BlitCommandEncoder struct {
CommandEncoder CommandEncoder
} }
@ -977,7 +995,7 @@ type BlitCommandEncoder struct {
// Synchronize flushes any copy of the specified resource from its corresponding // Synchronize flushes any copy of the specified resource from its corresponding
// Device caches and, if needed, invalidates any CPU caches. // 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) { func (bce BlitCommandEncoder) Synchronize(resource Resource) {
if runtime.GOOS == "ios" { if runtime.GOOS == "ios" {
return return
@ -985,6 +1003,9 @@ func (bce BlitCommandEncoder) Synchronize(resource Resource) {
bce.commandEncoder.Send(sel_synchronizeResource, resource.resource()) bce.commandEncoder.Send(sel_synchronizeResource, resource.resource())
} }
// SynchronizeTexture encodes a command that synchronizes a part of the CPUs copy of a texture so that it matches the GPUs copy.
//
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400757-synchronizetexture?language=objc.
func (bce BlitCommandEncoder) SynchronizeTexture(texture Texture, slice int, level int) { func (bce BlitCommandEncoder) SynchronizeTexture(texture Texture, slice int, level int) {
if runtime.GOOS == "ios" { if runtime.GOOS == "ios" {
return 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) bce.commandEncoder.Send(sel_synchronizeTexture_slice_level, texture.texture, slice, level)
} }
// CopyFromTexture encodes a command that copies image data from a textures 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) { 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 := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:@QQ{MTLOrigin=qqq}{MTLSize=qqq}@QQ{MTLOrigin=qqq}"))
inv.SetTarget(bce.commandEncoder) 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. // 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 { type Library struct {
library objc.ID 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. // Reference: https://developer.apple.com/documentation/metal/mtllibrary/1515524-newfunctionwithname?language=objc.
func (l Library) MakeFunction(name string) (Function, error) { func (l Library) NewFunctionWithName(name string) (Function, error) {
f := l.library.Send(sel_newFunctionWithName, f := l.library.Send(sel_newFunctionWithName,
cocoa.NSString_alloc().InitWithUTF8String(name).ID, cocoa.NSString_alloc().InitWithUTF8String(name).ID,
) )
@ -1028,10 +1052,14 @@ func (l Library) MakeFunction(name string) (Function, error) {
return Function{f}, nil return Function{f}, nil
} }
func (l Library) Release() {
l.library.Send(sel_release)
}
// Texture is a memory allocation for storing formatted // Texture is a memory allocation for storing formatted
// image data that is accessible to the GPU. // 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 { type Texture struct {
texture objc.ID texture objc.ID
} }
@ -1042,7 +1070,9 @@ func NewTexture(texture objc.ID) Texture {
} }
// resource implements the Resource interface. // 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() { func (t Texture) Release() {
t.texture.Send(sel_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 // GetBytes copies a block of pixels from the storage allocation of texture
// slice zero into system memory at a specified address. // 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) { 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 := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:^vQ{MTLRegion={MTLOrigin=qqq}{MTLSize=qqq}}Q"))
inv.SetTarget(t.texture) 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. // 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) { 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 := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:{MTLRegion={MTLOrigin=qqq}{MTLSize=qqq}}Q^vQ"))
inv.SetTarget(t.texture) 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. // 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 { func (t Texture) Width() int {
return int(t.texture.Send(sel_width)) return int(t.texture.Send(sel_width))
} }
// Height is the height of the texture image for the base level mipmap, in pixels. // 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 { func (t Texture) Height() int {
return int(t.texture.Send(sel_height)) 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 // Buffer is a memory allocation for storing unformatted data
// that is accessible to the GPU. // 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 { type Buffer struct {
buffer objc.ID 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 { func (b Buffer) Length() uintptr {
return uintptr(b.buffer.Send(sel_length)) return uintptr(b.buffer.Send(sel_length))
} }
@ -1121,13 +1157,9 @@ func (b Buffer) Release() {
b.buffer.Send(sel_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. // 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 { type Function struct {
function objc.ID function objc.ID
} }
@ -1139,7 +1171,7 @@ func (f Function) Release() {
// RenderPipelineState contains the graphics functions // RenderPipelineState contains the graphics functions
// and configuration state used in a render pass. // 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 { type RenderPipelineState struct {
renderPipelineState objc.ID renderPipelineState objc.ID
} }
@ -1151,7 +1183,7 @@ func (r RenderPipelineState) Release() {
// Region is a rectangular block of pixels in an image or texture, // Region is a rectangular block of pixels in an image or texture,
// defined by its upper-left corner and its size. // 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 { type Region struct {
Origin Origin // The location of the upper-left corner of the block. Origin Origin // The location of the upper-left corner of the block.
Size Size // The size 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 // Origin represents the location of a pixel in an image or texture relative
// to the upper-left corner, whose coordinates are (0, 0). // to the upper-left corner, whose coordinates are (0, 0).
// //
// Reference: https://developer.apple.com/documentation/metal/mtlorigin. // Reference: https://developer.apple.com/documentation/metal/mtlorigin?language=objc.
type Origin struct{ X, Y, Z int } type Origin struct {
X int
Y int
Z int
}
// Size represents the set of dimensions that declare the size of an object, // Size represents the set of dimensions that declare the size of an object,
// such as an image, texture, threadgroup, or grid. // such as an image, texture, threadgroup, or grid.
// //
// Reference: https://developer.apple.com/documentation/metal/mtlsize. // Reference: https://developer.apple.com/documentation/metal/mtlsize?language=objc.
type Size struct{ Width, Height, Depth int } type Size struct {
Width int
Height int
Depth int
}
// RegionMake2D returns a 2D, rectangular region for image or texture data. // 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 { func RegionMake2D(x, y, width, height int) Region {
return Region{ return Region{
Origin: Origin{x, y, 0}, Origin: Origin{X: x, Y: y, Z: 0},
Size: Size{width, height, 1}, 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 { type Viewport struct {
OriginX float64 OriginX float64
OriginY float64 OriginY float64
@ -1188,9 +1231,9 @@ type Viewport struct {
ZFar float64 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 { type ScissorRect struct {
X int X int
Y 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. // 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 { type DepthStencilState struct {
depthStencilState objc.ID depthStencilState objc.ID
} }
@ -1211,7 +1254,7 @@ func (d DepthStencilState) Release() {
// DepthStencilDescriptor is an object that configures new MTLDepthStencilState objects. // 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 { type DepthStencilDescriptor struct {
// BackFaceStencil is the stencil descriptor for back-facing primitives. // BackFaceStencil is the stencil descriptor for back-facing primitives.
BackFaceStencil StencilDescriptor 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. // 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 { type StencilDescriptor struct {
// StencilFailureOperation is the operation that is performed to update the values in the stencil attachment when the stencil test fails. // StencilFailureOperation is the operation that is performed to update the values in the stencil attachment when the stencil test fails.
StencilFailureOperation StencilOperation StencilFailureOperation StencilOperation

View File

@ -56,15 +56,15 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) {
return in.color; return in.color;
} }
` `
lib, err := device.MakeLibrary(source, mtl.CompileOptions{}) lib, err := device.NewLibraryWithSource(source, mtl.CompileOptions{})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
vs, err := lib.MakeFunction("VertexShader") vs, err := lib.NewFunctionWithName("VertexShader")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
fs, err := lib.MakeFunction("FragmentShader") fs, err := lib.NewFunctionWithName("FragmentShader")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -72,7 +72,7 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) {
rpld.VertexFunction = vs rpld.VertexFunction = vs
rpld.FragmentFunction = fs rpld.FragmentFunction = fs
rpld.ColorAttachments[0].PixelFormat = mtl.PixelFormatRGBA8UNorm rpld.ColorAttachments[0].PixelFormat = mtl.PixelFormatRGBA8UNorm
rps, err := device.MakeRenderPipelineState(rpld) rps, err := device.NewRenderPipelineStateWithDescriptor(rpld)
if err != nil { if err != nil {
t.Fatal(err) 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, 1, 0, 1}},
{f32.Vec4{+0.75, -0.75, 0, 1}, f32.Vec4{0, 0, 1, 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. // Create an output texture to render into.
td := mtl.TextureDescriptor{ td := mtl.TextureDescriptor{
@ -97,10 +97,10 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) {
Height: 512, Height: 512,
StorageMode: mtl.StorageModeManaged, StorageMode: mtl.StorageModeManaged,
} }
texture := device.MakeTexture(td) texture := device.NewTextureWithDescriptor(td)
cq := device.MakeCommandQueue() cq := device.NewCommandQueue()
cb := cq.MakeCommandBuffer() cb := cq.CommandBuffer()
// Encode all render commands. // Encode all render commands.
var rpd mtl.RenderPassDescriptor var rpd mtl.RenderPassDescriptor
@ -108,14 +108,14 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) {
rpd.ColorAttachments[0].StoreAction = mtl.StoreActionStore 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].ClearColor = mtl.ClearColor{Red: 0.35, Green: 0.65, Blue: 0.85, Alpha: 1}
rpd.ColorAttachments[0].Texture = texture rpd.ColorAttachments[0].Texture = texture
rce := cb.MakeRenderCommandEncoder(rpd) rce := cb.RenderCommandEncoderWithDescriptor(rpd)
rce.SetRenderPipelineState(rps) rce.SetRenderPipelineState(rps)
rce.SetVertexBuffer(vertexBuffer, 0, 0) rce.SetVertexBuffer(vertexBuffer, 0, 0)
rce.DrawPrimitives(mtl.PrimitiveTypeTriangle, 0, 3) rce.DrawPrimitives(mtl.PrimitiveTypeTriangle, 0, 3)
rce.EndEncoding() rce.EndEncoding()
// Encode all blit commands. // Encode all blit commands.
bce := cb.MakeBlitCommandEncoder() bce := cb.BlitCommandEncoder()
bce.Synchronize(texture) bce.Synchronize(texture)
bce.EndEncoding() bce.EndEncoding()

View File

@ -16,6 +16,7 @@ package metal
import ( import (
"fmt" "fmt"
"sync"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl"
@ -23,6 +24,37 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/shaderir/msl" "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 { type shaderRpsKey struct {
blend graphicsdriver.Blend blend graphicsdriver.Blend
stencilMode stencilMode stencilMode stencilMode
@ -33,9 +65,12 @@ type Shader struct {
id graphicsdriver.ShaderID id graphicsdriver.ShaderID
ir *shaderir.Program ir *shaderir.Program
lib mtl.Library
fs mtl.Function fs mtl.Function
vs mtl.Function vs mtl.Function
rpss map[shaderRpsKey]mtl.RenderPipelineState rpss map[shaderRpsKey]mtl.RenderPipelineState
libraryPrecompiled bool
} }
func newShader(device mtl.Device, id graphicsdriver.ShaderID, program *shaderir.Program) (*Shader, error) { 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.vs.Release()
s.fs.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 { func (s *Shader) init(device mtl.Device) error {
src := msl.Compile(s.ir) var src string
lib, err := device.MakeLibrary(src, mtl.CompileOptions{}) if libBin := thePrecompiledLibraries.get(s.ir.SourceHash); len(libBin) > 0 {
if err != nil { lib, err := device.NewLibraryWithData(libBin)
return fmt.Errorf("metal: device.MakeLibrary failed: %w, source: %s", err, src) 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 { 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 { 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.fs = fs
s.vs = vs s.vs = vs
@ -120,7 +176,7 @@ func (s *Shader) RenderPipelineState(view *view, blend graphicsdriver.Blend, ste
rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskNone rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskNone
} }
rps, err := view.getMTLDevice().MakeRenderPipelineState(rpld) rps, err := view.getMTLDevice().NewRenderPipelineStateWithDescriptor(rpld)
if err != nil { if err != nil {
return mtl.RenderPipelineState{}, err return mtl.RenderPipelineState{}, err
} }

View File

@ -61,7 +61,7 @@ func (v *view) colorPixelFormat() mtl.PixelFormat {
func (v *view) initialize(device mtl.Device) error { func (v *view) initialize(device mtl.Device) error {
v.device = device v.device = device
ml, err := ca.MakeMetalLayer() ml, err := ca.NewMetalLayer()
if err != nil { if err != nil {
return err return err
} }

View File

@ -2,7 +2,7 @@
// SPDX-FileCopyrightText: 2014 Eric Woroshow // SPDX-FileCopyrightText: 2014 Eric Woroshow
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors // SPDX-FileCopyrightText: 2022 The Ebitengine Authors
//go:build !darwin && !js && !windows && !playstation5 //go:build nintendosdk
package gl package gl

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build darwin || windows //go:build (darwin || freebsd || linux || netbsd || openbsd || windows) && !nintendosdk && !playstation5
package gl package gl

View File

@ -16,38 +16,18 @@
package gl 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 ( import (
"fmt" "fmt"
"os" "os"
"runtime" "runtime"
"strings" "strings"
"unsafe"
"github.com/ebitengine/purego"
) )
var ( var (
libGL unsafe.Pointer libGL uintptr
libGLES unsafe.Pointer libGLES uintptr
) )
func (c *defaultContext) init() error { 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. // Try OpenGL first. OpenGL is preferable as this doesn't cause context losses.
if !preferES { if !preferES {
// Usually libGL.so or libGL.so.1 is used. libGL.so.2 might exist only on NetBSD. // 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"} { for _, name := range []string{"libGL.so", "libGL.so.2", "libGL.so.1", "libGL.so.0"} {
cname := C.CString(name) lib, err := purego.Dlopen(name, purego.RTLD_LAZY|purego.RTLD_GLOBAL)
lib := C.dlopen(cname, C.RTLD_LAZY|C.RTLD_GLOBAL) if err == nil {
C.free(unsafe.Pointer(cname))
if lib != nil {
libGL = lib libGL = lib
return nil return nil
} }
@ -81,10 +62,8 @@ func (c *defaultContext) init() error {
// Try OpenGL ES. // Try OpenGL ES.
for _, name := range []string{"libGLESv2.so", "libGLESv2.so.2", "libGLESv2.so.1", "libGLESv2.so.0"} { for _, name := range []string{"libGLESv2.so", "libGLESv2.so.2", "libGLESv2.so.1", "libGLESv2.so.0"} {
cname := C.CString(name) lib, err := purego.Dlopen(name, purego.RTLD_LAZY|purego.RTLD_GLOBAL)
lib := C.dlopen(cname, C.RTLD_LAZY|C.RTLD_GLOBAL) if err == nil {
C.free(unsafe.Pointer(cname))
if lib != nil {
libGLES = lib libGLES = lib
c.isES = true c.isES = true
return nil return nil
@ -96,19 +75,32 @@ func (c *defaultContext) init() error {
func (c *defaultContext) getProcAddress(name string) (uintptr, error) { func (c *defaultContext) getProcAddress(name string) (uintptr, error) {
if c.isES { if c.isES {
return getProcAddressGLES(name), nil return getProcAddressGLES(name)
} }
return getProcAddressGL(name), nil return getProcAddressGL(name)
} }
func getProcAddressGL(name string) uintptr { var glXGetProcAddress func(name string) uintptr
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname)) func getProcAddressGL(name string) (uintptr, error) {
return uintptr(C.getProcAddressGL(libGL, cname)) 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 { func getProcAddressGLES(name string) (uintptr, error) {
cname := C.CString(name) proc, err := purego.Dlsym(libGLES, name)
defer C.free(unsafe.Pointer(cname)) if err != nil {
return uintptr(C.getProcAddressGLES(libGLES, cname)) return 0, err
}
return proc, nil
} }

View File

@ -329,7 +329,7 @@ func (g *Graphics) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphicsdr
} }
g.uniformVars = g.uniformVars[:0] g.uniformVars = g.uniformVars[:0]
if fillRule != graphicsdriver.FillAll { if fillRule != graphicsdriver.FillRuleFillAll {
if err := dsts[firstTarget].ensureStencilBuffer(framebufferNative(f)); err != nil { if err := dsts[firstTarget].ensureStencilBuffer(framebufferNative(f)); err != nil {
return err return err
} }
@ -345,7 +345,7 @@ func (g *Graphics) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphicsdr
) )
switch fillRule { switch fillRule {
case graphicsdriver.NonZero: case graphicsdriver.FillRuleNonZero:
g.context.ctx.Clear(gl.STENCIL_BUFFER_BIT) g.context.ctx.Clear(gl.STENCIL_BUFFER_BIT)
g.context.ctx.StencilFunc(gl.ALWAYS, 0x00, 0xff) g.context.ctx.StencilFunc(gl.ALWAYS, 0x00, 0xff)
g.context.ctx.StencilOpSeparate(gl.FRONT, gl.KEEP, gl.KEEP, gl.INCR_WRAP) 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.ColorMask(false, false, false, false)
g.context.ctx.DrawElements(gl.TRIANGLES, int32(dstRegion.IndexCount), gl.UNSIGNED_INT, indexOffset*int(unsafe.Sizeof(uint32(0)))) 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.Clear(gl.STENCIL_BUFFER_BIT)
g.context.ctx.StencilFunc(gl.ALWAYS, 0x00, 0xff) g.context.ctx.StencilFunc(gl.ALWAYS, 0x00, 0xff)
g.context.ctx.StencilOpSeparate(gl.FRONT_AND_BACK, gl.KEEP, gl.KEEP, gl.INVERT) 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)))) 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.StencilFunc(gl.NOTEQUAL, 0x00, 0xff)
g.context.ctx.StencilOpSeparate(gl.FRONT_AND_BACK, gl.KEEP, gl.KEEP, gl.KEEP) g.context.ctx.StencilOpSeparate(gl.FRONT_AND_BACK, gl.KEEP, gl.KEEP, gl.KEEP)
g.context.ctx.ColorMask(true, true, true, true) g.context.ctx.ColorMask(true, true, true, true)
@ -370,7 +370,7 @@ func (g *Graphics) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphicsdr
indexOffset += dstRegion.IndexCount indexOffset += dstRegion.IndexCount
} }
if fillRule != graphicsdriver.FillAll { if fillRule != graphicsdriver.FillRuleFillAll {
g.context.ctx.Disable(gl.STENCIL_TEST) g.context.ctx.Disable(gl.STENCIL_TEST)
} }

View 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.
}

View File

@ -262,7 +262,7 @@ func (m *Mipmap) level(level int) *buffered.Image {
s := buffered.NewImage(w2, h2, m.imageType) s := buffered.NewImage(w2, h2, m.imageType)
dstRegion := image.Rect(0, 0, w2, h2) 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) m.setImg(level, s)
return m.imgs[level] return m.imgs[level]

View File

@ -23,13 +23,13 @@ import (
"go/parser" "go/parser"
"go/token" "go/token"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
exec "golang.org/x/sys/execabs"
"golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/ast/astutil"
) )

View File

@ -20,14 +20,13 @@ import (
"bytes" "bytes"
"context" "context"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
"testing" "testing"
"time" "time"
exec "golang.org/x/sys/execabs"
) )
func isWSL() (bool, error) { func isWSL() (bool, error) {

View File

@ -59,7 +59,7 @@ func (g *Game) Update() error {
} }
func (g *Game) Draw(screen *ebiten.Image) { 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) // panic: directx: IDXGISwapChain4::Present failed: HRESULT(2289696773)
screen.DrawImage(debugCircleImage, nil) 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) p.Arc(100, 100, 6, 0, 2*math.Pi, vector.Clockwise)
filling, indicies := p.AppendVerticesAndIndicesForFilling(nil, nil) filling, indicies := p.AppendVerticesAndIndicesForFilling(nil, nil)
screen.DrawTriangles(filling, indicies, whiteTextureImage, &ebiten.DrawTrianglesOptions{ screen.DrawTriangles(filling, indicies, whiteTextureImage, &ebiten.DrawTrianglesOptions{
FillRule: ebiten.EvenOdd, FillRule: ebiten.FillRuleEvenOdd,
}) })
} }

74
internal/processtest/testdata/shader.go vendored Normal file
View 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)
}
}

View File

@ -199,7 +199,7 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
return nil, nil, nil, false return nil, nil, nil, false
} }
for _, expr := range es { 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)) cs.addError(e.Pos(), fmt.Sprintf("function name cannot be an argument: %s", e.Fun))
return nil, nil, nil, false return nil, nil, nil, false
} }

View File

@ -202,6 +202,7 @@ func Compile(src []byte, vertexEntry, fragmentEntry string, textureCount int) (*
fragmentEntry: fragmentEntry, fragmentEntry: fragmentEntry,
unit: unit, unit: unit,
} }
s.ir.SourceHash = shaderir.CalcSourceHash(src)
s.global.ir = &shaderir.Block{} s.global.ir = &shaderir.Block{}
s.parse(f) s.parse(f)

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