diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 74c51fd85..cdd2c5351 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - go: ['1.18.x', '1.19.x', '1.20.x', '1.21.x', '1.22.x'] + go: ['1.19.x', '1.20.x', '1.21.x', '1.22.x'] name: Test with Go ${{ matrix.go }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} env: @@ -44,9 +44,10 @@ jobs: - name: Install wasmbrowsertest run: | - go install github.com/agnivade/wasmbrowsertest@bb77d443e302d06f0d2462336bc2765942e0e862 + wasmbrowsertest_version=6e494bb3a5ddfe6cccb449250dbdcaa5777b593d + go install github.com/agnivade/wasmbrowsertest@${wasmbrowsertest_version} mv $(go env GOPATH)/bin/wasmbrowsertest${{ runner.os == 'Windows' && '.exe' || '' }} $(go env GOPATH)/bin/go_js_wasm_exec${{ runner.os == 'Windows' && '.exe' || '' }} - go install github.com/agnivade/wasmbrowsertest/cmd/cleanenv@bb77d443e302d06f0d2462336bc2765942e0e862 + go install github.com/agnivade/wasmbrowsertest/cmd/cleanenv@${wasmbrowsertest_version} - name: Prepare ebitenmobile test run: | @@ -77,9 +78,6 @@ jobs: go list ./... | grep -v -x -F -f .github/workflows/govetblock_windows.txt | xargs go vet - name: go vet (vettool) - # Stop vettools for old Go versions. Apparently this is an issue in golang.org/x/tools (golang/go#62519) - # TODO: Update golang.org/x/tools and remove this restriction. - if: ${{ !startsWith(matrix.go, '1.18.') && !startsWith(matrix.go, '1.19.') }} run: | go install ./internal/vettools go vet -vettool=$(which vettools)${{ runner.os == 'Windows' && '.exe' || '' }} -v ./... @@ -144,8 +142,6 @@ jobs: run: | sudo dpkg --add-architecture i386 sudo apt-get update - sudo apt-get install ppa-purge - sudo ppa-purge -y ppa:ubuntu-toolchain-r/test # Hack for 32bit Linux (#2667) sudo apt-get install gcc-multilib sudo apt-get install libasound2-dev:i386 libgl1-mesa-dev:i386 libxcursor-dev:i386 libxi-dev:i386 libxinerama-dev:i386 libxrandr-dev:i386 libxxf86vm-dev:i386 env CGO_ENABLED=1 GOARCH=386 go test -shuffle=on -v -p=1 ./... @@ -169,8 +165,11 @@ jobs: env GOARCH=386 EBITENGINE_DIRECTX=version=12 go test -shuffle=on -v ./... - name: go test (Wasm) + if: ${{ runner.os != 'macOS' && runner.os != 'Windows' }} run: | - env GOOS=js GOARCH=wasm cleanenv -remove-prefix GITHUB_ -remove-prefix JAVA_ -- go test -shuffle=on -v ./... + # Wasm tests don't work on macOS with the headless mode enabled, but the headless mode cannot be disabled in GitHub Actions (#2972). + # Wasm tests don't work on Windows well due to mysterious timeouts (#2982). + env GOOS=js GOARCH=wasm cleanenv -remove-prefix GITHUB_ -remove-prefix JAVA_ -remove-prefix PSModulePath -remove-prefix STATS_ -remove-prefix RUNNER_ -- go test -shuffle=on -v ./... - name: Install ebitenmobile run: | diff --git a/.gitignore b/.gitignore index 14ecd3c00..981311b14 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,8 @@ .vscode go.work go.work.sum + +*.fxc +!dummy.fxc +*.metallib +!dummy.metallib diff --git a/README.md b/README.md index a87e26423..5e8ecbd9d 100644 --- a/README.md +++ b/README.md @@ -61,3 +61,132 @@ For installation on desktops, see [the installation instruction](https://ebiteng Ebitengine is licensed under Apache license version 2.0. See [LICENSE](LICENSE) file. [The Ebitengine logo](https://ebitengine.org/images/logo.png) by Hajime Hoshi is licensed under [the Creative Commons Attribution-NoDerivatives 4.0](https://creativecommons.org/licenses/by-nd/4.0/). + +### GLFW + +https://github.com/glfw/glfw + + +``` +Copyright (c) 2002-2006 Marcus Geelnard + +Copyright (c) 2006-2019 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. +``` + +### Go + +https://cs.opensource.google/go/go + + +``` +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +### go-gl/gl + +https://github.com/go-gl/gl + + +``` +The MIT License (MIT) + +Copyright (c) 2014 Eric Woroshow + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +### go-gl/glfw + +https://github.com/go-gl/glfw + + +``` +Copyright (c) 2012 The glfw3-go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` diff --git a/audio/mp3/decode.go b/audio/mp3/decode.go index 4d43d8b63..815be418f 100644 --- a/audio/mp3/decode.go +++ b/audio/mp3/decode.go @@ -31,6 +31,7 @@ import ( type Stream struct { orig *mp3.Decoder resampling *convert.Resampling + sampleRate int } // Read is implementation of io.Reader's Read. @@ -57,6 +58,11 @@ func (s *Stream) Length() int64 { return s.orig.Length() } +// SampleRate returns the sample rate of the decoded stream. +func (s *Stream) SampleRate() int { + return s.sampleRate +} + // DecodeWithoutResampling decodes an MP3 source and returns a decoded stream. // // DecodeWithoutResampling returns error when decoding fails or IO error happens. @@ -73,6 +79,7 @@ func DecodeWithoutResampling(src io.Reader) (*Stream, error) { s := &Stream{ orig: d, resampling: nil, + sampleRate: d.SampleRate(), } return s, nil } @@ -87,6 +94,9 @@ func DecodeWithoutResampling(src io.Reader) (*Stream, error) { // // A Stream doesn't close src even if src implements io.Closer. // Closing the source is src owner's responsibility. +// +// Resampling can be a very heavy task. Stream has a cache for resampling, but the size is limited. +// Do not expect that Stream has a resampling cache even after whole data is played. func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) { d, err := mp3.NewDecoder(src) if err != nil { @@ -100,6 +110,7 @@ func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) { s := &Stream{ orig: d, resampling: r, + sampleRate: sampleRate, } return s, nil } diff --git a/audio/vorbis/vorbis.go b/audio/vorbis/vorbis.go index 63cf13ee1..6751c499d 100644 --- a/audio/vorbis/vorbis.go +++ b/audio/vorbis/vorbis.go @@ -27,50 +27,48 @@ import ( // Stream is a decoded audio stream. type Stream struct { - decoded io.ReadSeeker - size int64 + readSeeker io.ReadSeeker + length int64 + sampleRate int } // Read is implementation of io.Reader's Read. func (s *Stream) Read(p []byte) (int, error) { - return s.decoded.Read(p) + return s.readSeeker.Read(p) } // Seek is implementation of io.Seeker's Seek. // // Note that Seek can take long since decoding is a relatively heavy task. func (s *Stream) Seek(offset int64, whence int) (int64, error) { - return s.decoded.Seek(offset, whence) + return s.readSeeker.Seek(offset, whence) } // Length returns the size of decoded stream in bytes. // // If the source is not io.Seeker, Length returns 0. func (s *Stream) Length() int64 { - return s.size + return s.length } -type decoder interface { - Read([]float32) (int, error) - SetPosition(int64) error - Length() int64 - Channels() int - SampleRate() int +// SampleRate returns the sample rate of the decoded stream. +func (s *Stream) SampleRate() int { + return s.sampleRate } -type decoded struct { - totalBytes int - posInBytes int - decoder decoder - decoderr io.Reader +type i16Stream struct { + totalBytes int + posInBytes int + vorbisReader *oggvorbis.Reader + i16Reader io.Reader } -func (d *decoded) Read(b []byte) (int, error) { - if d.decoderr == nil { - d.decoderr = convert.NewReaderFromFloat32Reader(d.decoder) +func (s *i16Stream) Read(b []byte) (int, error) { + if s.i16Reader == nil { + s.i16Reader = convert.NewReaderFromFloat32Reader(s.vorbisReader) } - l := d.totalBytes - d.posInBytes + l := s.totalBytes - s.posInBytes if l > len(b) { l = len(b) } @@ -79,7 +77,7 @@ func (d *decoded) Read(b []byte) (int, error) { } retry: - n, err := d.decoderr.Read(b[:l]) + n, err := s.i16Reader.Read(b[:l]) if err != nil && err != io.EOF { return 0, err } @@ -88,59 +86,63 @@ retry: goto retry } - d.posInBytes += n - if d.posInBytes == d.totalBytes || err == io.EOF { + s.posInBytes += n + if s.posInBytes == s.totalBytes || err == io.EOF { return n, io.EOF } return n, nil } -func (d *decoded) Seek(offset int64, whence int) (int64, error) { +func (s *i16Stream) Seek(offset int64, whence int) (int64, error) { next := int64(0) switch whence { case io.SeekStart: next = offset case io.SeekCurrent: - next = int64(d.posInBytes) + offset + next = int64(s.posInBytes) + offset case io.SeekEnd: - next = int64(d.totalBytes) + offset + next = int64(s.totalBytes) + offset } // pos should be always even next = next / 2 * 2 - d.posInBytes = int(next) - if err := d.decoder.SetPosition(next / int64(d.decoder.Channels()) / 2); err != nil { + s.posInBytes = int(next) + if err := s.vorbisReader.SetPosition(next / int64(s.vorbisReader.Channels()) / 2); err != nil { return 0, err } - d.decoderr = nil + s.i16Reader = nil return next, nil } -func (d *decoded) Length() int64 { - return int64(d.totalBytes) +func (s *i16Stream) Length() int64 { + return int64(s.totalBytes) } // decode accepts an ogg stream and returns a decorded stream. -func decode(in io.Reader) (*decoded, int, int, error) { +func decode(in io.Reader) (*i16Stream, int, int, error) { r, err := oggvorbis.NewReader(in) if err != nil { return nil, 0, 0, err } - d := &decoded{ + if r.Channels() != 1 && r.Channels() != 2 { + return nil, 0, 0, fmt.Errorf("vorbis: number of channels must be 1 or 2 but was %d", r.Channels()) + } + + s := &i16Stream{ // TODO: r.Length() returns 0 when the format is unknown. // Should we check that? - totalBytes: int(r.Length()) * r.Channels() * 2, // 2 means 16bit per sample. - posInBytes: 0, - decoder: r, + totalBytes: int(r.Length()) * r.Channels() * 2, // 2 means 16bit per sample. + posInBytes: 0, + vorbisReader: r, } if _, ok := in.(io.Seeker); ok { - if _, err := d.Read(make([]byte, 65536)); err != nil && err != io.EOF { + if _, err := s.Read(make([]byte, 65536)); err != nil && err != io.EOF { return nil, 0, 0, err } - if _, err := d.Seek(0, io.SeekStart); err != nil { + if _, err := s.Seek(0, io.SeekStart); err != nil { return nil, 0, 0, err } } - return d, r.Channels(), r.SampleRate(), nil + return s, r.Channels(), r.SampleRate(), nil } // DecodeWithoutResampling decodes Ogg/Vorbis data to playable stream. @@ -152,22 +154,22 @@ func decode(in io.Reader) (*decoded, int, int, error) { // A Stream doesn't close src even if src implements io.Closer. // Closing the source is src owner's responsibility. func DecodeWithoutResampling(src io.Reader) (*Stream, error) { - decoded, channelCount, _, err := decode(src) + i16Stream, channelCount, sampleRate, err := decode(src) if err != nil { return nil, err } - if channelCount != 1 && channelCount != 2 { - return nil, fmt.Errorf("vorbis: number of channels must be 1 or 2 but was %d", channelCount) - } - var s io.ReadSeeker = decoded - size := decoded.Length() + + var s io.ReadSeeker = i16Stream + length := i16Stream.Length() if channelCount == 1 { s = convert.NewStereo16(s, true, false) - size *= 2 + length *= 2 } + stream := &Stream{ - decoded: s, - size: size, + readSeeker: s, + length: length, + sampleRate: sampleRate, } return stream, nil } @@ -182,26 +184,31 @@ func DecodeWithoutResampling(src io.Reader) (*Stream, error) { // // A Stream doesn't close src even if src implements io.Closer. // Closing the source is src owner's responsibility. +// +// Resampling can be a very heavy task. Stream has a cache for resampling, but the size is limited. +// Do not expect that Stream has a resampling cache even after whole data is played. func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) { - decoded, channelCount, origSampleRate, err := decode(src) + i16Stream, channelCount, origSampleRate, err := decode(src) if err != nil { return nil, err } - if channelCount != 1 && channelCount != 2 { - return nil, fmt.Errorf("vorbis: number of channels must be 1 or 2 but was %d", channelCount) - } - var s io.ReadSeeker = decoded - size := decoded.Length() + + var s io.ReadSeeker = i16Stream + length := i16Stream.Length() if channelCount == 1 { s = convert.NewStereo16(s, true, false) - size *= 2 + length *= 2 } if origSampleRate != sampleRate { - r := convert.NewResampling(s, size, origSampleRate, sampleRate) + r := convert.NewResampling(s, length, origSampleRate, sampleRate) s = r - size = r.Length() + length = r.Length() + } + stream := &Stream{ + readSeeker: s, + length: length, + sampleRate: sampleRate, } - stream := &Stream{decoded: s, size: size} return stream, nil } diff --git a/audio/vorbis/vorbis_test.go b/audio/vorbis/vorbis_test.go index 97f2da280..ef028e333 100644 --- a/audio/vorbis/vorbis_test.go +++ b/audio/vorbis/vorbis_test.go @@ -50,16 +50,16 @@ func TestMono(t *testing.T) { } // Stream decoded by audio/vorbis.DecodeWithSampleRate() is always 16bit stereo. - got := s.Length() - // On the other hand, the original vorbis package is monoral. // As Length() represents the number of samples, // this needs to be doubled by 2 (= bytes in 16bits). - want := r.Length() * 2 * 2 - - if got != want { + if got, want := s.Length(), r.Length()*2*2; got != want { t.Errorf("s.Length(): got: %d, want: %d", got, want) } + + if got, want := s.SampleRate(), audioContext.SampleRate(); got != want { + t.Errorf("s.SampleRate(): got: %d, want: %d", got, want) + } } func TestTooShort(t *testing.T) { @@ -70,11 +70,13 @@ func TestTooShort(t *testing.T) { t.Fatal(err) } - got := s.Length() - want := int64(79424) - if got != want { + if got, want := s.Length(), int64(79424); got != want { t.Errorf("s.Length(): got: %d, want: %d", got, want) } + + if got, want := s.SampleRate(), audioContext.SampleRate(); got != want { + t.Errorf("s.SampleRate(): got: %d, want: %d", got, want) + } } type reader struct { @@ -93,9 +95,11 @@ func TestNonSeeker(t *testing.T) { t.Fatal(err) } - got := s.Length() - want := int64(0) - if got != want { + if got, want := s.Length(), int64(0); got != want { t.Errorf("s.Length(): got: %d, want: %d", got, want) } + + if got, want := s.SampleRate(), audioContext.SampleRate(); got != want { + t.Errorf("s.SampleRate(): got: %d, want: %d", got, want) + } } diff --git a/audio/wav/decode.go b/audio/wav/decode.go index 1dc0489b3..097795d3f 100644 --- a/audio/wav/decode.go +++ b/audio/wav/decode.go @@ -26,8 +26,9 @@ import ( // Stream is a decoded audio stream. type Stream struct { - inner io.ReadSeeker - size int64 + inner io.ReadSeeker + size int64 + sampleRate int } // Read is implementation of io.Reader's Read. @@ -49,6 +50,11 @@ func (s *Stream) Length() int64 { return s.size } +// SampleRate returns the sample rate of the decoded stream. +func (s *Stream) SampleRate() int { + return s.sampleRate +} + type stream struct { src io.Reader headerSize int64 @@ -114,7 +120,7 @@ func (s *stream) Seek(offset int64, whence int) (int64, error) { // A Stream doesn't close src even if src implements io.Closer. // Closing the source is src owner's responsibility. func DecodeWithoutResampling(src io.Reader) (*Stream, error) { - s, _, err := decode(src) + s, err := decode(src) if err != nil { return nil, err } @@ -134,37 +140,41 @@ func DecodeWithoutResampling(src io.Reader) (*Stream, error) { // // A Stream doesn't close src even if src implements io.Closer. // Closing the source is src owner's responsibility. +// +// Resampling can be a very heavy task. Stream has a cache for resampling, but the size is limited. +// Do not expect that Stream has a resampling cache even after whole data is played. func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) { - s, origSampleRate, err := decode(src) + s, err := decode(src) if err != nil { return nil, err } - if sampleRate == origSampleRate { + if sampleRate == s.sampleRate { return s, nil } - r := convert.NewResampling(s.inner, s.size, origSampleRate, sampleRate) + r := convert.NewResampling(s.inner, s.size, s.sampleRate, sampleRate) return &Stream{ - inner: r, - size: r.Length(), + inner: r, + size: r.Length(), + sampleRate: sampleRate, }, nil } -func decode(src io.Reader) (*Stream, int, error) { +func decode(src io.Reader) (*Stream, error) { buf := make([]byte, 12) n, err := io.ReadFull(src, buf) if n != len(buf) { - return nil, 0, fmt.Errorf("wav: invalid header") + return nil, fmt.Errorf("wav: invalid header") } if err != nil { - return nil, 0, err + return nil, err } if !bytes.Equal(buf[0:4], []byte("RIFF")) { - return nil, 0, fmt.Errorf("wav: invalid header: 'RIFF' not found") + return nil, fmt.Errorf("wav: invalid header: 'RIFF' not found") } if !bytes.Equal(buf[8:12], []byte("WAVE")) { - return nil, 0, fmt.Errorf("wav: invalid header: 'WAVE' not found") + return nil, fmt.Errorf("wav: invalid header: 'WAVE' not found") } // Read chunks @@ -178,10 +188,10 @@ chunks: buf := make([]byte, 8) n, err := io.ReadFull(src, buf) if n != len(buf) { - return nil, 0, fmt.Errorf("wav: invalid header") + return nil, fmt.Errorf("wav: invalid header") } if err != nil { - return nil, 0, err + return nil, err } headerSize += 8 size := int64(buf[4]) | int64(buf[5])<<8 | int64(buf[6])<<16 | int64(buf[7])<<24 @@ -189,19 +199,19 @@ chunks: case bytes.Equal(buf[0:4], []byte("fmt ")): // Size of 'fmt' header is usually 16, but can be more than 16. if size < 16 { - return nil, 0, fmt.Errorf("wav: invalid header: maybe non-PCM file?") + return nil, fmt.Errorf("wav: invalid header: maybe non-PCM file?") } buf := make([]byte, size) n, err := io.ReadFull(src, buf) if n != len(buf) { - return nil, 0, fmt.Errorf("wav: invalid header") + return nil, fmt.Errorf("wav: invalid header") } if err != nil { - return nil, 0, err + return nil, err } format := int(buf[0]) | int(buf[1])<<8 if format != 1 { - return nil, 0, fmt.Errorf("wav: format must be linear PCM") + return nil, fmt.Errorf("wav: format must be linear PCM") } channelCount := int(buf[2]) | int(buf[3])<<8 switch channelCount { @@ -210,11 +220,11 @@ chunks: case 2: mono = false default: - return nil, 0, fmt.Errorf("wav: number of channels must be 1 or 2 but was %d", channelCount) + return nil, fmt.Errorf("wav: number of channels must be 1 or 2 but was %d", channelCount) } bitsPerSample = int(buf[14]) | int(buf[15])<<8 if bitsPerSample != 8 && bitsPerSample != 16 { - return nil, 0, fmt.Errorf("wav: bits per sample must be 8 or 16 but was %d", bitsPerSample) + return nil, fmt.Errorf("wav: bits per sample must be 8 or 16 but was %d", bitsPerSample) } sampleRate = int(buf[4]) | int(buf[5])<<8 | int(buf[6])<<16 | int(buf[7])<<24 headerSize += size @@ -225,10 +235,10 @@ chunks: buf := make([]byte, size) n, err := io.ReadFull(src, buf) if n != len(buf) { - return nil, 0, fmt.Errorf("wav: invalid header") + return nil, fmt.Errorf("wav: invalid header") } if err != nil { - return nil, 0, err + return nil, err } headerSize += size } @@ -249,7 +259,11 @@ chunks: dataSize *= 2 } } - return &Stream{inner: s, size: dataSize}, sampleRate, nil + return &Stream{ + inner: s, + size: dataSize, + sampleRate: sampleRate, + }, nil } // Decode decodes WAV (RIFF) data to playable stream. diff --git a/cmd/ebitenmobile/gobind.go b/cmd/ebitenmobile/gobind.go index db23da703..18ccb9a6a 100644 --- a/cmd/ebitenmobile/gobind.go +++ b/cmd/ebitenmobile/gobind.go @@ -23,10 +23,10 @@ import ( "fmt" "log" "os" + "os/exec" "path/filepath" "strings" - exec "golang.org/x/sys/execabs" "golang.org/x/tools/go/packages" ) diff --git a/cmd/ebitenmobile/gomobile.go b/cmd/ebitenmobile/gomobile.go index 560155ac3..29ad52d55 100644 --- a/cmd/ebitenmobile/gomobile.go +++ b/cmd/ebitenmobile/gomobile.go @@ -18,13 +18,13 @@ import ( _ "embed" "fmt" "os" + "os/exec" "path/filepath" "runtime" "runtime/debug" // Add a dependency on gomobile in order to get the version via debug.ReadBuildInfo(). _ "github.com/ebitengine/gomobile/geom" - exec "golang.org/x/sys/execabs" ) //go:embed gobind.go diff --git a/cmd/ebitenmobile/main.go b/cmd/ebitenmobile/main.go index da6e60418..dbdef5558 100644 --- a/cmd/ebitenmobile/main.go +++ b/cmd/ebitenmobile/main.go @@ -27,12 +27,12 @@ import ( "fmt" "log" "os" + "os/exec" "path/filepath" "strings" "text/template" "unicode" - exec "golang.org/x/sys/execabs" "golang.org/x/tools/go/packages" ) diff --git a/colorm/colorm.go b/colorm/colorm.go index 5e6d0f2aa..a3e1f0bee 100644 --- a/colorm/colorm.go +++ b/colorm/colorm.go @@ -179,7 +179,7 @@ func builtinShader(filter builtinshader.Filter, address builtinshader.Address) * return s } - src := builtinshader.Shader(filter, address, true) + src := builtinshader.ShaderSource(filter, address, true) s, err := ebiten.NewShader(src) if err != nil { panic(fmt.Sprintf("colorm: NewShader for a built-in shader failed: %v", err)) diff --git a/colorm/draw.go b/colorm/draw.go index 56408fbb6..3992f527d 100644 --- a/colorm/draw.go +++ b/colorm/draw.go @@ -72,11 +72,11 @@ type DrawTrianglesOptions struct { // FillRule indicates the rule how an overlapped region is rendered. // - // The rules NonZero and EvenOdd are useful when you want to render a complex polygon. + // The rules FileRuleNonZero and FillRuleEvenOdd are useful when you want to render a complex polygon. // A complex polygon is a non-convex polygon like a concave polygon, a polygon with holes, or a self-intersecting polygon. // See examples/vector for actual usages. // - // The default (zero) value is ebiten.FillAll. + // The default (zero) value is ebiten.FillRuleFillAll. FillRule ebiten.FillRule // AntiAlias indicates whether the rendering uses anti-alias or not. diff --git a/ebiten_test.go b/ebiten_test.go new file mode 100644 index 000000000..bf0ceb254 --- /dev/null +++ b/ebiten_test.go @@ -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) + } +} diff --git a/ebitenutil/file_js.go b/ebitenutil/file_js.go index 9c6be7eb4..d477eb795 100644 --- a/ebitenutil/file_js.go +++ b/ebitenutil/file_js.go @@ -40,6 +40,7 @@ func OpenFile(path string) (ReadSeekCloser, error) { if err != nil { return nil, err } + defer res.Body.Close() body, err := io.ReadAll(res.Body) if err != nil { return nil, err diff --git a/examples/audio/main.go b/examples/audio/main.go index 0a2fdbdd1..5d8ca344b 100644 --- a/examples/audio/main.go +++ b/examples/audio/main.go @@ -171,7 +171,7 @@ func NewPlayer(game *Game, audioContext *audio.Context, musicType musicType) (*P player.audioPlayer.Play() go func() { - s, err := wav.DecodeWithSampleRate(sampleRate, bytes.NewReader(raudio.Jab_wav)) + s, err := wav.DecodeWithoutResampling(bytes.NewReader(raudio.Jab_wav)) if err != nil { log.Fatal(err) return diff --git a/examples/shaderprecomp/defaultshader.go b/examples/shaderprecomp/defaultshader.go new file mode 100644 index 000000000..1c7f70197 --- /dev/null +++ b/examples/shaderprecomp/defaultshader.go @@ -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) +} diff --git a/examples/shaderprecomp/fxc/dummy.fxc b/examples/shaderprecomp/fxc/dummy.fxc new file mode 100644 index 000000000..2a3f3dd8e --- /dev/null +++ b/examples/shaderprecomp/fxc/dummy.fxc @@ -0,0 +1 @@ +This is a dummy .fxc file to trick Go's embed package. diff --git a/examples/shaderprecomp/fxc/gen.go b/examples/shaderprecomp/fxc/gen.go new file mode 100644 index 000000000..4f7c4203f --- /dev/null +++ b/examples/shaderprecomp/fxc/gen.go @@ -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 +} diff --git a/examples/shaderprecomp/fxc/generate.go b/examples/shaderprecomp/fxc/generate.go new file mode 100644 index 000000000..601e6532c --- /dev/null +++ b/examples/shaderprecomp/fxc/generate.go @@ -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 diff --git a/examples/shaderprecomp/main.go b/examples/shaderprecomp/main.go new file mode 100644 index 000000000..60d85edce --- /dev/null +++ b/examples/shaderprecomp/main.go @@ -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) + } +} diff --git a/examples/shaderprecomp/metallib/dummy.metallib b/examples/shaderprecomp/metallib/dummy.metallib new file mode 100644 index 000000000..4f8a5ca8c --- /dev/null +++ b/examples/shaderprecomp/metallib/dummy.metallib @@ -0,0 +1 @@ +This is a dummy .metallib file to trick Go's embed package. diff --git a/examples/shaderprecomp/metallib/gen.go b/examples/shaderprecomp/metallib/gen.go new file mode 100644 index 000000000..50264e8f2 --- /dev/null +++ b/examples/shaderprecomp/metallib/gen.go @@ -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 +} diff --git a/examples/shaderprecomp/metallib/generate.go b/examples/shaderprecomp/metallib/generate.go new file mode 100644 index 000000000..658ccc004 --- /dev/null +++ b/examples/shaderprecomp/metallib/generate.go @@ -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 diff --git a/examples/shaderprecomp/register_darwin.go b/examples/shaderprecomp/register_darwin.go new file mode 100644 index 000000000..c2fcb0902 --- /dev/null +++ b/examples/shaderprecomp/register_darwin.go @@ -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 +} diff --git a/examples/shaderprecomp/register_others.go b/examples/shaderprecomp/register_others.go new file mode 100644 index 000000000..b0e37ea6f --- /dev/null +++ b/examples/shaderprecomp/register_others.go @@ -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 +} diff --git a/examples/shaderprecomp/register_windows.go b/examples/shaderprecomp/register_windows.go new file mode 100644 index 000000000..f27b84ecf --- /dev/null +++ b/examples/shaderprecomp/register_windows.go @@ -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 +} diff --git a/examples/vector/main.go b/examples/vector/main.go index 674388d62..02ac37350 100644 --- a/examples/vector/main.go +++ b/examples/vector/main.go @@ -141,14 +141,14 @@ func drawEbitenText(screen *ebiten.Image, x, y int, aa bool, line bool) { op := &ebiten.DrawTrianglesOptions{} op.AntiAlias = aa - // For strokes (AppendVerticesAndIndicesForStroke), FillAll and NonZero work. + // For strokes (AppendVerticesAndIndicesForStroke), FillRuleFillAll and FillRuleNonZero work. // - // For filling (AppendVerticesAndIndicesForFilling), NonZero and EvenOdd work. - // NonZero and EvenOdd differ when rendering a complex polygons with self-intersections and/or holes. + // For filling (AppendVerticesAndIndicesForFilling), FillRuleNonZero and FillRuleEvenOdd work. + // FillRuleNonZero and FillRuleEvenOdd differ when rendering a complex polygons with self-intersections and/or holes. // See https://en.wikipedia.org/wiki/Nonzero-rule and https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule . // - // For simplicity, this example always uses NonZero, whichever strokes or filling is done. - op.FillRule = ebiten.NonZero + // For simplicity, this example always uses FillRuleNonZero, whichever strokes or filling is done. + op.FillRule = ebiten.FillRuleNonZero screen.DrawTriangles(vs, is, whiteSubImage, op) } @@ -203,7 +203,7 @@ func drawEbitenLogo(screen *ebiten.Image, x, y int, aa bool, line bool) { op := &ebiten.DrawTrianglesOptions{} op.AntiAlias = aa - op.FillRule = ebiten.NonZero + op.FillRule = ebiten.FillRuleNonZero screen.DrawTriangles(vs, is, whiteSubImage, op) } @@ -245,7 +245,7 @@ func drawArc(screen *ebiten.Image, count int, aa bool, line bool) { op := &ebiten.DrawTrianglesOptions{} op.AntiAlias = aa - op.FillRule = ebiten.NonZero + op.FillRule = ebiten.FillRuleNonZero screen.DrawTriangles(vs, is, whiteSubImage, op) } @@ -300,7 +300,7 @@ func drawWave(screen *ebiten.Image, counter int, aa bool, line bool) { op := &ebiten.DrawTrianglesOptions{} op.AntiAlias = aa - op.FillRule = ebiten.NonZero + op.FillRule = ebiten.FillRuleNonZero screen.DrawTriangles(vs, is, whiteSubImage, op) } diff --git a/gameforui.go b/gameforui.go index bff312f75..d86e4fda8 100644 --- a/gameforui.go +++ b/gameforui.go @@ -21,44 +21,14 @@ import ( "sync/atomic" "github.com/hajimehoshi/ebiten/v2/internal/atlas" + "github.com/hajimehoshi/ebiten/v2/internal/builtinshader" "github.com/hajimehoshi/ebiten/v2/internal/ui" ) -const screenShaderSrc = `//kage:unit pixels +var screenFilterEnabled atomic.Bool -package main - -func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 { - // Blend source colors in a square region, which size is 1/scale. - scale := imageDstSize()/imageSrc0Size() - pos := srcPos - p0 := pos - 1/2.0/scale - p1 := pos + 1/2.0/scale - - // Texels must be in the source rect, so it is not necessary to check. - c0 := imageSrc0UnsafeAt(p0) - c1 := imageSrc0UnsafeAt(vec2(p1.x, p0.y)) - c2 := imageSrc0UnsafeAt(vec2(p0.x, p1.y)) - c3 := imageSrc0UnsafeAt(p1) - - // p is the p1 value in one pixel assuming that the pixel's upper-left is (0, 0) and the lower-right is (1, 1). - rate := clamp(fract(p1)*scale, 0, 1) - return mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y) -} -` - -var screenFilterEnabled = int32(1) - -func isScreenFilterEnabled() bool { - return atomic.LoadInt32(&screenFilterEnabled) != 0 -} - -func setScreenFilterEnabled(enabled bool) { - v := int32(0) - if enabled { - v = 1 - } - atomic.StoreInt32(&screenFilterEnabled, v) +func init() { + screenFilterEnabled.Store(true) } type gameForUI struct { @@ -76,7 +46,7 @@ func newGameForUI(game Game, transparent bool) *gameForUI { transparent: transparent, } - s, err := NewShader([]byte(screenShaderSrc)) + s, err := NewShader(builtinshader.ScreenShaderSource) if err != nil { panic(fmt.Sprintf("ebiten: compiling the screen shader failed: %v", err)) } @@ -167,7 +137,7 @@ func (g *gameForUI) DrawFinalScreen(scale, offsetX, offsetY float64) { } switch { - case !isScreenFilterEnabled(), math.Floor(scale) == scale: + case !screenFilterEnabled.Load(), math.Floor(scale) == scale: op := &DrawImageOptions{} op.GeoM = geoM g.screen.DrawImage(g.offscreen, op) diff --git a/go.mod b/go.mod index a45785ef2..d90c6dfa7 100644 --- a/go.mod +++ b/go.mod @@ -1,28 +1,28 @@ module github.com/hajimehoshi/ebiten/v2 -go 1.18 +go 1.19 require ( - github.com/ebitengine/gomobile v0.0.0-20240329170434-1771503ff0a8 + github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 github.com/ebitengine/hideconsole v1.0.0 github.com/ebitengine/oto/v3 v3.3.0-alpha.1 - github.com/ebitengine/purego v0.8.0-alpha.1 + github.com/ebitengine/purego v0.8.0-alpha.2 github.com/gen2brain/mpeg v0.3.2-0.20240412154320-a2ac4fc8a46f - github.com/go-text/typesetting v0.1.1-0.20240402181327-ced1d6822703 - github.com/hajimehoshi/bitmapfont/v3 v3.0.0 + github.com/go-text/typesetting v0.1.1 + github.com/hajimehoshi/bitmapfont/v3 v3.2.0-alpha.1 github.com/hajimehoshi/go-mp3 v0.3.4 github.com/jakecoffman/cp v1.2.1 github.com/jezek/xgb v1.1.1 github.com/jfreymuth/oggvorbis v1.0.5 github.com/kisielk/errcheck v1.7.0 - golang.org/x/image v0.15.0 - golang.org/x/sync v0.6.0 - golang.org/x/sys v0.18.0 - golang.org/x/text v0.14.0 - golang.org/x/tools v0.19.0 + golang.org/x/image v0.16.0 + golang.org/x/sync v0.7.0 + golang.org/x/sys v0.20.0 + golang.org/x/text v0.15.0 + golang.org/x/tools v0.21.0 ) require ( github.com/jfreymuth/vorbis v1.0.2 // indirect - golang.org/x/mod v0.16.0 // indirect + golang.org/x/mod v0.17.0 // indirect ) diff --git a/go.sum b/go.sum index f3d81e503..aa95e3d6e 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,18 @@ -github.com/ebitengine/gomobile v0.0.0-20240329170434-1771503ff0a8 h1:5e8X7WEdOWrjrKvgaWF6PRnDvJicfrkEnwAkWtMN74g= -github.com/ebitengine/gomobile v0.0.0-20240329170434-1771503ff0a8/go.mod h1:tWboRRNagZwwwis4QIgEFG1ZNFwBJ3LAhSLAXAAxobQ= +github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 h1:48bCqKTuD7Z0UovDfvpCn7wZ0GUZ+yosIteNDthn3FU= +github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895/go.mod h1:XZdLv05c5hOZm3fM2NlJ92FyEZjnslcMcNRrhxs8+8M= github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE= github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A= github.com/ebitengine/oto/v3 v3.3.0-alpha.1 h1:J2nBmQwPLKc4+yLObytq1jKNydI96l6EjZfgefiqGbk= github.com/ebitengine/oto/v3 v3.3.0-alpha.1/go.mod h1:T2/VV0UWG97GEEf4kORMU2nCneYT/YmwSTxPutSVaUg= -github.com/ebitengine/purego v0.8.0-alpha.1 h1:52AgJTNaQRi7YtOtdJl4hkxNWhAGMxuDuDjOVIp5Ojk= -github.com/ebitengine/purego v0.8.0-alpha.1/go.mod h1:y8L+ZRLphbdPW2xs41fur/KaW57yTzrFsqsclHyHrTM= +github.com/ebitengine/purego v0.8.0-alpha.2 h1:+Kyr9n4eXAGMzhtWJxfdQ7AzGn0+6ZWihfCCxul3Dso= +github.com/ebitengine/purego v0.8.0-alpha.2/go.mod h1:w5fARo4H5UrAgQTz0yqDfZ6bjstTQwUFmO+TN+nHlWE= github.com/gen2brain/mpeg v0.3.2-0.20240412154320-a2ac4fc8a46f h1:ysqRe+lvUiL0dH5XzkH0Bz68bFMPJ4f5Si4L/HD9SGk= github.com/gen2brain/mpeg v0.3.2-0.20240412154320-a2ac4fc8a46f/go.mod h1:i/ebyRRv/IoHixuZ9bElZnXbmfoUVPGQpdsJ4sVuX38= -github.com/go-text/typesetting v0.1.1-0.20240402181327-ced1d6822703 h1:AqtMl9yw7r319Ah4W2afQm3Ql+PEsQKHds18tGvKhog= -github.com/go-text/typesetting v0.1.1-0.20240402181327-ced1d6822703/go.mod h1:2+owI/sxa73XA581LAzVuEBZ3WEEV2pXeDswCH/3i1I= -github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66 h1:GUrm65PQPlhFSKjLPGOZNPNxLCybjzjYBzjfoBGaDUY= -github.com/hajimehoshi/bitmapfont/v3 v3.0.0 h1:r2+6gYK38nfztS/et50gHAswb9hXgxXECYgE8Nczmi4= -github.com/hajimehoshi/bitmapfont/v3 v3.0.0/go.mod h1:+CxxG+uMmgU4mI2poq944i3uZ6UYFfAkj9V6WqmuvZA= +github.com/go-text/typesetting v0.1.1 h1:bGAesCuo85nXnEN5LmFMVGAGpGkCPtHrZLi//qD7EJo= +github.com/go-text/typesetting v0.1.1/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI= +github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04 h1:zBx+p/W2aQYtNuyZNcTfinWvXBQwYtDfme051PR/lAY= +github.com/hajimehoshi/bitmapfont/v3 v3.2.0-alpha.1 h1:GLoMNCbvXOd39tFkqk9w/MI0xSLJaDzEOOl8mT1ILtI= +github.com/hajimehoshi/bitmapfont/v3 v3.2.0-alpha.1/go.mod h1:VVaVK/4HpV1MHWswCl5miFOuLoRVyIplB3qEJxZK2OA= github.com/hajimehoshi/go-mp3 v0.3.4 h1:NUP7pBYH8OguP4diaTZ9wJbUbk3tC0KlfzsEpWmYj68= github.com/hajimehoshi/go-mp3 v0.3.4/go.mod h1:fRtZraRFcWb0pu7ok0LqyFhCUrPeMsGRSVop0eemFmo= github.com/hajimehoshi/oto/v2 v2.3.1/go.mod h1:seWLbgHH7AyUMYKfKYT9pg7PhUu9/SisyJvNTT+ASQo= @@ -26,43 +26,50 @@ github.com/jfreymuth/vorbis v1.0.2 h1:m1xH6+ZI4thH927pgKD8JOH4eaGRm18rEE9/0WKjvN github.com/jfreymuth/vorbis v1.0.2/go.mod h1:DoftRo4AznKnShRl1GxiTFCseHr4zR9BN3TWXyuzrqQ= github.com/kisielk/errcheck v1.7.0 h1:+SbscKmWJ5mOK/bO1zS60F5I9WwZDWOfRsC4RwfwRV0= github.com/kisielk/errcheck v1.7.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c= -golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= +golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw= +golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= +golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/image.go b/image.go index c8d1143ce..5545975af 100644 --- a/image.go +++ b/image.go @@ -262,7 +262,7 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) { }) } - i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillAll, canSkipMipmap(geoM, filter), false) + i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRuleFillAll, canSkipMipmap(geoM, filter), false) } // Vertex represents a vertex passed to DrawTriangles. @@ -310,17 +310,36 @@ const ( // FillRule is the rule whether an overlapped region is rendered with DrawTriangles(Shader). type FillRule int +const ( + // FillRuleFillAll indicates all the triangles are rendered regardless of overlaps. + FillRuleFillAll FillRule = FillRule(graphicsdriver.FillRuleFillAll) + + // FillRuleNonZero means that triangles are rendered based on the non-zero rule. + // If and only if the number of overlaps is not 0, the region is rendered. + FillRuleNonZero FillRule = FillRule(graphicsdriver.FillRuleNonZero) + + // FillRuleEvenOdd means that triangles are rendered based on the even-odd rule. + // If and only if the number of overlaps is odd, the region is rendered. + FillRuleEvenOdd FillRule = FillRule(graphicsdriver.FillRuleEvenOdd) +) + const ( // FillAll indicates all the triangles are rendered regardless of overlaps. - FillAll FillRule = FillRule(graphicsdriver.FillAll) + // + // Deprecated: as of v2.8. Use FillRuleFillAll instead. + FillAll = FillRuleFillAll // NonZero means that triangles are rendered based on the non-zero rule. // If and only if the number of overlaps is not 0, the region is rendered. - NonZero FillRule = FillRule(graphicsdriver.NonZero) + // + // Deprecated: as of v2.8. Use FillRuleNonZero instead. + NonZero = FillRuleNonZero // EvenOdd means that triangles are rendered based on the even-odd rule. // If and only if the number of overlaps is odd, the region is rendered. - EvenOdd FillRule = FillRule(graphicsdriver.EvenOdd) + // + // Deprecated: as of v2.8. Use FillRuleEvenOdd instead. + EvenOdd = FillRuleEvenOdd ) // ColorScaleMode is the mode of color scales in vertices. @@ -371,11 +390,11 @@ type DrawTrianglesOptions struct { // FillRule indicates the rule how an overlapped region is rendered. // - // The rules NonZero and EvenOdd are useful when you want to render a complex polygon. + // The rules FillRuleNonZero and FillRuleEvenOdd are useful when you want to render a complex polygon. // A complex polygon is a non-convex polygon like a concave polygon, a polygon with holes, or a self-intersecting polygon. // See examples/vector for actual usages. // - // The default (zero) value is FillAll. + // The default (zero) value is FillRuleFillAll. FillRule FillRule // AntiAlias indicates whether the rendering uses anti-alias or not. @@ -547,11 +566,11 @@ type DrawTrianglesShaderOptions struct { // FillRule indicates the rule how an overlapped region is rendered. // - // The rules NonZero and EvenOdd are useful when you want to render a complex polygon. + // The rules FillRuleNonZero and FillRuleEvenOdd are useful when you want to render a complex polygon. // A complex polygon is a non-convex polygon like a concave polygon, a polygon with holes, or a self-intersecting polygon. // See examples/vector for actual usages. // - // The default (zero) value is FillAll. + // The default (zero) value is FillRuleFillAll. FillRule FillRule // AntiAlias indicates whether the rendering uses anti-alias or not. @@ -949,7 +968,7 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR i.tmpUniforms = i.tmpUniforms[:0] i.tmpUniforms = shader.appendUniforms(i.tmpUniforms, options.Uniforms) - i.image.DrawTriangles(imgs, vs, is, blend, i.adjustedBounds(), srcRegions, shader.shader, i.tmpUniforms, graphicsdriver.FillAll, true, false) + i.image.DrawTriangles(imgs, vs, is, blend, i.adjustedBounds(), srcRegions, shader.shader, i.tmpUniforms, graphicsdriver.FillRuleFillAll, true, false) } // SubImage returns an image representing the portion of the image p visible through r. diff --git a/image_test.go b/image_test.go index 660418a48..2f58e7f36 100644 --- a/image_test.go +++ b/image_test.go @@ -2696,7 +2696,7 @@ func TestImageEvenOdd(t *testing.T) { // Draw all the vertices once. The even-odd rule is applied for all the vertices once. dst := ebiten.NewImage(16, 16) op := &ebiten.DrawTrianglesOptions{ - FillRule: ebiten.EvenOdd, + FillRule: ebiten.FillRuleEvenOdd, } dst.DrawTriangles(append(append(vs0, vs1...), vs2...), append(append(is0, is1...), is2...), emptySubImage, op) for j := 0; j < 16; j++ { @@ -2794,15 +2794,15 @@ func TestImageEvenOdd(t *testing.T) { } func TestImageFillRule(t *testing.T) { - for _, fillRule := range []ebiten.FillRule{ebiten.FillAll, ebiten.NonZero, ebiten.EvenOdd} { + for _, fillRule := range []ebiten.FillRule{ebiten.FillRuleFillAll, ebiten.FillRuleNonZero, ebiten.FillRuleEvenOdd} { fillRule := fillRule var name string switch fillRule { - case ebiten.FillAll: + case ebiten.FillRuleFillAll: name = "FillAll" - case ebiten.NonZero: + case ebiten.FillRuleNonZero: name = "NonZero" - case ebiten.EvenOdd: + case ebiten.FillRuleEvenOdd: name = "EvenOdd" } t.Run(name, func(t *testing.T) { @@ -2885,11 +2885,11 @@ func TestImageFillRule(t *testing.T) { var want color.RGBA switch { case 2 <= i && i < 7 && 2 <= j && j < 7: - if fillRule != ebiten.EvenOdd { + if fillRule != ebiten.FillRuleEvenOdd { want = color.RGBA{G: 0xff, A: 0xff} } case 9 <= i && i < 14 && 9 <= j && j < 14: - if fillRule == ebiten.FillAll { + if fillRule == ebiten.FillRuleFillAll { want = color.RGBA{B: 0xff, A: 0xff} } case 1 <= i && i < 15 && 1 <= j && j < 15: @@ -2922,11 +2922,11 @@ func TestImageFillRule(t *testing.T) { var want color.RGBA switch { case 3 <= i && i < 8 && 3 <= j && j < 8: - if fillRule != ebiten.EvenOdd { + if fillRule != ebiten.FillRuleEvenOdd { want = color.RGBA{G: 0xff, A: 0xff} } case 10 <= i && i < 15 && 10 <= j && j < 15: - if fillRule == ebiten.FillAll { + if fillRule == ebiten.FillRuleFillAll { want = color.RGBA{B: 0xff, A: 0xff} } case 2 <= i && i < 16 && 2 <= j && j < 16: @@ -3726,7 +3726,7 @@ func TestImageTooManyConstantBuffersInDirectX(t *testing.T) { dst0 := ebiten.NewImage(16, 16) dst1 := ebiten.NewImage(16, 16) op := &ebiten.DrawTrianglesOptions{ - FillRule: ebiten.EvenOdd, + FillRule: ebiten.FillRuleEvenOdd, } for i := 0; i < 100; i++ { dst0.DrawTriangles(vs, is, src, op) diff --git a/internal/atlas/export_test.go b/internal/atlas/export_test.go index dc0a00bf5..eb8af636c 100644 --- a/internal/atlas/export_test.go +++ b/internal/atlas/export_test.go @@ -14,16 +14,12 @@ package atlas -import ( - "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" -) - const ( BaseCountToPutOnSourceBackend = baseCountToPutOnSourceBackend ) -func PutImagesOnSourceBackendForTesting(graphicsDriver graphicsdriver.Graphics) { - putImagesOnSourceBackend(graphicsDriver) +func PutImagesOnSourceBackendForTesting() { + putImagesOnSourceBackend() } var ( diff --git a/internal/atlas/image.go b/internal/atlas/image.go index d446d8c4c..c6f744876 100644 --- a/internal/atlas/image.go +++ b/internal/atlas/image.go @@ -36,13 +36,6 @@ var ( maxSize = 0 ) -func max(a, b int) int { - if a > b { - return a - } - return b -} - func min(a, b int) int { if a < b { return a @@ -81,7 +74,7 @@ func flushDeferred() { // Actual time duration is increased in an exponential way for each usage as a rendering target. const baseCountToPutOnSourceBackend = 10 -func putImagesOnSourceBackend(graphicsDriver graphicsdriver.Graphics) { +func putImagesOnSourceBackend() { // The counter usedAsDestinationCount is updated at most once per frame (#2676). imagesUsedAsDestination.forEach(func(i *Image) { // This counter is not updated when the backend is created in this frame. @@ -97,7 +90,7 @@ func putImagesOnSourceBackend(graphicsDriver graphicsdriver.Graphics) { i.usedAsSourceCount++ } if int64(i.usedAsSourceCount) >= int64(baseCountToPutOnSourceBackend*(1< 0 { + andPlane = &pvANDPlane[0] + } + var xorPlane *byte + if len(pvXORPlane) > 0 { + xorPlane = &pvXORPlane[0] + } + + r, _, e := procCreateCursor.Call(uintptr(hInst), uintptr(xHotSpot), uintptr(yHotSpot), uintptr(nWidth), uintptr(nHeight), uintptr(unsafe.Pointer(andPlane)), uintptr(unsafe.Pointer(xorPlane))) + runtime.KeepAlive(pvANDPlane) + runtime.KeepAlive(pvXORPlane) + + if _HCURSOR(r) == 0 && !errors.Is(e, windows.ERROR_SUCCESS) { + return 0, fmt.Errorf("glfw: CreateCursor failed: %w", e) + } + return _HCURSOR(r), nil +} + func _CreateBitmap(nWidth int32, nHeight int32, nPlanes uint32, nBitCount uint32, lpBits unsafe.Pointer) (_HBITMAP, error) { r, _, e := procCreateBitmap.Call(uintptr(nWidth), uintptr(nHeight), uintptr(nPlanes), uintptr(nBitCount), uintptr(lpBits)) if _HBITMAP(r) == 0 { @@ -984,6 +1016,14 @@ func _DefWindowProcW(hWnd windows.HWND, uMsg uint32, wParam _WPARAM, lParam _LPA return _LRESULT(r) } +func _DestroyCursor(hCursor _HCURSOR) error { + r, _, e := procDestroyCursor.Call(uintptr(hCursor)) + if int32(r) == 0 && !errors.Is(e, windows.ERROR_SUCCESS) { + return fmt.Errorf("glfw: DestroyCursor failed: %w", e) + } + return nil +} + func _DestroyIcon(hIcon _HICON) error { r, _, e := procDestroyIcon.Call(uintptr(hIcon)) if int32(r) == 0 && !errors.Is(e, windows.ERROR_SUCCESS) { diff --git a/internal/glfw/cocoa_init_darwin.m b/internal/glfw/cocoa_init_darwin.m index 177500d5c..af1c5d314 100644 --- a/internal/glfw/cocoa_init_darwin.m +++ b/internal/glfw/cocoa_init_darwin.m @@ -227,7 +227,7 @@ static void createKeyTables(void) _glfw.ns.keycodes[0x6D] = GLFW_KEY_F10; _glfw.ns.keycodes[0x67] = GLFW_KEY_F11; _glfw.ns.keycodes[0x6F] = GLFW_KEY_F12; - _glfw.ns.keycodes[0x69] = GLFW_KEY_F13; + _glfw.ns.keycodes[0x69] = GLFW_KEY_PRINT_SCREEN; _glfw.ns.keycodes[0x6B] = GLFW_KEY_F14; _glfw.ns.keycodes[0x71] = GLFW_KEY_F15; _glfw.ns.keycodes[0x6A] = GLFW_KEY_F16; @@ -420,7 +420,6 @@ static GLFWbool initializeTIS(void) - (void)applicationDidFinishLaunching:(NSNotification *)notification { - _glfw.ns.finishedLaunching = GLFW_TRUE; _glfwPlatformPostEmptyEvent(); // In case we are unbundled, make us a proper UI application @@ -455,9 +454,6 @@ int _glfwPlatformInit(void) toTarget:_glfw.ns.helper withObject:nil]; - if (NSApp) - _glfw.ns.finishedLaunching = GLFW_TRUE; - [NSApplication sharedApplication]; _glfw.ns.delegate = [[GLFWApplicationDelegate alloc] init]; @@ -509,6 +505,10 @@ int _glfwPlatformInit(void) _glfwInitTimerNS(); _glfwPollMonitorsNS(); + + if (![[NSRunningApplication currentApplication] isFinishedLaunching]) + [NSApp run]; + return GLFW_TRUE; } // autoreleasepool diff --git a/internal/glfw/cocoa_platform_darwin.h b/internal/glfw/cocoa_platform_darwin.h index ef7a7b018..add6b0ebc 100644 --- a/internal/glfw/cocoa_platform_darwin.h +++ b/internal/glfw/cocoa_platform_darwin.h @@ -109,7 +109,6 @@ typedef struct _GLFWlibraryNS { CGEventSourceRef eventSource; id delegate; - GLFWbool finishedLaunching; GLFWbool cursorHidden; TISInputSourceRef inputSource; id unicodeData; diff --git a/internal/glfw/cocoa_window_darwin.m b/internal/glfw/cocoa_window_darwin.m index 111a6d16b..15e461e47 100644 --- a/internal/glfw/cocoa_window_darwin.m +++ b/internal/glfw/cocoa_window_darwin.m @@ -285,10 +285,15 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; - (void)windowDidChangeOcclusionState:(NSNotification* )notification { - if ([window->ns.object occlusionState] & NSWindowOcclusionStateVisible) - window->ns.occluded = GLFW_FALSE; - else - window->ns.occluded = GLFW_TRUE; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1090 + if ([window->ns.object respondsToSelector:@selector(occlusionState)]) + { + if ([window->ns.object occlusionState] & NSWindowOcclusionStateVisible) + window->ns.occluded = GLFW_FALSE; + else + window->ns.occluded = GLFW_TRUE; + } +#endif } @end @@ -878,9 +883,6 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window, { @autoreleasepool { - if (!_glfw.ns.finishedLaunching) - [NSApp run]; - if (!createNativeWindow(window, wndconfig, fbconfig)) return GLFW_FALSE; @@ -1239,7 +1241,7 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, if (window->monitor) { - styleMask &= ~(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable); + styleMask &= ~(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable); styleMask |= NSWindowStyleMaskBorderless; } else @@ -1472,9 +1474,6 @@ void _glfwPlatformPollEvents(void) { @autoreleasepool { - if (!_glfw.ns.finishedLaunching) - [NSApp run]; - for (;;) { NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny @@ -1494,9 +1493,6 @@ void _glfwPlatformWaitEvents(void) { @autoreleasepool { - if (!_glfw.ns.finishedLaunching) - [NSApp run]; - // I wanted to pass NO to dequeue:, and rely on PollEvents to // dequeue and send. For reasons not at all clear to me, passing // NO to dequeue: causes this method never to return. @@ -1515,9 +1511,6 @@ void _glfwPlatformWaitEventsTimeout(double timeout) { @autoreleasepool { - if (!_glfw.ns.finishedLaunching) - [NSApp run]; - NSDate* date = [NSDate dateWithTimeIntervalSinceNow:timeout]; NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:date @@ -1535,9 +1528,6 @@ void _glfwPlatformPostEmptyEvent(void) { @autoreleasepool { - if (!_glfw.ns.finishedLaunching) - [NSApp run]; - NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined location:NSMakePoint(0, 0) modifierFlags:0 @@ -1616,14 +1606,15 @@ const char* _glfwPlatformGetScancodeName(int scancode) { @autoreleasepool { - if (scancode < 0 || scancode > 0xff || - _glfw.ns.keycodes[scancode] == GLFW_KEY_UNKNOWN) + if (scancode < 0 || scancode > 0xff) { _glfwInputError(GLFW_INVALID_VALUE, "Invalid scancode %i", scancode); return NULL; } const int key = _glfw.ns.keycodes[scancode]; + if (key == GLFW_KEY_UNKNOWN) + return NULL; UInt32 deadKeyState = 0; UniChar characters[4]; diff --git a/internal/glfw/context_unix.c b/internal/glfw/context_unix.c index 3b01f86c4..58cba6951 100644 --- a/internal/glfw/context_unix.c +++ b/internal/glfw/context_unix.c @@ -26,16 +26,6 @@ // GLFWbool _glfwIsValidContextConfig(const _GLFWctxconfig* ctxconfig) { - if (ctxconfig->share) - { - if (ctxconfig->client == GLFW_NO_API || - ctxconfig->share->context.client == GLFW_NO_API) - { - _glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL); - return GLFW_FALSE; - } - } - if (ctxconfig->source != GLFW_NATIVE_CONTEXT_API && ctxconfig->source != GLFW_EGL_CONTEXT_API && ctxconfig->source != GLFW_OSMESA_CONTEXT_API) @@ -56,6 +46,23 @@ GLFWbool _glfwIsValidContextConfig(const _GLFWctxconfig* ctxconfig) return GLFW_FALSE; } + if (ctxconfig->share) + { + if (ctxconfig->client == GLFW_NO_API || + ctxconfig->share->context.client == GLFW_NO_API) + { + _glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL); + return GLFW_FALSE; + } + + if (ctxconfig->source != ctxconfig->share->context.source) + { + _glfwInputError(GLFW_INVALID_ENUM, + "Context creation APIs do not match between contexts"); + return GLFW_FALSE; + } + } + if (ctxconfig->client == GLFW_OPENGL_API) { if ((ctxconfig->major < 1 || ctxconfig->minor < 0) || @@ -334,6 +341,8 @@ GLFWbool _glfwRefreshContextAttribs(_GLFWwindow* window, previous = _glfwPlatformGetTls(&_glfw.contextSlot); glfwMakeContextCurrent((GLFWwindow*) window); + if (_glfwPlatformGetTls(&_glfw.contextSlot) != window) + return GLFW_FALSE; window->context.GetIntegerv = (PFNGLGETINTEGERVPROC) window->context.getProcAddress("glGetIntegerv"); diff --git a/internal/glfw/context_windows.go b/internal/glfw/context_windows.go index 2f7ea8d10..49e0fdc4c 100644 --- a/internal/glfw/context_windows.go +++ b/internal/glfw/context_windows.go @@ -17,12 +17,6 @@ import ( ) func checkValidContextConfig(ctxconfig *ctxconfig) error { - if ctxconfig.share != nil { - if ctxconfig.client == NoAPI || ctxconfig.share.context.client == NoAPI { - return NoWindowContext - } - } - if ctxconfig.source != NativeContextAPI && ctxconfig.source != EGLContextAPI && ctxconfig.source != OSMesaContextAPI { @@ -35,6 +29,15 @@ func checkValidContextConfig(ctxconfig *ctxconfig) error { return fmt.Errorf("glfw: invalid client API 0x%08X: %w", ctxconfig.client, InvalidEnum) } + if ctxconfig.share != nil { + if ctxconfig.client == NoAPI || ctxconfig.share.context.client == NoAPI { + return NoWindowContext + } + if ctxconfig.source != ctxconfig.share.context.source { + return fmt.Errorf("glfw: context creation APIs do not match between contexts: %w", InvalidEnum) + } + } + if ctxconfig.client == OpenGLAPI { if (ctxconfig.major < 1 || ctxconfig.minor < 0) || (ctxconfig.major == 1 && ctxconfig.minor > 5) || @@ -249,11 +252,11 @@ func (w *Window) refreshContextAttribs(ctxconfig *ctxconfig) (ferr error) { w.context.source = ctxconfig.source w.context.client = OpenGLAPI - p, err := _glfw.contextSlot.get() + p1, err := _glfw.contextSlot.get() if err != nil { return err } - previous := (*Window)(unsafe.Pointer(p)) + previous := (*Window)(unsafe.Pointer(p1)) defer func() { err := previous.MakeContextCurrent() if ferr == nil { @@ -264,6 +267,14 @@ func (w *Window) refreshContextAttribs(ctxconfig *ctxconfig) (ferr error) { return err } + p2, err := _glfw.contextSlot.get() + if err != nil { + return err + } + if (*Window)(unsafe.Pointer(p2)) != w { + return nil + } + getIntegerv := w.context.getProcAddress("glGetIntegerv") getString := w.context.getProcAddress("glGetString") if getIntegerv == 0 || getString == 0 { diff --git a/internal/glfw/egl_context_unix.c b/internal/glfw/egl_context_unix.c index f415aef90..ba1fd9804 100644 --- a/internal/glfw/egl_context_unix.c +++ b/internal/glfw/egl_context_unix.c @@ -66,13 +66,30 @@ static int getEGLConfigAttrib(EGLConfig config, int attrib) // Return the EGLConfig most closely matching the specified hints // static GLFWbool chooseEGLConfig(const _GLFWctxconfig* ctxconfig, - const _GLFWfbconfig* desired, + const _GLFWfbconfig* fbconfig, EGLConfig* result) { EGLConfig* nativeConfigs; _GLFWfbconfig* usableConfigs; const _GLFWfbconfig* closest; - int i, nativeCount, usableCount; + int i, nativeCount, usableCount, apiBit; + GLFWbool wrongApiAvailable = GLFW_FALSE; + + if (ctxconfig->client == GLFW_OPENGL_ES_API) + { + if (ctxconfig->major == 1) + apiBit = EGL_OPENGL_ES_BIT; + else + apiBit = EGL_OPENGL_ES2_BIT; + } + else + apiBit = EGL_OPENGL_BIT; + + if (fbconfig->stereo) + { + _glfwInputError(GLFW_FORMAT_UNAVAILABLE, "EGL: Stereo rendering not supported"); + return GLFW_FALSE; + } eglGetConfigs(_glfw.egl.display, NULL, 0, &nativeCount); if (!nativeCount) @@ -109,7 +126,7 @@ static GLFWbool chooseEGLConfig(const _GLFWctxconfig* ctxconfig, if (!vi.visualid) continue; - if (desired->transparent) + if (fbconfig->transparent) { int count; XVisualInfo* vis = @@ -123,23 +140,10 @@ static GLFWbool chooseEGLConfig(const _GLFWctxconfig* ctxconfig, } #endif // _GLFW_X11 - if (ctxconfig->client == GLFW_OPENGL_ES_API) + if (!(getEGLConfigAttrib(n, EGL_RENDERABLE_TYPE) & apiBit)) { - if (ctxconfig->major == 1) - { - if (!(getEGLConfigAttrib(n, EGL_RENDERABLE_TYPE) & EGL_OPENGL_ES_BIT)) - continue; - } - else - { - if (!(getEGLConfigAttrib(n, EGL_RENDERABLE_TYPE) & EGL_OPENGL_ES2_BIT)) - continue; - } - } - else if (ctxconfig->client == GLFW_OPENGL_API) - { - if (!(getEGLConfigAttrib(n, EGL_RENDERABLE_TYPE) & EGL_OPENGL_BIT)) - continue; + wrongApiAvailable = GLFW_TRUE; + continue; } u->redBits = getEGLConfigAttrib(n, EGL_RED_SIZE); @@ -151,15 +155,44 @@ static GLFWbool chooseEGLConfig(const _GLFWctxconfig* ctxconfig, u->stencilBits = getEGLConfigAttrib(n, EGL_STENCIL_SIZE); u->samples = getEGLConfigAttrib(n, EGL_SAMPLES); - u->doublebuffer = desired->doublebuffer; + u->doublebuffer = fbconfig->doublebuffer; u->handle = (uintptr_t) n; usableCount++; } - closest = _glfwChooseFBConfig(desired, usableConfigs, usableCount); + closest = _glfwChooseFBConfig(fbconfig, usableConfigs, usableCount); if (closest) *result = (EGLConfig) closest->handle; + else + { + if (wrongApiAvailable) + { + if (ctxconfig->client == GLFW_OPENGL_ES_API) + { + if (ctxconfig->major == 1) + { + _glfwInputError(GLFW_API_UNAVAILABLE, + "EGL: Failed to find support for OpenGL ES 1.x"); + } + else + { + _glfwInputError(GLFW_API_UNAVAILABLE, + "EGL: Failed to find support for OpenGL ES 2 or later"); + } + } + else + { + _glfwInputError(GLFW_API_UNAVAILABLE, + "EGL: Failed to find support for OpenGL"); + } + } + else + { + _glfwInputError(GLFW_FORMAT_UNAVAILABLE, + "EGL: Failed to find a suitable EGLConfig"); + } + } free(nativeConfigs); free(usableConfigs); @@ -231,6 +264,7 @@ static int extensionSupportedEGL(const char* extension) static GLFWglproc getProcAddressEGL(const char* procname) { _GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot); + assert(window != NULL); if (window->context.egl.client) { @@ -454,11 +488,7 @@ GLFWbool _glfwCreateContextEGL(_GLFWwindow* window, share = ctxconfig->share->context.egl.handle; if (!chooseEGLConfig(ctxconfig, fbconfig, &config)) - { - _glfwInputError(GLFW_FORMAT_UNAVAILABLE, - "EGL: Failed to find a suitable EGLConfig"); return GLFW_FALSE; - } if (ctxconfig->client == GLFW_OPENGL_ES_API) { @@ -515,18 +545,18 @@ GLFWbool _glfwCreateContextEGL(_GLFWwindow* window, flags |= EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR; } - if (ctxconfig->noerror) - { - if (_glfw.egl.KHR_create_context_no_error) - setAttrib(EGL_CONTEXT_OPENGL_NO_ERROR_KHR, GLFW_TRUE); - } - if (ctxconfig->major != 1 || ctxconfig->minor != 0) { setAttrib(EGL_CONTEXT_MAJOR_VERSION_KHR, ctxconfig->major); setAttrib(EGL_CONTEXT_MINOR_VERSION_KHR, ctxconfig->minor); } + if (ctxconfig->noerror) + { + if (_glfw.egl.KHR_create_context_no_error) + setAttrib(EGL_CONTEXT_OPENGL_NO_ERROR_KHR, GLFW_TRUE); + } + if (mask) setAttrib(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, mask); @@ -578,9 +608,6 @@ GLFWbool _glfwCreateContextEGL(_GLFWwindow* window, if (!fbconfig->doublebuffer) setAttrib(EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER); - if (_glfw.egl.EXT_present_opaque) - setAttrib(EGL_PRESENT_OPAQUE_EXT, !fbconfig->transparent); - setAttrib(EGL_NONE, EGL_NONE); window->context.egl.surface = @@ -640,6 +667,7 @@ GLFWbool _glfwCreateContextEGL(_GLFWwindow* window, #elif defined(__OpenBSD__) || defined(__NetBSD__) "libGL.so", #else + "libOpenGL.so.0", "libGL.so.1", #endif NULL @@ -702,11 +730,7 @@ GLFWbool _glfwChooseVisualEGL(const _GLFWwndconfig* wndconfig, const long vimask = VisualScreenMask | VisualIDMask; if (!chooseEGLConfig(ctxconfig, fbconfig, &native)) - { - _glfwInputError(GLFW_FORMAT_UNAVAILABLE, - "EGL: Failed to find a suitable EGLConfig"); return GLFW_FALSE; - } eglGetConfigAttrib(_glfw.egl.display, native, EGL_NATIVE_VISUAL_ID, &visualID); diff --git a/internal/glfw/glfw3_unix.h b/internal/glfw/glfw3_unix.h index 2a7cff514..7386382a4 100644 --- a/internal/glfw/glfw3_unix.h +++ b/internal/glfw/glfw3_unix.h @@ -249,7 +249,7 @@ extern "C" { * release is made that does not contain any API changes. * @ingroup init */ -#define GLFW_VERSION_REVISION 8 +#define GLFW_VERSION_REVISION 10 /*! @} */ /*! @brief One. @@ -296,8 +296,12 @@ extern "C" { #define GLFW_REPEAT 2 /*! @} */ -/*! @defgroup keys Keyboard keys - * @brief Keyboard key IDs. +/*! @ingroup input + */ +#define GLFW_KEY_UNKNOWN -1 + +/*! @defgroup keys Keyboard key tokens + * @brief Keyboard key tokens. * * See [key input](@ref input_key) for how these are used. * @@ -320,8 +324,6 @@ extern "C" { * @{ */ -/* The unknown key */ -#define GLFW_KEY_UNKNOWN -1 /* Printable keys */ #define GLFW_KEY_SPACE 32 @@ -1590,6 +1592,14 @@ typedef struct GLFWimage * bundle, if present. This can be disabled with the @ref * GLFW_COCOA_CHDIR_RESOURCES init hint. * + * @remark @macos This function will create the main menu and dock icon for the + * application. If GLFW finds a `MainMenu.nib` it is loaded and assumed to + * contain a menu bar. Otherwise a minimal menu bar is created manually with + * common commands like Hide, Quit and About. The About entry opens a minimal + * about dialog with information from the application's bundle. The menu bar + * and dock icon can be disabled entirely with the @ref GLFW_COCOA_MENUBAR init + * hint. + * * @remark @x11 This function will set the `LC_CTYPE` category of the * application locale according to the current environment if that category is * still "C". This is because the "C" locale breaks Unicode text input. @@ -2410,8 +2420,8 @@ GLFWAPI void glfwWindowHintString(int hint, const char* value); * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_INVALID_ENUM, @ref GLFW_INVALID_VALUE, @ref GLFW_API_UNAVAILABLE, @ref - * GLFW_VERSION_UNAVAILABLE, @ref GLFW_FORMAT_UNAVAILABLE and @ref - * GLFW_PLATFORM_ERROR. + * GLFW_VERSION_UNAVAILABLE, @ref GLFW_FORMAT_UNAVAILABLE, @ref + * GLFW_NO_WINDOW_CONTEXT and @ref GLFW_PLATFORM_ERROR. * * @remark @win32 Window creation will fail if the Microsoft GDI software * OpenGL implementation is the only one available. @@ -2437,13 +2447,6 @@ GLFWAPI void glfwWindowHintString(int hint, const char* value); * [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/) * in the Mac Developer Library. * - * @remark @macos The first time a window is created the menu bar is created. - * If GLFW finds a `MainMenu.nib` it is loaded and assumed to contain a menu - * bar. Otherwise a minimal menu bar is created manually with common commands - * like Hide, Quit and About. The About entry opens a minimal about dialog - * with information from the application's bundle. Menu bar creation can be - * disabled entirely with the @ref GLFW_COCOA_MENUBAR init hint. - * * @remark @macos On OS X 10.10 and later the window frame will not be rendered * at full resolution on Retina displays unless the * [GLFW_COCOA_RETINA_FRAMEBUFFER](@ref GLFW_COCOA_RETINA_FRAMEBUFFER_hint) @@ -3360,11 +3363,15 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* window, int attrib); * @param[in] value `GLFW_TRUE` or `GLFW_FALSE`. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref - * GLFW_INVALID_ENUM, @ref GLFW_INVALID_VALUE and @ref GLFW_PLATFORM_ERROR. + * GLFW_INVALID_ENUM, @ref GLFW_INVALID_VALUE, @ref GLFW_PLATFORM_ERROR. * * @remark Calling @ref glfwGetWindowAttrib will always return the latest * value, even if that value is ignored by the current mode of the window. * + * @remark @wayland The [GLFW_FLOATING](@ref GLFW_FLOATING_attrib) window + * attribute is not supported. Setting this will emit @ref + * GLFW_PLATFORM_ERROR. + * * @thread_safety This function must only be called from the main thread. * * @sa @ref window_attribs @@ -4039,8 +4046,8 @@ GLFWAPI int glfwRawMouseMotionSupported(void); * @param[in] scancode The scancode of the key to query. * @return The UTF-8 encoded, layout-specific name of the key, or `NULL`. * - * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref - * GLFW_PLATFORM_ERROR. + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref + * GLFW_INVALID_VALUE, @ref GLFW_INVALID_ENUM and @ref GLFW_PLATFORM_ERROR. * * @remark The contents of the returned string may change when a keyboard * layout change event is received. @@ -4062,15 +4069,18 @@ GLFWAPI const char* glfwGetKeyName(int key, int scancode); * * This function returns the platform-specific scancode of the specified key. * - * If the key is `GLFW_KEY_UNKNOWN` or does not exist on the keyboard this - * method will return `-1`. + * If the specified [key token](@ref keys) corresponds to a physical key not + * supported on the current platform then this method will return `-1`. + * Calling this function with anything other than a key token will return `-1` + * and generate a @ref GLFW_INVALID_ENUM error. * - * @param[in] key Any [named key](@ref keys). - * @return The platform-specific scancode for the key, or `-1` if an - * [error](@ref error_handling) occurred. + * @param[in] key Any [key token](@ref keys). + * @return The platform-specific scancode for the key, or `-1` if the key is + * not supported on the current platform or an [error](@ref error_handling) + * occurred. * - * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref - * GLFW_INVALID_ENUM and @ref GLFW_PLATFORM_ERROR. + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref + * GLFW_INVALID_ENUM. * * @thread_safety This function may be called from any thread. * @@ -4354,10 +4364,9 @@ GLFWAPI void glfwSetCursor(GLFWwindow* window, GLFWcursor* cursor); * [character callback](@ref glfwSetCharCallback) instead. * * When a window loses input focus, it will generate synthetic key release - * events for all pressed keys. You can tell these events from user-generated - * events by the fact that the synthetic ones are generated after the focus - * loss event has been processed, i.e. after the - * [window focus callback](@ref glfwSetWindowFocusCallback) has been called. + * events for all pressed keys with associated key tokens. You can tell these + * events from user-generated events by the fact that the synthetic ones are + * generated after the focus loss event has been processed, i.e. after the * * The scancode of a key is specific to that platform or sometimes even to that * machine. Scancodes are intended to allow users to bind keys that don't have @@ -4708,12 +4717,15 @@ GLFWAPI const char* glfwGetClipboardString(GLFWwindow* window); * thread. * * This function makes the OpenGL or OpenGL ES context of the specified window - * current on the calling thread. A context must only be made current on - * a single thread at a time and each thread can have only a single current - * context at a time. + * current on the calling thread. It can also detach the current context from + * the calling thread without making a new one current by passing in `NULL`. * - * When moving a context between threads, you must make it non-current on the - * old thread before making it current on the new one. + * A context must only be made current on a single thread at a time and each + * thread can have only a single current context at a time. Making a context + * current detaches any previously current context on the calling thread. + * + * When moving a context between threads, you must detach it (make it + * non-current) on the old thread before making it current on the new one. * * By default, making a context non-current implicitly forces a pipeline flush. * On machines that support `GL_KHR_context_flush_control`, you can control @@ -4728,6 +4740,10 @@ GLFWAPI const char* glfwGetClipboardString(GLFWwindow* window); * @param[in] window The window whose context to make current, or `NULL` to * detach the current context. * + * @remarks If the previously current context was created via a different + * context creation API than the one passed to this function, GLFW will still + * detach the previous one from its API before making the new one current. + * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * GLFW_NO_WINDOW_CONTEXT and @ref GLFW_PLATFORM_ERROR. * diff --git a/internal/glfw/glfw3native_unix.h b/internal/glfw/glfw3native_unix.h index 565eb7471..4dfd56871 100644 --- a/internal/glfw/glfw3native_unix.h +++ b/internal/glfw/glfw3native_unix.h @@ -99,7 +99,9 @@ extern "C" { #include #include #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 #include #endif diff --git a/internal/glfw/glx_context_linbsd.c b/internal/glfw/glx_context_linbsd.c index 4de3fe2b0..33afbae19 100644 --- a/internal/glfw/glx_context_linbsd.c +++ b/internal/glfw/glx_context_linbsd.c @@ -168,6 +168,7 @@ static void swapBuffersGLX(_GLFWwindow* window) static void swapIntervalGLX(int interval) { _GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot); + assert(window != NULL); if (_glfw.glx.EXT_swap_control) { @@ -204,7 +205,10 @@ static GLFWglproc getProcAddressGLX(const char* procname) else if (_glfw.glx.GetProcAddressARB) return _glfw.glx.GetProcAddressARB((const GLubyte*) procname); else + { + // NOTE: glvnd provides GLX 1.4, so this can only happen with libGL return _glfw_dlsym(_glfw.glx.handle, procname); + } } static void destroyContextGLX(_GLFWwindow* window) diff --git a/internal/glfw/glx_context_linbsd.h b/internal/glfw/glx_context_linbsd.h index 46f95bc2e..e11d718cf 100644 --- a/internal/glfw/glx_context_linbsd.h +++ b/internal/glfw/glx_context_linbsd.h @@ -107,7 +107,6 @@ typedef struct _GLFWlibraryGLX int eventBase; int errorBase; - // dlopen handle for libGL.so.1 void* handle; // GLX 1.3 functions diff --git a/internal/glfw/input_unix.c b/internal/glfw/input_unix.c index 9d51a4842..099a35772 100644 --- a/internal/glfw/input_unix.c +++ b/internal/glfw/input_unix.c @@ -280,6 +280,12 @@ GLFWAPI const char* glfwGetKeyName(int key, int scancode) if (key != GLFW_KEY_UNKNOWN) { + if (key < GLFW_KEY_SPACE || key > GLFW_KEY_LAST) + { + _glfwInputError(GLFW_INVALID_ENUM, "Invalid key %i", key); + return NULL; + } + if (key != GLFW_KEY_KP_EQUAL && (key < GLFW_KEY_KP_0 || key > GLFW_KEY_KP_ADD) && (key < GLFW_KEY_APOSTROPHE || key > GLFW_KEY_WORLD_2)) @@ -295,12 +301,12 @@ GLFWAPI const char* glfwGetKeyName(int key, int scancode) GLFWAPI int glfwGetKeyScancode(int key) { - _GLFW_REQUIRE_INIT_OR_RETURN(-1); + _GLFW_REQUIRE_INIT_OR_RETURN(0); if (key < GLFW_KEY_SPACE || key > GLFW_KEY_LAST) { _glfwInputError(GLFW_INVALID_ENUM, "Invalid key %i", key); - return GLFW_RELEASE; + return -1; } return _glfwPlatformGetKeyScancode(key); diff --git a/internal/glfw/input_windows.go b/internal/glfw/input_windows.go index 4632c3189..f8c7c124c 100644 --- a/internal/glfw/input_windows.go +++ b/internal/glfw/input_windows.go @@ -246,6 +246,9 @@ func GetKeyName(key Key, scancode int) (string, error) { } if key != KeyUnknown { + if key < KeySpace || key > KeyLast { + return "", fmt.Errorf("glfw: invalid key %d: %w", key, InvalidEnum) + } if key != KeyKPEqual && (key < KeyKP0 || key > KeyKPAdd) && (key < KeyApostrophe || key > KeyWorld2) { return "", nil } diff --git a/internal/glfw/nsgl_context_darwin.m b/internal/glfw/nsgl_context_darwin.m index 637986531..3d96557f5 100644 --- a/internal/glfw/nsgl_context_darwin.m +++ b/internal/glfw/nsgl_context_darwin.m @@ -57,11 +57,10 @@ static void swapIntervalNSGL(int interval) @autoreleasepool { _GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot); - if (window) - { - [window->context.nsgl.object setValues:&interval - forParameter:NSOpenGLContextParameterSwapInterval]; - } + assert(window != NULL); + + [window->context.nsgl.object setValues:&interval + forParameter:NSOpenGLContextParameterSwapInterval]; } // autoreleasepool } @@ -138,7 +137,7 @@ GLFWbool _glfwCreateContextNSGL(_GLFWwindow* window, if (ctxconfig->client == GLFW_OPENGL_ES_API) { _glfwInputError(GLFW_API_UNAVAILABLE, - "NSGL: OpenGL ES is not available on macOS"); + "NSGL: OpenGL ES is not available via NSGL"); return GLFW_FALSE; } diff --git a/internal/glfw/osmesa_context_unix.c b/internal/glfw/osmesa_context_unix.c index 950fbfa3c..787c3075b 100644 --- a/internal/glfw/osmesa_context_unix.c +++ b/internal/glfw/osmesa_context_unix.c @@ -5,13 +5,12 @@ //go:build darwin || freebsd || linux || netbsd || openbsd +#include "internal_unix.h" + #include #include #include -#include "internal_unix.h" - - static void makeContextCurrentOSMesa(_GLFWwindow* window) { if (window) diff --git a/internal/glfw/posix_thread_darwin.go b/internal/glfw/posix_thread_darwin.go index 51b4deb5e..d3228e8e0 100644 --- a/internal/glfw/posix_thread_darwin.go +++ b/internal/glfw/posix_thread_darwin.go @@ -19,8 +19,9 @@ func _glfwPlatformCreateTls(tls *C._GLFWtls) C.GLFWbool { panic("glfw: TLS must not be allocated") } if pthread_key_create(&tls.posix.key, 0) != 0 { - _glfwInputError(int32(PlatformError), - C.CString("POSIX: Failed to create context TLS")) + errstr := C.CString("POSIX: Failed to create context TLS") + defer C.free(unsafe.Pointer(errstr)) + _glfwInputError(int32(PlatformError), errstr) return False } tls.posix.allocated = True @@ -58,7 +59,9 @@ func _glfwPlatformCreateMutex(mutex *C._GLFWmutex) C.GLFWbool { panic("glfw: mutex must not be allocated") } if pthread_mutex_init(&mutex.posix.handle, nil) != 0 { - _glfwInputError(int32(PlatformError), C.CString("POSIX: Failed to create mutex")) + errstr := C.CString("POSIX: Failed to create mutex") + defer C.free(unsafe.Pointer(errstr)) + _glfwInputError(int32(PlatformError), errstr) return False } mutex.posix.allocated = True diff --git a/internal/glfw/wgl_context_windows.go b/internal/glfw/wgl_context_windows.go index f3aebfb70..305755f97 100644 --- a/internal/glfw/wgl_context_windows.go +++ b/internal/glfw/wgl_context_windows.go @@ -53,10 +53,23 @@ func (w *Window) choosePixelFormat(ctxconfig *ctxconfig, fbconfig_ *fbconfig) (i var nativeCount int32 var attribs []int32 + c, err := _DescribePixelFormat(w.context.platform.dc, 1, uint32(unsafe.Sizeof(_PIXELFORMATDESCRIPTOR{})), nil) + if err != nil { + return 0, err + } + nativeCount = c + if _glfw.platformContext.ARB_pixel_format { + // NOTE: In a Parallels VM WGL_ARB_pixel_format returns fewer pixel formats than + // DescribePixelFormat, violating the guarantees of the extension spec + // HACK: Iterate through the minimum of both counts var attrib int32 = _WGL_NUMBER_PIXEL_FORMATS_ARB - if err := wglGetPixelFormatAttribivARB(w.context.platform.dc, 1, 0, 1, &attrib, &nativeCount); err != nil { - return 0, err + var extensionCount int32 + if err := wglGetPixelFormatAttribivARB(w.context.platform.dc, 1, 0, 1, &attrib, &extensionCount); err != nil { + return 0, fmt.Errorf("glfw: WGL: failed to retrieve pixel format attribute: %w", err) + } + if nativeCount > extensionCount { + nativeCount = extensionCount } attribs = append(attribs, @@ -96,12 +109,6 @@ func (w *Window) choosePixelFormat(ctxconfig *ctxconfig, fbconfig_ *fbconfig) (i attribs = append(attribs, _WGL_COLORSPACE_EXT) } } - } else { - c, err := _DescribePixelFormat(w.context.platform.dc, 1, uint32(unsafe.Sizeof(_PIXELFORMATDESCRIPTOR{})), nil) - if err != nil { - return 0, err - } - nativeCount = c } usableConfigs := make([]*fbconfig, 0, nativeCount) diff --git a/internal/glfw/win32_init_windows.go b/internal/glfw/win32_init_windows.go index 4e7f5eaa7..b33027786 100644 --- a/internal/glfw/win32_init_windows.go +++ b/internal/glfw/win32_init_windows.go @@ -239,6 +239,55 @@ func createHelperWindow() error { return nil } +func createBlankCursor() error { + // HACK: Create a transparent cursor as using the NULL cursor breaks + // using SetCursorPos when connected over RDP + cursorWidth, err := _GetSystemMetrics(_SM_CXCURSOR) + if err != nil { + return err + } + cursorHeight, err := _GetSystemMetrics(_SM_CYCURSOR) + if err != nil { + return err + } + andMask := make([]byte, cursorWidth*cursorHeight/8) + for i := range andMask { + andMask[i] = 0xff + } + xorMask := make([]byte, cursorWidth*cursorHeight/8) + + // Cursor creation might fail, but that's fine as we get NULL in that case, + // which serves as an acceptable fallback blank cursor (other than on RDP) + c, _ := _CreateCursor(0, 0, 0, cursorWidth, cursorHeight, andMask, xorMask) + _glfw.platformWindow.blankCursor = c + + return nil +} + +func initRemoteSession() error { + if microsoftgdk.IsXbox() { + return nil + } + + // Check if the current progress was started with Remote Desktop. + r, err := _GetSystemMetrics(_SM_REMOTESESSION) + if err != nil { + return err + } + _glfw.platformWindow.isRemoteSession = r > 0 + + // With Remote desktop, we need to create a blank cursor because of the cursor is Set to nil + // if cannot be moved to center in capture mode. If not Remote Desktop platformWindow.blankCursor stays nil + // and will perform has before (normal). + if _glfw.platformWindow.isRemoteSession { + if err := createBlankCursor(); err != nil { + return err + } + } + + return nil +} + func platformInit() error { // Changing the foreground lock timeout was removed from the original code. // See https://github.com/glfw/glfw/commit/58b48a3a00d9c2a5ca10cc23069a71d8773cc7a4 @@ -293,6 +342,10 @@ func platformInit() error { return err } } else { + // Some hacks are needed to support Remote Desktop... + if err := initRemoteSession(); err != nil { + return err + } if err := pollMonitorsWin32(); err != nil { return err } @@ -301,6 +354,12 @@ func platformInit() error { } func platformTerminate() error { + if _glfw.platformWindow.blankCursor != 0 { + if err := _DestroyCursor(_glfw.platformWindow.blankCursor); err != nil { + return err + } + } + if _glfw.platformWindow.deviceNotificationHandle != 0 { if err := _UnregisterDeviceNotification(_glfw.platformWindow.deviceNotificationHandle); err != nil { return err diff --git a/internal/glfw/win32_platform_windows.go b/internal/glfw/win32_platform_windows.go index f4f5bb3f4..4199c5dcd 100644 --- a/internal/glfw/win32_platform_windows.go +++ b/internal/glfw/win32_platform_windows.go @@ -66,12 +66,18 @@ type platformLibraryWindowState struct { scancodes [KeyLast + 1]int keynames [KeyLast + 1]string - // Where to place the cursor when re-enabled + // restoreCursorPosX and restoreCursorPosY indicates where to place the cursor when re-enabled restoreCursorPosX float64 restoreCursorPosY float64 - // The window whose disabled cursor mode is active + // disabledCursorWindow is the window whose disabled cursor mode is active disabledCursorWindow *Window + // capturedCursorWindow is the window the cursor is captured in + capturedCursorWindow *Window rawInput []byte mouseTrailSize uint32 + // isRemoteSession indicates if the process was started behind Remote Destop + isRemoteSession bool + // blankCursor is an invisible cursor, needed for special cases (see WM_INPUT handler) + blankCursor _HCURSOR } diff --git a/internal/glfw/win32_window_windows.go b/internal/glfw/win32_window_windows.go index e1f8ef101..081aff4fe 100644 --- a/internal/glfw/win32_window_windows.go +++ b/internal/glfw/win32_window_windows.go @@ -127,48 +127,29 @@ func createIcon(image *Image, xhot, yhot int, icon bool) (_HICON, error) { return handle, nil } -func getFullWindowSize(style uint32, exStyle uint32, contentWidth, contentHeight int, dpi uint32) (fullWidth, fullHeight int, err error) { - if microsoftgdk.IsXbox() { - return contentWidth, contentHeight, nil - } +func (w *Window) applyAspectRatio(edge int, area *_RECT) error { + var frame _RECT + + ratio := float32(w.numer) / float32(w.denom) + style := w.getWindowStyle() + exStyle := w.getWindowExStyle() - rect := _RECT{ - left: 0, - top: 0, - right: int32(contentWidth), - bottom: int32(contentHeight), - } if winver.IsWindows10AnniversaryUpdateOrGreater() { - if err := _AdjustWindowRectExForDpi(&rect, style, false, exStyle, dpi); err != nil { - return 0, 0, err + if err := _AdjustWindowRectExForDpi(&frame, style, false, exStyle, _GetDpiForWindow(w.platform.handle)); err != nil { + return err } } else { - if err := _AdjustWindowRectEx(&rect, style, false, exStyle); err != nil { - return 0, 0, err + if err := _AdjustWindowRectEx(&frame, style, false, exStyle); err != nil { + return err } } - return int(rect.right - rect.left), int(rect.bottom - rect.top), nil -} - -func (w *Window) applyAspectRatio(edge int, area *_RECT) error { - ratio := float32(w.numer) / float32(w.denom) - - var dpi uint32 = _USER_DEFAULT_SCREEN_DPI - if winver.IsWindows10AnniversaryUpdateOrGreater() { - dpi = _GetDpiForWindow(w.platform.handle) - } - - xoff, yoff, err := getFullWindowSize(w.getWindowStyle(), w.getWindowExStyle(), 0, 0, dpi) - if err != nil { - return err - } if edge == _WMSZ_LEFT || edge == _WMSZ_BOTTOMLEFT || edge == _WMSZ_RIGHT || edge == _WMSZ_BOTTOMRIGHT { - area.bottom = area.top + int32(yoff) + int32(float32(area.right-area.left-int32(xoff))/ratio) + area.bottom = area.top + int32(frame.bottom-frame.top) + int32(float32(area.right-area.left-int32(frame.right-frame.left))/ratio) } else if edge == _WMSZ_TOPLEFT || edge == _WMSZ_TOPRIGHT { - area.top = area.bottom - int32(yoff) - int32(float32(area.right-area.left-int32(xoff))/ratio) + area.top = area.bottom - int32(frame.bottom-frame.top) - int32(float32(area.right-area.left-int32(frame.right-frame.left))/ratio) } else if edge == _WMSZ_TOP || edge == _WMSZ_BOTTOM { - area.right = area.left + int32(xoff) + int32(float32(area.bottom-area.top-int32(yoff))*ratio) + area.right = area.left + int32(frame.right-frame.left) + int32(float32(area.bottom-area.top-int32(frame.bottom-frame.top))*ratio) } return nil @@ -186,7 +167,10 @@ func (w *Window) updateCursorImage() error { _SetCursor(cursor) } } else { - _SetCursor(0) + // Connected via Remote Desktop, nil cursor will present SetCursorPos the move the cursor. + // using a blank cursor fix that. + // When not via Remote Desktop, platformWindow.blankCursor should be nil. + _SetCursor(_glfw.platformWindow.blankCursor) } return nil } @@ -214,26 +198,27 @@ func (w *Window) clientToScreen(rect _RECT) (_RECT, error) { return rect, nil } -func updateClipRect(window *Window) error { - if window != nil { - clipRect, err := _GetClientRect(window.platform.handle) - if err != nil { - return err - } - - clipRect, err = window.clientToScreen(clipRect) - if err != nil { - return err - } - - if err := _ClipCursor(&clipRect); err != nil { - return err - } - } else { - if err := _ClipCursor(nil); err != nil { - return err - } +func captureCursor(window *Window) error { + clipRect, err := _GetClientRect(window.platform.handle) + if err != nil { + return err } + clipRect, err = window.clientToScreen(clipRect) + if err != nil { + return err + } + if err := _ClipCursor(&clipRect); err != nil { + return err + } + _glfw.platformWindow.capturedCursorWindow = window + return nil +} + +func releaseCursor() error { + if err := _ClipCursor(nil); err != nil { + return err + } + _glfw.platformWindow.capturedCursorWindow = nil return nil } @@ -274,7 +259,7 @@ func (w *Window) disableCursor() error { if err := w.centerCursorInContentArea(); err != nil { return err } - if err := updateClipRect(w); err != nil { + if err := captureCursor(w); err != nil { return err } if w.rawMouseMotion { @@ -292,7 +277,7 @@ func (w *Window) enableCursor() error { } } _glfw.platformWindow.disabledCursorWindow = nil - if err := updateClipRect(nil); err != nil { + if err := releaseCursor(); err != nil { return err } if err := w.platformSetCursorPos(_glfw.platformWindow.restoreCursorPosX, _glfw.platformWindow.restoreCursorPosY); err != nil { @@ -925,8 +910,46 @@ func windowProc(hWnd windows.HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM) var dx, dy int data := (*_RAWINPUT)(unsafe.Pointer(&_glfw.platformWindow.rawInput[0])) if data.mouse.usFlags&_MOUSE_MOVE_ABSOLUTE != 0 { - dx = int(data.mouse.lLastX) - window.platform.lastCursorPosX - dy = int(data.mouse.lLastY) - window.platform.lastCursorPosY + if _glfw.platformWindow.isRemoteSession { + // Remote Desktop Mode + // As per https://github.com/Microsoft/DirectXTK/commit/ef56b63f3739381e451f7a5a5bd2c9779d2a7555 + // MOUSE_MOVE_ABSOLUTE is a range from 0 through 65535, based on the screen size. + // Apparently, absolute mode only occurs over RDP though. + var smx int32 = _SM_CXSCREEN + var smy int32 = _SM_CYSCREEN + if data.mouse.usFlags&_MOUSE_VIRTUAL_DESKTOP != 0 { + smx = _SM_CXVIRTUALSCREEN + smy = _SM_CYVIRTUALSCREEN + } + + width, err := _GetSystemMetrics(smx) + if err != nil { + _glfw.errors = append(_glfw.errors, err) + return 0 + } + height, err := _GetSystemMetrics(smy) + if err != nil { + _glfw.errors = append(_glfw.errors, err) + return 0 + } + + pos := _POINT{ + x: int32(float64(data.mouse.lLastX) / 65535.0 * float64(width)), + y: int32(float64(data.mouse.lLastY) / 65535.0 * float64(height)), + } + if err := _ScreenToClient(window.platform.handle, &pos); err != nil { + _glfw.errors = append(_glfw.errors, err) + return 0 + } + + dx = int(pos.x) - window.platform.lastCursorPosX + dy = int(pos.y) - window.platform.lastCursorPosY + } else { + // Normal mode + // We should have the right absolute coords in data.mouse + dx = int(data.mouse.lLastX) - window.platform.lastCursorPosX + dy = int(data.mouse.lLastY) - window.platform.lastCursorPosY + } } else { dx = int(data.mouse.lLastX) dy = int(data.mouse.lLastY) @@ -986,8 +1009,8 @@ func windowProc(hWnd windows.HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM) iconified := wParam == _SIZE_MINIMIZED maximized := wParam == _SIZE_MAXIMIZED || (window.platform.maximized && wParam != _SIZE_RESTORED) - if _glfw.platformWindow.disabledCursorWindow == window { - if err := updateClipRect(window); err != nil { + if _glfw.platformWindow.capturedCursorWindow == window { + if err := captureCursor(window); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } @@ -1032,8 +1055,8 @@ func windowProc(hWnd windows.HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM) return 0 case _WM_MOVE: - if _glfw.platformWindow.disabledCursorWindow == window { - if err := updateClipRect(window); err != nil { + if _glfw.platformWindow.capturedCursorWindow == window { + if err := captureCursor(window); err != nil { _glfw.errors = append(_glfw.errors, err) return 0 } @@ -1056,31 +1079,35 @@ func windowProc(hWnd windows.HWND, uMsg uint32, wParam _WPARAM, lParam _LPARAM) return 1 case _WM_GETMINMAXINFO: - var dpi uint32 = _USER_DEFAULT_SCREEN_DPI + var frame _RECT mmi := (*_MINMAXINFO)(unsafe.Pointer(lParam)) + style := window.getWindowStyle() + exStyle := window.getWindowExStyle() if window.monitor != nil { break } if winver.IsWindows10AnniversaryUpdateOrGreater() { - dpi = _GetDpiForWindow(window.platform.handle) - } - - xoff, yoff, err := getFullWindowSize(window.getWindowStyle(), window.getWindowExStyle(), 0, 0, dpi) - if err != nil { - _glfw.errors = append(_glfw.errors, err) - return 0 + if err := _AdjustWindowRectExForDpi(&frame, style, false, exStyle, _GetDpiForWindow(window.platform.handle)); err != nil { + _glfw.errors = append(_glfw.errors, err) + return 0 + } + } else { + if err := _AdjustWindowRectEx(&frame, style, false, exStyle); err != nil { + _glfw.errors = append(_glfw.errors, err) + return 0 + } } if window.minwidth != DontCare && window.minheight != DontCare { - mmi.ptMinTrackSize.x = int32(window.minwidth + xoff) - mmi.ptMinTrackSize.y = int32(window.minheight + yoff) + mmi.ptMinTrackSize.x = int32(window.minwidth) + (frame.right - frame.left) + mmi.ptMinTrackSize.y = int32(window.minheight) + (frame.bottom - frame.top) } if window.maxwidth != DontCare && window.maxheight != DontCare { - mmi.ptMaxTrackSize.x = int32(window.maxwidth + xoff) - mmi.ptMaxTrackSize.y = int32(window.maxheight + yoff) + mmi.ptMaxTrackSize.x = int32(window.maxwidth) + (frame.right - frame.left) + mmi.ptMaxTrackSize.y = int32(window.maxheight) + (frame.bottom - frame.top) } if !window.decorated { @@ -1205,7 +1232,7 @@ func (w *Window) createNativeWindow(wndconfig *wndconfig, fbconfig *fbconfig) er style := w.getWindowStyle() exStyle := w.getWindowExStyle() - var xpos, ypos, fullWidth, fullHeight int32 + var frameX, frameY, frameWidth, frameHeight int32 if w.monitor != nil { mi, ok := _GetMonitorInfoW(w.monitor.platform.handle) if !ok { @@ -1214,27 +1241,29 @@ func (w *Window) createNativeWindow(wndconfig *wndconfig, fbconfig *fbconfig) er // NOTE: This window placement is temporary and approximate, as the // correct position and size cannot be known until the monitor // video mode has been picked in _glfwSetVideoModeWin32 - xpos = mi.rcMonitor.left - ypos = mi.rcMonitor.top - fullWidth = mi.rcMonitor.right - mi.rcMonitor.left - fullHeight = mi.rcMonitor.bottom - mi.rcMonitor.top + frameX = mi.rcMonitor.left + frameY = mi.rcMonitor.top + frameWidth = mi.rcMonitor.right - mi.rcMonitor.left + frameHeight = mi.rcMonitor.bottom - mi.rcMonitor.top } else { - xpos = _CW_USEDEFAULT - ypos = _CW_USEDEFAULT + rect := _RECT{0, 0, int32(wndconfig.width), int32(wndconfig.height)} w.platform.maximized = wndconfig.maximized if wndconfig.maximized { style |= _WS_MAXIMIZE } - w, h, err := getFullWindowSize(style, exStyle, wndconfig.width, wndconfig.height, _USER_DEFAULT_SCREEN_DPI) - if err != nil { + if err := _AdjustWindowRectEx(&rect, style, false, exStyle); err != nil { return err } - fullWidth, fullHeight = int32(w), int32(h) + + frameX = _CW_USEDEFAULT + frameY = _CW_USEDEFAULT + frameWidth = rect.right - rect.left + frameHeight = rect.bottom - rect.top } - h, err := _CreateWindowExW(exStyle, _GLFW_WNDCLASSNAME, wndconfig.title, style, xpos, ypos, fullWidth, fullHeight, + h, err := _CreateWindowExW(exStyle, _GLFW_WNDCLASSNAME, wndconfig.title, style, frameX, frameY, frameWidth, frameHeight, 0, // No parent window 0, // No window menu _glfw.platformWindow.instance, unsafe.Pointer(wndconfig)) @@ -1459,7 +1488,15 @@ func (w *Window) platformDestroyWindow() error { } if _glfw.platformWindow.disabledCursorWindow == w { - _glfw.platformWindow.disabledCursorWindow = nil + if err := w.enableCursor(); err != nil { + return err + } + } + + if _glfw.platformWindow.capturedCursorWindow == w { + if err := releaseCursor(); err != nil { + return err + } } if w.platform.handle != 0 { @@ -2163,6 +2200,7 @@ func platformPollEvents() error { // NOTE: Re-center the cursor only if it has moved since the last call, // to avoid breaking glfwWaitEvents with WM_MOUSEMOVE + // The re-center is required in order to prevent the mouse cursor stopping at the edges of the screen. if window.platform.lastCursorPosX != width/2 || window.platform.lastCursorPosY != height/2 { if err := window.platformSetCursorPos(float64(width/2), float64(height/2)); err != nil { return err @@ -2184,7 +2222,7 @@ func platformWaitEvents() error { } func platformWaitEventsTimeout(timeout float64) error { - if _, err := _MsgWaitForMultipleObjects(0, nil, false, uint32(timeout*1e3), _QS_ALLEVENTS); err != nil { + if _, err := _MsgWaitForMultipleObjects(0, nil, false, uint32(timeout*1e3), _QS_ALLINPUT); err != nil { return err } if err := platformPollEvents(); err != nil { @@ -2235,20 +2273,48 @@ func (w *Window) platformSetCursorPos(xpos, ypos float64) error { } func (w *Window) platformSetCursorMode(mode int) error { - if mode == CursorDisabled { - if w.platformWindowFocused() { - if err := w.disableCursor(); err != nil { + if w.platformWindowFocused() { + if mode == CursorDisabled { + xpos, ypos, err := w.platformGetCursorPos() + if err != nil { + return err + } + _glfw.platformWindow.restoreCursorPosX = xpos + _glfw.platformWindow.restoreCursorPosY = ypos + if err := w.centerCursorInContentArea(); err != nil { + return err + } + if w.rawMouseMotion { + if err := w.enableRawMouseMotion(); err != nil { + return err + } + } + } else if _glfw.platformWindow.disabledCursorWindow == w { + if w.rawMouseMotion { + if err := w.disableRawMouseMotion(); err != nil { + return err + } + } + } + + if mode == CursorDisabled { + if err := captureCursor(w); err != nil { + return err + } + } else { + if err := releaseCursor(); err != nil { return err } } - return nil - } - if _glfw.platformWindow.disabledCursorWindow == w { - if err := w.enableCursor(); err != nil { - return err + if mode == CursorDisabled { + _glfw.platformWindow.disabledCursorWindow = w + } else if _glfw.platformWindow.disabledCursorWindow == w { + _glfw.platformWindow.disabledCursorWindow = nil + if err := w.platformSetCursorPos(_glfw.platformWindow.restoreCursorPosX, _glfw.platformWindow.restoreCursorPosY); err != nil { + return err + } } - return nil } in, err := w.cursorInContentArea() @@ -2265,10 +2331,14 @@ func (w *Window) platformSetCursorMode(mode int) error { } func platformGetScancodeName(scancode int) (string, error) { - if scancode < 0 || scancode > (_KF_EXTENDED|0xff) || _glfw.platformWindow.keycodes[scancode] == KeyUnknown { + if scancode < 0 || scancode > (_KF_EXTENDED|0xff) { return "", fmt.Errorf("glwfwin: invalid scancode %d: %w", scancode, InvalidValue) } - return _glfw.platformWindow.keynames[_glfw.platformWindow.keycodes[scancode]], nil + key := _glfw.platformWindow.keycodes[scancode] + if key == KeyUnknown { + return "", nil + } + return _glfw.platformWindow.keynames[key], nil } func platformGetKeyScancode(key Key) int { diff --git a/internal/glfw/window_unix.c b/internal/glfw/window_unix.c index 494b557f1..1e1d4556d 100644 --- a/internal/glfw/window_unix.c +++ b/internal/glfw/window_unix.c @@ -676,7 +676,7 @@ GLFWAPI float glfwGetWindowOpacity(GLFWwindow* handle) _GLFWwindow* window = (_GLFWwindow*) handle; assert(window != NULL); - _GLFW_REQUIRE_INIT_OR_RETURN(1.f); + _GLFW_REQUIRE_INIT_OR_RETURN(0.f); return _glfwPlatformGetWindowOpacity(window); } diff --git a/internal/glfw/x11_window_linbsd.c b/internal/glfw/x11_window_linbsd.c index b221823ac..db3f04bd8 100644 --- a/internal/glfw/x11_window_linbsd.c +++ b/internal/glfw/x11_window_linbsd.c @@ -357,6 +357,11 @@ static void updateNormalHints(_GLFWwindow* window, int width, int height) { XSizeHints* hints = XAllocSizeHints(); + long supplied; + XGetWMNormalHints(_glfw.x11.display, window->x11.handle, hints, &supplied); + + hints->flags &= ~(PMinSize | PMaxSize | PAspect); + if (!window->monitor) { if (window->resizable) @@ -393,9 +398,6 @@ static void updateNormalHints(_GLFWwindow* window, int width, int height) } } - hints->flags |= PWinGravity; - hints->win_gravity = StaticGravity; - XSetWMNormalHints(_glfw.x11.display, window->x11.handle, hints); XFree(hints); @@ -561,6 +563,25 @@ static void updateCursorImage(_GLFWwindow* window) } } +// Grabs the cursor and confines it to the window +// +static void captureCursor(_GLFWwindow* window) +{ + XGrabPointer(_glfw.x11.display, window->x11.handle, True, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask, + GrabModeAsync, GrabModeAsync, + window->x11.handle, + None, + CurrentTime); +} + +// Ungrabs the cursor +// +static void releaseCursor(void) +{ + XUngrabPointer(_glfw.x11.display, CurrentTime); +} + // Enable XI2 raw mouse motion events // static void enableRawMouseMotion(_GLFWwindow* window) @@ -603,12 +624,7 @@ static void disableCursor(_GLFWwindow* window) &_glfw.x11.restoreCursorPosY); updateCursorImage(window); _glfwCenterCursorInContentArea(window); - XGrabPointer(_glfw.x11.display, window->x11.handle, True, - ButtonPressMask | ButtonReleaseMask | PointerMotionMask, - GrabModeAsync, GrabModeAsync, - window->x11.handle, - _glfw.x11.hiddenCursorHandle, - CurrentTime); + captureCursor(window); } // Exit disabled cursor mode for the specified window @@ -619,7 +635,7 @@ static void enableCursor(_GLFWwindow* window) disableRawMouseMotion(window); _glfw.x11.disabledCursorWindow = NULL; - XUngrabPointer(_glfw.x11.display, CurrentTime); + releaseCursor(); _glfwPlatformSetCursorPos(window, _glfw.x11.restoreCursorPosX, _glfw.x11.restoreCursorPosY); @@ -764,7 +780,28 @@ static GLFWbool createNativeWindow(_GLFWwindow* window, XFree(hints); } - updateNormalHints(window, width, height); + // Set ICCCM WM_NORMAL_HINTS property + { + XSizeHints* hints = XAllocSizeHints(); + if (!hints) + { + _glfwInputError(GLFW_OUT_OF_MEMORY, "X11: Failed to allocate size hints"); + return GLFW_FALSE; + } + + if (!wndconfig->resizable) + { + hints->flags |= (PMinSize | PMaxSize); + hints->min_width = hints->max_width = width; + hints->min_height = hints->max_height = height; + } + + hints->flags |= PWinGravity; + hints->win_gravity = StaticGravity; + + XSetWMNormalHints(_glfw.x11.display, window->x11.handle, hints); + XFree(hints); + } // Set ICCCM WM_CLASS property { @@ -1563,6 +1600,9 @@ static void processEvent(XEvent *event) if (event->xconfigure.width != window->x11.width || event->xconfigure.height != window->x11.height) { + window->x11.width = event->xconfigure.width; + window->x11.height = event->xconfigure.height; + _glfwInputFramebufferSize(window, event->xconfigure.width, event->xconfigure.height); @@ -1570,9 +1610,6 @@ static void processEvent(XEvent *event) _glfwInputWindowSize(window, event->xconfigure.width, event->xconfigure.height); - - window->x11.width = event->xconfigure.width; - window->x11.height = event->xconfigure.height; } int xpos = event->xconfigure.x; @@ -1600,9 +1637,10 @@ static void processEvent(XEvent *event) if (xpos != window->x11.xpos || ypos != window->x11.ypos) { - _glfwInputWindowPos(window, xpos, ypos); window->x11.xpos = xpos; window->x11.ypos = ypos; + + _glfwInputWindowPos(window, xpos, ypos); } return; @@ -2085,7 +2123,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window, void _glfwPlatformDestroyWindow(_GLFWwindow* window) { if (_glfw.x11.disabledCursorWindow == window) - _glfw.x11.disabledCursorWindow = NULL; + enableCursor(window); if (window->monitor) releaseMonitor(window); @@ -2891,16 +2929,40 @@ void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y) void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) { - if (mode == GLFW_CURSOR_DISABLED) + if (_glfwPlatformWindowFocused(window)) { - if (_glfwPlatformWindowFocused(window)) - disableCursor(window); - } - else if (_glfw.x11.disabledCursorWindow == window) - enableCursor(window); - else - updateCursorImage(window); + if (mode == GLFW_CURSOR_DISABLED) + { + _glfwPlatformGetCursorPos(window, + &_glfw.x11.restoreCursorPosX, + &_glfw.x11.restoreCursorPosY); + _glfwCenterCursorInContentArea(window); + if (window->rawMouseMotion) + enableRawMouseMotion(window); + } + else if (_glfw.x11.disabledCursorWindow == window) + { + if (window->rawMouseMotion) + disableRawMouseMotion(window); + } + if (mode == GLFW_CURSOR_DISABLED) + captureCursor(window); + else + releaseCursor(); + + if (mode == GLFW_CURSOR_DISABLED) + _glfw.x11.disabledCursorWindow = window; + else if (_glfw.x11.disabledCursorWindow == window) + { + _glfw.x11.disabledCursorWindow = NULL; + _glfwPlatformSetCursorPos(window, + _glfw.x11.restoreCursorPosX, + _glfw.x11.restoreCursorPosY); + } + } + + updateCursorImage(window); XFlush(_glfw.x11.display); } @@ -2909,14 +2971,15 @@ const char* _glfwPlatformGetScancodeName(int scancode) if (!_glfw.x11.xkb.available) return NULL; - if (scancode < 0 || scancode > 0xff || - _glfw.x11.keycodes[scancode] == GLFW_KEY_UNKNOWN) + if (scancode < 0 || scancode > 0xff) { _glfwInputError(GLFW_INVALID_VALUE, "Invalid scancode %i", scancode); return NULL; } const int key = _glfw.x11.keycodes[scancode]; + if (key == GLFW_KEY_UNKNOWN) + return NULL; const KeySym keysym = XkbKeycodeToKeysym(_glfw.x11.display, scancode, _glfw.x11.xkb.group, 0); if (keysym == NoSymbol) diff --git a/internal/graphics/shader.go b/internal/graphics/shader.go index 85d8d1d1c..8553cb313 100644 --- a/internal/graphics/shader.go +++ b/internal/graphics/shader.go @@ -161,8 +161,8 @@ func __vertex(dstPos vec2, srcPos vec2, color vec4) (vec4, vec2, vec4) { return shaderSuffix, nil } -func CompileShader(src []byte) (*shaderir.Program, error) { - unit, err := shader.ParseCompilerDirectives(src) +func completeShaderSource(fragmentSrc []byte) ([]byte, error) { + unit, err := shader.ParseCompilerDirectives(fragmentSrc) if err != nil { return nil, err } @@ -172,14 +172,23 @@ func CompileShader(src []byte) (*shaderir.Program, error) { } var buf bytes.Buffer - buf.Write(src) + buf.Write(fragmentSrc) buf.WriteString(suffix) + return buf.Bytes(), nil +} + +func CompileShader(fragmentSrc []byte) (*shaderir.Program, error) { + src, err := completeShaderSource(fragmentSrc) + if err != nil { + return nil, err + } + const ( vert = "__vertex" frag = "Fragment" ) - ir, err := shader.Compile(buf.Bytes(), vert, frag, ShaderSrcImageCount) + ir, err := shader.Compile(src, vert, frag, ShaderSrcImageCount) if err != nil { return nil, err } @@ -193,3 +202,11 @@ func CompileShader(src []byte) (*shaderir.Program, error) { return ir, nil } + +func CalcSourceHash(fragmentSrc []byte) (shaderir.SourceHash, error) { + src, err := completeShaderSource(fragmentSrc) + if err != nil { + return shaderir.SourceHash{}, err + } + return shaderir.CalcSourceHash(src), nil +} diff --git a/internal/graphicscommand/command.go b/internal/graphicscommand/command.go index 3139a55bc..f14faeb41 100644 --- a/internal/graphicscommand/command.go +++ b/internal/graphicscommand/command.go @@ -182,7 +182,7 @@ func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dsts [graphics.S if c.fillRule != fillRule { return false } - if c.fillRule != graphicsdriver.FillAll && mightOverlapDstRegions(c.vertices, vertices) { + if c.fillRule != graphicsdriver.FillRuleFillAll && mightOverlapDstRegions(c.vertices, vertices) { return false } return true diff --git a/internal/graphicscommand/commandqueue.go b/internal/graphicscommand/commandqueue.go index 3d74c9a09..a82b86f2d 100644 --- a/internal/graphicscommand/commandqueue.go +++ b/internal/graphicscommand/commandqueue.go @@ -43,14 +43,14 @@ const ( maxVertexFloatCount = MaxVertexCount * graphics.VertexFloatCount ) -var vsyncEnabled int32 = 1 +var vsyncEnabled atomic.Bool + +func init() { + vsyncEnabled.Store(true) +} func SetVsyncEnabled(enabled bool, graphicsDriver graphicsdriver.Graphics) { - if enabled { - atomic.StoreInt32(&vsyncEnabled, 1) - } else { - atomic.StoreInt32(&vsyncEnabled, 0) - } + vsyncEnabled.Store(enabled) runOnRenderThread(func() { graphicsDriver.SetVsyncEnabled(enabled) @@ -185,7 +185,7 @@ func (q *commandQueue) Flush(graphicsDriver graphicsdriver.Graphics, endFrame bo var sync bool // Disable asynchronous rendering when vsync is on, as this causes a rendering delay (#2822). - if endFrame && atomic.LoadInt32(&vsyncEnabled) != 0 { + if endFrame && vsyncEnabled.Load() { sync = true } if !sync { diff --git a/internal/graphicscommand/image_test.go b/internal/graphicscommand/image_test.go index 0ed3d5e31..851a416b1 100644 --- a/internal/graphicscommand/image_test.go +++ b/internal/graphicscommand/image_test.go @@ -31,7 +31,7 @@ import ( var nearestFilterShader *graphicscommand.Shader func init() { - ir, err := graphics.CompileShader([]byte(builtinshader.Shader(builtinshader.FilterNearest, builtinshader.AddressUnsafe, false))) + ir, err := graphics.CompileShader([]byte(builtinshader.ShaderSource(builtinshader.FilterNearest, builtinshader.AddressUnsafe, false))) if err != nil { panic(fmt.Sprintf("graphicscommand: compiling the nearest shader failed: %v", err)) } @@ -59,7 +59,7 @@ func TestClear(t *testing.T) { vs := quadVertices(w/2, h/2) is := graphics.QuadIndices() dr := image.Rect(0, 0, w, h) - dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{src}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, nearestFilterShader, nil, graphicsdriver.FillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{src}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, nearestFilterShader, nil, graphicsdriver.FillRuleFillAll) pix := make([]byte, 4*w*h) if err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), []graphicsdriver.PixelsArgs{ @@ -90,8 +90,8 @@ func TestWritePixelsPartAfterDrawTriangles(t *testing.T) { vs := quadVertices(w/2, h/2) is := graphics.QuadIndices() dr := image.Rect(0, 0, w, h) - dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{clr}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, nearestFilterShader, nil, graphicsdriver.FillAll) - dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, nearestFilterShader, nil, graphicsdriver.FillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{clr}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, nearestFilterShader, nil, graphicsdriver.FillRuleFillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, nearestFilterShader, nil, graphicsdriver.FillRuleFillAll) bs := graphics.NewManagedBytes(4, func(bs []byte) { for i := range bs { bs[i] = 0 @@ -109,11 +109,11 @@ func TestShader(t *testing.T) { vs := quadVertices(w, h) is := graphics.QuadIndices() dr := image.Rect(0, 0, w, h) - dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{clr}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, nearestFilterShader, nil, graphicsdriver.FillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{clr}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, nearestFilterShader, nil, graphicsdriver.FillRuleFillAll) g := ui.Get().GraphicsDriverForTesting() s := graphicscommand.NewShader(etesting.ShaderProgramFill(0xff, 0, 0, 0xff)) - dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillAll) + dst.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillRuleFillAll) pix := make([]byte, 4*w*h) if err := dst.ReadPixels(g, []graphicsdriver.PixelsArgs{ diff --git a/internal/graphicsdriver/directx/d3d_windows.go b/internal/graphicsdriver/directx/d3d_windows.go index be90829a1..845bbda1c 100644 --- a/internal/graphicsdriver/directx/d3d_windows.go +++ b/internal/graphicsdriver/directx/d3d_windows.go @@ -71,7 +71,8 @@ const ( ) var ( - procD3DCompile *windows.LazyProc + procD3DCompile *windows.LazyProc + procD3DCreateBlob *windows.LazyProc ) func init() { @@ -93,6 +94,7 @@ func init() { } procD3DCompile = d3dcompiler.NewProc("D3DCompile") + procD3DCreateBlob = d3dcompiler.NewProc("D3DCreateBlob") } func isD3DCompilerDLLAvailable() bool { @@ -135,6 +137,19 @@ func _D3DCompile(srcData []byte, sourceName string, pDefines []_D3D_SHADER_MACRO return code, nil } +func _D3DCreateBlob(size uint) (*_ID3DBlob, error) { + if !isD3DCompilerDLLAvailable() { + return nil, fmt.Errorf("directx: d3dcompiler_*.dll is missing in this environment") + } + + var blob *_ID3DBlob + r, _, _ := procD3DCreateBlob.Call(uintptr(size), uintptr(unsafe.Pointer(&blob))) + if uint32(r) != uint32(windows.S_OK) { + return nil, fmt.Errorf("directx: D3DCreateBlob failed: %w", handleError(windows.Handle(uint32(r)))) + } + return blob, nil +} + type _D3D_SHADER_MACRO struct { Name *byte Definition *byte diff --git a/internal/graphicsdriver/directx/graphics11_windows.go b/internal/graphicsdriver/directx/graphics11_windows.go index 8f37a8831..ea5591fc3 100644 --- a/internal/graphicsdriver/directx/graphics11_windows.go +++ b/internal/graphicsdriver/directx/graphics11_windows.go @@ -482,8 +482,7 @@ func (g *graphics11) MaxImageSize() int { } func (g *graphics11) NewShader(program *shaderir.Program) (graphicsdriver.Shader, error) { - vs, ps, offsets := hlsl.Compile(program) - vsh, psh, err := compileShader(vs, ps) + vsh, psh, err := compileShader(program) if err != nil { return nil, err } @@ -492,7 +491,7 @@ func (g *graphics11) NewShader(program *shaderir.Program) (graphicsdriver.Shader graphics: g, id: g.genNextShaderID(), uniformTypes: program.Uniforms, - uniformOffsets: offsets, + uniformOffsets: hlsl.CalcUniformMemoryOffsets(program), vertexShaderBlob: vsh, pixelShaderBlob: psh, } @@ -629,7 +628,7 @@ func (g *graphics11) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics } g.deviceContext.RSSetViewports([]_D3D11_VIEWPORT{vp}) - if err := g.setAsRenderTargets(dsts[:targetCount], fillRule != graphicsdriver.FillAll); err != nil { + if err := g.setAsRenderTargets(dsts[:targetCount], fillRule != graphicsdriver.FillRuleFillAll); err != nil { return err } @@ -639,7 +638,7 @@ func (g *graphics11) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics return err } - if fillRule == graphicsdriver.FillAll { + if fillRule == graphicsdriver.FillRuleFillAll { bs, err := g.blendState(blend, noStencil) if err != nil { return err @@ -664,9 +663,9 @@ func (g *graphics11) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics }) switch fillRule { - case graphicsdriver.FillAll: + case graphicsdriver.FillRuleFillAll: g.deviceContext.DrawIndexed(uint32(dstRegion.IndexCount), uint32(indexOffset), 0) - case graphicsdriver.NonZero: + case graphicsdriver.FillRuleNonZero: bs, err := g.blendState(blend, incrementStencil) if err != nil { return err @@ -678,7 +677,7 @@ func (g *graphics11) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics } g.deviceContext.OMSetDepthStencilState(dss, 0) g.deviceContext.DrawIndexed(uint32(dstRegion.IndexCount), uint32(indexOffset), 0) - case graphicsdriver.EvenOdd: + case graphicsdriver.FillRuleEvenOdd: bs, err := g.blendState(blend, invertStencil) if err != nil { return err @@ -692,7 +691,7 @@ func (g *graphics11) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics g.deviceContext.DrawIndexed(uint32(dstRegion.IndexCount), uint32(indexOffset), 0) } - if fillRule != graphicsdriver.FillAll { + if fillRule != graphicsdriver.FillRuleFillAll { bs, err := g.blendState(blend, drawWithStencil) if err != nil { return err diff --git a/internal/graphicsdriver/directx/graphics12_windows.go b/internal/graphicsdriver/directx/graphics12_windows.go index 953689260..5c7bfa7d5 100644 --- a/internal/graphicsdriver/directx/graphics12_windows.go +++ b/internal/graphicsdriver/directx/graphics12_windows.go @@ -1093,8 +1093,7 @@ func (g *graphics12) MaxImageSize() int { } func (g *graphics12) NewShader(program *shaderir.Program) (graphicsdriver.Shader, error) { - vs, ps, offsets := hlsl.Compile(program) - vsh, psh, err := compileShader(vs, ps) + vsh, psh, err := compileShader(program) if err != nil { return nil, err } @@ -1103,7 +1102,7 @@ func (g *graphics12) NewShader(program *shaderir.Program) (graphicsdriver.Shader graphics: g, id: g.genNextShaderID(), uniformTypes: program.Uniforms, - uniformOffsets: offsets, + uniformOffsets: hlsl.CalcUniformMemoryOffsets(program), vertexShader: vsh, pixelShader: psh, } @@ -1197,7 +1196,7 @@ func (g *graphics12) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics // Release constant buffers when too many ones will be created. numPipelines := 1 - if fillRule != graphicsdriver.FillAll { + if fillRule != graphicsdriver.FillRuleFillAll { numPipelines = 2 } if len(g.pipelineStates.constantBuffers[g.frameIndex])+numPipelines > numDescriptorsPerFrame { @@ -1264,7 +1263,7 @@ func (g *graphics12) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics targetCount = graphics.ShaderDstImageCount } - if err := g.setAsRenderTargets(dsts[:targetCount], fillRule != graphicsdriver.FillAll); err != nil { + if err := g.setAsRenderTargets(dsts[:targetCount], fillRule != graphicsdriver.FillRuleFillAll); err != nil { return err } diff --git a/internal/graphicsdriver/directx/pipeline12_windows.go b/internal/graphicsdriver/directx/pipeline12_windows.go index 8dbe78e9a..730194bd9 100644 --- a/internal/graphicsdriver/directx/pipeline12_windows.go +++ b/internal/graphicsdriver/directx/pipeline12_windows.go @@ -289,7 +289,7 @@ func (p *pipelineStates) drawTriangles(device *_ID3D12Device, commandList *_ID3D } commandList.SetGraphicsRootDescriptorTable(2, sh) - if fillRule == graphicsdriver.FillAll { + if fillRule == graphicsdriver.FillRuleFillAll { s, err := shader.pipelineState(blend, noStencil, screen) if err != nil { return err @@ -307,16 +307,16 @@ func (p *pipelineStates) drawTriangles(device *_ID3D12Device, commandList *_ID3D }, }) switch fillRule { - case graphicsdriver.FillAll: + case graphicsdriver.FillRuleFillAll: commandList.DrawIndexedInstanced(uint32(dstRegion.IndexCount), 1, uint32(indexOffset), 0, 0) - case graphicsdriver.NonZero: + case graphicsdriver.FillRuleNonZero: s, err := shader.pipelineState(blend, incrementStencil, screen) if err != nil { return err } commandList.SetPipelineState(s) commandList.DrawIndexedInstanced(uint32(dstRegion.IndexCount), 1, uint32(indexOffset), 0, 0) - case graphicsdriver.EvenOdd: + case graphicsdriver.FillRuleEvenOdd: s, err := shader.pipelineState(blend, invertStencil, screen) if err != nil { return err @@ -325,7 +325,7 @@ func (p *pipelineStates) drawTriangles(device *_ID3D12Device, commandList *_ID3D commandList.DrawIndexedInstanced(uint32(dstRegion.IndexCount), 1, uint32(indexOffset), 0, 0) } - if fillRule != graphicsdriver.FillAll { + if fillRule != graphicsdriver.FillRuleFillAll { s, err := shader.pipelineState(blend, drawWithStencil, screen) if err != nil { return err diff --git a/internal/graphicsdriver/directx/shader_windows.go b/internal/graphicsdriver/directx/shader_windows.go index 3c3b7509a..311567659 100644 --- a/internal/graphicsdriver/directx/shader_windows.go +++ b/internal/graphicsdriver/directx/shader_windows.go @@ -16,18 +16,67 @@ package directx import ( "fmt" + "sync" + "unsafe" "golang.org/x/sync/errgroup" "github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/shaderir" + "github.com/hajimehoshi/ebiten/v2/internal/shaderir/hlsl" ) +const ( + VertexShaderProfile = "vs_4_0" + PixelShaderProfile = "ps_4_0" + + VertexShaderEntryPoint = "VSMain" + PixelShaderEntryPoint = "PSMain" +) + +type fxcPair struct { + vertex []byte + pixel []byte +} + +type precompiledFXCs struct { + binaries map[shaderir.SourceHash]fxcPair + m sync.Mutex +} + +func (c *precompiledFXCs) put(hash shaderir.SourceHash, vertex, pixel []byte) { + c.m.Lock() + defer c.m.Unlock() + + if c.binaries == nil { + c.binaries = map[shaderir.SourceHash]fxcPair{} + } + if _, ok := c.binaries[hash]; ok { + panic(fmt.Sprintf("directx: the precompiled library for the hash %s is already registered", hash.String())) + } + c.binaries[hash] = fxcPair{ + vertex: vertex, + pixel: pixel, + } +} + +func (c *precompiledFXCs) get(hash shaderir.SourceHash) ([]byte, []byte) { + c.m.Lock() + defer c.m.Unlock() + + f := c.binaries[hash] + return f.vertex, f.pixel +} + +var thePrecompiledFXCs precompiledFXCs + +func RegisterPrecompiledFXCs(source []byte, vertex, pixel []byte) { + thePrecompiledFXCs.put(shaderir.CalcSourceHash(source), vertex, pixel) +} + var vertexShaderCache = map[string]*_ID3DBlob{} -func compileShader(vs, ps string) (vsh, psh *_ID3DBlob, ferr error) { - var flag uint32 = uint32(_D3DCOMPILE_OPTIMIZATION_LEVEL3) - +func compileShader(program *shaderir.Program) (vsh, psh *_ID3DBlob, ferr error) { defer func() { if ferr == nil { return @@ -40,6 +89,22 @@ func compileShader(vs, ps string) (vsh, psh *_ID3DBlob, ferr error) { } }() + if vshBin, pshBin := thePrecompiledFXCs.get(program.SourceHash); vshBin != nil && pshBin != nil { + var err error + if vsh, err = _D3DCreateBlob(uint(len(vshBin))); err != nil { + return nil, nil, err + } + if psh, err = _D3DCreateBlob(uint(len(pshBin))); err != nil { + return nil, nil, err + } + copy(unsafe.Slice((*byte)(vsh.GetBufferPointer()), vsh.GetBufferSize()), vshBin) + copy(unsafe.Slice((*byte)(psh.GetBufferPointer()), psh.GetBufferSize()), pshBin) + return vsh, psh, nil + } + + vs, ps := hlsl.Compile(program) + var flag uint32 = uint32(_D3DCOMPILE_OPTIMIZATION_LEVEL3) + var wg errgroup.Group // Vertex shaders are likely the same. If so, reuse the same _ID3DBlob. @@ -56,7 +121,7 @@ func compileShader(vs, ps string) (vsh, psh *_ID3DBlob, ferr error) { } }() wg.Go(func() error { - v, err := _D3DCompile([]byte(vs), "shader", nil, nil, "VSMain", "vs_4_0", flag, 0) + v, err := _D3DCompile([]byte(vs), "shader", nil, nil, VertexShaderEntryPoint, VertexShaderProfile, flag, 0) if err != nil { return fmt.Errorf("directx: D3DCompile for VSMain failed, original source: %s, %w", vs, err) } @@ -65,7 +130,7 @@ func compileShader(vs, ps string) (vsh, psh *_ID3DBlob, ferr error) { }) } wg.Go(func() error { - p, err := _D3DCompile([]byte(ps), "shader", nil, nil, "PSMain", "ps_4_0", flag, 0) + p, err := _D3DCompile([]byte(ps), "shader", nil, nil, PixelShaderEntryPoint, PixelShaderProfile, flag, 0) if err != nil { return fmt.Errorf("directx: D3DCompile for PSMain failed, original source: %s, %w", ps, err) } @@ -77,7 +142,7 @@ func compileShader(vs, ps string) (vsh, psh *_ID3DBlob, ferr error) { return nil, nil, err } - return + return vsh, psh, nil } func constantBufferSize(uniformTypes []shaderir.Type, uniformOffsets []int) int { diff --git a/internal/graphicsdriver/graphics.go b/internal/graphicsdriver/graphics.go index 2d019f4e0..2f6fa144e 100644 --- a/internal/graphicsdriver/graphics.go +++ b/internal/graphicsdriver/graphics.go @@ -30,19 +30,19 @@ type DstRegion struct { type FillRule int const ( - FillAll FillRule = iota - NonZero - EvenOdd + FillRuleFillAll FillRule = iota + FillRuleNonZero + FillRuleEvenOdd ) func (f FillRule) String() string { switch f { - case FillAll: - return "FillAll" - case NonZero: - return "NonZero" - case EvenOdd: - return "EvenOdd" + case FillRuleFillAll: + return "FillRuleFillAll" + case FillRuleNonZero: + return "FillRuleNonZero" + case FillRuleEvenOdd: + return "FillRuleEvenOdd" default: return fmt.Sprintf("FillRule(%d)", f) } diff --git a/internal/graphicsdriver/metal/ca/ca_darwin.go b/internal/graphicsdriver/metal/ca/ca_darwin.go index 528dba204..8c5bce768 100644 --- a/internal/graphicsdriver/metal/ca/ca_darwin.go +++ b/internal/graphicsdriver/metal/ca/ca_darwin.go @@ -35,7 +35,7 @@ import ( // Layer is an object that manages image-based content and // allows you to perform animations on that content. // -// Reference: https://developer.apple.com/documentation/quartzcore/calayer. +// Reference: https://developer.apple.com/documentation/quartzcore/calayer?language=objc. type Layer interface { // Layer returns the underlying CALayer * pointer. Layer() unsafe.Pointer @@ -43,15 +43,15 @@ type Layer interface { // MetalLayer is a Core Animation Metal layer, a layer that manages a pool of Metal drawables. // -// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer. +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer?language=objc. type MetalLayer struct { metalLayer objc.ID } -// MakeMetalLayer creates a new Core Animation Metal layer. +// NewMetalLayer creates a new Core Animation Metal layer. // -// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer. -func MakeMetalLayer() (MetalLayer, error) { +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer?language=objc. +func NewMetalLayer() (MetalLayer, error) { coreGraphics, err := purego.Dlopen("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics", purego.RTLD_LAZY|purego.RTLD_GLOBAL) if err != nil { return MetalLayer{}, err @@ -88,14 +88,14 @@ func (ml MetalLayer) Layer() unsafe.Pointer { // PixelFormat returns the pixel format of textures for rendering layer content. // -// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat. +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat?language=objc. func (ml MetalLayer) PixelFormat() mtl.PixelFormat { return mtl.PixelFormat(ml.metalLayer.Send(objc.RegisterName("pixelFormat"))) } // SetDevice sets the Metal device responsible for the layer's drawable resources. // -// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478163-device. +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478163-device?language=objc. func (ml MetalLayer) SetDevice(device mtl.Device) { ml.metalLayer.Send(objc.RegisterName("setDevice:"), uintptr(device.Device())) } @@ -111,7 +111,7 @@ func (ml MetalLayer) SetOpaque(opaque bool) { // PixelFormatRGBA16Float, PixelFormatBGRA10XR, or PixelFormatBGRA10XRSRGB. // SetPixelFormat panics for other values. // -// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat. +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478155-pixelformat?language=objc. func (ml MetalLayer) SetPixelFormat(pf mtl.PixelFormat) { switch pf { case mtl.PixelFormatRGBA8UNorm, mtl.PixelFormatRGBA8UNormSRGB, mtl.PixelFormatBGRA8UNorm, mtl.PixelFormatBGRA8UNormSRGB, mtl.PixelFormatStencil8: @@ -126,7 +126,7 @@ func (ml MetalLayer) SetPixelFormat(pf mtl.PixelFormat) { // // It can set to 2 or 3 only. SetMaximumDrawableCount panics for other values. // -// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2938720-maximumdrawablecount. +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2938720-maximumdrawablecount?language=objc. func (ml MetalLayer) SetMaximumDrawableCount(count int) { if count < 2 || count > 3 { panic(errors.New(fmt.Sprintf("failed trying to set maximumDrawableCount to %d outside of the valid range of [2, 3]", count))) @@ -137,7 +137,7 @@ func (ml MetalLayer) SetMaximumDrawableCount(count int) { // SetDisplaySyncEnabled controls whether the Metal layer and its drawables // are synchronized with the display's refresh rate. // -// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2887087-displaysyncenabled. +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/2887087-displaysyncenabled?language=objc. func (ml MetalLayer) SetDisplaySyncEnabled(enabled bool) { if runtime.GOOS == "ios" { return @@ -147,7 +147,7 @@ func (ml MetalLayer) SetDisplaySyncEnabled(enabled bool) { // SetDrawableSize sets the size, in pixels, of textures for rendering layer content. // -// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478174-drawablesize. +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478174-drawablesize?language=objc. func (ml MetalLayer) SetDrawableSize(width, height int) { // TODO: once objc supports calling functions with struct arguments replace this with just a ID.Send call var sel_setDrawableSize = objc.RegisterName("setDrawableSize:") @@ -161,7 +161,7 @@ func (ml MetalLayer) SetDrawableSize(width, height int) { // NextDrawable returns a Metal drawable. // -// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478172-nextdrawable. +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478172-nextdrawable?language=objc. func (ml MetalLayer) NextDrawable() (MetalDrawable, error) { md := ml.metalLayer.Send(objc.RegisterName("nextDrawable")) if md == 0 { @@ -172,28 +172,28 @@ func (ml MetalLayer) NextDrawable() (MetalDrawable, error) { // PresentsWithTransaction returns a Boolean value that determines whether the layer presents its content using a Core Animation transaction. // -// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction?language=objc func (ml MetalLayer) PresentsWithTransaction() bool { return ml.metalLayer.Send(objc.RegisterName("presentsWithTransaction")) != 0 } // SetPresentsWithTransaction sets a Boolean value that determines whether the layer presents its content using a Core Animation transaction. // -// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction +// Reference: https://developer.apple.com/documentation/quartzcore/cametallayer/1478157-presentswithtransaction?language=objc func (ml MetalLayer) SetPresentsWithTransaction(presentsWithTransaction bool) { ml.metalLayer.Send(objc.RegisterName("setPresentsWithTransaction:"), presentsWithTransaction) } // SetFramebufferOnly sets a Boolean value that determines whether the layer’s textures are used only for rendering. // -// https://developer.apple.com/documentation/quartzcore/cametallayer/1478168-framebufferonly +// https://developer.apple.com/documentation/quartzcore/cametallayer/1478168-framebufferonly?language=objc func (ml MetalLayer) SetFramebufferOnly(framebufferOnly bool) { ml.metalLayer.Send(objc.RegisterName("setFramebufferOnly:"), framebufferOnly) } // MetalDrawable is a displayable resource that can be rendered or written to by Metal. // -// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable. +// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable?language=objc. type MetalDrawable struct { metalDrawable objc.ID } @@ -205,14 +205,14 @@ func (md MetalDrawable) Drawable() unsafe.Pointer { // Texture returns a Metal texture object representing the drawable object's content. // -// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable/1478159-texture. +// Reference: https://developer.apple.com/documentation/quartzcore/cametaldrawable/1478159-texture?language=objc. func (md MetalDrawable) Texture() mtl.Texture { return mtl.NewTexture(md.metalDrawable.Send(objc.RegisterName("texture"))) } // Present presents the drawable onscreen as soon as possible. // -// Reference: https://developer.apple.com/documentation/metal/mtldrawable/1470284-present. +// Reference: https://developer.apple.com/documentation/metal/mtldrawable/1470284-present?language=objc. func (md MetalDrawable) Present() { md.metalDrawable.Send(objc.RegisterName("present")) } diff --git a/internal/graphicsdriver/metal/graphics_darwin.go b/internal/graphicsdriver/metal/graphics_darwin.go index 3cc8a6a5a..d4437f603 100644 --- a/internal/graphicsdriver/metal/graphics_darwin.go +++ b/internal/graphicsdriver/metal/graphics_darwin.go @@ -184,7 +184,7 @@ func (g *Graphics) gcBuffers() { func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer { if g.cb == (mtl.CommandBuffer{}) { - g.cb = g.cq.MakeCommandBuffer() + g.cb = g.cq.CommandBuffer() } var newBuf mtl.Buffer @@ -197,7 +197,7 @@ func (g *Graphics) availableBuffer(length uintptr) mtl.Buffer { } if newBuf == (mtl.Buffer{}) { - newBuf = g.view.getMTLDevice().MakeBufferWithLength(pow2(length), resourceStorageMode) + newBuf = g.view.getMTLDevice().NewBufferWithLength(pow2(length), resourceStorageMode) } if g.buffers == nil { @@ -286,7 +286,7 @@ func (g *Graphics) NewImage(width, height int) (graphicsdriver.Image, error) { StorageMode: storageMode, Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget, } - t := g.view.getMTLDevice().MakeTexture(td) + t := g.view.getMTLDevice().NewTextureWithDescriptor(td) i := &Image{ id: g.genNextImageID(), graphics: g, @@ -397,7 +397,7 @@ func (g *Graphics) Initialize() error { } // The stencil reference value is always 0 (default). - g.dsss[noStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{ + g.dsss[noStencil] = g.view.getMTLDevice().NewDepthStencilStateWithDescriptor(mtl.DepthStencilDescriptor{ BackFaceStencil: mtl.StencilDescriptor{ StencilFailureOperation: mtl.StencilOperationKeep, DepthFailureOperation: mtl.StencilOperationKeep, @@ -411,7 +411,7 @@ func (g *Graphics) Initialize() error { StencilCompareFunction: mtl.CompareFunctionAlways, }, }) - g.dsss[incrementStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{ + g.dsss[incrementStencil] = g.view.getMTLDevice().NewDepthStencilStateWithDescriptor(mtl.DepthStencilDescriptor{ BackFaceStencil: mtl.StencilDescriptor{ StencilFailureOperation: mtl.StencilOperationKeep, DepthFailureOperation: mtl.StencilOperationKeep, @@ -425,7 +425,7 @@ func (g *Graphics) Initialize() error { StencilCompareFunction: mtl.CompareFunctionAlways, }, }) - g.dsss[invertStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{ + g.dsss[invertStencil] = g.view.getMTLDevice().NewDepthStencilStateWithDescriptor(mtl.DepthStencilDescriptor{ BackFaceStencil: mtl.StencilDescriptor{ StencilFailureOperation: mtl.StencilOperationKeep, DepthFailureOperation: mtl.StencilOperationKeep, @@ -439,7 +439,7 @@ func (g *Graphics) Initialize() error { StencilCompareFunction: mtl.CompareFunctionAlways, }, }) - g.dsss[drawWithStencil] = g.view.getMTLDevice().MakeDepthStencilState(mtl.DepthStencilDescriptor{ + g.dsss[drawWithStencil] = g.view.getMTLDevice().NewDepthStencilStateWithDescriptor(mtl.DepthStencilDescriptor{ BackFaceStencil: mtl.StencilDescriptor{ StencilFailureOperation: mtl.StencilOperationKeep, DepthFailureOperation: mtl.StencilOperationKeep, @@ -454,7 +454,7 @@ func (g *Graphics) Initialize() error { }, }) - g.cq = g.view.getMTLDevice().MakeCommandQueue() + g.cq = g.view.getMTLDevice().NewCommandQueue() return nil } @@ -471,7 +471,7 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs // When preparing a stencil buffer, flush the current render command encoder // to make sure the stencil buffer is cleared when loading. // TODO: What about clearing the stencil buffer by vertices? - if g.lastDst != dst || g.lastFillRule != fillRule || fillRule != graphicsdriver.FillAll { + if g.lastDst != dst || g.lastFillRule != fillRule || fillRule != graphicsdriver.FillRuleFillAll { g.flushRenderCommandEncoderIfNeeded() } g.lastDst = dst @@ -497,7 +497,7 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs rpd.ColorAttachments[0].Texture = t rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{} - if fillRule != graphicsdriver.FillAll { + if fillRule != graphicsdriver.FillRuleFillAll { dst.ensureStencil() rpd.StencilAttachment.LoadAction = mtl.LoadActionClear rpd.StencilAttachment.StoreAction = mtl.StoreActionDontCare @@ -505,9 +505,9 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs } if g.cb == (mtl.CommandBuffer{}) { - g.cb = g.cq.MakeCommandBuffer() + g.cb = g.cq.CommandBuffer() } - g.rce = g.cb.MakeRenderCommandEncoder(rpd) + g.rce = g.cb.RenderCommandEncoderWithDescriptor(rpd) } w, h := dst.internalSize() @@ -544,26 +544,26 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs drawWithStencilRpss mtl.RenderPipelineState ) switch fillRule { - case graphicsdriver.FillAll: + case graphicsdriver.FillRuleFillAll: s, err := shader.RenderPipelineState(&g.view, blend, noStencil, dst.screen) if err != nil { return err } noStencilRpss = s - case graphicsdriver.NonZero: + case graphicsdriver.FillRuleNonZero: s, err := shader.RenderPipelineState(&g.view, blend, incrementStencil, dst.screen) if err != nil { return err } incrementStencilRpss = s - case graphicsdriver.EvenOdd: + case graphicsdriver.FillRuleEvenOdd: s, err := shader.RenderPipelineState(&g.view, blend, invertStencil, dst.screen) if err != nil { return err } invertStencilRpss = s } - if fillRule != graphicsdriver.FillAll { + if fillRule != graphicsdriver.FillRuleFillAll { s, err := shader.RenderPipelineState(&g.view, blend, drawWithStencil, dst.screen) if err != nil { return err @@ -580,20 +580,20 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs }) switch fillRule { - case graphicsdriver.FillAll: + case graphicsdriver.FillRuleFillAll: g.rce.SetDepthStencilState(g.dsss[noStencil]) g.rce.SetRenderPipelineState(noStencilRpss) g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0)))) - case graphicsdriver.NonZero: + case graphicsdriver.FillRuleNonZero: g.rce.SetDepthStencilState(g.dsss[incrementStencil]) g.rce.SetRenderPipelineState(incrementStencilRpss) g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0)))) - case graphicsdriver.EvenOdd: + case graphicsdriver.FillRuleEvenOdd: g.rce.SetDepthStencilState(g.dsss[invertStencil]) g.rce.SetRenderPipelineState(invertStencilRpss) g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0)))) } - if fillRule != graphicsdriver.FillAll { + if fillRule != graphicsdriver.FillRuleFillAll { g.rce.SetDepthStencilState(g.dsss[drawWithStencil]) g.rce.SetRenderPipelineState(drawWithStencilRpss) g.rce.DrawIndexedPrimitives(mtl.PrimitiveTypeTriangle, dstRegion.IndexCount, mtl.IndexTypeUInt32, g.ib, indexOffset*int(unsafe.Sizeof(uint32(0)))) @@ -810,8 +810,8 @@ func (i *Image) syncTexture() { panic("metal: command buffer must be empty at syncTexture: flushIfNeeded is not called yet?") } - cb := i.graphics.cq.MakeCommandBuffer() - bce := cb.MakeBlitCommandEncoder() + cb := i.graphics.cq.CommandBuffer() + bce := cb.BlitCommandEncoder() bce.SynchronizeTexture(i.texture, 0, 0) bce.EndEncoding() @@ -859,7 +859,7 @@ func (i *Image) WritePixels(args []graphicsdriver.PixelsArgs) error { StorageMode: storageMode, Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget, } - t := g.view.getMTLDevice().MakeTexture(td) + t := g.view.getMTLDevice().NewTextureWithDescriptor(td) g.tmpTextures = append(g.tmpTextures, t) for _, a := range args { @@ -870,9 +870,9 @@ func (i *Image) WritePixels(args []graphicsdriver.PixelsArgs) error { } if g.cb == (mtl.CommandBuffer{}) { - g.cb = i.graphics.cq.MakeCommandBuffer() + g.cb = i.graphics.cq.CommandBuffer() } - bce := g.cb.MakeBlitCommandEncoder() + bce := g.cb.BlitCommandEncoder() for _, a := range args { so := mtl.Origin{X: a.Region.Min.X - region.Min.X, Y: a.Region.Min.Y - region.Min.Y, Z: 0} ss := mtl.Size{Width: a.Region.Dx(), Height: a.Region.Dy(), Depth: 1} @@ -914,5 +914,5 @@ func (i *Image) ensureStencil() { StorageMode: mtl.StorageModePrivate, Usage: mtl.TextureUsageRenderTarget, } - i.stencil = i.graphics.view.getMTLDevice().MakeTexture(td) + i.stencil = i.graphics.view.getMTLDevice().NewTextureWithDescriptor(td) } diff --git a/internal/graphicsdriver/metal/mtl/dispatch_darwin.go b/internal/graphicsdriver/metal/mtl/dispatch_darwin.go new file mode 100644 index 000000000..f9dc1bacb --- /dev/null +++ b/internal/graphicsdriver/metal/mtl/dispatch_darwin.go @@ -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") +} diff --git a/internal/graphicsdriver/metal/mtl/example_darwin_test.go b/internal/graphicsdriver/metal/mtl/example_darwin_test.go index d06522ce6..485b7b16a 100644 --- a/internal/graphicsdriver/metal/mtl/example_darwin_test.go +++ b/internal/graphicsdriver/metal/mtl/example_darwin_test.go @@ -112,15 +112,15 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) { return in.color; } ` - lib, err := device.MakeLibrary(source, mtl.CompileOptions{}) + lib, err := device.NewLibraryWithSource(source, mtl.CompileOptions{}) if err != nil { log.Fatalln(err) } - vs, err := lib.MakeFunction("VertexShader") + vs, err := lib.NewFunctionWithName("VertexShader") if err != nil { log.Fatalln(err) } - fs, err := lib.MakeFunction("FragmentShader") + fs, err := lib.NewFunctionWithName("FragmentShader") if err != nil { log.Fatalln(err) } @@ -129,7 +129,7 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) { rpld.FragmentFunction = fs rpld.ColorAttachments[0].PixelFormat = mtl.PixelFormatRGBA8UNorm rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskAll - rps, err := device.MakeRenderPipelineState(rpld) + rps, err := device.NewRenderPipelineStateWithDescriptor(rpld) if err != nil { log.Fatalln(err) } @@ -144,7 +144,7 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) { {f32.Vec4{-0.75, -0.75, 0, 1}, f32.Vec4{1, 1, 1, 1}}, {f32.Vec4{+0.75, -0.75, 0, 1}, f32.Vec4{0, 0, 0, 1}}, } - vertexBuffer := device.MakeBufferWithBytes(unsafe.Pointer(&vertexData[0]), unsafe.Sizeof(vertexData), mtl.ResourceStorageModeManaged) + vertexBuffer := device.NewBufferWithBytes(unsafe.Pointer(&vertexData[0]), unsafe.Sizeof(vertexData), mtl.ResourceStorageModeManaged) // Create an output texture to render into. td := mtl.TextureDescriptor{ @@ -154,10 +154,10 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) { Height: 20, StorageMode: mtl.StorageModeManaged, } - texture := device.MakeTexture(td) + texture := device.NewTextureWithDescriptor(td) - cq := device.MakeCommandQueue() - cb := cq.MakeCommandBuffer() + cq := device.NewCommandQueue() + cb := cq.CommandBuffer() // Encode all render commands. var rpd mtl.RenderPassDescriptor @@ -165,14 +165,14 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) { rpd.ColorAttachments[0].StoreAction = mtl.StoreActionStore rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{Red: 0, Green: 0, Blue: 0, Alpha: 1} rpd.ColorAttachments[0].Texture = texture - rce := cb.MakeRenderCommandEncoder(rpd) + rce := cb.RenderCommandEncoderWithDescriptor(rpd) rce.SetRenderPipelineState(rps) rce.SetVertexBuffer(vertexBuffer, 0, 0) rce.DrawPrimitives(mtl.PrimitiveTypeTriangle, 0, 3) rce.EndEncoding() // Encode all blit commands. - bce := cb.MakeBlitCommandEncoder() + bce := cb.BlitCommandEncoder() bce.Synchronize(texture) bce.EndEncoding() diff --git a/internal/graphicsdriver/metal/mtl/mtl_darwin.go b/internal/graphicsdriver/metal/mtl/mtl_darwin.go index a79e60455..85da17128 100644 --- a/internal/graphicsdriver/metal/mtl/mtl_darwin.go +++ b/internal/graphicsdriver/metal/mtl/mtl_darwin.go @@ -36,7 +36,7 @@ import ( // GPUFamily represents the functionality for families of GPUs. // -// Reference: https://developer.apple.com/documentation/metal/mtlgpufamily +// Reference: https://developer.apple.com/documentation/metal/mtlgpufamily?language=objc. type GPUFamily int const ( @@ -54,7 +54,7 @@ const ( // FeatureSet defines a specific platform, hardware, and software configuration. // -// Reference: https://developer.apple.com/documentation/metal/mtlfeatureset. +// Reference: https://developer.apple.com/documentation/metal/mtlfeatureset?language=objc. type FeatureSet uint16 const ( @@ -92,7 +92,7 @@ const ( // TextureType defines The dimension of each image, including whether multiple images are arranged into an array or // a cube. // -// Reference: https://developer.apple.com/documentation/metal/mtltexturetype +// Reference: https://developer.apple.com/documentation/metal/mtltexturetype?language=objc. type TextureType uint16 const ( @@ -102,7 +102,7 @@ const ( // PixelFormat defines data formats that describe the organization // and characteristics of individual pixels in a texture. // -// Reference: https://developer.apple.com/documentation/metal/mtlpixelformat. +// Reference: https://developer.apple.com/documentation/metal/mtlpixelformat?language=objc. type PixelFormat uint16 // The data formats that describe the organization and characteristics @@ -117,7 +117,7 @@ const ( // PrimitiveType defines geometric primitive types for drawing commands. // -// Reference: https://developer.apple.com/documentation/metal/mtlprimitivetype. +// Reference: https://developer.apple.com/documentation/metal/mtlprimitivetype?language=objc. type PrimitiveType uint8 // Geometric primitive types for drawing commands. @@ -132,7 +132,7 @@ const ( // LoadAction defines actions performed at the start of a rendering pass // for a render command encoder. // -// Reference: https://developer.apple.com/documentation/metal/mtlloadaction. +// Reference: https://developer.apple.com/documentation/metal/mtlloadaction?language=objc. type LoadAction uint8 // Actions performed at the start of a rendering pass for a render command encoder. @@ -145,7 +145,7 @@ const ( // StoreAction defines actions performed at the end of a rendering pass // for a render command encoder. // -// Reference: https://developer.apple.com/documentation/metal/mtlstoreaction. +// Reference: https://developer.apple.com/documentation/metal/mtlstoreaction?language=objc. type StoreAction uint8 // Actions performed at the end of a rendering pass for a render command encoder. @@ -160,7 +160,7 @@ const ( // StorageMode defines the memory location and access permissions of a resource. // -// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode. +// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode?language=objc. type StorageMode uint8 const ( @@ -189,7 +189,7 @@ const ( // ResourceOptions defines optional arguments used to create // and influence behavior of buffer and texture objects. // -// Reference: https://developer.apple.com/documentation/metal/mtlresourceoptions. +// Reference: https://developer.apple.com/documentation/metal/mtlresourceoptions?language=objc. type ResourceOptions uint16 const ( @@ -237,7 +237,7 @@ const ( // CPUCacheMode is the CPU cache mode that defines the CPU mapping of a resource. // -// Reference: https://developer.apple.com/documentation/metal/mtlcpucachemode. +// Reference: https://developer.apple.com/documentation/metal/mtlcpucachemode?language=objc. type CPUCacheMode uint8 const ( @@ -252,7 +252,7 @@ const ( // IndexType is the index type for an index buffer that references vertices of geometric primitives. // -// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode +// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode?language=objc type IndexType uint8 const ( @@ -358,7 +358,7 @@ const ( // Resource represents a memory allocation for storing specialized data // that is accessible to the GPU. // -// Reference: https://developer.apple.com/documentation/metal/mtlresource. +// Reference: https://developer.apple.com/documentation/metal/mtlresource?language=objc. type Resource interface { // resource returns the underlying id pointer. resource() unsafe.Pointer @@ -366,7 +366,7 @@ type Resource interface { // RenderPipelineDescriptor configures new RenderPipelineState objects. // -// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinedescriptor. +// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinedescriptor?language=objc. type RenderPipelineDescriptor struct { // VertexFunction is a programmable function that processes individual vertices in a rendering pass. VertexFunction Function @@ -384,7 +384,7 @@ type RenderPipelineDescriptor struct { // RenderPipelineColorAttachmentDescriptor describes a color render target that specifies // the color configuration and color operations associated with a render pipeline. // -// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinecolorattachmentdescriptor. +// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinecolorattachmentdescriptor?language=objc. type RenderPipelineColorAttachmentDescriptor struct { // PixelFormat is the pixel format of the color attachment's texture. PixelFormat PixelFormat @@ -404,7 +404,7 @@ type RenderPipelineColorAttachmentDescriptor struct { // RenderPassDescriptor describes a group of render targets that serve as // the output destination for pixels generated by a render pass. // -// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassdescriptor. +// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassdescriptor?language=objc. type RenderPassDescriptor struct { // ColorAttachments is array of state information for attachments that store color data. ColorAttachments [1]RenderPassColorAttachmentDescriptor @@ -416,7 +416,7 @@ type RenderPassDescriptor struct { // RenderPassColorAttachmentDescriptor describes a color render target that serves // as the output destination for color pixels generated by a render pass. // -// Reference: https://developer.apple.com/documentation/metal/mtlrenderpasscolorattachmentdescriptor. +// Reference: https://developer.apple.com/documentation/metal/mtlrenderpasscolorattachmentdescriptor?language=objc. type RenderPassColorAttachmentDescriptor struct { RenderPassAttachmentDescriptor ClearColor ClearColor @@ -425,7 +425,7 @@ type RenderPassColorAttachmentDescriptor struct { // RenderPassStencilAttachment describes a stencil render target that serves as the output // destination for stencil pixels generated by a render pass. // -// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassstencilattachmentdescriptor +// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassstencilattachmentdescriptor?language=objc. type RenderPassStencilAttachment struct { RenderPassAttachmentDescriptor } @@ -433,7 +433,7 @@ type RenderPassStencilAttachment struct { // RenderPassAttachmentDescriptor describes a render target that serves // as the output destination for pixels generated by a render pass. // -// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassattachmentdescriptor. +// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassattachmentdescriptor?language=objc. type RenderPassAttachmentDescriptor struct { LoadAction LoadAction StoreAction StoreAction @@ -442,14 +442,14 @@ type RenderPassAttachmentDescriptor struct { // ClearColor is an RGBA value used for a color pixel. // -// Reference: https://developer.apple.com/documentation/metal/mtlclearcolor. +// Reference: https://developer.apple.com/documentation/metal/mtlclearcolor?language=objc. type ClearColor struct { Red, Green, Blue, Alpha float64 } // TextureDescriptor configures new Texture objects. // -// Reference: https://developer.apple.com/documentation/metal/mtltexturedescriptor. +// Reference: https://developer.apple.com/documentation/metal/mtltexturedescriptor?language=objc. type TextureDescriptor struct { TextureType TextureType PixelFormat PixelFormat @@ -462,7 +462,7 @@ type TextureDescriptor struct { // Device is abstract representation of the GPU that // serves as the primary interface for a Metal app. // -// Reference: https://developer.apple.com/documentation/metal/mtldevice. +// Reference: https://developer.apple.com/documentation/metal/mtldevice?language=objc. type Device struct { device objc.ID @@ -493,6 +493,7 @@ var ( sel_supportsFeatureSet = objc.RegisterName("supportsFeatureSet:") sel_newCommandQueue = objc.RegisterName("newCommandQueue") sel_newLibraryWithSource_options_error = objc.RegisterName("newLibraryWithSource:options:error:") + sel_newLibraryWithData_error = objc.RegisterName("newLibraryWithData:error:") sel_release = objc.RegisterName("release") sel_retain = objc.RegisterName("retain") sel_new = objc.RegisterName("new") @@ -567,7 +568,7 @@ var ( // CreateSystemDefaultDevice returns the preferred system default Metal device. // -// Reference: https://developer.apple.com/documentation/metal/1433401-mtlcreatesystemdefaultdevice. +// Reference: https://developer.apple.com/documentation/metal/1433401-mtlcreatesystemdefaultdevice?language=objc. func CreateSystemDefaultDevice() (Device, error) { metal, err := purego.Dlopen("/System/Library/Frameworks/Metal.framework/Metal", purego.RTLD_LAZY|purego.RTLD_GLOBAL) if err != nil { @@ -607,37 +608,36 @@ func (d Device) Device() unsafe.Pointer { return *(*unsafe.Pointer)(unsafe.Point // RespondsToSelector returns a Boolean value that indicates whether the receiver implements or inherits a method that can respond to a specified message. // -// Reference: https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418583-respondstoselector +// Reference: https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418583-respondstoselector?language=objc. func (d Device) RespondsToSelector(sel objc.SEL) bool { return d.device.Send(sel_respondsToSelector, sel) != 0 } // SupportsFamily returns a Boolean value that indicates whether the GPU device supports the feature set of a specific GPU family. // -// Reference: https://developer.apple.com/documentation/metal/mtldevice/3143473-supportsfamily +// Reference: https://developer.apple.com/documentation/metal/mtldevice/3143473-supportsfamily?language=objc. func (d Device) SupportsFamily(gpuFamily GPUFamily) bool { return d.device.Send(sel_supportsFamily, uintptr(gpuFamily)) != 0 } // SupportsFeatureSet reports whether device d supports feature set fs. // -// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433418-supportsfeatureset. +// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433418-supportsfeatureset?language=objc. func (d Device) SupportsFeatureSet(fs FeatureSet) bool { return d.device.Send(sel_supportsFeatureSet, uintptr(fs)) != 0 } -// MakeCommandQueue creates a serial command submission queue. +// NewCommandQueue creates a queue you use to submit rendering and computation commands to a GPU. // -// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433388-makecommandqueue. -func (d Device) MakeCommandQueue() CommandQueue { +// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433388-newcommandqueue?language=objc. +func (d Device) NewCommandQueue() CommandQueue { return CommandQueue{d.device.Send(sel_newCommandQueue)} } -// MakeLibrary creates a new library that contains -// the functions stored in the specified source string. +// NewLibraryWithSource synchronously creates a Metal library instance by compiling the functions in a source string. // -// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433431-makelibrary. -func (d Device) MakeLibrary(source string, opt CompileOptions) (Library, error) { +// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433431-newlibrarywithsource?language=objc. +func (d Device) NewLibraryWithSource(source string, opt CompileOptions) (Library, error) { var err cocoa.NSError l := d.device.Send( sel_newLibraryWithSource_options_error, @@ -652,10 +652,31 @@ func (d Device) MakeLibrary(source string, opt CompileOptions) (Library, error) return Library{l}, nil } -// MakeRenderPipelineState creates a render pipeline state object. +// NewLibraryWithData Creates a Metal library instance that contains the functions in a precompiled Metal library. // -// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433369-makerenderpipelinestate. -func (d Device) MakeRenderPipelineState(rpd RenderPipelineDescriptor) (RenderPipelineState, error) { +// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433391-newlibrarywithdata?language=objc. +func (d Device) NewLibraryWithData(buffer []byte) (Library, error) { + defer runtime.KeepAlive(buffer) + + data := dispatchDataCreate(unsafe.Pointer(&buffer[0]), uint(len(buffer)), 0, 0) + defer dispatchRelease(data) + + var err cocoa.NSError + l := d.device.Send( + sel_newLibraryWithData_error, + data, + unsafe.Pointer(&err), + ) + if l == 0 { + return Library{}, errors.New(cocoa.NSString{ID: err.Send(sel_localizedDescription)}.String()) + } + return Library{l}, nil +} + +// NewRenderPipelineStateWithDescriptor synchronously creates a render pipeline state. +// +// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433369-newrenderpipelinestatewithdescri?language=objc. +func (d Device) NewRenderPipelineStateWithDescriptor(rpd RenderPipelineDescriptor) (RenderPipelineState, error) { renderPipelineDescriptor := objc.ID(class_MTLRenderPipelineDescriptor).Send(sel_new) renderPipelineDescriptor.Send(sel_setVertexFunction, rpd.VertexFunction.function) renderPipelineDescriptor.Send(sel_setFragmentFunction, rpd.FragmentFunction.function) @@ -683,26 +704,24 @@ func (d Device) MakeRenderPipelineState(rpd RenderPipelineDescriptor) (RenderPip return RenderPipelineState{renderPipelineState}, nil } -// MakeBufferWithBytes allocates a new buffer of a given length -// and initializes its contents by copying existing data into it. +// NewBufferWithBytes allocates a new buffer of a given length and initializes its contents by copying existing data into it. // -// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433429-makebuffer. -func (d Device) MakeBufferWithBytes(bytes unsafe.Pointer, length uintptr, opt ResourceOptions) Buffer { +// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433429-newbufferwithbytes?language=objc. +func (d Device) NewBufferWithBytes(bytes unsafe.Pointer, length uintptr, opt ResourceOptions) Buffer { return Buffer{d.device.Send(sel_newBufferWithBytes_length_options, bytes, length, uintptr(opt))} } -// MakeBufferWithLength allocates a new zero-filled buffer of a given length. +// NewBufferWithLength allocates a new zero-filled buffer of a given length. // -// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433375-newbufferwithlength -func (d Device) MakeBufferWithLength(length uintptr, opt ResourceOptions) Buffer { +// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433375-newbufferwithlength?language=objc. +func (d Device) NewBufferWithLength(length uintptr, opt ResourceOptions) Buffer { return Buffer{d.device.Send(sel_newBufferWithLength_options, length, uintptr(opt))} } -// MakeTexture creates a texture object with privately owned storage -// that contains texture state. +// NewTextureWithDescriptor creates a new texture instance. // -// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433425-maketexture. -func (d Device) MakeTexture(td TextureDescriptor) Texture { +// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433425-newtexturewithdescriptor?language=objc. +func (d Device) NewTextureWithDescriptor(td TextureDescriptor) Texture { textureDescriptor := objc.ID(class_MTLTextureDescriptor).Send(sel_new) textureDescriptor.Send(sel_setTextureType, uintptr(td.TextureType)) textureDescriptor.Send(sel_setPixelFormat, uintptr(td.PixelFormat)) @@ -717,10 +736,10 @@ func (d Device) MakeTexture(td TextureDescriptor) Texture { } } -// MakeDepthStencilState creates a new object that contains depth and stencil test state. +// NewDepthStencilStateWithDescriptor creates a depth-stencil state instance. // -// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433412-makedepthstencilstate -func (d Device) MakeDepthStencilState(dsd DepthStencilDescriptor) DepthStencilState { +// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433412-newdepthstencilstatewithdescript?language=objc. +func (d Device) NewDepthStencilStateWithDescriptor(dsd DepthStencilDescriptor) DepthStencilState { depthStencilDescriptor := objc.ID(class_MTLDepthStencilDescriptor).Send(sel_new) backFaceStencil := depthStencilDescriptor.Send(sel_backFaceStencil) backFaceStencil.Send(sel_setStencilFailureOperation, uintptr(dsd.BackFaceStencil.StencilFailureOperation)) @@ -742,14 +761,14 @@ func (d Device) MakeDepthStencilState(dsd DepthStencilDescriptor) DepthStencilSt // CompileOptions specifies optional compilation settings for // the graphics or compute functions within a library. // -// Reference: https://developer.apple.com/documentation/metal/mtlcompileoptions. +// Reference: https://developer.apple.com/documentation/metal/mtlcompileoptions?language=objc. type CompileOptions struct { // TODO. } // Drawable is a displayable resource that can be rendered or written to. // -// Reference: https://developer.apple.com/documentation/metal/mtldrawable. +// Reference: https://developer.apple.com/documentation/metal/mtldrawable?language=objc. type Drawable interface { // Drawable returns the underlying id pointer. Drawable() unsafe.Pointer @@ -758,7 +777,7 @@ type Drawable interface { // CommandQueue is a queue that organizes the order // in which command buffers are executed by the GPU. // -// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue. +// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue?language=objc. type CommandQueue struct { commandQueue objc.ID } @@ -767,17 +786,17 @@ func (cq CommandQueue) Release() { cq.commandQueue.Send(sel_release) } -// MakeCommandBuffer creates a command buffer. +// CommandBuffer returns a command buffer from the command queue that maintains strong references to resources. // -// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue/1508686-makecommandbuffer. -func (cq CommandQueue) MakeCommandBuffer() CommandBuffer { +// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue/1508686-commandbuffer?language=objc. +func (cq CommandQueue) CommandBuffer() CommandBuffer { return CommandBuffer{cq.commandQueue.Send(sel_commandBuffer)} } // CommandBuffer is a container that stores encoded commands // that are committed to and executed by the GPU. // -// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer. +// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer?language=objc. type CommandBuffer struct { commandBuffer objc.ID } @@ -792,44 +811,43 @@ func (cb CommandBuffer) Release() { // Status returns the current stage in the lifetime of the command buffer. // -// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443048-status +// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443048-status?language=objc. func (cb CommandBuffer) Status() CommandBufferStatus { return CommandBufferStatus(cb.commandBuffer.Send(sel_status)) } // PresentDrawable registers a drawable presentation to occur as soon as possible. // -// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443029-presentdrawable. +// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443029-presentdrawable?language=objc. func (cb CommandBuffer) PresentDrawable(d Drawable) { cb.commandBuffer.Send(sel_presentDrawable, d.Drawable()) } // Commit commits this command buffer for execution as soon as possible. // -// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443003-commit. +// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443003-commit?language=objc. func (cb CommandBuffer) Commit() { cb.commandBuffer.Send(sel_commit) } // WaitUntilCompleted waits for the execution of this command buffer to complete. // -// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443039-waituntilcompleted. +// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443039-waituntilcompleted?language=objc. func (cb CommandBuffer) WaitUntilCompleted() { cb.commandBuffer.Send(sel_waitUntilCompleted) } // WaitUntilScheduled blocks execution of the current thread until the command buffer is scheduled. // -// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443036-waituntilscheduled. +// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443036-waituntilscheduled?language=objc. func (cb CommandBuffer) WaitUntilScheduled() { cb.commandBuffer.Send(sel_waitUntilScheduled) } -// MakeRenderCommandEncoder creates an encoder object that can -// encode graphics rendering commands into this command buffer. +// RenderCommandEncoderWithDescriptor creates a render command encoder from a descriptor. // -// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1442999-makerendercommandencoder. -func (cb CommandBuffer) MakeRenderCommandEncoder(rpd RenderPassDescriptor) RenderCommandEncoder { +// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1442999-rendercommandencoderwithdescript?language=objc. +func (cb CommandBuffer) RenderCommandEncoderWithDescriptor(rpd RenderPassDescriptor) RenderCommandEncoder { var renderPassDescriptor = objc.ID(class_MTLRenderPassDescriptor).Send(sel_new) var colorAttachments0 = renderPassDescriptor.Send(sel_colorAttachments).Send(sel_objectAtIndexedSubscript, 0) colorAttachments0.Send(sel_setLoadAction, int(rpd.ColorAttachments[0].LoadAction)) @@ -850,11 +868,11 @@ func (cb CommandBuffer) MakeRenderCommandEncoder(rpd RenderPassDescriptor) Rende return RenderCommandEncoder{CommandEncoder{rce}} } -// MakeBlitCommandEncoder creates an encoder object that can encode +// BlitCommandEncoder creates an encoder object that can encode // memory operation (blit) commands into this command buffer. // -// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443001-makeblitcommandencoder. -func (cb CommandBuffer) MakeBlitCommandEncoder() BlitCommandEncoder { +// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443001-makeblitcommandencoder?language=objc. +func (cb CommandBuffer) BlitCommandEncoder() BlitCommandEncoder { ce := cb.commandBuffer.Send(sel_blitCommandEncoder) return BlitCommandEncoder{CommandEncoder{ce}} } @@ -862,14 +880,14 @@ func (cb CommandBuffer) MakeBlitCommandEncoder() BlitCommandEncoder { // CommandEncoder is an encoder that writes sequential GPU commands // into a command buffer. // -// Reference: https://developer.apple.com/documentation/metal/mtlcommandencoder. +// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443001-blitcommandencoder?language=objc. type CommandEncoder struct { commandEncoder objc.ID } // EndEncoding declares that all command generation from this encoder is completed. // -// Reference: https://developer.apple.com/documentation/metal/mtlcommandencoder/1458038-endencoding. +// Reference: https://developer.apple.com/documentation/metal/mtlcommandencoder/1458038-endencoding?language=objc. func (ce CommandEncoder) EndEncoding() { ce.commandEncoder.Send(sel_endEncoding) } @@ -877,7 +895,7 @@ func (ce CommandEncoder) EndEncoding() { // RenderCommandEncoder is an encoder that specifies graphics-rendering commands // and executes graphics functions. // -// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder. +// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder?language=objc. type RenderCommandEncoder struct { CommandEncoder } @@ -888,7 +906,7 @@ func (rce RenderCommandEncoder) Release() { // SetRenderPipelineState sets the current render pipeline state object. // -// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515811-setrenderpipelinestate. +// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515811-setrenderpipelinestate?language=objc. func (rce RenderCommandEncoder) SetRenderPipelineState(rps RenderPipelineState) { rce.commandEncoder.Send(sel_setRenderPipelineState, rps.renderPipelineState) } @@ -903,7 +921,7 @@ func (rce RenderCommandEncoder) SetViewport(viewport Viewport) { // SetScissorRect sets the scissor rectangle for a fragment scissor test. // -// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515583-setscissorrect +// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515583-setscissorrect?language=objc. func (rce RenderCommandEncoder) SetScissorRect(scissorRect ScissorRect) { inv := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:{MTLScissorRect=qqqq}")) inv.SetTarget(rce.commandEncoder) @@ -915,14 +933,14 @@ func (rce RenderCommandEncoder) SetScissorRect(scissorRect ScissorRect) { // SetVertexBuffer sets a buffer for the vertex shader function at an index // in the buffer argument table with an offset that specifies the start of the data. // -// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515829-setvertexbuffer. +// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515829-setvertexbuffer?language=objc. func (rce RenderCommandEncoder) SetVertexBuffer(buf Buffer, offset, index int) { rce.commandEncoder.Send(sel_setVertexBuffer_offset_atIndex, buf.buffer, offset, index) } // SetVertexBytes sets a block of data for the vertex function. // -// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515846-setvertexbytes. +// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515846-setvertexbytes?language=objc. func (rce RenderCommandEncoder) SetVertexBytes(bytes unsafe.Pointer, length uintptr, index int) { rce.commandEncoder.Send(sel_setVertexBytes_length_atIndex, bytes, length, index) } @@ -933,7 +951,7 @@ func (rce RenderCommandEncoder) SetFragmentBytes(bytes unsafe.Pointer, length ui // SetFragmentTexture sets a texture for the fragment function at an index in the texture argument table. // -// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515390-setfragmenttexture +// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515390-setfragmenttexture?language=objc. func (rce RenderCommandEncoder) SetFragmentTexture(texture Texture, index int) { rce.commandEncoder.Send(sel_setFragmentTexture_atIndex, texture.texture, index) } @@ -944,7 +962,7 @@ func (rce RenderCommandEncoder) SetBlendColor(red, green, blue, alpha float32) { // SetDepthStencilState sets the depth and stencil test state. // -// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516119-setdepthstencilstate +// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516119-setdepthstencilstate?language=objc. func (rce RenderCommandEncoder) SetDepthStencilState(depthStencilState DepthStencilState) { rce.commandEncoder.Send(sel_setDepthStencilState, depthStencilState.depthStencilState) } @@ -952,7 +970,7 @@ func (rce RenderCommandEncoder) SetDepthStencilState(depthStencilState DepthSten // DrawPrimitives renders one instance of primitives using vertex data // in contiguous array elements. // -// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516326-drawprimitives. +// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516326-drawprimitives?language=objc. func (rce RenderCommandEncoder) DrawPrimitives(typ PrimitiveType, vertexStart, vertexCount int) { rce.commandEncoder.Send(sel_drawPrimitives_vertexStart_vertexCount, uintptr(typ), vertexStart, vertexCount) } @@ -969,7 +987,7 @@ func (rce RenderCommandEncoder) DrawIndexedPrimitives(typ PrimitiveType, indexCo // BlitCommandEncoder is an encoder that specifies resource copy // and resource synchronization commands. // -// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder. +// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder?language=objc. type BlitCommandEncoder struct { CommandEncoder } @@ -977,7 +995,7 @@ type BlitCommandEncoder struct { // Synchronize flushes any copy of the specified resource from its corresponding // Device caches and, if needed, invalidates any CPU caches. // -// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400775-synchronize. +// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400775-synchronize?language=objc. func (bce BlitCommandEncoder) Synchronize(resource Resource) { if runtime.GOOS == "ios" { return @@ -985,6 +1003,9 @@ func (bce BlitCommandEncoder) Synchronize(resource Resource) { bce.commandEncoder.Send(sel_synchronizeResource, resource.resource()) } +// SynchronizeTexture encodes a command that synchronizes a part of the CPU’s copy of a texture so that it matches the GPU’s copy. +// +// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400757-synchronizetexture?language=objc. func (bce BlitCommandEncoder) SynchronizeTexture(texture Texture, slice int, level int) { if runtime.GOOS == "ios" { return @@ -992,6 +1013,9 @@ func (bce BlitCommandEncoder) SynchronizeTexture(texture Texture, slice int, lev bce.commandEncoder.Send(sel_synchronizeTexture_slice_level, texture.texture, slice, level) } +// CopyFromTexture encodes a command that copies image data from a texture’s slice into another slice. +// +// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400754-copyfromtexture?language=objc. func (bce BlitCommandEncoder) CopyFromTexture(sourceTexture Texture, sourceSlice int, sourceLevel int, sourceOrigin Origin, sourceSize Size, destinationTexture Texture, destinationSlice int, destinationLevel int, destinationOrigin Origin) { inv := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:@QQ{MTLOrigin=qqq}{MTLSize=qqq}@QQ{MTLOrigin=qqq}")) inv.SetTarget(bce.commandEncoder) @@ -1010,15 +1034,15 @@ func (bce BlitCommandEncoder) CopyFromTexture(sourceTexture Texture, sourceSlice // Library is a collection of compiled graphics or compute functions. // -// Reference: https://developer.apple.com/documentation/metal/mtllibrary. +// Reference: https://developer.apple.com/documentation/metal/mtllibrary?language=objc. type Library struct { library objc.ID } -// MakeFunction returns a pre-compiled, non-specialized function. +// NewFunctionWithName returns a pre-compiled, non-specialized function. // -// Reference: https://developer.apple.com/documentation/metal/mtllibrary/1515524-makefunction. -func (l Library) MakeFunction(name string) (Function, error) { +// Reference: https://developer.apple.com/documentation/metal/mtllibrary/1515524-newfunctionwithname?language=objc. +func (l Library) NewFunctionWithName(name string) (Function, error) { f := l.library.Send(sel_newFunctionWithName, cocoa.NSString_alloc().InitWithUTF8String(name).ID, ) @@ -1028,10 +1052,14 @@ func (l Library) MakeFunction(name string) (Function, error) { return Function{f}, nil } +func (l Library) Release() { + l.library.Send(sel_release) +} + // Texture is a memory allocation for storing formatted // image data that is accessible to the GPU. // -// Reference: https://developer.apple.com/documentation/metal/mtltexture. +// Reference: https://developer.apple.com/documentation/metal/mtltexture?language=objc. type Texture struct { texture objc.ID } @@ -1042,7 +1070,9 @@ func NewTexture(texture objc.ID) Texture { } // resource implements the Resource interface. -func (t Texture) resource() unsafe.Pointer { return *(*unsafe.Pointer)(unsafe.Pointer(&t.texture)) } +func (t Texture) resource() unsafe.Pointer { + return *(*unsafe.Pointer)(unsafe.Pointer(&t.texture)) +} func (t Texture) Release() { t.texture.Send(sel_release) @@ -1051,7 +1081,7 @@ func (t Texture) Release() { // GetBytes copies a block of pixels from the storage allocation of texture // slice zero into system memory at a specified address. // -// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515751-getbytes. +// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515751-getbytes?language=objc. func (t Texture) GetBytes(pixelBytes *byte, bytesPerRow uintptr, region Region, level int) { inv := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:^vQ{MTLRegion={MTLOrigin=qqq}{MTLSize=qqq}}Q")) inv.SetTarget(t.texture) @@ -1065,7 +1095,7 @@ func (t Texture) GetBytes(pixelBytes *byte, bytesPerRow uintptr, region Region, // ReplaceRegion copies a block of pixels from the caller's pointer into the storage allocation for slice 0 of a texture. // -// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515464-replaceregion +// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515464-replaceregion?language=objc. func (t Texture) ReplaceRegion(region Region, level int, pixelBytes unsafe.Pointer, bytesPerRow int) { inv := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:{MTLRegion={MTLOrigin=qqq}{MTLSize=qqq}}Q^vQ")) inv.SetTarget(t.texture) @@ -1079,14 +1109,14 @@ func (t Texture) ReplaceRegion(region Region, level int, pixelBytes unsafe.Point // Width is the width of the texture image for the base level mipmap, in pixels. // -// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515339-width +// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515339-width?language=objc. func (t Texture) Width() int { return int(t.texture.Send(sel_width)) } // Height is the height of the texture image for the base level mipmap, in pixels. // -// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515938-height +// Reference: https://developer.apple.com/documentation/metal/mtltexture/1515938-height?language=objc. func (t Texture) Height() int { return int(t.texture.Send(sel_height)) } @@ -1094,13 +1124,19 @@ func (t Texture) Height() int { // Buffer is a memory allocation for storing unformatted data // that is accessible to the GPU. // -// Reference: https://developer.apple.com/documentation/metal/mtlbuffer. +// Reference: https://developer.apple.com/documentation/metal/mtlbuffer?language=objc. type Buffer struct { buffer objc.ID } -func (b Buffer) resource() unsafe.Pointer { return *(*unsafe.Pointer)(unsafe.Pointer(&b.buffer)) } +// resource implements the Resource interface. +func (b Buffer) resource() unsafe.Pointer { + return *(*unsafe.Pointer)(unsafe.Pointer(&b.buffer)) +} +// Length returns the logical size of the buffer, in bytes. +// +// Reference: https://developer.apple.com/documentation/metal/mtlbuffer/1515373-length?language=objc. func (b Buffer) Length() uintptr { return uintptr(b.buffer.Send(sel_length)) } @@ -1121,13 +1157,9 @@ func (b Buffer) Release() { b.buffer.Send(sel_release) } -func (b Buffer) Native() unsafe.Pointer { - return *(*unsafe.Pointer)(unsafe.Pointer(&b.buffer)) -} - // Function represents a programmable graphics or compute function executed by the GPU. // -// Reference: https://developer.apple.com/documentation/metal/mtlfunction. +// Reference: https://developer.apple.com/documentation/metal/mtlfunction?language=objc. type Function struct { function objc.ID } @@ -1139,7 +1171,7 @@ func (f Function) Release() { // RenderPipelineState contains the graphics functions // and configuration state used in a render pass. // -// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinestate. +// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinestate?language=objc. type RenderPipelineState struct { renderPipelineState objc.ID } @@ -1151,7 +1183,7 @@ func (r RenderPipelineState) Release() { // Region is a rectangular block of pixels in an image or texture, // defined by its upper-left corner and its size. // -// Reference: https://developer.apple.com/documentation/metal/mtlregion. +// Reference: https://developer.apple.com/documentation/metal/mtlregion?language=objc. type Region struct { Origin Origin // The location of the upper-left corner of the block. Size Size // The size of the block. @@ -1160,25 +1192,36 @@ type Region struct { // Origin represents the location of a pixel in an image or texture relative // to the upper-left corner, whose coordinates are (0, 0). // -// Reference: https://developer.apple.com/documentation/metal/mtlorigin. -type Origin struct{ X, Y, Z int } +// Reference: https://developer.apple.com/documentation/metal/mtlorigin?language=objc. +type Origin struct { + X int + Y int + Z int +} // Size represents the set of dimensions that declare the size of an object, // such as an image, texture, threadgroup, or grid. // -// Reference: https://developer.apple.com/documentation/metal/mtlsize. -type Size struct{ Width, Height, Depth int } +// Reference: https://developer.apple.com/documentation/metal/mtlsize?language=objc. +type Size struct { + Width int + Height int + Depth int +} // RegionMake2D returns a 2D, rectangular region for image or texture data. // -// Reference: https://developer.apple.com/documentation/metal/1515675-mtlregionmake2d. +// Reference: https://developer.apple.com/documentation/metal/1515675-mtlregionmake2d?language=objc. func RegionMake2D(x, y, width, height int) Region { return Region{ - Origin: Origin{x, y, 0}, - Size: Size{width, height, 1}, + Origin: Origin{X: x, Y: y, Z: 0}, + Size: Size{Width: width, Height: height, Depth: 1}, } } +// Viewport is a 3D rectangular region for the viewport clipping. +// +// Reference: https://developer.apple.com/documentation/metal/mtlviewport?language=objc. type Viewport struct { OriginX float64 OriginY float64 @@ -1188,9 +1231,9 @@ type Viewport struct { ZFar float64 } -// ScissorRect represents a rectangle for the scissor fragment test. +// ScissorRect is a rectangle for the scissor fragment test. // -// Reference: https://developer.apple.com/documentation/metal/mtlscissorrect +// Reference: https://developer.apple.com/documentation/metal/mtlscissorrect?language=objc. type ScissorRect struct { X int Y int @@ -1200,7 +1243,7 @@ type ScissorRect struct { // DepthStencilState is a depth and stencil state object that specifies the depth and stencil configuration and operations used in a render pass. // -// Reference: https://developer.apple.com/documentation/metal/mtldepthstencilstate +// Reference: https://developer.apple.com/documentation/metal/mtldepthstencilstate?language=objc. type DepthStencilState struct { depthStencilState objc.ID } @@ -1211,7 +1254,7 @@ func (d DepthStencilState) Release() { // DepthStencilDescriptor is an object that configures new MTLDepthStencilState objects. // -// Reference: https://developer.apple.com/documentation/metal/mtldepthstencildescriptor +// Reference: https://developer.apple.com/documentation/metal/mtldepthstencildescriptor?language=objc. type DepthStencilDescriptor struct { // BackFaceStencil is the stencil descriptor for back-facing primitives. BackFaceStencil StencilDescriptor @@ -1222,7 +1265,7 @@ type DepthStencilDescriptor struct { // StencilDescriptor is an object that defines the front-facing or back-facing stencil operations of a depth and stencil state object. // -// Reference: https://developer.apple.com/documentation/metal/mtlstencildescriptor +// Reference: https://developer.apple.com/documentation/metal/mtlstencildescriptor?language=objc. type StencilDescriptor struct { // StencilFailureOperation is the operation that is performed to update the values in the stencil attachment when the stencil test fails. StencilFailureOperation StencilOperation diff --git a/internal/graphicsdriver/metal/mtl/mtl_darwin_test.go b/internal/graphicsdriver/metal/mtl/mtl_darwin_test.go index 4abc65c09..39e2c1bfb 100644 --- a/internal/graphicsdriver/metal/mtl/mtl_darwin_test.go +++ b/internal/graphicsdriver/metal/mtl/mtl_darwin_test.go @@ -56,15 +56,15 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) { return in.color; } ` - lib, err := device.MakeLibrary(source, mtl.CompileOptions{}) + lib, err := device.NewLibraryWithSource(source, mtl.CompileOptions{}) if err != nil { t.Fatal(err) } - vs, err := lib.MakeFunction("VertexShader") + vs, err := lib.NewFunctionWithName("VertexShader") if err != nil { t.Fatal(err) } - fs, err := lib.MakeFunction("FragmentShader") + fs, err := lib.NewFunctionWithName("FragmentShader") if err != nil { t.Fatal(err) } @@ -72,7 +72,7 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) { rpld.VertexFunction = vs rpld.FragmentFunction = fs rpld.ColorAttachments[0].PixelFormat = mtl.PixelFormatRGBA8UNorm - rps, err := device.MakeRenderPipelineState(rpld) + rps, err := device.NewRenderPipelineStateWithDescriptor(rpld) if err != nil { t.Fatal(err) } @@ -87,7 +87,7 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) { {f32.Vec4{-0.75, -0.75, 0, 1}, f32.Vec4{0, 1, 0, 1}}, {f32.Vec4{+0.75, -0.75, 0, 1}, f32.Vec4{0, 0, 1, 1}}, } - vertexBuffer := device.MakeBufferWithBytes(unsafe.Pointer(&vertexData[0]), unsafe.Sizeof(vertexData), mtl.ResourceStorageModeManaged) + vertexBuffer := device.NewBufferWithBytes(unsafe.Pointer(&vertexData[0]), unsafe.Sizeof(vertexData), mtl.ResourceStorageModeManaged) // Create an output texture to render into. td := mtl.TextureDescriptor{ @@ -97,10 +97,10 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) { Height: 512, StorageMode: mtl.StorageModeManaged, } - texture := device.MakeTexture(td) + texture := device.NewTextureWithDescriptor(td) - cq := device.MakeCommandQueue() - cb := cq.MakeCommandBuffer() + cq := device.NewCommandQueue() + cb := cq.CommandBuffer() // Encode all render commands. var rpd mtl.RenderPassDescriptor @@ -108,14 +108,14 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) { rpd.ColorAttachments[0].StoreAction = mtl.StoreActionStore rpd.ColorAttachments[0].ClearColor = mtl.ClearColor{Red: 0.35, Green: 0.65, Blue: 0.85, Alpha: 1} rpd.ColorAttachments[0].Texture = texture - rce := cb.MakeRenderCommandEncoder(rpd) + rce := cb.RenderCommandEncoderWithDescriptor(rpd) rce.SetRenderPipelineState(rps) rce.SetVertexBuffer(vertexBuffer, 0, 0) rce.DrawPrimitives(mtl.PrimitiveTypeTriangle, 0, 3) rce.EndEncoding() // Encode all blit commands. - bce := cb.MakeBlitCommandEncoder() + bce := cb.BlitCommandEncoder() bce.Synchronize(texture) bce.EndEncoding() diff --git a/internal/graphicsdriver/metal/shader_darwin.go b/internal/graphicsdriver/metal/shader_darwin.go index 428fab19f..dce5ebc8a 100644 --- a/internal/graphicsdriver/metal/shader_darwin.go +++ b/internal/graphicsdriver/metal/shader_darwin.go @@ -16,6 +16,7 @@ package metal import ( "fmt" + "sync" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl" @@ -23,6 +24,37 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/shaderir/msl" ) +type precompiledLibraries struct { + binaries map[shaderir.SourceHash][]byte + m sync.Mutex +} + +func (c *precompiledLibraries) put(hash shaderir.SourceHash, bin []byte) { + c.m.Lock() + defer c.m.Unlock() + + if c.binaries == nil { + c.binaries = map[shaderir.SourceHash][]byte{} + } + if _, ok := c.binaries[hash]; ok { + panic(fmt.Sprintf("metal: the precompiled library for the hash %s is already registered", hash.String())) + } + c.binaries[hash] = bin +} + +func (c *precompiledLibraries) get(hash shaderir.SourceHash) []byte { + c.m.Lock() + defer c.m.Unlock() + + return c.binaries[hash] +} + +var thePrecompiledLibraries precompiledLibraries + +func RegisterPrecompiledLibrary(source []byte, bin []byte) { + thePrecompiledLibraries.put(shaderir.CalcSourceHash(source), bin) +} + type shaderRpsKey struct { blend graphicsdriver.Blend stencilMode stencilMode @@ -33,9 +65,12 @@ type Shader struct { id graphicsdriver.ShaderID ir *shaderir.Program + lib mtl.Library fs mtl.Function vs mtl.Function rpss map[shaderRpsKey]mtl.RenderPipelineState + + libraryPrecompiled bool } func newShader(device mtl.Device, id graphicsdriver.ShaderID, program *shaderir.Program) (*Shader, error) { @@ -60,21 +95,42 @@ func (s *Shader) Dispose() { } s.vs.Release() s.fs.Release() + // Do not release s.lib if this is precompiled. This is a shared precompiled library. + if !s.libraryPrecompiled { + s.lib.Release() + } } func (s *Shader) init(device mtl.Device) error { - src := msl.Compile(s.ir) - lib, err := device.MakeLibrary(src, mtl.CompileOptions{}) - if err != nil { - return fmt.Errorf("metal: device.MakeLibrary failed: %w, source: %s", err, src) + var src string + if libBin := thePrecompiledLibraries.get(s.ir.SourceHash); len(libBin) > 0 { + lib, err := device.NewLibraryWithData(libBin) + if err != nil { + return err + } + s.lib = lib + } else { + src = msl.Compile(s.ir) + lib, err := device.NewLibraryWithSource(src, mtl.CompileOptions{}) + if err != nil { + return fmt.Errorf("metal: device.MakeLibrary failed: %w, source: %s", err, src) + } + s.lib = lib } - vs, err := lib.MakeFunction(msl.VertexName) + + vs, err := s.lib.NewFunctionWithName(msl.VertexName) if err != nil { - return fmt.Errorf("metal: lib.MakeFunction for vertex failed: %w, source: %s", err, src) + if src != "" { + return fmt.Errorf("metal: lib.MakeFunction for vertex failed: %w, source: %s", err, src) + } + return fmt.Errorf("metal: lib.MakeFunction for vertex failed: %w", err) } - fs, err := lib.MakeFunction(msl.FragmentName) + fs, err := s.lib.NewFunctionWithName(msl.FragmentName) if err != nil { - return fmt.Errorf("metal: lib.MakeFunction for fragment failed: %w, source: %s", err, src) + if src != "" { + return fmt.Errorf("metal: lib.MakeFunction for fragment failed: %w, source: %s", err, src) + } + return fmt.Errorf("metal: lib.MakeFunction for fragment failed: %w", err) } s.fs = fs s.vs = vs @@ -120,7 +176,7 @@ func (s *Shader) RenderPipelineState(view *view, blend graphicsdriver.Blend, ste rpld.ColorAttachments[0].WriteMask = mtl.ColorWriteMaskNone } - rps, err := view.getMTLDevice().MakeRenderPipelineState(rpld) + rps, err := view.getMTLDevice().NewRenderPipelineStateWithDescriptor(rpld) if err != nil { return mtl.RenderPipelineState{}, err } diff --git a/internal/graphicsdriver/metal/view_darwin.go b/internal/graphicsdriver/metal/view_darwin.go index b22047fbf..d45690b81 100644 --- a/internal/graphicsdriver/metal/view_darwin.go +++ b/internal/graphicsdriver/metal/view_darwin.go @@ -61,7 +61,7 @@ func (v *view) colorPixelFormat() mtl.PixelFormat { func (v *view) initialize(device mtl.Device) error { v.device = device - ml, err := ca.MakeMetalLayer() + ml, err := ca.NewMetalLayer() if err != nil { return err } diff --git a/internal/graphicsdriver/opengl/gl/default_cgo.go b/internal/graphicsdriver/opengl/gl/default_cgo.go index efe9271f6..7f070d869 100644 --- a/internal/graphicsdriver/opengl/gl/default_cgo.go +++ b/internal/graphicsdriver/opengl/gl/default_cgo.go @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2014 Eric Woroshow // SPDX-FileCopyrightText: 2022 The Ebitengine Authors -//go:build !darwin && !js && !windows && !playstation5 +//go:build nintendosdk package gl diff --git a/internal/graphicsdriver/opengl/gl/default_purego.go b/internal/graphicsdriver/opengl/gl/default_purego.go index ac7d219df..93d5e7fe8 100644 --- a/internal/graphicsdriver/opengl/gl/default_purego.go +++ b/internal/graphicsdriver/opengl/gl/default_purego.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build darwin || windows +//go:build (darwin || freebsd || linux || netbsd || openbsd || windows) && !nintendosdk && !playstation5 package gl diff --git a/internal/graphicsdriver/opengl/gl/procaddr_linbsd.go b/internal/graphicsdriver/opengl/gl/procaddr_linbsd.go index 3047376c6..63527eed4 100644 --- a/internal/graphicsdriver/opengl/gl/procaddr_linbsd.go +++ b/internal/graphicsdriver/opengl/gl/procaddr_linbsd.go @@ -16,38 +16,18 @@ package gl -// #cgo LDFLAGS: -ldl -// -// #include -// #include -// -// static void* getProcAddressGL(void* libGL, const char* name) { -// static void*(*glXGetProcAddress)(const char*); -// if (!glXGetProcAddress) { -// glXGetProcAddress = dlsym(libGL, "glXGetProcAddress"); -// if (!glXGetProcAddress) { -// glXGetProcAddress = dlsym(libGL, "glXGetProcAddressARB"); -// } -// } -// return glXGetProcAddress(name); -// } -// -// static void* getProcAddressGLES(void* libGLES, const char* name) { -// return dlsym(libGLES, name); -// } -import "C" - import ( "fmt" "os" "runtime" "strings" - "unsafe" + + "github.com/ebitengine/purego" ) var ( - libGL unsafe.Pointer - libGLES unsafe.Pointer + libGL uintptr + libGLES uintptr ) func (c *defaultContext) init() error { @@ -68,11 +48,12 @@ func (c *defaultContext) init() error { // Try OpenGL first. OpenGL is preferable as this doesn't cause context losses. if !preferES { // Usually libGL.so or libGL.so.1 is used. libGL.so.2 might exist only on NetBSD. + // TODO: Should "libOpenGL.so.0" [1] and "libGLX.so.0" [2] be added? These were added as of GLFW 3.3.9. + // [1] https://github.com/glfw/glfw/commit/55aad3c37b67f17279378db52da0a3ab81bbf26d + // [2] https://github.com/glfw/glfw/commit/c18851f52ec9704eb06464058a600845ec1eada1 for _, name := range []string{"libGL.so", "libGL.so.2", "libGL.so.1", "libGL.so.0"} { - cname := C.CString(name) - lib := C.dlopen(cname, C.RTLD_LAZY|C.RTLD_GLOBAL) - C.free(unsafe.Pointer(cname)) - if lib != nil { + lib, err := purego.Dlopen(name, purego.RTLD_LAZY|purego.RTLD_GLOBAL) + if err == nil { libGL = lib return nil } @@ -81,10 +62,8 @@ func (c *defaultContext) init() error { // Try OpenGL ES. for _, name := range []string{"libGLESv2.so", "libGLESv2.so.2", "libGLESv2.so.1", "libGLESv2.so.0"} { - cname := C.CString(name) - lib := C.dlopen(cname, C.RTLD_LAZY|C.RTLD_GLOBAL) - C.free(unsafe.Pointer(cname)) - if lib != nil { + lib, err := purego.Dlopen(name, purego.RTLD_LAZY|purego.RTLD_GLOBAL) + if err == nil { libGLES = lib c.isES = true return nil @@ -96,19 +75,32 @@ func (c *defaultContext) init() error { func (c *defaultContext) getProcAddress(name string) (uintptr, error) { if c.isES { - return getProcAddressGLES(name), nil + return getProcAddressGLES(name) } - return getProcAddressGL(name), nil + return getProcAddressGL(name) } -func getProcAddressGL(name string) uintptr { - cname := C.CString(name) - defer C.free(unsafe.Pointer(cname)) - return uintptr(C.getProcAddressGL(libGL, cname)) +var glXGetProcAddress func(name string) uintptr + +func getProcAddressGL(name string) (uintptr, error) { + if glXGetProcAddress == nil { + if _, err := purego.Dlsym(libGL, "glXGetProcAddress"); err == nil { + purego.RegisterLibFunc(&glXGetProcAddress, libGL, "glXGetProcAddress") + } else if _, err := purego.Dlsym(libGL, "glXGetProcAddressARB"); err == nil { + purego.RegisterLibFunc(&glXGetProcAddress, libGL, "glXGetProcAddressARB") + } + } + if glXGetProcAddress == nil { + return 0, fmt.Errorf("gl: failed to find glXGetProcAddress or glXGetProcAddressARB in libGL.so") + } + + return glXGetProcAddress(name), nil } -func getProcAddressGLES(name string) uintptr { - cname := C.CString(name) - defer C.free(unsafe.Pointer(cname)) - return uintptr(C.getProcAddressGLES(libGLES, cname)) +func getProcAddressGLES(name string) (uintptr, error) { + proc, err := purego.Dlsym(libGLES, name) + if err != nil { + return 0, err + } + return proc, nil } diff --git a/internal/graphicsdriver/opengl/graphics.go b/internal/graphicsdriver/opengl/graphics.go index 5881cd7f6..3dea16128 100644 --- a/internal/graphicsdriver/opengl/graphics.go +++ b/internal/graphicsdriver/opengl/graphics.go @@ -329,7 +329,7 @@ func (g *Graphics) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphicsdr } g.uniformVars = g.uniformVars[:0] - if fillRule != graphicsdriver.FillAll { + if fillRule != graphicsdriver.FillRuleFillAll { if err := dsts[firstTarget].ensureStencilBuffer(framebufferNative(f)); err != nil { return err } @@ -345,7 +345,7 @@ func (g *Graphics) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphicsdr ) switch fillRule { - case graphicsdriver.NonZero: + case graphicsdriver.FillRuleNonZero: g.context.ctx.Clear(gl.STENCIL_BUFFER_BIT) g.context.ctx.StencilFunc(gl.ALWAYS, 0x00, 0xff) g.context.ctx.StencilOpSeparate(gl.FRONT, gl.KEEP, gl.KEEP, gl.INCR_WRAP) @@ -353,7 +353,7 @@ func (g *Graphics) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphicsdr g.context.ctx.ColorMask(false, false, false, false) g.context.ctx.DrawElements(gl.TRIANGLES, int32(dstRegion.IndexCount), gl.UNSIGNED_INT, indexOffset*int(unsafe.Sizeof(uint32(0)))) - case graphicsdriver.EvenOdd: + case graphicsdriver.FillRuleEvenOdd: g.context.ctx.Clear(gl.STENCIL_BUFFER_BIT) g.context.ctx.StencilFunc(gl.ALWAYS, 0x00, 0xff) g.context.ctx.StencilOpSeparate(gl.FRONT_AND_BACK, gl.KEEP, gl.KEEP, gl.INVERT) @@ -361,7 +361,7 @@ func (g *Graphics) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphicsdr g.context.ctx.DrawElements(gl.TRIANGLES, int32(dstRegion.IndexCount), gl.UNSIGNED_INT, indexOffset*int(unsafe.Sizeof(uint32(0)))) } - if fillRule != graphicsdriver.FillAll { + if fillRule != graphicsdriver.FillRuleFillAll { g.context.ctx.StencilFunc(gl.NOTEQUAL, 0x00, 0xff) g.context.ctx.StencilOpSeparate(gl.FRONT_AND_BACK, gl.KEEP, gl.KEEP, gl.KEEP) g.context.ctx.ColorMask(true, true, true, true) @@ -370,7 +370,7 @@ func (g *Graphics) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphicsdr indexOffset += dstRegion.IndexCount } - if fillRule != graphicsdriver.FillAll { + if fillRule != graphicsdriver.FillRuleFillAll { g.context.ctx.Disable(gl.STENCIL_TEST) } diff --git a/internal/graphicsdriver/playstation5/shader_paystation5.go b/internal/graphicsdriver/playstation5/shader_paystation5.go new file mode 100644 index 000000000..c4c9ef1e5 --- /dev/null +++ b/internal/graphicsdriver/playstation5/shader_paystation5.go @@ -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. +} diff --git a/internal/mipmap/mipmap.go b/internal/mipmap/mipmap.go index 4868e1dd8..99e689124 100644 --- a/internal/mipmap/mipmap.go +++ b/internal/mipmap/mipmap.go @@ -262,7 +262,7 @@ func (m *Mipmap) level(level int) *buffered.Image { s := buffered.NewImage(w2, h2, m.imageType) dstRegion := image.Rect(0, 0, w2, h2) - s.DrawTriangles([graphics.ShaderSrcImageCount]*buffered.Image{src}, vs, is, graphicsdriver.BlendCopy, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{}, shader, nil, graphicsdriver.FillAll) + s.DrawTriangles([graphics.ShaderSrcImageCount]*buffered.Image{src}, vs, is, graphicsdriver.BlendCopy, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{}, shader, nil, graphicsdriver.FillRuleFillAll) m.setImg(level, s) return m.imgs[level] diff --git a/internal/png/gen.go b/internal/png/gen.go index 4020ede23..b4e4dfce4 100644 --- a/internal/png/gen.go +++ b/internal/png/gen.go @@ -23,13 +23,13 @@ import ( "go/parser" "go/token" "os" + "os/exec" "path/filepath" "regexp" "runtime" "strconv" "strings" - exec "golang.org/x/sys/execabs" "golang.org/x/tools/go/ast/astutil" ) diff --git a/internal/processtest/processtest_test.go b/internal/processtest/processtest_test.go index 1d814b9df..e72e0df3a 100644 --- a/internal/processtest/processtest_test.go +++ b/internal/processtest/processtest_test.go @@ -20,14 +20,13 @@ import ( "bytes" "context" "os" + "os/exec" "path/filepath" "runtime" "strings" "sync" "testing" "time" - - exec "golang.org/x/sys/execabs" ) func isWSL() (bool, error) { diff --git a/internal/processtest/testdata/issue2138.go b/internal/processtest/testdata/issue2138.go index 34e8dcb98..18f3ab214 100644 --- a/internal/processtest/testdata/issue2138.go +++ b/internal/processtest/testdata/issue2138.go @@ -59,7 +59,7 @@ func (g *Game) Update() error { } func (g *Game) Draw(screen *ebiten.Image) { - // Before the fix, some complex renderings with EvenOdd might cause a DirectX error like this (#2138): + // Before the fix, some complex renderings with FillRuleEvenOdd might cause a DirectX error like this (#2138): // panic: directx: IDXGISwapChain4::Present failed: HRESULT(2289696773) screen.DrawImage(debugCircleImage, nil) @@ -74,7 +74,7 @@ func (g *Game) Draw(screen *ebiten.Image) { p.Arc(100, 100, 6, 0, 2*math.Pi, vector.Clockwise) filling, indicies := p.AppendVerticesAndIndicesForFilling(nil, nil) screen.DrawTriangles(filling, indicies, whiteTextureImage, &ebiten.DrawTrianglesOptions{ - FillRule: ebiten.EvenOdd, + FillRule: ebiten.FillRuleEvenOdd, }) } diff --git a/internal/processtest/testdata/shader.go b/internal/processtest/testdata/shader.go new file mode 100644 index 000000000..cae4d3bb6 --- /dev/null +++ b/internal/processtest/testdata/shader.go @@ -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) + } +} diff --git a/internal/shader/expr.go b/internal/shader/expr.go index 1c063820f..f5460a831 100644 --- a/internal/shader/expr.go +++ b/internal/shader/expr.go @@ -199,7 +199,7 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar return nil, nil, nil, false } for _, expr := range es { - if expr.Type == shaderir.FunctionExpr { + if expr.Type == shaderir.FunctionExpr || expr.Type == shaderir.BuiltinFuncExpr { cs.addError(e.Pos(), fmt.Sprintf("function name cannot be an argument: %s", e.Fun)) return nil, nil, nil, false } diff --git a/internal/shader/shader.go b/internal/shader/shader.go index b58d101ed..67edbe91a 100644 --- a/internal/shader/shader.go +++ b/internal/shader/shader.go @@ -202,6 +202,7 @@ func Compile(src []byte, vertexEntry, fragmentEntry string, textureCount int) (* fragmentEntry: fragmentEntry, unit: unit, } + s.ir.SourceHash = shaderir.CalcSourceHash(src) s.global.ir = &shaderir.Block{} s.parse(f) diff --git a/internal/shader/shader_test.go b/internal/shader/shader_test.go index 6a855e332..38f3cdc05 100644 --- a/internal/shader/shader_test.go +++ b/internal/shader/shader_test.go @@ -188,7 +188,7 @@ func TestCompile(t *testing.T) { } if tc.HLSL != nil { - vs, _, _ := hlsl.Compile(s) + vs, _ := hlsl.Compile(s) if got, want := hlslNormalize(vs), hlslNormalize(string(tc.HLSL)); got != want { compare(t, "HLSL", got, want) } diff --git a/internal/shader/syntax_test.go b/internal/shader/syntax_test.go index 61512ee2e..3c07065b2 100644 --- a/internal/shader/syntax_test.go +++ b/internal/shader/syntax_test.go @@ -1897,6 +1897,16 @@ func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 { Foo(Bar()) return color } +`)); err == nil { + t.Errorf("error must be non-nil but was nil") + } + // Issue #2965 + if _, err := compileToIR([]byte(`package main + +func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 { + abs(sign) + return color +} `)); err == nil { t.Errorf("error must be non-nil but was nil") } diff --git a/internal/shaderir/bench_test.go b/internal/shaderir/bench_test.go index 07102934c..ad7fabc8b 100644 --- a/internal/shaderir/bench_test.go +++ b/internal/shaderir/bench_test.go @@ -22,7 +22,7 @@ import ( ) func BenchmarkFilter(b *testing.B) { - src := builtinshader.Shader(builtinshader.FilterNearest, builtinshader.AddressUnsafe, false) + src := builtinshader.ShaderSource(builtinshader.FilterNearest, builtinshader.AddressUnsafe, false) s, err := graphics.CompileShader(src) if err != nil { b.Fatal(err) diff --git a/internal/shaderir/hlsl/hlsl.go b/internal/shaderir/hlsl/hlsl.go index 9454024d5..a44aceb8e 100644 --- a/internal/shaderir/hlsl/hlsl.go +++ b/internal/shaderir/hlsl/hlsl.go @@ -86,8 +86,8 @@ float4x4 float4x4FromScalar(float x) { return float4x4(x, 0, 0, 0, 0, x, 0, 0, 0, 0, x, 0, 0, 0, 0, x); }` -func Compile(p *shaderir.Program) (vertexShader, pixelShader string, offsets []int) { - offsets = calculateMemoryOffsets(p.Uniforms) +func Compile(p *shaderir.Program) (vertexShader, pixelShader string) { + offsets := CalcUniformMemoryOffsets(p) p = adjustProgram(p) c := &compileContext{ diff --git a/internal/shaderir/hlsl/packing.go b/internal/shaderir/hlsl/packing.go index ee28d01ee..217a12576 100644 --- a/internal/shaderir/hlsl/packing.go +++ b/internal/shaderir/hlsl/packing.go @@ -22,7 +22,7 @@ import ( const boundaryInBytes = 16 -func calculateMemoryOffsets(uniforms []shaderir.Type) []int { +func CalcUniformMemoryOffsets(program *shaderir.Program) []int { // https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-packing-rules // https://github.com/microsoft/DirectXShaderCompiler/wiki/Buffer-Packing @@ -38,7 +38,7 @@ func calculateMemoryOffsets(uniforms []shaderir.Type) []int { // TODO: Reorder the variables with packoffset. // See https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-variable-packoffset - for _, u := range uniforms { + for _, u := range program.Uniforms { switch u.Main { case shaderir.Float: offsets = append(offsets, head) diff --git a/internal/shaderir/program.go b/internal/shaderir/program.go index e4bdbe5de..906713602 100644 --- a/internal/shaderir/program.go +++ b/internal/shaderir/program.go @@ -16,8 +16,10 @@ package shaderir import ( + "encoding/hex" "go/constant" "go/token" + "hash/fnv" "sort" "strings" ) @@ -29,6 +31,21 @@ const ( Pixels ) +type SourceHash [16]byte + +func CalcSourceHash(source []byte) SourceHash { + h := fnv.New128a() + _, _ = h.Write(source) + + var hash SourceHash + h.Sum(hash[:0]) + return hash +} + +func (s SourceHash) String() string { + return hex.EncodeToString(s[:]) +} + type Program struct { UniformNames []string Uniforms []Type @@ -41,6 +58,8 @@ type Program struct { FragmentFunc FragmentFunc Unit Unit + SourceHash SourceHash + uniformFactors []uint32 } diff --git a/internal/shaderir/pssl/pssl.go b/internal/shaderir/pssl/pssl.go new file mode 100644 index 000000000..2f5c4ba1e --- /dev/null +++ b/internal/shaderir/pssl/pssl.go @@ -0,0 +1,25 @@ +// 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. + +// The actual implementation will be provided by -overlay. + +package pssl + +import ( + "github.com/hajimehoshi/ebiten/v2/internal/shaderir" +) + +func Compile(p *shaderir.Program) (vertexShader, pixelShader string) { + panic("not implemented") +} diff --git a/internal/ui/context.go b/internal/ui/context.go index ff419c975..48274c171 100644 --- a/internal/ui/context.go +++ b/internal/ui/context.go @@ -308,7 +308,6 @@ func (c *context) runInFrame(f func()) { f() } <-ch - return } func (c *context) processFuncsInFrame(ui *UserInterface) error { diff --git a/internal/ui/image.go b/internal/ui/image.go index a51daf882..c16c5351e 100644 --- a/internal/ui/image.go +++ b/internal/ui/image.go @@ -98,7 +98,7 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertice i.bigOffscreenBuffer = i.ui.newBigOffscreenImage(i, imageType) } - i.bigOffscreenBuffer.drawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, canSkipMipmap, false) + i.bigOffscreenBuffer.drawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, canSkipMipmap) return } @@ -210,7 +210,7 @@ func (i *Image) Fill(r, g, b, a float32, region image.Rectangle) { blend = graphicsdriver.BlendSourceOver } // i.lastBlend is updated in DrawTriangles. - i.DrawTriangles(srcs, i.tmpVerticesForFill, is, blend, region, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillAll, true, false) + i.DrawTriangles(srcs, i.tmpVerticesForFill, is, blend, region, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, true, false) } type bigOffscreenImage struct { @@ -244,7 +244,7 @@ func (i *bigOffscreenImage) deallocate() { i.dirty = false } -func (i *bigOffscreenImage) drawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, canSkipMipmap bool, antialias bool) { +func (i *bigOffscreenImage) drawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRuleFillAll, canSkipMipmap bool, antialias bool) { if i.blend != blend { i.flush() } @@ -279,7 +279,7 @@ func (i *bigOffscreenImage) drawTriangles(srcs [graphics.ShaderSrcImageCount]*Im 1, 1, 1, 1) is := graphics.QuadIndices() dstRegion := image.Rect(0, 0, i.region.Dx()*bigOffscreenScale, i.region.Dy()*bigOffscreenScale) - i.image.DrawTriangles(srcs, i.tmpVerticesForCopying, is, graphicsdriver.BlendCopy, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillAll, true, false) + i.image.DrawTriangles(srcs, i.tmpVerticesForCopying, is, graphicsdriver.BlendCopy, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, true, false) } for idx := 0; idx < len(vertices); idx += graphics.VertexFloatCount { @@ -327,7 +327,7 @@ func (i *bigOffscreenImage) flush() { if i.blend != graphicsdriver.BlendSourceOver { blend = graphicsdriver.BlendCopy } - i.orig.DrawTriangles(srcs, i.tmpVerticesForFlushing, is, blend, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{}, LinearFilterShader, nil, graphicsdriver.FillAll, true, false) + i.orig.DrawTriangles(srcs, i.tmpVerticesForFlushing, is, blend, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{}, LinearFilterShader, nil, graphicsdriver.FillRuleFillAll, true, false) i.image.clear() i.dirty = false diff --git a/internal/ui/monitor_glfw.go b/internal/ui/monitor_glfw.go index aa12765cb..ef8feb95b 100644 --- a/internal/ui/monitor_glfw.go +++ b/internal/ui/monitor_glfw.go @@ -65,13 +65,13 @@ type monitors struct { m sync.Mutex - updateCalled int32 + updateCalled atomic.Bool } var theMonitors monitors func (m *monitors) append(ms []*Monitor) []*Monitor { - if atomic.LoadInt32(&m.updateCalled) == 0 { + if !m.updateCalled.Load() { panic("ui: (*monitors).update must be called before (*monitors).append is called") } @@ -82,7 +82,7 @@ func (m *monitors) append(ms []*Monitor) []*Monitor { } func (m *monitors) primaryMonitor() *Monitor { - if atomic.LoadInt32(&m.updateCalled) == 0 { + if !m.updateCalled.Load() { panic("ui: (*monitors).update must be called before (*monitors).primaryMonitor is called") } @@ -97,13 +97,6 @@ func (m *monitors) primaryMonitor() *Monitor { return m.monitors[0] } -func (m *monitors) monitorFromID(id int) *Monitor { - m.m.Lock() - defer m.m.Unlock() - - return m.monitors[id] -} - // monitorFromPosition returns a monitor for the given position (x, y), // or returns nil if monitor is not found. // The position is in GLFW pixels. @@ -179,6 +172,6 @@ func (m *monitors) update() error { m.monitors = newMonitors m.m.Unlock() - atomic.StoreInt32(&m.updateCalled, 1) + m.updateCalled.Store(true) return nil } diff --git a/internal/ui/screensizeinfullscreen_js.go b/internal/ui/screensizeinfullscreen_js.go index 40ed9b5c0..336400614 100644 --- a/internal/ui/screensizeinfullscreen_js.go +++ b/internal/ui/screensizeinfullscreen_js.go @@ -16,5 +16,5 @@ package ui func (u *UserInterface) ScreenSizeInFullscreen() (int, int) { // On browsers, ScreenSizeInFullscreen returns the 'window' (global object) size, not 'screen' size for backward compatibility (#2145). - return window.Get("width").Int(), window.Get("height").Int() + return window.Get("innerWidth").Int(), window.Get("innerHeight").Int() } diff --git a/internal/ui/ui.go b/internal/ui/ui.go index 5f1a9dca9..c04d1c859 100644 --- a/internal/ui/ui.go +++ b/internal/ui/ui.go @@ -75,10 +75,10 @@ type UserInterface struct { err error errM sync.Mutex - isScreenClearedEveryFrame int32 - graphicsLibrary int32 - running int32 - terminated int32 + isScreenClearedEveryFrame atomic.Bool + graphicsLibrary atomic.Int32 + running atomic.Bool + terminated atomic.Bool whiteImage *Image @@ -106,10 +106,9 @@ func Get() *UserInterface { // newUserInterface must be called from the main thread. func newUserInterface() (*UserInterface, error) { - u := &UserInterface{ - isScreenClearedEveryFrame: 1, - graphicsLibrary: int32(GraphicsLibraryUnknown), - } + u := &UserInterface{} + u.isScreenClearedEveryFrame.Store(true) + u.graphicsLibrary.Store(int32(GraphicsLibraryUnknown)) u.whiteImage = u.NewImage(3, 3, atlas.ImageTypeRegular) pix := make([]byte, 4*u.whiteImage.width*u.whiteImage.height) @@ -127,6 +126,10 @@ func newUserInterface() (*UserInterface, error) { } func (u *UserInterface) readPixels(mipmap *mipmap.Mipmap, pixels []byte, region image.Rectangle) error { + if !u.running.Load() { + panic("ui: ReadPixels cannot be called before the game starts") + } + ok, err := mipmap.ReadPixels(u.graphicsDriver, pixels, region) if err != nil { return err @@ -196,41 +199,33 @@ func (u *UserInterface) setError(err error) { } func (u *UserInterface) IsScreenClearedEveryFrame() bool { - return atomic.LoadInt32(&u.isScreenClearedEveryFrame) != 0 + return u.isScreenClearedEveryFrame.Load() } func (u *UserInterface) SetScreenClearedEveryFrame(cleared bool) { - v := int32(0) - if cleared { - v = 1 - } - atomic.StoreInt32(&u.isScreenClearedEveryFrame, v) + u.isScreenClearedEveryFrame.Store(cleared) } func (u *UserInterface) setGraphicsLibrary(library GraphicsLibrary) { - atomic.StoreInt32(&u.graphicsLibrary, int32(library)) + u.graphicsLibrary.Store(int32(library)) } func (u *UserInterface) GraphicsLibrary() GraphicsLibrary { - return GraphicsLibrary(atomic.LoadInt32(&u.graphicsLibrary)) + return GraphicsLibrary(u.graphicsLibrary.Load()) } func (u *UserInterface) isRunning() bool { - return atomic.LoadInt32(&u.running) != 0 && !u.isTerminated() + return u.running.Load() && !u.isTerminated() } func (u *UserInterface) setRunning(running bool) { - if running { - atomic.StoreInt32(&u.running, 1) - } else { - atomic.StoreInt32(&u.running, 0) - } + u.running.Store(running) } func (u *UserInterface) isTerminated() bool { - return atomic.LoadInt32(&u.terminated) != 0 + return u.terminated.Load() } func (u *UserInterface) setTerminated() { - atomic.StoreInt32(&u.terminated, 1) + u.terminated.Store(true) } diff --git a/internal/ui/ui_darwin.go b/internal/ui/ui_darwin.go index 7d3a36c09..f1fd13f3f 100644 --- a/internal/ui/ui_darwin.go +++ b/internal/ui/ui_darwin.go @@ -242,7 +242,6 @@ var ( sel_setOrigResizable = objc.RegisterName("setOrigResizable:") sel_toggleFullScreen = objc.RegisterName("toggleFullScreen:") sel_windowDidBecomeKey = objc.RegisterName("windowDidBecomeKey:") - sel_windowDidDeminiaturize = objc.RegisterName("windowDidDeminiaturize:") sel_windowDidEnterFullScreen = objc.RegisterName("windowDidEnterFullScreen:") sel_windowDidExitFullScreen = objc.RegisterName("windowDidExitFullScreen:") sel_windowDidMiniaturize = objc.RegisterName("windowDidMiniaturize:") diff --git a/internal/ui/ui_mobile.go b/internal/ui/ui_mobile.go index d95ece36c..b86e9a755 100644 --- a/internal/ui/ui_mobile.go +++ b/internal/ui/ui_mobile.go @@ -40,7 +40,6 @@ var ( func (u *UserInterface) init() error { u.userInterfaceImpl = userInterfaceImpl{ - foreground: 1, graphicsLibraryInitCh: make(chan struct{}), errCh: make(chan error), @@ -48,6 +47,7 @@ func (u *UserInterface) init() error { outsideWidth: 640, outsideHeight: 480, } + u.foreground.Store(true) return nil } @@ -89,7 +89,7 @@ type userInterfaceImpl struct { outsideWidth float64 outsideHeight float64 - foreground int32 + foreground atomic.Bool errCh chan error context *context @@ -97,18 +97,14 @@ type userInterfaceImpl struct { inputState InputState touches []TouchForInput - fpsMode int32 + fpsMode atomic.Int32 renderRequester RenderRequester m sync.RWMutex } func (u *UserInterface) SetForeground(foreground bool) error { - var v int32 - if foreground { - v = 1 - } - atomic.StoreInt32(&u.foreground, v) + u.foreground.Store(foreground) if foreground { return hook.ResumeAudio() @@ -220,7 +216,7 @@ func (u *UserInterface) SetFullscreen(fullscreen bool) { } func (u *UserInterface) IsFocused() bool { - return atomic.LoadInt32(&u.foreground) != 0 + return u.foreground.Load() } func (u *UserInterface) IsRunnableOnUnfocused() bool { @@ -232,11 +228,11 @@ func (u *UserInterface) SetRunnableOnUnfocused(runnableOnUnfocused bool) { } func (u *UserInterface) FPSMode() FPSModeType { - return FPSModeType(atomic.LoadInt32(&u.fpsMode)) + return FPSModeType(u.fpsMode.Load()) } func (u *UserInterface) SetFPSMode(mode FPSModeType) { - atomic.StoreInt32(&u.fpsMode, int32(mode)) + u.fpsMode.Store(int32(mode)) u.updateExplicitRenderingModeIfNeeded(mode) } @@ -298,7 +294,7 @@ func (u *UserInterface) Monitor() *Monitor { func (u *UserInterface) UpdateInput(keys map[Key]struct{}, runes []rune, touches []TouchForInput) { u.updateInputStateFromOutside(keys, runes, touches) - if FPSModeType(atomic.LoadInt32(&u.fpsMode)) == FPSModeVsyncOffMinimum { + if FPSModeType(u.fpsMode.Load()) == FPSModeVsyncOffMinimum { u.renderRequester.RequestRenderIfNeeded() } } @@ -310,11 +306,11 @@ type RenderRequester interface { func (u *UserInterface) SetRenderRequester(renderRequester RenderRequester) { u.renderRequester = renderRequester - u.updateExplicitRenderingModeIfNeeded(FPSModeType(atomic.LoadInt32(&u.fpsMode))) + u.updateExplicitRenderingModeIfNeeded(FPSModeType(u.fpsMode.Load())) } func (u *UserInterface) ScheduleFrame() { - if u.renderRequester != nil && FPSModeType(atomic.LoadInt32(&u.fpsMode)) == FPSModeVsyncOffMinimum { + if u.renderRequester != nil && FPSModeType(u.fpsMode.Load()) == FPSModeVsyncOffMinimum { u.renderRequester.RequestRenderIfNeeded() } } diff --git a/run.go b/run.go index fb49bc222..2291562e5 100644 --- a/run.go +++ b/run.go @@ -64,7 +64,8 @@ type Game interface { Draw(screen *Image) // Layout accepts a native outside size in device-independent pixels and returns the game's logical screen - // size. + // size in pixels. The logical size is used for 1) the screen size given at Draw and 2) calculation of the + // scale from the screen to the final screen size. // // On desktops, the outside is a window or a monitor (fullscreen mode). On browsers, the outside is a body // element. On mobiles, the outside is the view's size. @@ -90,6 +91,11 @@ type LayoutFer interface { // LayoutF is the float version of Game.Layout. // // If the game implements this interface, Layout is never called and LayoutF is called instead. + // + // LayoutF accepts a native outside size in device-independent pixels and returns the game's logical screen + // size in pixels. The logical size is used for 1) the screen size given at Draw and 2) calculation of the + // scale from the screen to the final screen size. For 1), the actual screen size is a rounded up of the + // logical size. LayoutF(outsideWidth, outsideHeight float64) (screenWidth, screenHeight float64) } @@ -149,7 +155,7 @@ func CurrentFPS() float64 { } var ( - isRunGameEnded_ = int32(0) + isRunGameEnded_ atomic.Bool ) // SetScreenClearedEveryFrame enables or disables the clearing of the screen at the beginning of each frame. @@ -179,7 +185,7 @@ func IsScreenClearedEveryFrame() bool { // // Deprecated: as of v2.5. Use FinalScreenDrawer instead. func SetScreenFilterEnabled(enabled bool) { - setScreenFilterEnabled(enabled) + screenFilterEnabled.Store(enabled) } // IsScreenFilterEnabled returns true if Ebitengine's "screen" filter is enabled. @@ -188,7 +194,7 @@ func SetScreenFilterEnabled(enabled bool) { // // Deprecated: as of v2.5. func IsScreenFilterEnabled() bool { - return isScreenFilterEnabled() + return screenFilterEnabled.Load() } // Termination is a special error which indicates Game termination without error. @@ -309,17 +315,13 @@ type RunGameOptions struct { // // Don't call RunGame or RunGameWithOptions twice or more in one process. func RunGameWithOptions(game Game, options *RunGameOptions) error { - defer atomic.StoreInt32(&isRunGameEnded_, 1) + defer isRunGameEnded_.Store(true) initializeWindowPositionIfNeeded(WindowSize()) op := toUIRunOptions(options) // This is necessary to change the result of IsScreenTransparent. - if op.ScreenTransparent { - atomic.StoreInt32(&screenTransparent, 1) - } else { - atomic.StoreInt32(&screenTransparent, 0) - } + screenTransparent.Store(op.ScreenTransparent) g := newGameForUI(game, op.ScreenTransparent) if err := ui.Get().Run(g, op); err != nil { @@ -333,7 +335,7 @@ func RunGameWithOptions(game Game, options *RunGameOptions) error { } func isRunGameEnded() bool { - return atomic.LoadInt32(&isRunGameEnded_) != 0 + return isRunGameEnded_.Load() } // ScreenSizeInFullscreen returns the size in device-independent pixels when the game is fullscreen. @@ -640,7 +642,7 @@ func IsScreenTransparent() bool { if !ui.IsScreenTransparentAvailable() { return false } - return atomic.LoadInt32(&screenTransparent) != 0 + return screenTransparent.Load() } // SetScreenTransparent sets the state if the window is transparent. @@ -653,14 +655,10 @@ func IsScreenTransparent() bool { // // Deprecated: as of v2.5. Use RunGameWithOptions instead. func SetScreenTransparent(transparent bool) { - if transparent { - atomic.StoreInt32(&screenTransparent, 1) - } else { - atomic.StoreInt32(&screenTransparent, 0) - } + screenTransparent.Store(transparent) } -var screenTransparent int32 = 0 +var screenTransparent atomic.Bool // SetInitFocused sets whether the application is focused on show. // The default value is true, i.e., the application is focused. @@ -673,14 +671,10 @@ var screenTransparent int32 = 0 // // Deprecated: as of v2.5. Use RunGameWithOptions instead. func SetInitFocused(focused bool) { - if focused { - atomic.StoreInt32(&initUnfocused, 0) - } else { - atomic.StoreInt32(&initUnfocused, 1) - } + initUnfocused.Store(!focused) } -var initUnfocused int32 = 0 +var initUnfocused atomic.Bool func toUIRunOptions(options *RunGameOptions) *ui.RunOptions { const ( @@ -690,8 +684,8 @@ func toUIRunOptions(options *RunGameOptions) *ui.RunOptions { if options == nil { return &ui.RunOptions{ - InitUnfocused: atomic.LoadInt32(&initUnfocused) != 0, - ScreenTransparent: atomic.LoadInt32(&screenTransparent) != 0, + InitUnfocused: initUnfocused.Load(), + ScreenTransparent: screenTransparent.Load(), X11ClassName: defaultX11ClassName, X11InstanceName: defaultX11InstanceName, } diff --git a/shader.go b/shader.go index 762289bc6..7ae669da4 100644 --- a/shader.go +++ b/shader.go @@ -107,7 +107,7 @@ func builtinShader(filter builtinshader.Filter, address builtinshader.Address, u shader = &Shader{shader: ui.LinearFilterShader} } } else { - src := builtinshader.Shader(filter, address, useColorM) + src := builtinshader.ShaderSource(filter, address, useColorM) s, err := NewShader(src) if err != nil { panic(fmt.Sprintf("ebiten: NewShader for a built-in shader failed: %v", err)) diff --git a/shaderprecomp/shaderprecomp.go b/shaderprecomp/shaderprecomp.go new file mode 100644 index 000000000..94eb0b408 --- /dev/null +++ b/shaderprecomp/shaderprecomp.go @@ -0,0 +1,43 @@ +// 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 shaderprecomp + +import ( + "github.com/hajimehoshi/ebiten/v2/internal/builtinshader" +) + +// AppendBuildinShaderSources appends all the built-in shader sources to the given slice. +// +// Do not modify the content of the shader source. +// +// AppendBuildinShaderSources is concurrent-safe. +func AppendBuildinShaderSources(sources []*ShaderSource) []*ShaderSource { + for _, s := range builtinshader.AppendShaderSources(nil) { + sources = append(sources, NewShaderSource(s)) + } + return sources +} + +// ShaderSource is an object encapsulating a shader source code. +type ShaderSource struct { + source []byte +} + +// NewShaderSource creates a new ShaderSource object from the given source code. +func NewShaderSource(source []byte) *ShaderSource { + return &ShaderSource{ + source: source, + } +} diff --git a/shaderprecomp/shaderprecomp_darwin.go b/shaderprecomp/shaderprecomp_darwin.go new file mode 100644 index 000000000..51bb06843 --- /dev/null +++ b/shaderprecomp/shaderprecomp_darwin.go @@ -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. + +//go:build !playstation5 + +package shaderprecomp + +import ( + "io" + + "github.com/hajimehoshi/ebiten/v2/internal/graphics" + "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal" + "github.com/hajimehoshi/ebiten/v2/internal/shaderir/msl" +) + +// CompileToMSL compiles the shader source to Metal Shader Language, and writes the result to w. +// +// CompileToMSL is concurrent-safe. +func CompileToMSL(w io.Writer, source *ShaderSource) error { + ir, err := graphics.CompileShader(source.source) + if err != nil { + return err + } + if _, err = w.Write([]byte(msl.Compile(ir))); err != nil { + return err + } + return nil +} + +// RegisterMetalLibrary registers a precompiled Metal library for a shader source. +// library must be the content of a .metallib file. +// For more details, see https://developer.apple.com/documentation/metal/shader_libraries/building_a_shader_library_by_precompiling_source_files. +// +// RegisterMetalLibrary is concurrent-safe. +func RegisterMetalLibrary(source *ShaderSource, library []byte) { + metal.RegisterPrecompiledLibrary(source.source, library) +} diff --git a/shaderprecomp/shaderprecomp_playstation5.go b/shaderprecomp/shaderprecomp_playstation5.go new file mode 100644 index 000000000..71229a048 --- /dev/null +++ b/shaderprecomp/shaderprecomp_playstation5.go @@ -0,0 +1,50 @@ +// 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 + +package shaderprecomp + +import ( + "io" + + "github.com/hajimehoshi/ebiten/v2/internal/graphics" + "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/playstation5" + "github.com/hajimehoshi/ebiten/v2/internal/shaderir/pssl" +) + +// CompileToPSSL compiles the shader source to PlayStaton Shader Language to writers. +// +// CompileToPSSL is concurrent-safe. +func CompileToPSSL(vertexWriter, pixelWriter io.Writer, source *ShaderSource) error { + ir, err := graphics.CompileShader(source.source) + if err != nil { + return err + } + vs, ps := pssl.Compile(ir) + if _, err = vertexWriter.Write([]byte(vs)); err != nil { + return err + } + if _, err = pixelWriter.Write([]byte(ps)); err != nil { + return err + } + return nil +} + +// RegisterPlayStationShaders registers a precompiled PlayStation Shader for a shader source. +// +// RegisterPlayStationShaders is concurrent-safe. +func RegisterPlayStationShaders(source *ShaderSource, vertexShader, pixelShader []byte) { + playstation5.RegisterPrecompiledShaders(source.source, vertexShader, pixelShader) +} diff --git a/shaderprecomp/shaderprecomp_windows.go b/shaderprecomp/shaderprecomp_windows.go new file mode 100644 index 000000000..19121e5c6 --- /dev/null +++ b/shaderprecomp/shaderprecomp_windows.go @@ -0,0 +1,66 @@ +// 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 + +package shaderprecomp + +import ( + "io" + + "github.com/hajimehoshi/ebiten/v2/internal/graphics" + "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/directx" + "github.com/hajimehoshi/ebiten/v2/internal/shaderir/hlsl" +) + +const ( + // HLSLVertexShaderProfile is the target profile for vertex shaders. + HLSLVertexShaderProfile = directx.VertexShaderProfile + + // HLSLPixelShaderProfile is the target profile for pixel shaders. + HLSLPixelShaderProfile = directx.PixelShaderProfile + + // HLSLVertexShaderEntryPoint is the entry point name for vertex shaders. + HLSLVertexShaderEntryPoint = directx.VertexShaderEntryPoint + + // HLSLPixelShaderEntryPoint is the entry point name for pixel shaders. + HLSLPixelShaderEntryPoint = directx.PixelShaderEntryPoint +) + +// CompileToHLSL compiles the shader source to High-Level Shader Language to writers. +// +// CompileToHLSL is concurrent-safe. +func CompileToHLSL(vertexWriter, pixelWriter io.Writer, source *ShaderSource) error { + ir, err := graphics.CompileShader(source.source) + if err != nil { + return err + } + vs, ps := hlsl.Compile(ir) + if _, err = vertexWriter.Write([]byte(vs)); err != nil { + return err + } + if _, err = pixelWriter.Write([]byte(ps)); err != nil { + return err + } + return nil +} + +// RegisterFXCs registers a precompiled HLSL (FXC) for a shader source. +// vertexFXC and pixelFXC must be the content of .fxc files generated by `fxc` command. +// For more details, see https://learn.microsoft.com/en-us/windows/win32/direct3dtools/dx-graphics-tools-fxc-using. +// +// RegisterFXCs is concurrent-safe. +func RegisterFXCs(source *ShaderSource, vertexFXC, pixelFXC []byte) { + directx.RegisterPrecompiledFXCs(source.source, vertexFXC, pixelFXC) +} diff --git a/text/v2/gotext.go b/text/v2/gotext.go index 65b036180..7b42576dd 100644 --- a/text/v2/gotext.go +++ b/text/v2/gotext.go @@ -51,7 +51,7 @@ type GoTextFace struct { // Size is the font size in pixels. Size float64 - // Language is a hiint for a language (BCP 47). + // Language is a hint for a language (BCP 47). Language language.Tag // Script is a hint for a script code hint of (ISO 15924). diff --git a/text/v2/gotextfacesource.go b/text/v2/gotextfacesource.go index 4964ab06f..3e84adde6 100644 --- a/text/v2/gotextfacesource.go +++ b/text/v2/gotextfacesource.go @@ -71,6 +71,8 @@ type GoTextFaceSource struct { addr *GoTextFaceSource + shaper shaping.HarfbuzzShaper + m sync.Mutex } @@ -176,14 +178,16 @@ func (g *GoTextFaceSource) shape(text string, face *GoTextFace) ([]shaping.Outpu return out.outputs, out.glyphs } - g.f.SetVariations(face.variations) + f := face.Source.f + f.SetVariations(face.variations) + runes := []rune(text) input := shaping.Input{ Text: runes, RunStart: 0, RunEnd: len(runes), Direction: face.diDirection(), - Face: face.Source.f, + Face: f, FontFeatures: face.features, Size: float64ToFixed26_6(face.Size), Script: face.gScript(), @@ -191,7 +195,7 @@ func (g *GoTextFaceSource) shape(text string, face *GoTextFace) ([]shaping.Outpu } var seg shaping.Segmenter - inputs := seg.Split(input, &singleFontmap{face: face.Source.f}) + inputs := seg.Split(input, &singleFontmap{face: f}) if face.Direction == DirectionRightToLeft { // Reverse the input for RTL texts. @@ -203,7 +207,7 @@ func (g *GoTextFaceSource) shape(text string, face *GoTextFace) ([]shaping.Outpu outputs := make([]shaping.Output, len(inputs)) var gs []glyph for i, input := range inputs { - out := (&shaping.HarfbuzzShaper{}).Shape(input) + out := g.shaper.Shape(input) outputs[i] = out (shaping.Line{out}).AdjustBaselines() @@ -220,7 +224,7 @@ func (g *GoTextFaceSource) shape(text string, face *GoTextFace) ([]shaping.Outpu switch data := g.f.GlyphData(gl.GlyphID).(type) { case api.GlyphOutline: if out.Direction.IsSideways() { - data.Sideways(fixed26_6ToFloat32(-gl.YOffset) / fixed26_6ToFloat32(out.Size) * float32(face.Source.f.Upem())) + data.Sideways(fixed26_6ToFloat32(-gl.YOffset) / fixed26_6ToFloat32(out.Size) * float32(f.Upem())) } segs = data.Segments case api.GlyphSVG: diff --git a/vector/path.go b/vector/path.go index 0474d75de..b381b15ad 100644 --- a/vector/path.go +++ b/vector/path.go @@ -396,7 +396,7 @@ func (p *Path) Close() { // // The returned vertice's SrcX and SrcY are 0, and ColorR, ColorG, ColorB, and ColorA are 1. // -// The returned values are intended to be passed to DrawTriangles or DrawTrianglesShader with the fill rule NonZero or EvenOdd +// The returned values are intended to be passed to DrawTriangles or DrawTrianglesShader with FileRuleNonZero or FillRuleEvenOdd // in order to render a complex polygon like a concave polygon, a polygon with holes, or a self-intersecting polygon. // // The returned vertices and indices should be rendered with a solid (non-transparent) color with the default Blend (source-over). @@ -480,7 +480,7 @@ type StrokeOptions struct { // The returned vertice's SrcX and SrcY are 0, and ColorR, ColorG, ColorB, and ColorA are 1. // // The returned values are intended to be passed to DrawTriangles or DrawTrianglesShader with a solid (non-transparent) color -// with FillAll or NonZero fill rule, not EvenOdd fill rule. +// with FillRuleFillAll or FillRuleNonZero, not FileRuleEvenOdd. func (p *Path) AppendVerticesAndIndicesForStroke(vertices []ebiten.Vertex, indices []uint16, op *StrokeOptions) ([]ebiten.Vertex, []uint16) { if op == nil { return vertices, indices @@ -536,7 +536,7 @@ func (p *Path) AppendVerticesAndIndicesForStroke(vertices []ebiten.Vertex, indic ColorA: 1, }) } - // All the triangles are rendered in clockwise order to enable NonZero filling rule (#2833). + // All the triangles are rendered in clockwise order to enable FillRuleNonZero (#2833). indices = append(indices, idx, idx+1, idx+2, idx+1, idx+3, idx+2) // Add line joints. diff --git a/window.go b/window.go index b5945fdcb..399785415 100644 --- a/window.go +++ b/window.go @@ -155,16 +155,16 @@ func WindowPosition() (x, y int) { // // SetWindowPosition is concurrent-safe. func SetWindowPosition(x, y int) { - atomic.StoreUint32(&windowPositionSetExplicitly, 1) + windowPositionSetExplicitly.Store(true) ui.Get().Window().SetPosition(x, y) } var ( - windowPositionSetExplicitly uint32 + windowPositionSetExplicitly atomic.Bool ) func initializeWindowPositionIfNeeded(width, height int) { - if atomic.LoadUint32(&windowPositionSetExplicitly) == 0 { + if !windowPositionSetExplicitly.Load() { sw, sh := ui.Get().Monitor().Size() x, y := ui.InitialWindowPosition(sw, sh, width, height) ui.Get().Window().SetPosition(x, y)