Compare commits

...

106 Commits

Author SHA1 Message Date
Bertrand Jung
419d187488
Merge f34c1c081f into 22fd1f107e 2024-06-09 17:09:16 +00:00
Zyko
f34c1c081f Merge with main - fixed conflicts 2024-06-09 19:09:03 +02:00
Bertrand Jung
22fd1f107e
internal/graphics: renamed shader image count to specify src (#3012)
This just specifies that the constant refers to the number of source images passed to a shader.

This makes a distinction with the number of dst images, that could potentially be more than 1 in the future.
2024-06-10 02:02:47 +09:00
Zyko
b405003a35 Fixed misses 2024-06-09 17:41:11 +02:00
Zyko
6a00b8680a Merge with main - fixed conflicts 2024-06-09 17:35:03 +02:00
Zyko
c180c3c9d1 Little tweak to dx12 2024-06-09 17:16:28 +02:00
Hajime Hoshi
aa6bc1690e .github/workflows: typo
Updates #2972
2024-06-09 11:54:52 +09:00
Hajime Hoshi
b121468991 ebiten: add FillRuleFillAll, FillRuleEvenOdd, and FillRuleNonZero
This change also deprecates the existing constants.

Closes #3006
2024-06-08 17:58:33 +09:00
Hajime Hoshi
d37301eeeb .github/workflow: disable wasm tests on Windows temporarily
Updates #2982
2024-06-08 14:34:04 +09:00
Hajime Hoshi
e5d10c47e7 internal/graphicsdriver: reland: rename FillRule constants
Updates #3006
2024-06-08 12:16:20 +09:00
Hajime Hoshi
6ac1270cb0 Revert "internal/graphicsdriver: rename FillRule constants"
This reverts commit ab4a3af1b5.

Reason: compile error on Windows
2024-06-08 12:10:27 +09:00
Hajime Hoshi
ab4a3af1b5 internal/graphicsdriver: rename FillRule constants
Updates #3006
2024-06-08 11:54:46 +09:00
Hajime Hoshi
78ba0ded93 Revert "internal/glfw: bug fix: limit the DWM swap interval to Vista and 7"
This reverts commit 86e0bcc264.

Reason: This caused some issues like too much GPU usages.

Updates #2961
Closes #3003
2024-06-04 21:06:51 +09:00
XXIV
216a110761
internal/glfw: fix memory leak (#3008) 2024-06-04 13:00:20 +09:00
Matúš Ollah
7ddc349ae6
text/v2: fix typo (#3004) 2024-05-31 00:57:32 +09:00
Hajime Hoshi
d0aaa23005 Revert "internal/atlas: reduce slice allocations"
This reverts commit 9442b244fc.

Reason: the slice was not escaped to heap, so this optimization was not needed
2024-05-27 23:47:39 +09:00
Hajime Hoshi
9442b244fc internal/atlas: reduce slice allocations 2024-05-27 23:32:17 +09:00
Hajime Hoshi
4818768965 shaderprecomp: remove ShaderSourceID
`ShaderSourceID` was confusing as there was no guarantee the same ID is
used for the same source if Ebitengine versions are different.

`ShaderSource` should be kept as the built-in shader contents should not
be exposed.

Updates #2861
Closes #2999
2024-05-27 00:01:05 +09:00
Hajime Hoshi
83ae577c80 Revert "shaderprecomp: remove ShaderSource and ShaderSourceID"
This reverts commit 8be3bb41d5.

Reason: removing `ShaderSource` unexpected exposes the source.

Updates #2999
2024-05-26 22:45:12 +09:00
Hajime Hoshi
8be3bb41d5 shaderprecomp: remove ShaderSource and ShaderSourceID
This change simplifies the APIs to avoid some confusions around IDs.

Updates #2861
Closes #2999
2024-05-26 22:42:52 +09:00
Hajime Hoshi
3279688dd6 Revert "shaderprecomp: accept an ID instead of source to register"
This reverts commit ecc3f29af1.

Reason: we are considering to remove ShaderSourceID

Updates #2861
Updates #2999
2024-05-26 21:29:44 +09:00
Hajime Hoshi
ecc3f29af1 shaderprecomp: accept an ID instead of source to register
Updates #2861
2024-05-26 19:39:30 +09:00
Hajime Hoshi
1c438cb5c8 examples/shaderprecomp/fxc, examples/shaderprecomp/metallib: add go:build 2024-05-26 16:05:58 +09:00
Hajime Hoshi
13c7518400 all: update bitmapfont 2024-05-26 15:55:48 +09:00
Hajime Hoshi
38d2892906 internal/glfw: bug fix: the cursor position was reset unexpectedly
There was a mistake when updating GLFW to v3.3.9 at 4647e9de53.
When the cursor mode is set to be enabled, the cursor position was
unexpectedly reset. This change fixes the issue.

Closes #2997
2024-05-26 15:11:50 +09:00
Hajime Hoshi
5942192b66 audio/vorbis: refactoring 2024-05-21 02:42:43 +09:00
Hajime Hoshi
b0a4b6ebbf internal/graphicsdriver/playstation5: update the copyright year 2024-05-19 23:01:47 +09:00
Hajime Hoshi
657e04d3d1 internal/graphicsdriver/playstation5: separate the shader part 2024-05-19 18:05:16 +09:00
Hajime Hoshi
002e375d64 examples/audio: remove unneeded resampling 2024-05-18 22:15:24 +09:00
Hajime Hoshi
a612e74031 audio/wav: add (*Stream).SampleRate
Closes #2996
2024-05-18 20:19:27 +09:00
Hajime Hoshi
ac83181403 audio/mp3: add (*Stream).SampleRate
Updates #2996
2024-05-18 20:13:36 +09:00
Hajime Hoshi
d2c58dac8c audio/vorbis: add (*Stream).SampleRate
Updates #2996
2024-05-18 20:09:37 +09:00
Hajime Hoshi
fbd067c96e .github/workflows: fix tests for Linux 386
Updates #2667
Closes #2995
2024-05-18 18:09:54 +09:00
Hajime Hoshi
53de367d47 all: update typesetting to v0.1.1 2024-05-18 17:08:05 +09:00
Hajime Hoshi
2c2a1fe859 all: update gomobile
This fixes the issue with Android SDK 34.

Closes #2992
2024-05-18 16:53:58 +09:00
Hajime Hoshi
5bb060b1e9 shaderprecomp: add pssl 2024-05-12 15:07:38 +09:00
Hajime Hoshi
a108dac797 internal/shaderir: add 'pssl' package 2024-05-11 18:58:59 +09:00
Hajime Hoshi
bfa9435906 ebiten: add comments about Layout and LayoutF
Updates #2988
2024-05-10 18:24:49 +09:00
Hajime Hoshi
db454548be all: update README 2024-05-09 12:09:47 +09:00
Hajime Hoshi
724ce25260 all: update dependencies 2024-05-09 03:32:22 +09:00
Hajime Hoshi
5926b37c98 all: update golang.org/x/sys to v0.20.0 2024-05-06 22:11:27 +09:00
Hajime Hoshi
b3ad97b22b internal/graphicsdriver/metal/mtl: add comments 2024-05-06 22:07:16 +09:00
Hajime Hoshi
f0ca3f1870 internal/graphicsdriver/metal/mtl: follow ObjC convention
Closes #2981
2024-05-06 21:51:57 +09:00
Hajime Hoshi
2261cf76de internal/graphicsdriver/metal/ca: follow ObjC convention
Updates #2981
2024-05-06 19:36:17 +09:00
Hajime Hoshi
a391da6c77 examples/shaderprecomp/metallib: stop using errgroup 2024-05-06 17:25:21 +09:00
Hajime Hoshi
10d9660125 shaderprecomp: implement for Windows
Closes #2861
2024-05-06 16:03:57 +09:00
Hajime Hoshi
5d4a68b0ea internal/shaderir/hlsl: refactoring: separate calculation uniform offsets 2024-05-05 20:47:35 +09:00
Hajime Hoshi
a41af4528b internal/graphicsdriver/directx: refactoring 2024-05-05 18:58:13 +09:00
Hajime Hoshi
caab1ee29f internal/graphicsdriver/metal: refactoring 2024-05-05 17:18:06 +09:00
Hajime Hoshi
aace620b7e all: update PureGo to v0.8.0-alpha.2 2024-05-05 15:10:05 +09:00
Hajime Hoshi
42eef43136 examples/shaderprecomp: add . 2024-05-05 04:53:14 +09:00
Hajime Hoshi
c46f62e184 all: add a new package shaderprecomp
The current implementation is only for macOS so far.

Updates #2861
2024-05-05 03:51:04 +09:00
Hajime Hoshi
d7df5ebcbd internal/builtinshader: move a clearing shader to builtinshader 2024-05-05 02:18:14 +09:00
Hajime Hoshi
ca9a80600d internal/graphicsdriver/metal: release MTLLibrary 2024-05-04 19:26:12 +09:00
Hajime Hoshi
322ad99568 audio/mp3, audio/vorbis, audio/wav: add comments about cache 2024-05-04 17:30:18 +09:00
Hajime Hoshi
c3b9afe8c4 internal/ui: bug fix: compile error for browsers 2024-05-04 00:30:43 +09:00
Hajime Hoshi
1ebfa8b911 internal/ui: refactoring: remove unused code 2024-05-04 00:26:40 +09:00
Hajime Hoshi
c658a25171 all: replace execabs with os/exec
os/exec no longer searches executablse in the current directory as of
Go 1.19. See https://go.dev/blog/path-security and https://go.dev/issue/43724.
2024-05-03 23:39:38 +09:00
Hajime Hoshi
903ab6727b internal/ui: better panic message at ReadPixels before RunGame
Closes #2979
2024-05-03 16:36:19 +09:00
Hajime Hoshi
35e29a29e7 internal/ui: bug fix: wrong property names were specified
Closes #2975
2024-04-30 23:32:54 +09:00
Hajime Hoshi
e567a8c693 .github/workflows: update wasmbrowsertest 2024-04-30 00:45:00 +09:00
Hajime Hoshi
0af5b41d48 internal/atlas: refactoring 2024-04-29 23:34:37 +09:00
Hajime Hoshi
12876343ff .github/workflows: allow go-vet for Go 1.19 2024-04-29 22:19:55 +09:00
Hajime Hoshi
bb799da51f internal/ui: use atomic.Int32 instead of atomic.Store/LoadInt32
Updates #2422
2024-04-29 21:53:43 +09:00
Hajime Hoshi
59fb259181 internal/graphicsdriver/gl: use PureGo for Linux and UNIX
Updates #2284
2024-04-29 21:25:15 +09:00
Hajime Hoshi
f34932151d all: use atomic.Bool instead of atomic.Store/LoadUint32
Updates #2422
2024-04-29 21:16:01 +09:00
Hajime Hoshi
f2a18ed6ba all: bug fix: upgrade the Go version to 1.19
golang.org/x/tools v0.20.0 no longer supports Go 1.18.
2024-04-29 18:58:17 +09:00
Hajime Hoshi
9c374a958e all: update gomobile 2024-04-29 18:52:14 +09:00
Hajime Hoshi
359b5abb62 all: update dependencies 2024-04-29 18:45:28 +09:00
Hajime Hoshi
c390f0a9fa internal/shader: bug fix: treat a built-in function as an invalid argument
Closes #2965
2024-04-29 16:44:33 +09:00
Hajime Hoshi
13d15b0ed9 .github/workflows: fix comments
Updates #2972
2024-04-29 09:58:00 +09:00
guangwu
97a9ee9601
ebitenutil: close an HTTP response body (#2971)
Signed-off-by: guoguangwu <guoguangwug@gmail.com>
2024-04-29 09:43:11 +09:00
Zyko
ec592390e7 nth attempt at fixing directx 12 2024-04-14 18:39:01 +02:00
Zyko
9881530b46 Attempt at fixing Directx 12 2024-04-14 11:19:10 +02:00
Zyko
483059184f Debug image entry index 2024-04-13 14:56:59 +02:00
Zyko
dc6074bec9 Fixed stencil tests for directx 2024-04-13 14:23:17 +02:00
Zyko
dbe06ec468 Add a test when a single image is used with DrawMRT 2024-04-13 14:04:39 +02:00
Zyko
d706d38ada Add test with empty locations + fix directx 11 && 12 nil first argument 2024-04-13 13:56:13 +02:00
Zyko
0b53525808 Add a basic MRT test 2024-04-13 11:40:31 +02:00
Zyko
7dd4aa9150 Directx 12 mrt working - 1st iteration 2024-04-13 01:11:48 +02:00
Zyko
2faf8a551d Set max dst images to 8 + some wording 2024-04-12 19:19:17 +02:00
Zyko
75b6e1fa6d Fixed conflict 2024-04-12 18:39:47 +02:00
Zyko
f021b5ded8 Fixed IR tests + skipping metal for now 2024-04-12 18:34:29 +02:00
Zyko
b2b88f4bdd Disable metal shader compilation tests tmp 2024-04-12 00:17:12 +02:00
Zyko
042fdab3d0 Fixed shader testdata (except msl) 2024-04-12 00:09:36 +02:00
Zyko
15ccaf1998 Fixed ps5 src argument constant name 2024-04-10 22:06:56 +02:00
Zyko
15b5c888ca Fixed DrawTriangles for ps5 2024-04-10 22:00:37 +02:00
Zyko
8c0bc0a2e0 Implement the correct DrawTriangles definition on metal 2024-04-10 21:55:00 +02:00
Zyko
ced0c62827 go vet error check 2024-04-10 21:11:32 +02:00
Zyko
6a8c00e0aa Fixed opengl tests 2024-04-10 21:08:03 +02:00
Zyko
92a257a557 directx: Better logic to assume MRT 2024-04-10 20:17:20 +02:00
Zyko
65646df8ed Temporary fix for directx11 2024-04-10 20:12:10 +02:00
Zyko
c73221ac16 Merge upstream - fixed conflicts 2024-04-10 19:15:34 +02:00
Zyko
1b3bfda17b Fixed empty target for directx11 2024-04-10 19:10:21 +02:00
Zyko
c9eb30d66f Fixed magic number 2024-04-10 18:59:21 +02:00
Zyko
55f1a5d32e (Fixed) webgl 2024-04-10 18:57:55 +02:00
Zyko
fc3a6ed373 Fixed nil dst image checks 2024-04-10 18:56:25 +02:00
Zyko
c3a358b44b Fixed missing arg on gl with CGo 2024-04-09 02:40:21 +02:00
Zyko
d1fd70495b Revert to gl_FragData (future webgl) 2024-04-09 02:26:29 +02:00
Zyko
c7eeae7189 Cleanup 2024-04-09 01:00:26 +02:00
Zyko
4536fadebe Update hlsl 2024-04-08 23:48:16 +02:00
Zyko
280cc1a732 Update glsl 2024-04-08 23:43:19 +02:00
Zyko
577664c1cb Dirty directx11 2024-04-06 14:54:32 +02:00
Zyko
c247da0f05 Remove useless debug + setviewport only once 2024-04-06 11:40:12 +02:00
Zyko
9003692630 Restore unmanaged images 2024-04-06 11:21:04 +02:00
Zyko
32b5e3edd8 Saving 2024-04-05 20:39:14 +02:00
117 changed files with 3441 additions and 946 deletions

View File

@ -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@ee76d31b7b9b1645576c1f51fec4c09fe6cf1bb3
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@ee76d31b7b9b1645576c1f51fec4c09fe6cf1bb3
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,9 +165,10 @@ jobs:
env GOARCH=386 EBITENGINE_DIRECTX=version=12 go test -shuffle=on -v ./...
- name: go test (Wasm)
if: runner.os != 'macOS'
if: ${{ runner.os != 'macOS' && runner.os != 'Windows' }}
run: |
# Wasm tests don't work on macOS with the headless mode, and the headless mode doesn't work in GitHub Actions (#2973).
# 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

5
.gitignore vendored
View File

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

129
README.md
View File

@ -61,3 +61,132 @@ For installation on desktops, see [the installation instruction](https://ebiteng
Ebitengine is licensed under Apache license version 2.0. See [LICENSE](LICENSE) file.
[The Ebitengine logo](https://ebitengine.org/images/logo.png) by Hajime Hoshi is licensed under [the Creative Commons Attribution-NoDerivatives 4.0](https://creativecommons.org/licenses/by-nd/4.0/).
### GLFW
https://github.com/glfw/glfw
```
Copyright (c) 2002-2006 Marcus Geelnard
Copyright (c) 2006-2019 Camilla Löwy
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would
be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not
be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
```
### Go
https://cs.opensource.google/go/go
```
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
```
### go-gl/gl
https://github.com/go-gl/gl
```
The MIT License (MIT)
Copyright (c) 2014 Eric Woroshow
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
### go-gl/glfw
https://github.com/go-gl/glfw
```
Copyright (c) 2012 The glfw3-go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
```

View File

@ -31,6 +31,7 @@ import (
type Stream struct {
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
}

View File

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

View File

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

View File

@ -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.

View File

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

View File

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

View File

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

View File

@ -72,11 +72,11 @@ type DrawTrianglesOptions struct {
// FillRule indicates the rule how an overlapped region is rendered.
//
// The rules NonZero and EvenOdd are useful when you want to render a complex polygon.
// The rules FileRuleNonZero and FillRuleEvenOdd are useful when you want to render a complex polygon.
// A complex polygon is a non-convex polygon like a concave polygon, a polygon with holes, or a self-intersecting polygon.
// See examples/vector for actual usages.
//
// The default (zero) value is ebiten.FillAll.
// The default (zero) value is ebiten.FillRuleFillAll.
FillRule ebiten.FillRule
// AntiAlias indicates whether the rendering uses anti-alias or not.

32
ebiten_test.go Normal file
View File

@ -0,0 +1,32 @@
// Copyright 2024 The Ebitengine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ebiten_test
import (
"testing"
"github.com/hajimehoshi/ebiten/v2"
)
func TestScreenSizeInFullscreen(t *testing.T) {
// Just call ScreenSizeInFullscreen. There was a crash bug on browsers (#2975).
w, h := ebiten.ScreenSizeInFullscreen()
if w <= 0 {
t.Errorf("w must be positive but not: %d", w)
}
if h <= 0 {
t.Errorf("h must be positive but not: %d", h)
}
}

View File

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

View File

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

134
examples/mrt/main.go Normal file
View File

@ -0,0 +1,134 @@
// 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 main
import (
"fmt"
"image"
_ "image/jpeg"
"log"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
)
const (
dstSize = 128
screenWidth = dstSize * 2
screenHeight = dstSize * 2
)
var (
dsts = [8]*ebiten.Image{
/*ebiten.NewImage(dstSize, dstSize),
ebiten.NewImage(dstSize, dstSize),
ebiten.NewImage(dstSize, dstSize),
ebiten.NewImage(dstSize, dstSize),*/
ebiten.NewImageWithOptions(image.Rect(0, 0, dstSize, dstSize), &ebiten.NewImageOptions{
Unmanaged: true,
}),
ebiten.NewImageWithOptions(image.Rect(0, 0, dstSize, dstSize), &ebiten.NewImageOptions{
Unmanaged: true,
}),
ebiten.NewImageWithOptions(image.Rect(0, 0, dstSize, dstSize), &ebiten.NewImageOptions{
Unmanaged: true,
}),
ebiten.NewImageWithOptions(image.Rect(0, 0, dstSize, dstSize), &ebiten.NewImageOptions{
Unmanaged: true,
}),
}
shaderSrc = []byte(
`
//kage:units pixels
package main
func Fragment(dst vec4, src vec2, color vec4) (vec4, vec4, vec4, vec4) {
return vec4(1,0,0,1), vec4(0,1,0,1), vec4(0,0,1,1), vec4(1,0,1,1)
}
`)
s *ebiten.Shader
)
func init() {
var err error
s, err = ebiten.NewShader(shaderSrc)
if err != nil {
log.Fatal(err)
}
}
type Game struct {
}
func (g *Game) Update() error {
return nil
}
func (g *Game) Draw(screen *ebiten.Image) {
vertices := []ebiten.Vertex{
{
DstX: 0,
DstY: 0,
},
{
DstX: dstSize,
DstY: 0,
},
{
DstX: 0,
DstY: dstSize,
},
{
DstX: dstSize,
DstY: dstSize,
},
}
indices := []uint16{0, 1, 2, 1, 2, 3}
ebiten.DrawTrianglesShaderMRT(dsts, vertices, indices, s, nil)
// Dst 0
screen.DrawImage(dsts[0], nil)
// Dst 1
opts := &ebiten.DrawImageOptions{}
opts.GeoM.Translate(dstSize, 0)
screen.DrawImage(dsts[1], opts)
// Dst 2
opts.GeoM.Reset()
opts.GeoM.Translate(0, dstSize)
screen.DrawImage(dsts[2], opts)
// Dst 3
opts.GeoM.Reset()
opts.GeoM.Translate(dstSize, dstSize)
screen.DrawImage(dsts[3], opts)
ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %.2f", ebiten.ActualFPS()))
}
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
return screenWidth, screenHeight
}
func main() {
ebiten.SetWindowSize(screenWidth, screenHeight)
ebiten.SetVsyncEnabled(false)
ebiten.SetWindowTitle("MRT (Ebitengine Demo)")
if err := ebiten.RunGameWithOptions(&Game{}, &ebiten.RunGameOptions{
GraphicsLibrary: ebiten.GraphicsLibraryDirectX,
}); err != nil {
log.Fatal(err)
}
}

View File

@ -0,0 +1,33 @@
// Copyright 2020 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build ignore
//kage:unit pixels
package main
var Time float
var Cursor vec2
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
pos := (dstPos.xy - imageDstOrigin()) / imageDstSize()
pos += Cursor / imageDstSize() / 4
clr := 0.0
clr += sin(pos.x*cos(Time/15)*80) + cos(pos.y*cos(Time/15)*10)
clr += sin(pos.y*sin(Time/10)*40) + cos(pos.x*sin(Time/25)*40)
clr += sin(pos.x*sin(Time/5)*10) + sin(pos.y*sin(Time/35)*80)
clr *= sin(Time/10) * 0.5
return vec4(clr, clr*0.5, sin(clr+Time/3)*0.75, 1)
}

View File

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

View File

@ -0,0 +1,123 @@
// Copyright 2024 The Ebitengine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build ignore
// This is a program to generate precompiled HLSL blobs (FXC files).
//
// See https://learn.microsoft.com/en-us/windows/win32/direct3dtools/fxc.
package main
import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"github.com/hajimehoshi/ebiten/v2/shaderprecomp"
)
func main() {
if err := run(); err != nil {
panic(err)
}
}
func run() error {
if _, err := exec.LookPath("fxc.exe"); err != nil {
if errors.Is(err, exec.ErrNotFound) {
fmt.Fprintln(os.Stderr, "fxc.exe not found. Please install Windows SDK.")
fmt.Fprintln(os.Stderr, "See https://learn.microsoft.com/en-us/windows/win32/direct3dtools/fxc for more details.")
fmt.Fprintln(os.Stderr, "HINT: On PowerShell, you can add a path to the PATH environment variable temporarily like:")
fmt.Fprintln(os.Stderr)
fmt.Fprintln(os.Stderr, ` & (Get-Process -Id $PID).Path { $env:PATH="C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64;"+$env:PATH; go generate .\examples\shaderprecomp\fxc\ }`)
fmt.Fprintln(os.Stderr)
os.Exit(1)
}
return err
}
tmpdir, err := os.MkdirTemp("", "")
if err != nil {
return err
}
defer os.RemoveAll(tmpdir)
srcs := shaderprecomp.AppendBuildinShaderSources(nil)
defaultSrcBytes, err := os.ReadFile(filepath.Join("..", "defaultshader.go"))
if err != nil {
return err
}
srcs = append(srcs, shaderprecomp.NewShaderSource(defaultSrcBytes))
for i, src := range srcs {
// Avoid using errgroup.Group.
// Compiling sources in parallel causes a mixed error message on the console.
if err := compile(src, i, tmpdir); err != nil {
return err
}
}
return nil
}
func generateHSLSFiles(source *shaderprecomp.ShaderSource, index int, tmpdir string) (vs, ps string, err error) {
vsHLSLFilePath := filepath.Join(tmpdir, fmt.Sprintf("%d_vs.hlsl", index))
vsf, err := os.Create(vsHLSLFilePath)
if err != nil {
return "", "", err
}
defer vsf.Close()
psHLSLFilePath := filepath.Join(tmpdir, fmt.Sprintf("%d_ps.hlsl", index))
psf, err := os.Create(psHLSLFilePath)
if err != nil {
return "", "", err
}
defer psf.Close()
if err := shaderprecomp.CompileToHLSL(vsf, psf, source); err != nil {
return "", "", err
}
return vsHLSLFilePath, psHLSLFilePath, nil
}
func compile(source *shaderprecomp.ShaderSource, index int, tmpdir string) error {
// Generate HLSL files. Make sure this process doesn't have any handlers of the files.
// Without closing the files, fxc.exe cannot access the files.
vsHLSLFilePath, psHLSLFilePath, err := generateHSLSFiles(source, index, tmpdir)
if err != nil {
return err
}
vsFXCFilePath := fmt.Sprintf("%d_vs.fxc", index)
cmd := exec.Command("fxc.exe", "/nologo", "/O3", "/T", shaderprecomp.HLSLVertexShaderProfile, "/E", shaderprecomp.HLSLVertexShaderEntryPoint, "/Fo", vsFXCFilePath, vsHLSLFilePath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
}
psFXCFilePath := fmt.Sprintf("%d_ps.fxc", index)
cmd = exec.Command("fxc.exe", "/nologo", "/O3", "/T", shaderprecomp.HLSLPixelShaderProfile, "/E", shaderprecomp.HLSLPixelShaderEntryPoint, "/Fo", psFXCFilePath, psHLSLFilePath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,19 @@
// Copyright 2024 The Ebitengine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build windows
//go:generate go run gen.go
package fxc

View File

@ -0,0 +1,74 @@
// Copyright 2024 The Ebitengine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
_ "embed"
"log"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
)
//go:embed defaultshader.go
var defaultShaderSourceBytes []byte
type Game struct {
defaultShader *ebiten.Shader
counter int
}
func (g *Game) Update() error {
g.counter++
if g.defaultShader == nil {
s, err := ebiten.NewShader(defaultShaderSourceBytes)
if err != nil {
return err
}
g.defaultShader = s
}
return nil
}
func (g *Game) Draw(screen *ebiten.Image) {
cx, cy := ebiten.CursorPosition()
w, h := screen.Bounds().Dx(), screen.Bounds().Dy()
op := &ebiten.DrawRectShaderOptions{}
op.Uniforms = map[string]interface{}{
"Time": float32(g.counter) / float32(ebiten.TPS()),
"Cursor": []float32{float32(cx), float32(cy)},
}
screen.DrawRectShader(w, h, g.defaultShader, op)
msg := `This is a test for shader precompilation.
Precompilation works only on macOS so far.
Note that this example still works even without shader precompilation.`
ebitenutil.DebugPrint(screen, msg)
}
func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
return outsideWidth, outsideHeight
}
func main() {
if err := registerPrecompiledShaders(); err != nil {
log.Fatal(err)
}
ebiten.SetWindowTitle("Ebitengine Example (Shader Precompilation)")
if err := ebiten.RunGame(&Game{}); err != nil {
log.Fatal(err)
}
}

View File

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

View File

@ -0,0 +1,93 @@
// Copyright 2024 The Ebitengine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build ignore
// This is a program to generate precompiled Metal libraries.
//
// See https://developer.apple.com/documentation/metal/shader_libraries/building_a_shader_library_by_precompiling_source_files.
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"github.com/hajimehoshi/ebiten/v2/shaderprecomp"
)
func main() {
if err := run(); err != nil {
panic(err)
}
}
func run() error {
tmpdir, err := os.MkdirTemp("", "")
if err != nil {
return err
}
defer os.RemoveAll(tmpdir)
srcs := shaderprecomp.AppendBuildinShaderSources(nil)
defaultSrcBytes, err := os.ReadFile(filepath.Join("..", "defaultshader.go"))
if err != nil {
return err
}
srcs = append(srcs, shaderprecomp.NewShaderSource(defaultSrcBytes))
for i, src := range srcs {
// Avoid using errgroup.Group.
// Compiling sources in parallel causes a mixed error message on the console.
if err := compile(src, i, tmpdir); err != nil {
return err
}
}
return nil
}
func compile(source *shaderprecomp.ShaderSource, index int, tmpdir string) error {
metalFilePath := filepath.Join(tmpdir, fmt.Sprintf("%d.metal", index))
f, err := os.Create(metalFilePath)
if err != nil {
return err
}
defer f.Close()
if err := shaderprecomp.CompileToMSL(f, source); err != nil {
return err
}
if err := f.Sync(); err != nil {
return err
}
irFilePath := filepath.Join(tmpdir, fmt.Sprintf("%d.ir", index))
cmd := exec.Command("xcrun", "-sdk", "macosx", "metal", "-o", irFilePath, "-c", metalFilePath)
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
}
metallibFilePath := fmt.Sprintf("%d.metallib", index)
cmd = exec.Command("xcrun", "-sdk", "macosx", "metallib", "-o", metallibFilePath, irFilePath)
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,19 @@
// Copyright 2024 The Ebitengine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build darwin
//go:generate go run gen.go
package metallib

View File

@ -0,0 +1,48 @@
// Copyright 2024 The Ebitengine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"embed"
"errors"
"fmt"
"io/fs"
"os"
"github.com/hajimehoshi/ebiten/v2/shaderprecomp"
)
//go:embed metallib/*.metallib
var metallibs embed.FS
func registerPrecompiledShaders() error {
srcs := shaderprecomp.AppendBuildinShaderSources(nil)
srcs = append(srcs, shaderprecomp.NewShaderSource(defaultShaderSourceBytes))
for i, src := range srcs {
name := fmt.Sprintf("%d.metallib", i)
lib, err := metallibs.ReadFile("metallib/" + name)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
fmt.Fprintf(os.Stderr, "precompiled Metal library %s was not found. Run 'go generate' for 'metallib' directory to generate them.\n", name)
continue
}
return err
}
shaderprecomp.RegisterMetalLibrary(src, lib)
}
return nil
}

View File

@ -0,0 +1,27 @@
// Copyright 2024 The Ebitengine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !darwin && !windows
package main
import (
"fmt"
"os"
)
func registerPrecompiledShaders() error {
fmt.Fprintf(os.Stderr, "precompiled shaders are not available in this environment.\n")
return nil
}

View File

@ -0,0 +1,61 @@
// Copyright 2024 The Ebitengine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"embed"
"errors"
"fmt"
"io/fs"
"os"
"github.com/hajimehoshi/ebiten/v2/shaderprecomp"
)
// https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/
//go:embed fxc/*.fxc
var fxcs embed.FS
func registerPrecompiledShaders() error {
srcs := shaderprecomp.AppendBuildinShaderSources(nil)
srcs = append(srcs, shaderprecomp.NewShaderSource(defaultShaderSourceBytes))
for i, src := range srcs {
vsname := fmt.Sprintf("%d_vs.fxc", i)
vs, err := fxcs.ReadFile("fxc/" + vsname)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
fmt.Fprintf(os.Stderr, "precompiled HLSL library %s was not found. Run 'go generate' for 'fxc' directory to generate them.\n", vsname)
continue
}
return err
}
psname := fmt.Sprintf("%d_ps.fxc", i)
ps, err := fxcs.ReadFile("fxc/" + psname)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
fmt.Fprintf(os.Stderr, "precompiled HLSL library %s was not found. Run 'go generate' for 'fxc' directory to generate them.\n", psname)
continue
}
return err
}
shaderprecomp.RegisterFXCs(src, vs, ps)
}
return nil
}

View File

@ -141,14 +141,14 @@ func drawEbitenText(screen *ebiten.Image, x, y int, aa bool, line bool) {
op := &ebiten.DrawTrianglesOptions{}
op.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)
}

View File

@ -25,18 +25,10 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/ui"
)
var screenFilterEnabled = int32(1)
var screenFilterEnabled atomic.Bool
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 {
@ -145,7 +137,7 @@ func (g *gameForUI) DrawFinalScreen(scale, offsetX, offsetY float64) {
}
switch {
case !isScreenFilterEnabled(), math.Floor(scale) == scale:
case !screenFilterEnabled.Load(), math.Floor(scale) == scale:
op := &DrawImageOptions{}
op.GeoM = geoM
g.screen.DrawImage(g.offscreen, op)

22
go.mod
View File

@ -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.1.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
)

40
go.sum
View File

@ -1,18 +1,18 @@
github.com/ebitengine/gomobile v0.0.0-20240329170434-1771503ff0a8 h1:5e8X7WEdOWrjrKvgaWF6PRnDvJicfrkEnwAkWtMN74g=
github.com/ebitengine/gomobile v0.0.0-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.1.0 h1:JLy/na2e83GewqebpFbS2LHpDVnGdzmyJOpqXtBgLm0=
github.com/hajimehoshi/bitmapfont/v3 v3.1.0/go.mod h1:VVaVK/4HpV1MHWswCl5miFOuLoRVyIplB3qEJxZK2OA=
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=
@ -30,12 +30,13 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjL
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.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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
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.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=
@ -43,8 +44,8 @@ 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.1.0/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.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=
@ -53,8 +54,8 @@ golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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=
@ -62,12 +63,13 @@ 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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
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.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=

188
image.go
View File

@ -247,7 +247,7 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
graphics.QuadVertices(vs, float32(sx0), float32(sy0), float32(sx1), float32(sy1), a, b, c, d, tx, ty, cr, cg, cb, ca)
is := graphics.QuadIndices()
srcs := [graphics.ShaderImageCount]*ui.Image{img.image}
srcs := [graphics.ShaderSrcImageCount]*ui.Image{img.image}
useColorM := !colorm.IsIdentity()
shader := builtinShader(filter, builtinshader.AddressUnsafe, useColorM)
@ -262,7 +262,7 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
})
}
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderImageCount]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.
@ -500,7 +519,7 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
is[i] = uint32(indices[i])
}
srcs := [graphics.ShaderImageCount]*ui.Image{img.image}
srcs := [graphics.ShaderSrcImageCount]*ui.Image{img.image}
useColorM := !colorm.IsIdentity()
shader := builtinShader(filter, address, useColorM)
@ -515,7 +534,7 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
})
}
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), filter != builtinshader.FilterLinear, options.AntiAlias)
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), filter != builtinshader.FilterLinear, options.AntiAlias)
}
// DrawTrianglesShaderOptions represents options for DrawTrianglesShader.
@ -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.
@ -565,7 +584,7 @@ type DrawTrianglesShaderOptions struct {
}
// Check the number of images.
var _ [len(DrawTrianglesShaderOptions{}.Images) - graphics.ShaderImageCount]struct{} = [0]struct{}{}
var _ [len(DrawTrianglesShaderOptions{}.Images) - graphics.ShaderSrcImageCount]struct{} = [0]struct{}{}
// DrawTrianglesShader draws triangles with the specified vertices and their indices with the specified shader.
//
@ -650,7 +669,7 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
is[i] = uint32(indices[i])
}
var imgs [graphics.ShaderImageCount]*ui.Image
var imgs [graphics.ShaderSrcImageCount]*ui.Image
var imgSize image.Point
for i, img := range options.Images {
if img == nil {
@ -672,7 +691,7 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
imgs[i] = img.image
}
var srcRegions [graphics.ShaderImageCount]image.Rectangle
var srcRegions [graphics.ShaderSrcImageCount]image.Rectangle
for i, img := range options.Images {
if img == nil {
continue
@ -686,6 +705,139 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
i.image.DrawTriangles(imgs, vs, is, blend, i.adjustedBounds(), srcRegions, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), true, options.AntiAlias)
}
// DrawTrianglesShader draws triangles with the specified vertices and their indices with the specified shader.
//
// Vertex contains color values, which can be interpreted for any purpose by the shader.
//
// For the details about the shader, see https://ebitengine.org/en/documents/shader.html.
//
// If the shader unit is texels, one of the specified image is non-nil and its size is different from (width, height),
// DrawTrianglesShader panics.
// If one of the specified image is non-nil and is disposed, DrawTrianglesShader panics.
//
// If len(vertices) is more than MaxVertexCount, the exceeding part is ignored.
//
// If len(indices) is not multiple of 3, DrawTrianglesShader panics.
//
// If a value in indices is out of range of vertices, or not less than MaxVertexCount, DrawTrianglesShader panics.
//
// When a specified image is non-nil and is disposed, DrawTrianglesShader panics.
//
// If a specified uniform variable's length or type doesn't match with an expected one, DrawTrianglesShader panics.
//
// Even if a result is an invalid color as a premultiplied-alpha color, i.e. an alpha value exceeds other color values,
// the value is kept and is not clamped.
//
// When the image i is disposed, DrawTrianglesShader does nothing.
func DrawTrianglesShaderMRT(dsts [graphics.ShaderDstImageCount]*Image, vertices []Vertex, indices []uint16, shader *Shader, options *DrawTrianglesShaderOptions) {
var dstImgs [graphics.ShaderDstImageCount]*ui.Image
var firstDst *Image
for i, dst := range dsts {
if dst == nil {
continue
}
dst.copyCheck()
if dst.isDisposed() {
panic("ebiten: the destination images given to DrawTrianglesShaderMRT must not be disposed")
}
if firstDst == nil {
firstDst = dst
}
dstImgs[i] = dst.image
}
if shader.isDisposed() {
panic("ebiten: the given shader to DrawTrianglesShaderMRT must not be disposed")
}
if len(vertices) > graphicscommand.MaxVertexCount {
// The last part cannot be specified by indices. Just omit them.
vertices = vertices[:graphicscommand.MaxVertexCount]
}
if len(indices)%3 != 0 {
panic("ebiten: len(indices) % 3 must be 0")
}
for i, idx := range indices {
if int(idx) >= len(vertices) {
panic(fmt.Sprintf("ebiten: indices[%d] must be less than len(vertices) (%d) but was %d", i, len(vertices), idx))
}
}
if options == nil {
options = &DrawTrianglesShaderOptions{}
}
var blend graphicsdriver.Blend
if options.CompositeMode == CompositeModeCustom {
blend = options.Blend.internalBlend()
} else {
blend = options.CompositeMode.blend().internalBlend()
}
dst := firstDst
vs := dst.ensureTmpVertices(len(vertices) * graphics.VertexFloatCount)
src := options.Images[0]
for i, v := range vertices {
dx, dy := dst.adjustPositionF32(v.DstX, v.DstY)
vs[i*graphics.VertexFloatCount] = dx
vs[i*graphics.VertexFloatCount+1] = dy
sx, sy := v.SrcX, v.SrcY
if src != nil {
sx, sy = src.adjustPositionF32(sx, sy)
}
vs[i*graphics.VertexFloatCount+2] = sx
vs[i*graphics.VertexFloatCount+3] = sy
vs[i*graphics.VertexFloatCount+4] = v.ColorR
vs[i*graphics.VertexFloatCount+5] = v.ColorG
vs[i*graphics.VertexFloatCount+6] = v.ColorB
vs[i*graphics.VertexFloatCount+7] = v.ColorA
}
is := make([]uint32, len(indices))
for i := range is {
is[i] = uint32(indices[i])
}
var srcImgs [graphics.ShaderSrcImageCount]*ui.Image
var imgSize image.Point
for i, img := range options.Images {
if img == nil {
continue
}
if img.isDisposed() {
panic("ebiten: the given image to DrawTrianglesShader must not be disposed")
}
if shader.unit == shaderir.Texels {
if i == 0 {
imgSize = img.Bounds().Size()
} else {
// TODO: Check imgw > 0 && imgh > 0
if img.Bounds().Size() != imgSize {
panic("ebiten: all the source images must be the same size with the rectangle")
}
}
}
srcImgs[i] = img.image
}
var srcRegions [graphics.ShaderSrcImageCount]image.Rectangle
for i, img := range options.Images {
if img == nil {
continue
}
srcRegions[i] = img.adjustedBounds()
}
for _, dst := range dsts {
if dst == nil {
continue
}
dst.tmpUniforms = dst.tmpUniforms[:0]
dst.tmpUniforms = shader.appendUniforms(dst.tmpUniforms, options.Uniforms)
}
ui.DrawTrianglesMRT(dstImgs, srcImgs, vs, is, blend, dst.adjustedBounds(), srcRegions, shader.shader, dst.tmpUniforms, graphicsdriver.FillRule(options.FillRule), true, options.AntiAlias)
}
// DrawRectShaderOptions represents options for DrawRectShader.
type DrawRectShaderOptions struct {
// GeoM is a geometry matrix to draw.
@ -724,7 +876,7 @@ type DrawRectShaderOptions struct {
}
// Check the number of images.
var _ [len(DrawRectShaderOptions{}.Images)]struct{} = [graphics.ShaderImageCount]struct{}{}
var _ [len(DrawRectShaderOptions{}.Images)]struct{} = [graphics.ShaderSrcImageCount]struct{}{}
// DrawRectShader draws a rectangle with the specified width and height with the specified shader.
//
@ -771,7 +923,7 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR
blend = options.CompositeMode.blend().internalBlend()
}
var imgs [graphics.ShaderImageCount]*ui.Image
var imgs [graphics.ShaderSrcImageCount]*ui.Image
for i, img := range options.Images {
if img == nil {
continue
@ -785,7 +937,7 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR
imgs[i] = img.image
}
var srcRegions [graphics.ShaderImageCount]image.Rectangle
var srcRegions [graphics.ShaderSrcImageCount]image.Rectangle
for i, img := range options.Images {
if img == nil {
if shader.unit == shaderir.Pixels && i == 0 {
@ -816,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.

View File

@ -2696,7 +2696,7 @@ func TestImageEvenOdd(t *testing.T) {
// Draw all the vertices once. The even-odd rule is applied for all the vertices once.
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)

View File

@ -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 (

View File

@ -36,13 +36,6 @@ var (
maxSize = 0
)
func max(a, b int) int {
if a > b {
return a
}
return b
}
func min(a, b int) int {
if a < b {
return a
@ -81,7 +74,7 @@ func flushDeferred() {
// Actual time duration is increased in an exponential way for each usage as a rendering target.
const baseCountToPutOnSourceBackend = 10
func putImagesOnSourceBackend(graphicsDriver graphicsdriver.Graphics) {
func putImagesOnSourceBackend() {
// The counter usedAsDestinationCount is updated at most once per frame (#2676).
imagesUsedAsDestination.forEach(func(i *Image) {
// This counter is not updated when the backend is created in this frame.
@ -97,7 +90,7 @@ func putImagesOnSourceBackend(graphicsDriver graphicsdriver.Graphics) {
i.usedAsSourceCount++
}
if int64(i.usedAsSourceCount) >= int64(baseCountToPutOnSourceBackend*(1<<uint(min(i.usedAsDestinationCount, 31)))) {
i.putOnSourceBackend(graphicsDriver)
i.putOnSourceBackend()
i.usedAsSourceCount = 0
}
})
@ -153,12 +146,12 @@ func (b *backend) extendIfNeeded(width, height int) {
// Use DrawTriangles instead of WritePixels because the image i might be stale and not have its pixels
// information.
srcs := [graphics.ShaderImageCount]*graphicscommand.Image{b.image}
srcs := [graphics.ShaderSrcImageCount]*graphicscommand.Image{b.image}
sw, sh := b.image.InternalSize()
vs := quadVertices(0, 0, float32(sw), float32(sh), 0, 0, float32(sw), float32(sh), 1, 1, 1, 1)
is := graphics.QuadIndices()
dr := image.Rect(0, 0, sw, sh)
newImg.DrawTriangles(srcs, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, NearestFilterShader.ensureShader(), nil, graphicsdriver.FillAll)
newImg.DrawTriangles(srcs, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader.ensureShader(), nil, graphicsdriver.FillRuleFillAll)
b.image.Dispose()
b.image = newImg
@ -182,7 +175,7 @@ func newClearedImage(width, height int, screen bool) *graphicscommand.Image {
func clearImage(i *graphicscommand.Image, region image.Rectangle) {
vs := quadVertices(float32(region.Min.X), float32(region.Min.Y), float32(region.Max.X), float32(region.Max.Y), 0, 0, 0, 0, 0, 0, 0, 0)
is := graphics.QuadIndices()
i.DrawTriangles([graphics.ShaderImageCount]*graphicscommand.Image{}, vs, is, graphicsdriver.BlendClear, region, [graphics.ShaderImageCount]image.Rectangle{}, clearShader.ensureShader(), nil, graphicsdriver.FillAll)
i.DrawTriangles([graphics.ShaderSrcImageCount]*graphicscommand.Image{}, vs, is, graphicsdriver.BlendClear, region, [graphics.ShaderSrcImageCount]image.Rectangle{}, clearShader.ensureShader(), nil, graphicsdriver.FillRuleFillAll)
}
func (b *backend) clearPixels(region image.Rectangle) {
@ -355,11 +348,11 @@ func (i *Image) ensureIsolatedFromSource(backends []*backend) {
is := graphics.QuadIndices()
dr := image.Rect(0, 0, i.width, i.height)
newI.drawTriangles([graphics.ShaderImageCount]*Image{i}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillAll)
newI.drawTriangles([graphics.ShaderSrcImageCount]*Image{i}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
newI.moveTo(i)
}
func (i *Image) putOnSourceBackend(graphicsDriver graphicsdriver.Graphics) {
func (i *Image) putOnSourceBackend() {
if i.backend == nil {
i.allocate(nil, true)
return
@ -385,7 +378,7 @@ func (i *Image) putOnSourceBackend(graphicsDriver graphicsdriver.Graphics) {
graphics.QuadVertices(vs, 0, 0, w, h, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
is := graphics.QuadIndices()
dr := image.Rect(0, 0, i.width, i.height)
newI.drawTriangles([graphics.ShaderImageCount]*Image{i}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillAll)
newI.drawTriangles([graphics.ShaderSrcImageCount]*Image{i}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
newI.moveTo(i)
i.usedAsSourceCount = 0
@ -417,7 +410,7 @@ func (i *Image) regionWithPadding() image.Rectangle {
// 5: Color G
// 6: Color B
// 7: Color Y
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) {
func (i *Image) 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) {
backendsM.Lock()
defer backendsM.Unlock()
@ -438,11 +431,33 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices [
i.drawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule)
}
func (i *Image) drawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) {
func DrawTrianglesMRT(dsts [graphics.ShaderDstImageCount]*Image, 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) {
backendsM.Lock()
defer backendsM.Unlock()
if !inFrame {
vs := make([]float32, len(vertices))
copy(vs, vertices)
is := make([]uint32, len(indices))
copy(is, indices)
us := make([]uint32, len(uniforms))
copy(us, uniforms)
appendDeferred(func() {
drawTrianglesMRT(dsts, srcs, vs, is, blend, dstRegion, srcRegions, shader, us, fillRule)
})
return
}
drawTrianglesMRT(dsts, srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule)
}
func (i *Image) 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) {
if len(vertices) == 0 {
return
}
// This slice is not escaped to the heap. This can be checked by `go build -gcflags=-m`.
backends := make([]*backend, 0, len(srcs))
for _, src := range srcs {
if src == nil {
@ -515,7 +530,7 @@ func (i *Image) drawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices [
srcRegions[i] = srcRegions[i].Add(r.Min)
}
var imgs [graphics.ShaderImageCount]*graphicscommand.Image
var imgs [graphics.ShaderSrcImageCount]*graphicscommand.Image
for i, src := range srcs {
if src == nil {
continue
@ -536,6 +551,115 @@ func (i *Image) drawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices [
}
}
func drawTrianglesMRT(dsts [graphics.ShaderDstImageCount]*Image, 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) {
if len(vertices) == 0 {
return
}
backends := make([]*backend, 0, len(srcs))
for _, src := range srcs {
if src == nil {
continue
}
if src.backend == nil {
// It is possible to spcify i.backend as a forbidden backend, but this might prevent a good allocation for a source image.
// If the backend becomes the same as i's, i's backend will be changed at ensureIsolatedFromSource.
src.allocate(nil, true)
}
backends = append(backends, src.backend)
src.backend.sourceInThisFrame = true
}
var firstDst *Image
var dstImgs [graphics.ShaderDstImageCount]*graphicscommand.Image
for i, dst := range dsts {
if dst == nil {
continue
}
dst.ensureIsolatedFromSource(backends)
firstDst = dst
dstImgs[i] = dst.backend.image
}
for _, src := range srcs {
// Compare i and source images after ensuring i is not on an atlas, or
// i and a source image might share the same atlas even though i != src.
for _, dst := range dsts {
if src != nil && dst != nil && dst.backend.image == src.backend.image {
panic("atlas: DrawTrianglesMRT: source must be different from the destination images")
}
}
}
r := firstDst.regionWithPadding()
// TODO: Check if dstRegion does not to violate the region.
dstRegion = dstRegion.Add(r.Min)
dx, dy := float32(r.Min.X), float32(r.Min.Y)
var oxf, oyf float32
if srcs[0] != nil {
r := srcs[0].regionWithPadding()
oxf, oyf = float32(r.Min.X), float32(r.Min.Y)
n := len(vertices)
for i := 0; i < n; i += graphics.VertexFloatCount {
vertices[i] += dx
vertices[i+1] += dy
vertices[i+2] += oxf
vertices[i+3] += oyf
}
if shader.ir.Unit == shaderir.Texels {
sw, sh := srcs[0].backend.image.InternalSize()
swf, shf := float32(sw), float32(sh)
for i := 0; i < n; i += graphics.VertexFloatCount {
vertices[i+2] /= swf
vertices[i+3] /= shf
}
}
} else {
n := len(vertices)
for i := 0; i < n; i += graphics.VertexFloatCount {
vertices[i] += dx
vertices[i+1] += dy
}
}
for i, src := range srcs {
if src == nil {
continue
}
// A source region can be deliberately empty when this is not needed in order to avoid unexpected
// performance issue (#1293).
if srcRegions[i].Empty() {
continue
}
r := src.regionWithPadding()
srcRegions[i] = srcRegions[i].Add(r.Min)
}
var srcImgs [graphics.ShaderSrcImageCount]*graphicscommand.Image
for i, src := range srcs {
if src == nil {
continue
}
srcImgs[i] = src.backend.image
}
graphicscommand.DrawTrianglesMRT(dstImgs, srcImgs, vertices, indices, blend, dstRegion, srcRegions, shader.ensureShader(), uniforms, fillRule)
for _, src := range srcs {
if src == nil {
continue
}
if !src.isOnSourceBackend() && src.canBePutOnAtlas() {
// src might already registered, but assigning it again is not harmful.
imagesToPutOnSourceBackend.add(src)
}
}
}
// WritePixels replaces the pixels on the image.
func (i *Image) WritePixels(pix []byte, region image.Rectangle) {
backendsM.Lock()
@ -942,7 +1066,7 @@ func BeginFrame(graphicsDriver graphicsdriver.Graphics) error {
}
flushDeferred()
putImagesOnSourceBackend(graphicsDriver)
putImagesOnSourceBackend()
return nil
}

View File

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

View File

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

View File

@ -37,12 +37,12 @@ func TestShaderFillTwice(t *testing.T) {
dr := image.Rect(0, 0, w, h)
g := ui.Get().GraphicsDriverForTesting()
s0 := atlas.NewShader(etesting.ShaderProgramFill(0xff, 0xff, 0xff, 0xff))
dst.DrawTriangles([graphics.ShaderImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, s0, nil, graphicsdriver.FillAll)
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s0, nil, graphicsdriver.FillRuleFillAll)
// Vertices must be recreated (#1755)
vs = quadVertices(w, h, 0, 0, 1)
s1 := atlas.NewShader(etesting.ShaderProgramFill(0x80, 0x80, 0x80, 0xff))
dst.DrawTriangles([graphics.ShaderImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, s1, nil, graphicsdriver.FillAll)
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s1, nil, graphicsdriver.FillRuleFillAll)
pix := make([]byte, 4*w*h)
ok, err := dst.ReadPixels(g, pix, image.Rect(0, 0, w, h))
@ -69,11 +69,11 @@ func TestImageDrawTwice(t *testing.T) {
vs := quadVertices(w, h, 0, 0, 1)
is := graphics.QuadIndices()
dr := image.Rect(0, 0, w, h)
dst.DrawTriangles([graphics.ShaderImageCount]*atlas.Image{src0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src0}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
// Vertices must be recreated (#1755)
vs = quadVertices(w, h, 0, 0, 1)
dst.DrawTriangles([graphics.ShaderImageCount]*atlas.Image{src1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{src1}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
pix := make([]byte, 4*w*h)
ok, err := dst.ReadPixels(ui.Get().GraphicsDriverForTesting(), pix, image.Rect(0, 0, w, h))
@ -97,7 +97,7 @@ func TestGCShader(t *testing.T) {
vs := quadVertices(w, h, 0, 0, 1)
is := graphics.QuadIndices()
dr := image.Rect(0, 0, w, h)
dst.DrawTriangles([graphics.ShaderImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillAll)
dst.DrawTriangles([graphics.ShaderSrcImageCount]*atlas.Image{}, vs, is, graphicsdriver.BlendCopy, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, s, nil, graphicsdriver.FillRuleFillAll)
// Ensure other objects are GCed, as GC appends deferred functions for collected objects.
ensureGC()

View File

@ -183,7 +183,7 @@ func (i *Image) WritePixels(pix []byte, region image.Rectangle) {
// DrawTriangles draws the src image with the given vertices.
//
// Copying vertices and indices is the caller's responsibility.
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderImageCount]image.Rectangle, shader *atlas.Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) {
func (i *Image) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *atlas.Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) {
for _, src := range srcs {
if i == src {
panic("buffered: Image.DrawTriangles: source images must be different from the receiver")
@ -197,7 +197,7 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices [
i.syncPixelsIfNeeded()
var imgs [graphics.ShaderImageCount]*atlas.Image
var imgs [graphics.ShaderSrcImageCount]*atlas.Image
for i, img := range srcs {
if img == nil {
continue
@ -211,6 +211,51 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices [
i.pixels = nil
}
func DrawTrianglesMRT(dsts [graphics.ShaderDstImageCount]*Image, srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *atlas.Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) {
for _, src := range srcs {
for _, dst := range dsts {
if dst == nil {
continue
}
if dst == src {
panic("buffered: DrawTrianglesMRT: source images must be different from the destination images")
}
}
if src != nil {
// src's pixels have to be synced between CPU and GPU,
// but doesn't have to be cleared since src is not modified in this function.
src.syncPixelsIfNeeded()
}
}
var dstImgs [graphics.ShaderDstImageCount]*atlas.Image
for i, dst := range dsts {
if dst == nil {
continue
}
dst.syncPixelsIfNeeded()
dstImgs[i] = dst.img
}
var srcImgs [graphics.ShaderSrcImageCount]*atlas.Image
for i, src := range srcs {
if src == nil {
continue
}
srcImgs[i] = src.img
}
atlas.DrawTrianglesMRT(dstImgs, srcImgs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule)
// After rendering, the pixel cache is no longer valid.
for _, dst := range dsts {
if dst == nil {
continue
}
dst.pixels = nil
}
}
// syncPixelsIfNeeded syncs the pixels between CPU and GPU.
// After syncPixelsIfNeeded, dotsBuffer is cleared, but pixels might remain.
func (i *Image) syncPixelsIfNeeded() {
@ -289,10 +334,10 @@ func (i *Image) syncPixelsIfNeeded() {
idx++
}
srcs := [graphics.ShaderImageCount]*atlas.Image{whiteImage.img}
srcs := [graphics.ShaderSrcImageCount]*atlas.Image{whiteImage.img}
dr := image.Rect(0, 0, i.width, i.height)
blend := graphicsdriver.BlendCopy
i.img.DrawTriangles(srcs, vs, is, blend, dr, [graphics.ShaderImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
i.img.DrawTriangles(srcs, vs, is, blend, dr, [graphics.ShaderSrcImageCount]image.Rectangle{}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
// TODO: Use clear if Go 1.21 is available.
for pos := range i.dotsBuffer {

View File

@ -55,8 +55,8 @@ func TestUnsyncedPixels(t *testing.T) {
graphics.QuadVertices(vs, 0, 0, 16, 16, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1)
is := graphics.QuadIndices()
dr := image.Rect(0, 0, 16, 16)
sr := [graphics.ShaderImageCount]image.Rectangle{image.Rect(0, 0, 16, 16)}
dst.DrawTriangles([graphics.ShaderImageCount]*buffered.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, sr, atlas.NearestFilterShader, nil, graphicsdriver.FillAll)
sr := [graphics.ShaderSrcImageCount]image.Rectangle{image.Rect(0, 0, 16, 16)}
dst.DrawTriangles([graphics.ShaderSrcImageCount]*buffered.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, sr, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll)
// Check the result is correct.
var got [4]byte

View File

@ -188,3 +188,22 @@ func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
return mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y)
}
`)
var ClearShaderSource = []byte(`//kage:unit pixels
package main
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
return vec4(0)
}
`)
func AppendShaderSources(sources [][]byte) [][]byte {
for filter := Filter(0); filter < FilterCount; filter++ {
for address := Address(0); address < AddressCount; address++ {
sources = append(sources, ShaderSource(filter, address, false), ShaderSource(filter, address, true))
}
}
sources = append(sources, ScreenShaderSource, ClearShaderSource)
return sources
}

View File

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

View File

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

View File

@ -261,18 +261,23 @@ func makeContextCurrentWGL(window *Window) error {
}
func swapBuffersWGL(window *Window) error {
if window.monitor == nil {
// HACK: Use DwmFlush when desktop composition is enabled on Windows Vista and 7
if !winver.IsWindows8OrGreater() && winver.IsWindowsVistaOrGreater() {
enabled, err := _DwmIsCompositionEnabled()
if window.monitor == nil && winver.IsWindowsVistaOrGreater() {
// DWM Composition is always enabled on Win8+
enabled := winver.IsWindows8OrGreater()
if !enabled {
var err error
enabled, err = _DwmIsCompositionEnabled()
if err != nil {
return err
}
if enabled {
for i := 0; i < window.context.platform.interval; i++ {
// Ignore an error from DWM functions as they might not be implemented e.g. on Proton (#2113).
_ = _DwmFlush()
}
}
// HACK: Use DwmFlush when desktop composition is enabled
if enabled {
for i := 0; i < window.context.platform.interval; i++ {
// Ignore an error from DWM functions as they might not be implemented e.g. on Proton (#2113).
_ = _DwmFlush()
}
}
}
@ -292,19 +297,23 @@ func swapIntervalWGL(interval int) error {
window.context.platform.interval = interval
if window.monitor == nil {
// HACK: Disable WGL swap interval when desktop composition is enabled on Windows
// Vista and 7 to avoid interfering with DWM vsync
if !winver.IsWindows8OrGreater() && winver.IsWindowsVistaOrGreater() {
enabled, err := _DwmIsCompositionEnabled()
if window.monitor == nil && winver.IsWindowsVistaOrGreater() {
// DWM Composition is always enabled on Win8+
enabled := winver.IsWindows8OrGreater()
if !enabled {
e, err := _DwmIsCompositionEnabled()
// Ignore an error from DWM functions as they might not be implemented e.g. on Proton (#2113).
if err == nil {
enabled = false
}
if enabled {
interval = 0
enabled = e
}
}
// HACK: Disable WGL swap interval when desktop composition is enabled to
// avoid interfering with DWM vsync
if enabled {
interval = 0
}
}
if _glfw.platformContext.EXT_swap_control {

View File

@ -2309,7 +2309,7 @@ func (w *Window) platformSetCursorMode(mode int) error {
if mode == CursorDisabled {
_glfw.platformWindow.disabledCursorWindow = w
} else {
} else if _glfw.platformWindow.disabledCursorWindow == w {
_glfw.platformWindow.disabledCursorWindow = nil
if err := w.platformSetCursorPos(_glfw.platformWindow.restoreCursorPosX, _glfw.platformWindow.restoreCursorPosY); err != nil {
return err

View File

@ -89,9 +89,9 @@ var __imageSrcRegionSizes [%[1]d]vec2
func imageSrcRegionOnTexture() (vec2, vec2) {
return __imageSrcRegionOrigins[0], __imageSrcRegionSizes[0]
}
`, ShaderImageCount)
`, ShaderSrcImageCount)
for i := 0; i < ShaderImageCount; i++ {
for i := 0; i < ShaderSrcImageCount; i++ {
shaderSuffix += fmt.Sprintf(`
// imageSrc%[1]dOrigin returns the source image's region origin on its texture.
// The unit is the source texture's pixel or texel.
@ -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, ShaderImageCount)
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
}

View File

@ -15,7 +15,12 @@
package graphics
const (
ShaderImageCount = 4
ShaderSrcImageCount = 4
// The minimum guaranteed value for the number of target seems to be 8
// OpenGL(8): https://www.khronos.org/opengl/wiki/Framebuffer_Object#Framebuffer_Object_Structure
// DirectX11(8): https://learn.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-output-merger-stage#multiple-rendertargets-overview
// Metal(8): Page 7 of 15: https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
ShaderDstImageCount = 8
// PreservedUniformVariablesCount represents the number of preserved uniform variables.
// Any shaders in Ebitengine must have these uniform variables.
@ -30,11 +35,11 @@ const (
ProjectionMatrixUniformVariableIndex = 6
PreservedUniformUint32Count = 2 + // the destination texture size
2*ShaderImageCount + // the source texture sizes array
2*ShaderSrcImageCount + // the source texture sizes array
2 + // the destination image region origin
2 + // the destination image region size
2*ShaderImageCount + // the source image region origins array
2*ShaderImageCount + // the source image region sizes array
2*ShaderSrcImageCount + // the source image region origins array
2*ShaderSrcImageCount + // the source image region sizes array
16 // the projection matrix
)

View File

@ -61,8 +61,8 @@ func (p *drawTrianglesCommandPool) put(v *drawTrianglesCommand) {
// drawTrianglesCommand represents a drawing command to draw an image on another image.
type drawTrianglesCommand struct {
dst *Image
srcs [graphics.ShaderImageCount]*Image
dsts [graphics.ShaderDstImageCount]*Image
srcs [graphics.ShaderSrcImageCount]*Image
vertices []float32
blend graphicsdriver.Blend
dstRegions []graphicsdriver.DstRegion
@ -81,12 +81,19 @@ func (c *drawTrianglesCommand) String() string {
c.blend.BlendOperationRGB,
c.blend.BlendOperationAlpha)
dst := fmt.Sprintf("%d", c.dst.id)
if c.dst.screen {
dst += " (screen)"
var dststrs [graphics.ShaderDstImageCount]string
for i, dst := range c.dsts {
if dst == nil {
dststrs[i] = "(nil)"
continue
}
dststrs[i] = fmt.Sprintf("%d", dst.id)
if dst.screen {
dststrs[i] += " (screen)"
}
}
var srcstrs [graphics.ShaderImageCount]string
var srcstrs [graphics.ShaderSrcImageCount]string
for i, src := range c.srcs {
if src == nil {
srcstrs[i] = "(nil)"
@ -98,7 +105,7 @@ func (c *drawTrianglesCommand) String() string {
}
}
return fmt.Sprintf("draw-triangles: dst: %s <- src: [%s], num of dst regions: %d, num of indices: %d, blend: %s, fill rule: %s, shader id: %d", dst, strings.Join(srcstrs[:], ", "), len(c.dstRegions), c.numIndices(), blend, c.fillRule, c.shader.id)
return fmt.Sprintf("draw-triangles: dst: [%s] <- src: [%s], num of dst regions: %d, num of indices: %d, blend: %s, fill rule: %s, shader id: %d", strings.Join(dststrs[:], ", "), strings.Join(srcstrs[:], ", "), len(c.dstRegions), c.numIndices(), blend, c.fillRule, c.shader.id)
}
// Exec executes the drawTrianglesCommand.
@ -108,16 +115,25 @@ func (c *drawTrianglesCommand) Exec(commandQueue *commandQueue, graphicsDriver g
return nil
}
var imgs [graphics.ShaderImageCount]graphicsdriver.ImageID
for i, src := range c.srcs {
if src == nil {
imgs[i] = graphicsdriver.InvalidImageID
var dsts [graphics.ShaderDstImageCount]graphicsdriver.ImageID
for i, dst := range c.dsts {
if dst == nil {
dsts[i] = graphicsdriver.InvalidImageID
continue
}
imgs[i] = src.image.ID()
dsts[i] = dst.image.ID()
}
return graphicsDriver.DrawTriangles(c.dst.image.ID(), imgs, c.shader.shader.ID(), c.dstRegions, indexOffset, c.blend, c.uniforms, c.fillRule)
var srcs [graphics.ShaderSrcImageCount]graphicsdriver.ImageID
for i, src := range c.srcs {
if src == nil {
srcs[i] = graphicsdriver.InvalidImageID
continue
}
srcs[i] = src.image.ID()
}
return graphicsDriver.DrawTriangles(dsts, srcs, c.shader.shader.ID(), c.dstRegions, indexOffset, c.blend, c.uniforms, c.fillRule)
}
func (c *drawTrianglesCommand) NeedsSync() bool {
@ -142,7 +158,7 @@ func (c *drawTrianglesCommand) setVertices(vertices []float32) {
// CanMergeWithDrawTrianglesCommand returns a boolean value indicating whether the other drawTrianglesCommand can be merged
// with the drawTrianglesCommand c.
func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageCount]*Image, vertices []float32, blend graphicsdriver.Blend, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) bool {
func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dsts [graphics.ShaderDstImageCount]*Image, srcs [graphics.ShaderSrcImageCount]*Image, vertices []float32, blend graphicsdriver.Blend, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) bool {
if c.shader != shader {
return false
}
@ -154,7 +170,7 @@ func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dst *Image, srcs
return false
}
}
if c.dst != dst {
if c.dsts != dsts {
return false
}
if c.srcs != srcs {
@ -166,7 +182,7 @@ func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dst *Image, srcs
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

View File

@ -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)
@ -105,7 +105,7 @@ func mustUseDifferentVertexBuffer(nextNumVertexFloats int) bool {
}
// EnqueueDrawTrianglesCommand enqueues a drawing-image command.
func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) {
func (q *commandQueue) EnqueueDrawTrianglesCommand(dsts [graphics.ShaderDstImageCount]*Image, 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) {
if len(vertices) > maxVertexFloatCount {
panic(fmt.Sprintf("graphicscommand: len(vertices) must equal to or less than %d but was %d", maxVertexFloatCount, len(vertices)))
}
@ -125,7 +125,7 @@ func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.Sh
// prependPreservedUniforms not only prepends values to the given slice but also creates a new slice.
// Allocating a new slice is necessary to make EnqueueDrawTrianglesCommand safe so far.
// TODO: This might cause a performance issue (#2601).
uniforms = q.prependPreservedUniforms(uniforms, shader, dst, srcs, dstRegion, srcRegions)
uniforms = q.prependPreservedUniforms(uniforms, shader, dsts, srcs, dstRegion, srcRegions)
// Remove unused uniform variables so that more commands can be merged.
shader.ir.FilterUniformVariables(uniforms)
@ -133,7 +133,7 @@ func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.Sh
// TODO: If dst is the screen, reorder the command to be the last.
if !split && 0 < len(q.commands) {
if last, ok := q.commands[len(q.commands)-1].(*drawTrianglesCommand); ok {
if last.CanMergeWithDrawTrianglesCommand(dst, srcs, vertices, blend, shader, uniforms, fillRule) {
if last.CanMergeWithDrawTrianglesCommand(dsts, srcs, vertices, blend, shader, uniforms, fillRule) {
last.setVertices(q.lastVertices(len(vertices) + last.numVertices()))
if last.dstRegions[len(last.dstRegions)-1].Region == dstRegion {
last.dstRegions[len(last.dstRegions)-1].IndexCount += len(indices)
@ -149,7 +149,7 @@ func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.Sh
}
c := q.drawTrianglesCommandPool.get()
c.dst = dst
c.dsts = dsts
c.srcs = srcs
c.vertices = q.lastVertices(len(vertices))
c.blend = blend
@ -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 {
@ -324,18 +324,25 @@ func imageRectangleToRectangleF32(r image.Rectangle) rectangleF32 {
}
}
func (q *commandQueue) prependPreservedUniforms(uniforms []uint32, shader *Shader, dst *Image, srcs [graphics.ShaderImageCount]*Image, dstRegion image.Rectangle, srcRegions [graphics.ShaderImageCount]image.Rectangle) []uint32 {
func (q *commandQueue) prependPreservedUniforms(uniforms []uint32, shader *Shader, dsts [graphics.ShaderDstImageCount]*Image, srcs [graphics.ShaderSrcImageCount]*Image, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle) []uint32 {
origUniforms := uniforms
uniforms = q.uint32sBuffer.alloc(len(origUniforms) + graphics.PreservedUniformUint32Count)
copy(uniforms[graphics.PreservedUniformUint32Count:], origUniforms)
// Set the destination texture size.
dw, dh := dst.InternalSize()
var firstDst *Image
for _, dst := range dsts {
if dst != nil {
firstDst = dst
break
}
}
dw, dh := firstDst.InternalSize()
uniforms[0] = math.Float32bits(float32(dw))
uniforms[1] = math.Float32bits(float32(dh))
uniformIndex := 2
for i := 0; i < graphics.ShaderImageCount; i++ {
for i := 0; i < graphics.ShaderSrcImageCount; i++ {
var floatW, floatH uint32
if srcs[i] != nil {
w, h := srcs[i].InternalSize()
@ -346,7 +353,7 @@ func (q *commandQueue) prependPreservedUniforms(uniforms []uint32, shader *Shade
uniforms[uniformIndex+i*2] = floatW
uniforms[uniformIndex+1+i*2] = floatH
}
uniformIndex += graphics.ShaderImageCount * 2
uniformIndex += graphics.ShaderSrcImageCount * 2
dr := imageRectangleToRectangleF32(dstRegion)
if shader.unit() == shaderir.Texels {
@ -366,7 +373,7 @@ func (q *commandQueue) prependPreservedUniforms(uniforms []uint32, shader *Shade
uniforms[uniformIndex+1] = math.Float32bits(dr.height)
uniformIndex += 2
var srs [graphics.ShaderImageCount]rectangleF32
var srs [graphics.ShaderSrcImageCount]rectangleF32
for i, r := range srcRegions {
srs[i] = imageRectangleToRectangleF32(r)
}
@ -384,18 +391,18 @@ func (q *commandQueue) prependPreservedUniforms(uniforms []uint32, shader *Shade
}
// Set the source region origins.
for i := 0; i < graphics.ShaderImageCount; i++ {
for i := 0; i < graphics.ShaderSrcImageCount; i++ {
uniforms[uniformIndex+i*2] = math.Float32bits(srs[i].x)
uniforms[uniformIndex+1+i*2] = math.Float32bits(srs[i].y)
}
uniformIndex += graphics.ShaderImageCount * 2
uniformIndex += graphics.ShaderSrcImageCount * 2
// Set the source region sizes.
for i := 0; i < graphics.ShaderImageCount; i++ {
for i := 0; i < graphics.ShaderSrcImageCount; i++ {
uniforms[uniformIndex+i*2] = math.Float32bits(srs[i].width)
uniforms[uniformIndex+1+i*2] = math.Float32bits(srs[i].height)
}
uniformIndex += graphics.ShaderImageCount * 2
uniformIndex += graphics.ShaderSrcImageCount * 2
// Set the projection matrix.
uniforms[uniformIndex] = math.Float32bits(2 / float32(dw))
@ -469,11 +476,11 @@ func (c *commandQueueManager) putCommandQueue(commandQueue *commandQueue) {
c.pool.put(commandQueue)
}
func (c *commandQueueManager) enqueueDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) {
func (c *commandQueueManager) enqueueDrawTrianglesCommand(dsts [graphics.ShaderDstImageCount]*Image, 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) {
if c.current == nil {
c.current, _ = c.pool.get()
}
c.current.EnqueueDrawTrianglesCommand(dst, srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule)
c.current.EnqueueDrawTrianglesCommand(dsts, srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule)
}
func (c *commandQueueManager) flush(graphicsDriver graphicsdriver.Graphics, endFrame bool) error {

View File

@ -130,7 +130,7 @@ func (i *Image) InternalSize() (int, int) {
//
// If the source image is not specified, i.e., src is nil and there is no image in the uniform variables, the
// elements for the source image are not used.
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderImageCount]image.Rectangle, shader *Shader, uniforms []uint32, fillRule graphicsdriver.FillRule) {
func (i *Image) 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) {
for _, src := range srcs {
if src == nil {
continue
@ -142,7 +142,48 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices [
}
i.flushBufferedWritePixels()
theCommandQueueManager.enqueueDrawTrianglesCommand(i, srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule)
theCommandQueueManager.enqueueDrawTrianglesCommand([graphics.ShaderDstImageCount]*Image{i}, srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule)
}
// DrawTriangles draws triangles with the given image.
//
// The vertex floats are:
//
// 0: Destination X in pixels
// 1: Destination Y in pixels
// 2: Source X in texels
// 3: Source Y in texels
// 4: Color R [0.0-1.0]
// 5: Color G
// 6: Color B
// 7: Color Y
//
// src and shader are exclusive and only either is non-nil.
//
// The elements that index is in between 2 and 7 are used for the source images.
// The source image is 1) src argument if non-nil, or 2) an image value in the uniform variables if it exists.
// If there are multiple images in the uniform variables, the smallest ID's value is adopted.
//
// If the source image is not specified, i.e., src is nil and there is no image in the uniform variables, the
// elements for the source image are not used.
func DrawTrianglesMRT(dsts [graphics.ShaderDstImageCount]*Image, 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) {
for _, src := range srcs {
if src == nil {
continue
}
if src.screen {
panic("graphicscommand: the screen image cannot be the rendering des")
}
src.flushBufferedWritePixels()
}
for _, dst := range dsts {
if dst == nil {
continue
}
dst.flushBufferedWritePixels()
}
theCommandQueueManager.enqueueDrawTrianglesCommand(dsts, srcs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule)
}
// ReadPixels reads the image's pixels.

View File

@ -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.ShaderImageCount]*graphicscommand.Image{src}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderImageCount]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.ShaderImageCount]*graphicscommand.Image{clr}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderImageCount]image.Rectangle{}, nearestFilterShader, nil, graphicsdriver.FillAll)
dst.DrawTriangles([graphics.ShaderImageCount]*graphicscommand.Image{src}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]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.ShaderImageCount]*graphicscommand.Image{clr}, vs, is, graphicsdriver.BlendClear, dr, [graphics.ShaderImageCount]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.ShaderImageCount]*graphicscommand.Image{}, vs, is, graphicsdriver.BlendSourceOver, dr, [graphics.ShaderImageCount]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{

View File

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

View File

@ -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,
}
@ -515,14 +514,103 @@ func (g *graphics11) removeShader(s *shader11) {
delete(g.shaders, s.id)
}
func (g *graphics11) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.ShaderImageCount]graphicsdriver.ImageID, shaderID graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
func (g *graphics11) setAsRenderTargets(dsts []*image11, useStencil bool) error {
var rtvs []*_ID3D11RenderTargetView
var dsv *_ID3D11DepthStencilView
for _, dst := range dsts {
// Ignore a nil image in case of MRT
if dst == nil {
rtvs = append(rtvs, nil)
continue
}
if dst.renderTargetView == nil {
rtv, err := g.device.CreateRenderTargetView(unsafe.Pointer(dst.texture), nil)
if err != nil {
return err
}
dst.renderTargetView = rtv
}
rtvs = append(rtvs, dst.renderTargetView)
if !useStencil || dsv != nil {
continue
}
if dst.screen {
return fmt.Errorf("directx: a stencil buffer is not available for a screen image")
}
if dst.stencil == nil {
w, h := dst.internalSize()
s, err := g.device.CreateTexture2D(&_D3D11_TEXTURE2D_DESC{
Width: uint32(w),
Height: uint32(h),
MipLevels: 0,
ArraySize: 1,
Format: _DXGI_FORMAT_D24_UNORM_S8_UINT,
SampleDesc: _DXGI_SAMPLE_DESC{
Count: 1,
Quality: 0,
},
Usage: _D3D11_USAGE_DEFAULT,
BindFlags: uint32(_D3D11_BIND_DEPTH_STENCIL),
CPUAccessFlags: 0,
MiscFlags: 0,
}, nil)
if err != nil {
return err
}
dst.stencil = s
}
if dst.stencilView == nil {
sv, err := g.device.CreateDepthStencilView(unsafe.Pointer(dst.stencil), nil)
if err != nil {
return err
}
dst.stencilView = sv
}
dsv = dst.stencilView
}
g.deviceContext.OMSetRenderTargets(rtvs, dsv)
if useStencil {
g.deviceContext.ClearDepthStencilView(dsv, uint8(_D3D11_CLEAR_STENCIL), 0, 0)
}
return nil
}
func (g *graphics11) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphicsdriver.ImageID, srcIDs [graphics.ShaderSrcImageCount]graphicsdriver.ImageID, shaderID graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
// Remove bound textures first. This is needed to avoid warnings on the debugger.
g.deviceContext.OMSetRenderTargets([]*_ID3D11RenderTargetView{nil}, nil)
srvs := [graphics.ShaderImageCount]*_ID3D11ShaderResourceView{}
srvs := [graphics.ShaderSrcImageCount]*_ID3D11ShaderResourceView{}
g.deviceContext.PSSetShaderResources(0, srvs[:])
dst := g.images[dstID]
var srcs [graphics.ShaderImageCount]*image11
var dsts [graphics.ShaderDstImageCount]*image11
var vp _D3D11_VIEWPORT
var targetCount int
firstTarget := -1
for i, id := range dstIDs {
img := g.images[id]
if img == nil {
continue
}
if firstTarget == -1 {
firstTarget = i
}
dsts[i] = img
w, h := img.internalSize()
vp = _D3D11_VIEWPORT{
TopLeftX: 0,
TopLeftY: 0,
Width: float32(w),
Height: float32(h),
MinDepth: 0,
MaxDepth: 1,
}
targetCount++
}
var srcs [graphics.ShaderSrcImageCount]*image11
for i, id := range srcIDs {
img := g.images[id]
if img == nil {
@ -531,19 +619,16 @@ func (g *graphics11) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphic
srcs[i] = img
}
w, h := dst.internalSize()
g.deviceContext.RSSetViewports([]_D3D11_VIEWPORT{
{
TopLeftX: 0,
TopLeftY: 0,
Width: float32(w),
Height: float32(h),
MinDepth: 0,
MaxDepth: 1,
},
})
// If the number of targets is more than one, or if the only target is the first one, then
// it is safe to assume that MRT is used.
// Also, it only matters in order to specify empty targets/viewports when not all slots are
// being filled, even though it's not a MRT scenario.
if targetCount > 1 || firstTarget > 0 {
targetCount = graphics.ShaderDstImageCount
}
if err := dst.setAsRenderTarget(fillRule != graphicsdriver.FillAll); err != nil {
g.deviceContext.RSSetViewports([]_D3D11_VIEWPORT{vp})
if err := g.setAsRenderTargets(dsts[:targetCount], fillRule != graphicsdriver.FillRuleFillAll); err != nil {
return err
}
@ -553,7 +638,7 @@ func (g *graphics11) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphic
return err
}
if fillRule == graphicsdriver.FillAll {
if fillRule == graphicsdriver.FillRuleFillAll {
bs, err := g.blendState(blend, noStencil)
if err != nil {
return err
@ -578,9 +663,9 @@ func (g *graphics11) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphic
})
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
@ -592,7 +677,7 @@ func (g *graphics11) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphic
}
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
@ -606,7 +691,7 @@ func (g *graphics11) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphic
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

View File

@ -40,13 +40,14 @@ func (r *resourceWithSize) release() {
}
type graphics12 struct {
debug *_ID3D12Debug
device *_ID3D12Device
commandQueue *_ID3D12CommandQueue
rtvDescriptorHeap *_ID3D12DescriptorHeap
rtvDescriptorSize uint32
renderTargets [frameCount]*_ID3D12Resource
framePipelineToken _D3D12XBOX_FRAME_PIPELINE_TOKEN
debug *_ID3D12Debug
device *_ID3D12Device
commandQueue *_ID3D12CommandQueue
rtvDescriptorHeap *_ID3D12DescriptorHeap
rtvEmptyDescriptorHeap *_ID3D12DescriptorHeap
rtvDescriptorSize uint32
renderTargets [frameCount]*_ID3D12Resource
framePipelineToken _D3D12XBOX_FRAME_PIPELINE_TOKEN
fence *_ID3D12Fence
fenceValues [frameCount]uint64
@ -418,6 +419,34 @@ func (g *graphics12) initializeMembers(frameIndex int) (ferr error) {
g.rtvDescriptorHeap = nil
}
}()
// Create a descriptor heap for empty RTV in case of MRT with empty locations.
h, err = g.device.CreateDescriptorHeap(&_D3D12_DESCRIPTOR_HEAP_DESC{
Type: _D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
NumDescriptors: frameCount,
Flags: _D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
NodeMask: 0,
})
if err != nil {
return err
}
g.rtvEmptyDescriptorHeap = h
defer func() {
if ferr != nil {
g.rtvEmptyDescriptorHeap.Release()
g.rtvEmptyDescriptorHeap = nil
}
}()
hnd, err := g.rtvEmptyDescriptorHeap.GetCPUDescriptorHandleForHeapStart()
if err != nil {
return err
}
// Create an empty render target for empty destinations at DrawTriangles
g.device.CreateRenderTargetView(nil, &_D3D12_RENDER_TARGET_VIEW_DESC{
Format: _DXGI_FORMAT_R8G8B8A8_UNORM,
ViewDimension: _D3D12_RTV_DIMENSION_TEXTURE2D,
}, hnd)
g.rtvDescriptorSize = g.device.GetDescriptorHandleIncrementSize(_D3D12_DESCRIPTOR_HEAP_TYPE_RTV)
if err := g.pipelineStates.initialize(g.device); err != nil {
@ -1064,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
}
@ -1074,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,
}
@ -1082,7 +1110,82 @@ func (g *graphics12) NewShader(program *shaderir.Program) (graphicsdriver.Shader
return s, nil
}
func (g *graphics12) DrawTriangles(dstID graphicsdriver.ImageID, srcs [graphics.ShaderImageCount]graphicsdriver.ImageID, shaderID graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
func (g *graphics12) setAsRenderTargets(dsts []*image12, useStencil bool) error {
var rtvs []_D3D12_CPU_DESCRIPTOR_HANDLE
var dsv *_D3D12_CPU_DESCRIPTOR_HANDLE
for i, img := range dsts {
// Ignore a nil image in case of MRT
if img == nil {
_ = i
rtv, err := g.rtvEmptyDescriptorHeap.GetCPUDescriptorHandleForHeapStart()
if err != nil {
return err
}
rtv.Offset(int32(g.frameIndex), g.rtvDescriptorSize)
rtvs = append(rtvs, rtv)
continue
}
if img.screen {
if useStencil {
return fmt.Errorf("directx: stencils are not available on the screen framebuffer")
}
rtvBase, err := g.rtvDescriptorHeap.GetCPUDescriptorHandleForHeapStart()
if err != nil {
return err
}
rtv := rtvBase
rtv.Offset(int32(g.frameIndex), g.rtvDescriptorSize)
rtvs = append(rtvs, rtv)
continue
}
if err := img.ensureRenderTargetView(g.device); err != nil {
return err
}
rtvBase, err := img.rtvDescriptorHeap.GetCPUDescriptorHandleForHeapStart()
if err != nil {
return err
}
rtv := rtvBase
rtvs = append(rtvs, rtv)
if !useStencil {
continue
}
if err := img.ensureDepthStencilView(g.device); err != nil {
return err
}
if dsv != nil {
continue
}
sv, err := img.dsvDescriptorHeap.GetCPUDescriptorHandleForHeapStart()
if err != nil {
return err
}
dsv = &sv
}
if !useStencil {
g.drawCommandList.OMSetRenderTargets(rtvs, false, nil)
return nil
}
g.drawCommandList.OMSetStencilRef(0)
g.drawCommandList.OMSetRenderTargets(rtvs, false, dsv)
g.drawCommandList.ClearDepthStencilView(*dsv, _D3D12_CLEAR_FLAG_STENCIL, 0, 0, nil)
return nil
}
func (g *graphics12) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphicsdriver.ImageID, srcIDs [graphics.ShaderSrcImageCount]graphicsdriver.ImageID, shaderID graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
if shaderID == graphicsdriver.InvalidShaderID {
return fmt.Errorf("directx: shader ID is invalid")
}
@ -1093,7 +1196,7 @@ func (g *graphics12) DrawTriangles(dstID graphicsdriver.ImageID, srcs [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 {
@ -1103,20 +1206,46 @@ func (g *graphics12) DrawTriangles(dstID graphicsdriver.ImageID, srcs [graphics.
g.pipelineStates.releaseConstantBuffers(g.frameIndex)
}
dst := g.images[dstID]
var resourceBarriers []_D3D12_RESOURCE_BARRIER_Transition
if rb, ok := dst.transiteState(_D3D12_RESOURCE_STATE_RENDER_TARGET); ok {
resourceBarriers = append(resourceBarriers, rb)
}
var srcImages [graphics.ShaderImageCount]*image12
for i, srcID := range srcs {
src := g.images[srcID]
if src == nil {
var dsts [graphics.ShaderDstImageCount]*image12
var vp _D3D12_VIEWPORT
var targetCount int
firstTarget := -1
for i, id := range dstIDs {
img := g.images[id]
if img == nil {
continue
}
srcImages[i] = src
if rb, ok := src.transiteState(_D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); ok {
if firstTarget == -1 {
firstTarget = i
}
dsts[i] = img
w, h := img.internalSize()
vp = _D3D12_VIEWPORT{
TopLeftX: 0,
TopLeftY: 0,
Width: float32(w),
Height: float32(h),
MinDepth: _D3D12_MIN_DEPTH,
MaxDepth: _D3D12_MAX_DEPTH,
}
if rb, ok := img.transiteState(_D3D12_RESOURCE_STATE_RENDER_TARGET); ok {
resourceBarriers = append(resourceBarriers, rb)
}
targetCount++
}
var srcs [graphics.ShaderSrcImageCount]*image12
for i, srcID := range srcIDs {
img := g.images[srcID]
if img == nil {
continue
}
srcs[i] = img
if rb, ok := img.transiteState(_D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); ok {
resourceBarriers = append(resourceBarriers, rb)
}
}
@ -1125,25 +1254,26 @@ func (g *graphics12) DrawTriangles(dstID graphicsdriver.ImageID, srcs [graphics.
g.drawCommandList.ResourceBarrier(resourceBarriers)
}
if err := dst.setAsRenderTarget(g.drawCommandList, g.device, fillRule != graphicsdriver.FillAll); err != nil {
// If the number of targets is more than one, or if the only target is the first one, then
// it is safe to assume that MRT is used.
// Also, it only matters in order to specify empty targets/viewports when not all slots are
// being filled, even though it's not a MRT scenario.
usesMRT := targetCount > 1 || firstTarget > 0
if usesMRT {
targetCount = graphics.ShaderDstImageCount
}
if err := g.setAsRenderTargets(dsts[:targetCount], fillRule != graphicsdriver.FillRuleFillAll); err != nil {
return err
}
shader := g.shaders[shaderID]
adjustedUniforms := adjustUniforms(shader.uniformTypes, shader.uniformOffsets, uniforms)
w, h := dst.internalSize()
g.needFlushDrawCommandList = true
g.drawCommandList.RSSetViewports([]_D3D12_VIEWPORT{
{
TopLeftX: 0,
TopLeftY: 0,
Width: float32(w),
Height: float32(h),
MinDepth: _D3D12_MIN_DEPTH,
MaxDepth: _D3D12_MAX_DEPTH,
},
})
g.drawCommandList.RSSetViewports([]_D3D12_VIEWPORT{vp})
g.drawCommandList.IASetPrimitiveTopology(_D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST)
g.drawCommandList.IASetVertexBuffers(0, []_D3D12_VERTEX_BUFFER_VIEW{
{
@ -1158,7 +1288,7 @@ func (g *graphics12) DrawTriangles(dstID graphicsdriver.ImageID, srcs [graphics.
Format: _DXGI_FORMAT_R32_UINT,
})
if err := g.pipelineStates.drawTriangles(g.device, g.drawCommandList, g.frameIndex, dst.screen, srcImages, shader, dstRegions, adjustedUniforms, blend, indexOffset, fillRule); err != nil {
if err := g.pipelineStates.drawTriangles(g.device, g.drawCommandList, g.frameIndex, !usesMRT && dsts[firstTarget].screen, srcs, shader, dstRegions, adjustedUniforms, blend, indexOffset, fillRule); err != nil {
return err
}

View File

@ -70,7 +70,7 @@ func (i *image12) disposeImpl() {
func (i *image12) ReadPixels(args []graphicsdriver.PixelsArgs) error {
if i.screen {
return errors.New("directx: Pixels cannot be called on the screen")
return errors.New("directx: ReadPixels cannot be called on the screen")
}
if err := i.graphics.flushCommandList(i.graphics.drawCommandList); err != nil {

View File

@ -128,7 +128,7 @@ type pipelineStates struct {
constantBufferMaps [frameCount][]uintptr
}
const numConstantBufferAndSourceTextures = 1 + graphics.ShaderImageCount
const numConstantBufferAndSourceTextures = 1 + graphics.ShaderSrcImageCount
func (p *pipelineStates) initialize(device *_ID3D12Device) (ferr error) {
// Create a CBV/SRV/UAV descriptor heap.
@ -180,7 +180,7 @@ func (p *pipelineStates) initialize(device *_ID3D12Device) (ferr error) {
return nil
}
func (p *pipelineStates) drawTriangles(device *_ID3D12Device, commandList *_ID3D12GraphicsCommandList, frameIndex int, screen bool, srcs [graphics.ShaderImageCount]*image12, shader *shader12, dstRegions []graphicsdriver.DstRegion, uniforms []uint32, blend graphicsdriver.Blend, indexOffset int, fillRule graphicsdriver.FillRule) error {
func (p *pipelineStates) drawTriangles(device *_ID3D12Device, commandList *_ID3D12GraphicsCommandList, frameIndex int, screen bool, srcs [graphics.ShaderSrcImageCount]*image12, shader *shader12, dstRegions []graphicsdriver.DstRegion, uniforms []uint32, blend graphicsdriver.Blend, indexOffset int, fillRule graphicsdriver.FillRule) error {
idx := len(p.constantBuffers[frameIndex])
if idx >= numDescriptorsPerFrame {
return fmt.Errorf("directx: too many constant buffers")
@ -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
@ -354,7 +354,7 @@ func (p *pipelineStates) ensureRootSignature(device *_ID3D12Device) (rootSignatu
}
srv := _D3D12_DESCRIPTOR_RANGE{
RangeType: _D3D12_DESCRIPTOR_RANGE_TYPE_SRV, // t0
NumDescriptors: graphics.ShaderImageCount,
NumDescriptors: graphics.ShaderSrcImageCount,
BaseShaderRegister: 0,
RegisterSpace: 0,
OffsetInDescriptorsFromTableStart: 1,
@ -484,6 +484,18 @@ func (p *pipelineStates) newPipelineState(device *_ID3D12Device, vsh, psh *_ID3D
}
// Create a pipeline state.
rtBlendDesc := _D3D12_RENDER_TARGET_BLEND_DESC{
BlendEnable: 1,
LogicOpEnable: 0,
SrcBlend: blendFactorToBlend12(blend.BlendFactorSourceRGB, false),
DestBlend: blendFactorToBlend12(blend.BlendFactorDestinationRGB, false),
BlendOp: blendOperationToBlendOp12(blend.BlendOperationRGB),
SrcBlendAlpha: blendFactorToBlend12(blend.BlendFactorSourceAlpha, true),
DestBlendAlpha: blendFactorToBlend12(blend.BlendFactorDestinationAlpha, true),
BlendOpAlpha: blendOperationToBlendOp12(blend.BlendOperationAlpha),
LogicOp: _D3D12_LOGIC_OP_NOOP,
RenderTargetWriteMask: writeMask,
}
psoDesc := _D3D12_GRAPHICS_PIPELINE_STATE_DESC{
pRootSignature: rootSignature,
VS: _D3D12_SHADER_BYTECODE{
@ -498,18 +510,8 @@ func (p *pipelineStates) newPipelineState(device *_ID3D12Device, vsh, psh *_ID3D
AlphaToCoverageEnable: 0,
IndependentBlendEnable: 0,
RenderTarget: [8]_D3D12_RENDER_TARGET_BLEND_DESC{
{
BlendEnable: 1,
LogicOpEnable: 0,
SrcBlend: blendFactorToBlend12(blend.BlendFactorSourceRGB, false),
DestBlend: blendFactorToBlend12(blend.BlendFactorDestinationRGB, false),
BlendOp: blendOperationToBlendOp12(blend.BlendOperationRGB),
SrcBlendAlpha: blendFactorToBlend12(blend.BlendFactorSourceAlpha, true),
DestBlendAlpha: blendFactorToBlend12(blend.BlendFactorDestinationAlpha, true),
BlendOpAlpha: blendOperationToBlendOp12(blend.BlendOperationAlpha),
LogicOp: _D3D12_LOGIC_OP_NOOP,
RenderTargetWriteMask: writeMask,
},
rtBlendDesc, rtBlendDesc, rtBlendDesc, rtBlendDesc,
rtBlendDesc, rtBlendDesc, rtBlendDesc, rtBlendDesc, // TODO: need to fill them all?
},
},
SampleMask: math.MaxUint32,
@ -532,9 +534,10 @@ func (p *pipelineStates) newPipelineState(device *_ID3D12Device, vsh, psh *_ID3D
NumElements: uint32(len(inputElementDescsForDX12)),
},
PrimitiveTopologyType: _D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
NumRenderTargets: 1,
NumRenderTargets: graphics.ShaderDstImageCount,
RTVFormats: [8]_DXGI_FORMAT{
rtvFormat,
rtvFormat, rtvFormat, rtvFormat, rtvFormat,
rtvFormat, rtvFormat, rtvFormat, rtvFormat,
},
DSVFormat: dsvFormat,
SampleDesc: _DXGI_SAMPLE_DESC{

View File

@ -78,7 +78,7 @@ func (s *shader11) disposeImpl() {
}
}
func (s *shader11) use(uniforms []uint32, srcs [graphics.ShaderImageCount]*image11) error {
func (s *shader11) use(uniforms []uint32, srcs [graphics.ShaderSrcImageCount]*image11) error {
vs, err := s.ensureVertexShader()
if err != nil {
return err
@ -114,7 +114,7 @@ func (s *shader11) use(uniforms []uint32, srcs [graphics.ShaderImageCount]*image
s.graphics.deviceContext.Unmap(unsafe.Pointer(cb), 0)
// Set the render sources.
var srvs [graphics.ShaderImageCount]*_ID3D11ShaderResourceView
var srvs [graphics.ShaderSrcImageCount]*_ID3D11ShaderResourceView
for i, src := range srcs {
if src == nil {
continue

View File

@ -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 {

View File

@ -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)
}
@ -68,7 +68,7 @@ type Graphics interface {
NewShader(program *shaderir.Program) (Shader, error)
// DrawTriangles draws an image onto another image with the given parameters.
DrawTriangles(dst ImageID, srcs [graphics.ShaderImageCount]ImageID, shader ShaderID, dstRegions []DstRegion, indexOffset int, blend Blend, uniforms []uint32, fillRule FillRule) error
DrawTriangles(dsts [graphics.ShaderDstImageCount]ImageID, srcs [graphics.ShaderSrcImageCount]ImageID, shader ShaderID, dstRegions []DstRegion, indexOffset int, blend Blend, uniforms []uint32, fillRule FillRule) error
}
type Resetter interface {

View File

@ -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 layers textures are used only for rendering.
//
// https://developer.apple.com/documentation/quartzcore/cametallayer/1478168-framebufferonly
// https://developer.apple.com/documentation/quartzcore/cametallayer/1478168-framebufferonly?language=objc
func (ml MetalLayer) SetFramebufferOnly(framebufferOnly bool) {
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"))
}

View File

@ -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
}
@ -467,11 +467,11 @@ func (g *Graphics) flushRenderCommandEncoderIfNeeded() {
g.lastDst = nil
}
func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs [graphics.ShaderImageCount]*Image, indexOffset int, shader *Shader, uniforms [][]uint32, blend graphicsdriver.Blend, fillRule graphicsdriver.FillRule) error {
func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs [graphics.ShaderSrcImageCount]*Image, indexOffset int, shader *Shader, uniforms [][]uint32, blend graphicsdriver.Blend, fillRule graphicsdriver.FillRule) error {
// 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))))
@ -605,18 +605,18 @@ func (g *Graphics) draw(dst *Image, dstRegions []graphicsdriver.DstRegion, srcs
return nil
}
func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.ShaderImageCount]graphicsdriver.ImageID, shaderID graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
func (g *Graphics) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphicsdriver.ImageID, srcIDs [graphics.ShaderSrcImageCount]graphicsdriver.ImageID, shaderID graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
if shaderID == graphicsdriver.InvalidShaderID {
return fmt.Errorf("metal: shader ID is invalid")
}
dst := g.images[dstID]
dst := g.images[dstIDs[0]]
if dst.screen {
g.view.update()
}
var srcs [graphics.ShaderImageCount]*Image
var srcs [graphics.ShaderSrcImageCount]*Image
for i, srcID := range srcIDs {
srcs[i] = g.images[srcID]
}
@ -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)
}

View File

@ -0,0 +1,39 @@
// Copyright 2024 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mtl
import (
"unsafe"
"github.com/ebitengine/purego"
)
var libSystem uintptr
var (
dispatchDataCreate func(buffer unsafe.Pointer, size uint, queue uintptr, destructor uintptr) uintptr
dispatchRelease func(obj uintptr)
)
func init() {
lib, err := purego.Dlopen("/usr/lib/libSystem.B.dylib", purego.RTLD_LAZY|purego.RTLD_GLOBAL)
if err != nil {
panic(err)
}
libSystem = lib
purego.RegisterLibFunc(&dispatchDataCreate, libSystem, "dispatch_data_create")
purego.RegisterLibFunc(&dispatchRelease, libSystem, "dispatch_release")
}

View File

@ -112,15 +112,15 @@ fragment float4 FragmentShader(Vertex in [[stage_in]]) {
return in.color;
}
`
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()

View File

@ -36,7 +36,7 @@ import (
// GPUFamily represents the functionality for families of GPUs.
//
// Reference: https://developer.apple.com/documentation/metal/mtlgpufamily
// Reference: https://developer.apple.com/documentation/metal/mtlgpufamily?language=objc.
type GPUFamily int
const (
@ -54,7 +54,7 @@ const (
// FeatureSet defines a specific platform, hardware, and software configuration.
//
// Reference: https://developer.apple.com/documentation/metal/mtlfeatureset.
// Reference: https://developer.apple.com/documentation/metal/mtlfeatureset?language=objc.
type FeatureSet uint16
const (
@ -92,7 +92,7 @@ const (
// TextureType defines The dimension of each image, including whether multiple images are arranged into an array or
// a cube.
//
// Reference: https://developer.apple.com/documentation/metal/mtltexturetype
// Reference: https://developer.apple.com/documentation/metal/mtltexturetype?language=objc.
type TextureType uint16
const (
@ -102,7 +102,7 @@ const (
// PixelFormat defines data formats that describe the organization
// and characteristics of individual pixels in a texture.
//
// Reference: https://developer.apple.com/documentation/metal/mtlpixelformat.
// Reference: https://developer.apple.com/documentation/metal/mtlpixelformat?language=objc.
type PixelFormat uint16
// The data formats that describe the organization and characteristics
@ -117,7 +117,7 @@ const (
// PrimitiveType defines geometric primitive types for drawing commands.
//
// Reference: https://developer.apple.com/documentation/metal/mtlprimitivetype.
// Reference: https://developer.apple.com/documentation/metal/mtlprimitivetype?language=objc.
type PrimitiveType uint8
// Geometric primitive types for drawing commands.
@ -132,7 +132,7 @@ const (
// LoadAction defines actions performed at the start of a rendering pass
// for a render command encoder.
//
// Reference: https://developer.apple.com/documentation/metal/mtlloadaction.
// Reference: https://developer.apple.com/documentation/metal/mtlloadaction?language=objc.
type LoadAction uint8
// Actions performed at the start of a rendering pass for a render command encoder.
@ -145,7 +145,7 @@ const (
// StoreAction defines actions performed at the end of a rendering pass
// for a render command encoder.
//
// Reference: https://developer.apple.com/documentation/metal/mtlstoreaction.
// Reference: https://developer.apple.com/documentation/metal/mtlstoreaction?language=objc.
type StoreAction uint8
// Actions performed at the end of a rendering pass for a render command encoder.
@ -160,7 +160,7 @@ const (
// StorageMode defines the memory location and access permissions of a resource.
//
// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode.
// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode?language=objc.
type StorageMode uint8
const (
@ -189,7 +189,7 @@ const (
// ResourceOptions defines optional arguments used to create
// and influence behavior of buffer and texture objects.
//
// Reference: https://developer.apple.com/documentation/metal/mtlresourceoptions.
// Reference: https://developer.apple.com/documentation/metal/mtlresourceoptions?language=objc.
type ResourceOptions uint16
const (
@ -237,7 +237,7 @@ const (
// CPUCacheMode is the CPU cache mode that defines the CPU mapping of a resource.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcpucachemode.
// Reference: https://developer.apple.com/documentation/metal/mtlcpucachemode?language=objc.
type CPUCacheMode uint8
const (
@ -252,7 +252,7 @@ const (
// IndexType is the index type for an index buffer that references vertices of geometric primitives.
//
// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode
// Reference: https://developer.apple.com/documentation/metal/mtlstoragemode?language=objc
type IndexType uint8
const (
@ -358,7 +358,7 @@ const (
// Resource represents a memory allocation for storing specialized data
// that is accessible to the GPU.
//
// Reference: https://developer.apple.com/documentation/metal/mtlresource.
// Reference: https://developer.apple.com/documentation/metal/mtlresource?language=objc.
type Resource interface {
// resource returns the underlying id<MTLResource> pointer.
resource() unsafe.Pointer
@ -366,7 +366,7 @@ type Resource interface {
// RenderPipelineDescriptor configures new RenderPipelineState objects.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinedescriptor.
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinedescriptor?language=objc.
type RenderPipelineDescriptor struct {
// VertexFunction is a programmable function that processes individual vertices in a rendering pass.
VertexFunction Function
@ -384,7 +384,7 @@ type RenderPipelineDescriptor struct {
// RenderPipelineColorAttachmentDescriptor describes a color render target that specifies
// the color configuration and color operations associated with a render pipeline.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinecolorattachmentdescriptor.
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpipelinecolorattachmentdescriptor?language=objc.
type RenderPipelineColorAttachmentDescriptor struct {
// PixelFormat is the pixel format of the color attachment's texture.
PixelFormat PixelFormat
@ -404,7 +404,7 @@ type RenderPipelineColorAttachmentDescriptor struct {
// RenderPassDescriptor describes a group of render targets that serve as
// the output destination for pixels generated by a render pass.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassdescriptor.
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassdescriptor?language=objc.
type RenderPassDescriptor struct {
// ColorAttachments is array of state information for attachments that store color data.
ColorAttachments [1]RenderPassColorAttachmentDescriptor
@ -416,7 +416,7 @@ type RenderPassDescriptor struct {
// RenderPassColorAttachmentDescriptor describes a color render target that serves
// as the output destination for color pixels generated by a render pass.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpasscolorattachmentdescriptor.
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpasscolorattachmentdescriptor?language=objc.
type RenderPassColorAttachmentDescriptor struct {
RenderPassAttachmentDescriptor
ClearColor ClearColor
@ -425,7 +425,7 @@ type RenderPassColorAttachmentDescriptor struct {
// RenderPassStencilAttachment describes a stencil render target that serves as the output
// destination for stencil pixels generated by a render pass.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassstencilattachmentdescriptor
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassstencilattachmentdescriptor?language=objc.
type RenderPassStencilAttachment struct {
RenderPassAttachmentDescriptor
}
@ -433,7 +433,7 @@ type RenderPassStencilAttachment struct {
// RenderPassAttachmentDescriptor describes a render target that serves
// as the output destination for pixels generated by a render pass.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassattachmentdescriptor.
// Reference: https://developer.apple.com/documentation/metal/mtlrenderpassattachmentdescriptor?language=objc.
type RenderPassAttachmentDescriptor struct {
LoadAction LoadAction
StoreAction StoreAction
@ -442,14 +442,14 @@ type RenderPassAttachmentDescriptor struct {
// ClearColor is an RGBA value used for a color pixel.
//
// Reference: https://developer.apple.com/documentation/metal/mtlclearcolor.
// Reference: https://developer.apple.com/documentation/metal/mtlclearcolor?language=objc.
type ClearColor struct {
Red, Green, Blue, Alpha float64
}
// TextureDescriptor configures new Texture objects.
//
// Reference: https://developer.apple.com/documentation/metal/mtltexturedescriptor.
// Reference: https://developer.apple.com/documentation/metal/mtltexturedescriptor?language=objc.
type TextureDescriptor struct {
TextureType TextureType
PixelFormat PixelFormat
@ -462,7 +462,7 @@ type TextureDescriptor struct {
// Device is abstract representation of the GPU that
// serves as the primary interface for a Metal app.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice.
// Reference: https://developer.apple.com/documentation/metal/mtldevice?language=objc.
type Device struct {
device objc.ID
@ -493,6 +493,7 @@ var (
sel_supportsFeatureSet = objc.RegisterName("supportsFeatureSet:")
sel_newCommandQueue = objc.RegisterName("newCommandQueue")
sel_newLibraryWithSource_options_error = objc.RegisterName("newLibraryWithSource:options:error:")
sel_newLibraryWithData_error = objc.RegisterName("newLibraryWithData:error:")
sel_release = objc.RegisterName("release")
sel_retain = objc.RegisterName("retain")
sel_new = objc.RegisterName("new")
@ -567,7 +568,7 @@ var (
// CreateSystemDefaultDevice returns the preferred system default Metal device.
//
// Reference: https://developer.apple.com/documentation/metal/1433401-mtlcreatesystemdefaultdevice.
// Reference: https://developer.apple.com/documentation/metal/1433401-mtlcreatesystemdefaultdevice?language=objc.
func CreateSystemDefaultDevice() (Device, error) {
metal, err := purego.Dlopen("/System/Library/Frameworks/Metal.framework/Metal", purego.RTLD_LAZY|purego.RTLD_GLOBAL)
if err != nil {
@ -607,37 +608,36 @@ func (d Device) Device() unsafe.Pointer { return *(*unsafe.Pointer)(unsafe.Point
// RespondsToSelector returns a Boolean value that indicates whether the receiver implements or inherits a method that can respond to a specified message.
//
// Reference: https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418583-respondstoselector
// Reference: https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418583-respondstoselector?language=objc.
func (d Device) RespondsToSelector(sel objc.SEL) bool {
return d.device.Send(sel_respondsToSelector, sel) != 0
}
// SupportsFamily returns a Boolean value that indicates whether the GPU device supports the feature set of a specific GPU family.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/3143473-supportsfamily
// Reference: https://developer.apple.com/documentation/metal/mtldevice/3143473-supportsfamily?language=objc.
func (d Device) SupportsFamily(gpuFamily GPUFamily) bool {
return d.device.Send(sel_supportsFamily, uintptr(gpuFamily)) != 0
}
// SupportsFeatureSet reports whether device d supports feature set fs.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433418-supportsfeatureset.
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433418-supportsfeatureset?language=objc.
func (d Device) SupportsFeatureSet(fs FeatureSet) bool {
return d.device.Send(sel_supportsFeatureSet, uintptr(fs)) != 0
}
// MakeCommandQueue creates a serial command submission queue.
// NewCommandQueue creates a queue you use to submit rendering and computation commands to a GPU.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433388-makecommandqueue.
func (d Device) MakeCommandQueue() CommandQueue {
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433388-newcommandqueue?language=objc.
func (d Device) NewCommandQueue() CommandQueue {
return CommandQueue{d.device.Send(sel_newCommandQueue)}
}
// MakeLibrary creates a new library that contains
// the functions stored in the specified source string.
// NewLibraryWithSource synchronously creates a Metal library instance by compiling the functions in a source string.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433431-makelibrary.
func (d Device) MakeLibrary(source string, opt CompileOptions) (Library, error) {
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433431-newlibrarywithsource?language=objc.
func (d Device) NewLibraryWithSource(source string, opt CompileOptions) (Library, error) {
var err cocoa.NSError
l := d.device.Send(
sel_newLibraryWithSource_options_error,
@ -652,10 +652,31 @@ func (d Device) MakeLibrary(source string, opt CompileOptions) (Library, error)
return Library{l}, nil
}
// MakeRenderPipelineState creates a render pipeline state object.
// NewLibraryWithData Creates a Metal library instance that contains the functions in a precompiled Metal library.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433369-makerenderpipelinestate.
func (d Device) MakeRenderPipelineState(rpd RenderPipelineDescriptor) (RenderPipelineState, error) {
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433391-newlibrarywithdata?language=objc.
func (d Device) NewLibraryWithData(buffer []byte) (Library, error) {
defer runtime.KeepAlive(buffer)
data := dispatchDataCreate(unsafe.Pointer(&buffer[0]), uint(len(buffer)), 0, 0)
defer dispatchRelease(data)
var err cocoa.NSError
l := d.device.Send(
sel_newLibraryWithData_error,
data,
unsafe.Pointer(&err),
)
if l == 0 {
return Library{}, errors.New(cocoa.NSString{ID: err.Send(sel_localizedDescription)}.String())
}
return Library{l}, nil
}
// NewRenderPipelineStateWithDescriptor synchronously creates a render pipeline state.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433369-newrenderpipelinestatewithdescri?language=objc.
func (d Device) NewRenderPipelineStateWithDescriptor(rpd RenderPipelineDescriptor) (RenderPipelineState, error) {
renderPipelineDescriptor := objc.ID(class_MTLRenderPipelineDescriptor).Send(sel_new)
renderPipelineDescriptor.Send(sel_setVertexFunction, rpd.VertexFunction.function)
renderPipelineDescriptor.Send(sel_setFragmentFunction, rpd.FragmentFunction.function)
@ -683,26 +704,24 @@ func (d Device) MakeRenderPipelineState(rpd RenderPipelineDescriptor) (RenderPip
return RenderPipelineState{renderPipelineState}, nil
}
// MakeBufferWithBytes allocates a new buffer of a given length
// and initializes its contents by copying existing data into it.
// NewBufferWithBytes allocates a new buffer of a given length and initializes its contents by copying existing data into it.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433429-makebuffer.
func (d Device) MakeBufferWithBytes(bytes unsafe.Pointer, length uintptr, opt ResourceOptions) Buffer {
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433429-newbufferwithbytes?language=objc.
func (d Device) NewBufferWithBytes(bytes unsafe.Pointer, length uintptr, opt ResourceOptions) Buffer {
return Buffer{d.device.Send(sel_newBufferWithBytes_length_options, bytes, length, uintptr(opt))}
}
// MakeBufferWithLength allocates a new zero-filled buffer of a given length.
// NewBufferWithLength allocates a new zero-filled buffer of a given length.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433375-newbufferwithlength
func (d Device) MakeBufferWithLength(length uintptr, opt ResourceOptions) Buffer {
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433375-newbufferwithlength?language=objc.
func (d Device) NewBufferWithLength(length uintptr, opt ResourceOptions) Buffer {
return Buffer{d.device.Send(sel_newBufferWithLength_options, length, uintptr(opt))}
}
// MakeTexture creates a texture object with privately owned storage
// that contains texture state.
// NewTextureWithDescriptor creates a new texture instance.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433425-maketexture.
func (d Device) MakeTexture(td TextureDescriptor) Texture {
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433425-newtexturewithdescriptor?language=objc.
func (d Device) NewTextureWithDescriptor(td TextureDescriptor) Texture {
textureDescriptor := objc.ID(class_MTLTextureDescriptor).Send(sel_new)
textureDescriptor.Send(sel_setTextureType, uintptr(td.TextureType))
textureDescriptor.Send(sel_setPixelFormat, uintptr(td.PixelFormat))
@ -717,10 +736,10 @@ func (d Device) MakeTexture(td TextureDescriptor) Texture {
}
}
// MakeDepthStencilState creates a new object that contains depth and stencil test state.
// NewDepthStencilStateWithDescriptor creates a depth-stencil state instance.
//
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433412-makedepthstencilstate
func (d Device) MakeDepthStencilState(dsd DepthStencilDescriptor) DepthStencilState {
// Reference: https://developer.apple.com/documentation/metal/mtldevice/1433412-newdepthstencilstatewithdescript?language=objc.
func (d Device) NewDepthStencilStateWithDescriptor(dsd DepthStencilDescriptor) DepthStencilState {
depthStencilDescriptor := objc.ID(class_MTLDepthStencilDescriptor).Send(sel_new)
backFaceStencil := depthStencilDescriptor.Send(sel_backFaceStencil)
backFaceStencil.Send(sel_setStencilFailureOperation, uintptr(dsd.BackFaceStencil.StencilFailureOperation))
@ -742,14 +761,14 @@ func (d Device) MakeDepthStencilState(dsd DepthStencilDescriptor) DepthStencilSt
// CompileOptions specifies optional compilation settings for
// the graphics or compute functions within a library.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcompileoptions.
// Reference: https://developer.apple.com/documentation/metal/mtlcompileoptions?language=objc.
type CompileOptions struct {
// TODO.
}
// Drawable is a displayable resource that can be rendered or written to.
//
// Reference: https://developer.apple.com/documentation/metal/mtldrawable.
// Reference: https://developer.apple.com/documentation/metal/mtldrawable?language=objc.
type Drawable interface {
// Drawable returns the underlying id<MTLDrawable> pointer.
Drawable() unsafe.Pointer
@ -758,7 +777,7 @@ type Drawable interface {
// CommandQueue is a queue that organizes the order
// in which command buffers are executed by the GPU.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue?language=objc.
type CommandQueue struct {
commandQueue objc.ID
}
@ -767,17 +786,17 @@ func (cq CommandQueue) Release() {
cq.commandQueue.Send(sel_release)
}
// MakeCommandBuffer creates a command buffer.
// CommandBuffer returns a command buffer from the command queue that maintains strong references to resources.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue/1508686-makecommandbuffer.
func (cq CommandQueue) MakeCommandBuffer() CommandBuffer {
// Reference: https://developer.apple.com/documentation/metal/mtlcommandqueue/1508686-commandbuffer?language=objc.
func (cq CommandQueue) CommandBuffer() CommandBuffer {
return CommandBuffer{cq.commandQueue.Send(sel_commandBuffer)}
}
// CommandBuffer is a container that stores encoded commands
// that are committed to and executed by the GPU.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer?language=objc.
type CommandBuffer struct {
commandBuffer objc.ID
}
@ -792,44 +811,43 @@ func (cb CommandBuffer) Release() {
// Status returns the current stage in the lifetime of the command buffer.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443048-status
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443048-status?language=objc.
func (cb CommandBuffer) Status() CommandBufferStatus {
return CommandBufferStatus(cb.commandBuffer.Send(sel_status))
}
// PresentDrawable registers a drawable presentation to occur as soon as possible.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443029-presentdrawable.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443029-presentdrawable?language=objc.
func (cb CommandBuffer) PresentDrawable(d Drawable) {
cb.commandBuffer.Send(sel_presentDrawable, d.Drawable())
}
// Commit commits this command buffer for execution as soon as possible.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443003-commit.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443003-commit?language=objc.
func (cb CommandBuffer) Commit() {
cb.commandBuffer.Send(sel_commit)
}
// WaitUntilCompleted waits for the execution of this command buffer to complete.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443039-waituntilcompleted.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443039-waituntilcompleted?language=objc.
func (cb CommandBuffer) WaitUntilCompleted() {
cb.commandBuffer.Send(sel_waitUntilCompleted)
}
// WaitUntilScheduled blocks execution of the current thread until the command buffer is scheduled.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443036-waituntilscheduled.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443036-waituntilscheduled?language=objc.
func (cb CommandBuffer) WaitUntilScheduled() {
cb.commandBuffer.Send(sel_waitUntilScheduled)
}
// MakeRenderCommandEncoder creates an encoder object that can
// encode graphics rendering commands into this command buffer.
// RenderCommandEncoderWithDescriptor creates a render command encoder from a descriptor.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1442999-makerendercommandencoder.
func (cb CommandBuffer) MakeRenderCommandEncoder(rpd RenderPassDescriptor) RenderCommandEncoder {
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1442999-rendercommandencoderwithdescript?language=objc.
func (cb CommandBuffer) RenderCommandEncoderWithDescriptor(rpd RenderPassDescriptor) RenderCommandEncoder {
var renderPassDescriptor = objc.ID(class_MTLRenderPassDescriptor).Send(sel_new)
var colorAttachments0 = renderPassDescriptor.Send(sel_colorAttachments).Send(sel_objectAtIndexedSubscript, 0)
colorAttachments0.Send(sel_setLoadAction, int(rpd.ColorAttachments[0].LoadAction))
@ -850,11 +868,11 @@ func (cb CommandBuffer) MakeRenderCommandEncoder(rpd RenderPassDescriptor) Rende
return RenderCommandEncoder{CommandEncoder{rce}}
}
// MakeBlitCommandEncoder creates an encoder object that can encode
// BlitCommandEncoder creates an encoder object that can encode
// memory operation (blit) commands into this command buffer.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443001-makeblitcommandencoder.
func (cb CommandBuffer) MakeBlitCommandEncoder() BlitCommandEncoder {
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443001-makeblitcommandencoder?language=objc.
func (cb CommandBuffer) BlitCommandEncoder() BlitCommandEncoder {
ce := cb.commandBuffer.Send(sel_blitCommandEncoder)
return BlitCommandEncoder{CommandEncoder{ce}}
}
@ -862,14 +880,14 @@ func (cb CommandBuffer) MakeBlitCommandEncoder() BlitCommandEncoder {
// CommandEncoder is an encoder that writes sequential GPU commands
// into a command buffer.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandencoder.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandbuffer/1443001-blitcommandencoder?language=objc.
type CommandEncoder struct {
commandEncoder objc.ID
}
// EndEncoding declares that all command generation from this encoder is completed.
//
// Reference: https://developer.apple.com/documentation/metal/mtlcommandencoder/1458038-endencoding.
// Reference: https://developer.apple.com/documentation/metal/mtlcommandencoder/1458038-endencoding?language=objc.
func (ce CommandEncoder) EndEncoding() {
ce.commandEncoder.Send(sel_endEncoding)
}
@ -877,7 +895,7 @@ func (ce CommandEncoder) EndEncoding() {
// RenderCommandEncoder is an encoder that specifies graphics-rendering commands
// and executes graphics functions.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder.
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder?language=objc.
type RenderCommandEncoder struct {
CommandEncoder
}
@ -888,7 +906,7 @@ func (rce RenderCommandEncoder) Release() {
// SetRenderPipelineState sets the current render pipeline state object.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515811-setrenderpipelinestate.
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515811-setrenderpipelinestate?language=objc.
func (rce RenderCommandEncoder) SetRenderPipelineState(rps RenderPipelineState) {
rce.commandEncoder.Send(sel_setRenderPipelineState, rps.renderPipelineState)
}
@ -903,7 +921,7 @@ func (rce RenderCommandEncoder) SetViewport(viewport Viewport) {
// SetScissorRect sets the scissor rectangle for a fragment scissor test.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515583-setscissorrect
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515583-setscissorrect?language=objc.
func (rce RenderCommandEncoder) SetScissorRect(scissorRect ScissorRect) {
inv := cocoa.NSInvocation_invocationWithMethodSignature(cocoa.NSMethodSignature_signatureWithObjCTypes("v@:{MTLScissorRect=qqqq}"))
inv.SetTarget(rce.commandEncoder)
@ -915,14 +933,14 @@ func (rce RenderCommandEncoder) SetScissorRect(scissorRect ScissorRect) {
// SetVertexBuffer sets a buffer for the vertex shader function at an index
// in the buffer argument table with an offset that specifies the start of the data.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515829-setvertexbuffer.
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515829-setvertexbuffer?language=objc.
func (rce RenderCommandEncoder) SetVertexBuffer(buf Buffer, offset, index int) {
rce.commandEncoder.Send(sel_setVertexBuffer_offset_atIndex, buf.buffer, offset, index)
}
// SetVertexBytes sets a block of data for the vertex function.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515846-setvertexbytes.
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515846-setvertexbytes?language=objc.
func (rce RenderCommandEncoder) SetVertexBytes(bytes unsafe.Pointer, length uintptr, index int) {
rce.commandEncoder.Send(sel_setVertexBytes_length_atIndex, bytes, length, index)
}
@ -933,7 +951,7 @@ func (rce RenderCommandEncoder) SetFragmentBytes(bytes unsafe.Pointer, length ui
// SetFragmentTexture sets a texture for the fragment function at an index in the texture argument table.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515390-setfragmenttexture
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1515390-setfragmenttexture?language=objc.
func (rce RenderCommandEncoder) SetFragmentTexture(texture Texture, index int) {
rce.commandEncoder.Send(sel_setFragmentTexture_atIndex, texture.texture, index)
}
@ -944,7 +962,7 @@ func (rce RenderCommandEncoder) SetBlendColor(red, green, blue, alpha float32) {
// SetDepthStencilState sets the depth and stencil test state.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516119-setdepthstencilstate
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516119-setdepthstencilstate?language=objc.
func (rce RenderCommandEncoder) SetDepthStencilState(depthStencilState DepthStencilState) {
rce.commandEncoder.Send(sel_setDepthStencilState, depthStencilState.depthStencilState)
}
@ -952,7 +970,7 @@ func (rce RenderCommandEncoder) SetDepthStencilState(depthStencilState DepthSten
// DrawPrimitives renders one instance of primitives using vertex data
// in contiguous array elements.
//
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516326-drawprimitives.
// Reference: https://developer.apple.com/documentation/metal/mtlrendercommandencoder/1516326-drawprimitives?language=objc.
func (rce RenderCommandEncoder) DrawPrimitives(typ PrimitiveType, vertexStart, vertexCount int) {
rce.commandEncoder.Send(sel_drawPrimitives_vertexStart_vertexCount, uintptr(typ), vertexStart, vertexCount)
}
@ -969,7 +987,7 @@ func (rce RenderCommandEncoder) DrawIndexedPrimitives(typ PrimitiveType, indexCo
// BlitCommandEncoder is an encoder that specifies resource copy
// and resource synchronization commands.
//
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder.
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder?language=objc.
type BlitCommandEncoder struct {
CommandEncoder
}
@ -977,7 +995,7 @@ type BlitCommandEncoder struct {
// Synchronize flushes any copy of the specified resource from its corresponding
// Device caches and, if needed, invalidates any CPU caches.
//
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400775-synchronize.
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400775-synchronize?language=objc.
func (bce BlitCommandEncoder) Synchronize(resource Resource) {
if runtime.GOOS == "ios" {
return
@ -985,6 +1003,9 @@ func (bce BlitCommandEncoder) Synchronize(resource Resource) {
bce.commandEncoder.Send(sel_synchronizeResource, resource.resource())
}
// SynchronizeTexture encodes a command that synchronizes a part of the CPUs copy of a texture so that it matches the GPUs copy.
//
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400757-synchronizetexture?language=objc.
func (bce BlitCommandEncoder) SynchronizeTexture(texture Texture, slice int, level int) {
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 textures slice into another slice.
//
// Reference: https://developer.apple.com/documentation/metal/mtlblitcommandencoder/1400754-copyfromtexture?language=objc.
func (bce BlitCommandEncoder) CopyFromTexture(sourceTexture Texture, sourceSlice int, sourceLevel int, sourceOrigin Origin, sourceSize Size, destinationTexture Texture, destinationSlice int, destinationLevel int, destinationOrigin Origin) {
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

View File

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

View File

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

View File

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

View File

@ -102,6 +102,7 @@ type context struct {
locationCache *locationCache
screenFramebuffer framebufferNative // This might not be the default frame buffer '0' (e.g. iOS).
mrtFramebuffer framebufferNative // The dynamic framebuffer used for MRT operations
lastFramebuffer framebufferNative
lastTexture textureNative
lastRenderbuffer renderbufferNative
@ -110,8 +111,6 @@ type context struct {
lastBlend graphicsdriver.Blend
maxTextureSize int
maxTextureSizeOnce sync.Once
highp bool
highpOnce sync.Once
initOnce sync.Once
}
@ -139,26 +138,25 @@ func (c *context) bindFramebuffer(f framebufferNative) {
c.lastFramebuffer = f
}
func (c *context) setViewport(f *framebuffer) {
c.bindFramebuffer(f.native)
if c.lastViewportWidth == f.viewportWidth && c.lastViewportHeight == f.viewportHeight {
func (c *context) setViewport(width, height int, screen bool) {
if c.lastViewportWidth == width && c.lastViewportHeight == height {
return
}
// On some environments, viewport size must be within the framebuffer size.
// e.g. Edge (#71), Chrome on GPD Pocket (#420), macOS Mojave (#691).
// Use the same size of the framebuffer here.
c.ctx.Viewport(0, 0, int32(f.viewportWidth), int32(f.viewportHeight))
c.ctx.Viewport(0, 0, int32(width), int32(height))
// glViewport must be called at least at every frame on iOS.
// As the screen framebuffer is the last render target, next SetViewport should be
// the first call at a frame.
if f.native == c.screenFramebuffer {
if screen {
c.lastViewportWidth = 0
c.lastViewportHeight = 0
} else {
c.lastViewportWidth = f.viewportWidth
c.lastViewportHeight = f.viewportHeight
c.lastViewportWidth = width
c.lastViewportHeight = height
}
}
@ -264,16 +262,6 @@ func (c *context) framebufferPixels(buf []byte, f *framebuffer, region image.Rec
return nil
}
func (c *context) framebufferPixelsToBuffer(f *framebuffer, buffer buffer, width, height int) {
c.ctx.Flush()
c.bindFramebuffer(f.native)
c.ctx.BindBuffer(gl.PIXEL_PACK_BUFFER, uint32(buffer))
c.ctx.ReadPixels(nil, 0, 0, int32(width), int32(height), gl.RGBA, gl.UNSIGNED_BYTE)
c.ctx.BindBuffer(gl.PIXEL_PACK_BUFFER, 0)
}
func (c *context) deleteTexture(t textureNative) {
if c.lastTexture == t {
c.lastTexture = 0
@ -357,7 +345,7 @@ func (c *context) bindStencilBuffer(f framebufferNative, r renderbufferNative) e
c.ctx.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, uint32(r))
if s := c.ctx.CheckFramebufferStatus(gl.FRAMEBUFFER); s != gl.FRAMEBUFFER_COMPLETE {
return errors.New(fmt.Sprintf("opengl: glFramebufferRenderbuffer failed: %d", s))
return fmt.Errorf("opengl: glFramebufferRenderbuffer failed: %d", s)
}
return nil
}

View File

@ -23,6 +23,7 @@ const (
BLEND = 0x0BE2
CLAMP_TO_EDGE = 0x812F
COLOR_ATTACHMENT0 = 0x8CE0
COLOR_BUFFER_BIT = 0x4000
COMPILE_STATUS = 0x8B81
DECR_WRAP = 0x8508
DEPTH24_STENCIL8 = 0x88F0
@ -52,6 +53,7 @@ const (
MIN = 0x8007
NEAREST = 0x2600
NO_ERROR = 0
NONE = 0
NOTEQUAL = 0x0205
ONE = 1
ONE_MINUS_DST_ALPHA = 0x0305

View File

@ -293,6 +293,14 @@ func (d *DebugContext) DisableVertexAttribArray(arg0 uint32) {
}
}
func (d *DebugContext) DrawBuffers(arg0 []uint32) {
d.Context.DrawBuffers(arg0)
fmt.Fprintln(os.Stderr, "DrawBuffers")
if e := d.Context.GetError(); e != NO_ERROR {
panic(fmt.Sprintf("gl: GetError() returned %d at DrawBuffers", e))
}
}
func (d *DebugContext) DrawElements(arg0 uint32, arg1 int32, arg2 uint32, arg3 int) {
d.Context.DrawElements(arg0, arg1, arg2, arg3)
fmt.Fprintln(os.Stderr, "DrawElements")

View File

@ -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
@ -128,6 +128,10 @@ package gl
// typedef void (*fn)(GLuint index);
// ((fn)(fnptr))(index);
// }
// static void glowDrawBuffers(uintptr_t fnptr, GLsizei n, const GLenum* bufs) {
// typedef void (*fn)(GLsizei n, const GLenum* bufs);
// ((fn)(fnptr))(n, bufs);
// }
// static void glowDrawElements(uintptr_t fnptr, GLenum mode, GLsizei count, GLenum type, const uintptr_t indices) {
// typedef void (*fn)(GLenum mode, GLsizei count, GLenum type, const uintptr_t indices);
// ((fn)(fnptr))(mode, count, type, indices);
@ -351,6 +355,7 @@ type defaultContext struct {
gpDeleteVertexArrays C.uintptr_t
gpDisable C.uintptr_t
gpDisableVertexAttribArray C.uintptr_t
gpDrawBuffers C.uintptr_t
gpDrawElements C.uintptr_t
gpEnable C.uintptr_t
gpEnableVertexAttribArray C.uintptr_t
@ -565,6 +570,10 @@ func (c *defaultContext) DisableVertexAttribArray(index uint32) {
C.glowDisableVertexAttribArray(c.gpDisableVertexAttribArray, C.GLuint(index))
}
func (c *defaultContext) DrawBuffers(bufs []uint32) {
C.glowDrawBuffers(c.gpDrawBuffers, C.GLsizei(len(bufs)), (*C.GLenum)(unsafe.Pointer(&bufs[0])))
}
func (c *defaultContext) DrawElements(mode uint32, count int32, xtype uint32, offset int) {
C.glowDrawElements(c.gpDrawElements, C.GLenum(mode), C.GLsizei(count), C.GLenum(xtype), C.uintptr_t(offset))
}
@ -801,6 +810,7 @@ func (c *defaultContext) LoadFunctions() error {
c.gpDeleteVertexArrays = C.uintptr_t(g.get("glDeleteVertexArrays"))
c.gpDisable = C.uintptr_t(g.get("glDisable"))
c.gpDisableVertexAttribArray = C.uintptr_t(g.get("glDisableVertexAttribArray"))
c.gpDrawBuffers = C.uintptr_t(g.get("glDrawBuffers"))
c.gpDrawElements = C.uintptr_t(g.get("glDrawElements"))
c.gpEnable = C.uintptr_t(g.get("glEnable"))
c.gpEnableVertexAttribArray = C.uintptr_t(g.get("glEnableVertexAttribArray"))

View File

@ -54,6 +54,7 @@ type defaultContext struct {
fnDeleteVertexArray js.Value
fnDisable js.Value
fnDisableVertexAttribArray js.Value
fnDrawBuffers js.Value
fnDrawElements js.Value
fnEnable js.Value
fnEnableVertexAttribArray js.Value
@ -184,6 +185,7 @@ func NewDefaultContext(v js.Value) (Context, error) {
fnDeleteVertexArray: v.Get("deleteVertexArray").Call("bind", v),
fnDisable: v.Get("disable").Call("bind", v),
fnDisableVertexAttribArray: v.Get("disableVertexAttribArray").Call("bind", v),
fnDrawBuffers: v.Get("drawBuffers").Call("bind", v),
fnDrawElements: v.Get("drawElements").Call("bind", v),
fnEnable: v.Get("enable").Call("bind", v),
fnEnableVertexAttribArray: v.Get("enableVertexAttribArray").Call("bind", v),
@ -384,6 +386,11 @@ func (c *defaultContext) DisableVertexAttribArray(index uint32) {
c.fnDisableVertexAttribArray.Invoke(index)
}
func (c *defaultContext) DrawBuffers(bufs []uint32) {
arr := jsutil.NewUint32Array(bufs)
c.fnDrawBuffers.Invoke(arr)
}
func (c *defaultContext) DrawElements(mode uint32, count int32, xtype uint32, offset int) {
c.fnDrawElements.Invoke(mode, count, xtype, offset)
}

View File

@ -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
@ -51,6 +51,7 @@ type defaultContext struct {
gpDeleteVertexArrays uintptr
gpDisable uintptr
gpDisableVertexAttribArray uintptr
gpDrawBuffers uintptr
gpDrawElements uintptr
gpEnable uintptr
gpEnableVertexAttribArray uintptr
@ -269,6 +270,10 @@ func (c *defaultContext) DrawElements(mode uint32, count int32, xtype uint32, of
purego.SyscallN(c.gpDrawElements, uintptr(mode), uintptr(count), uintptr(xtype), uintptr(offset))
}
func (c *defaultContext) DrawBuffers(buffers []uint32) {
purego.SyscallN(c.gpDrawBuffers, uintptr(len(buffers)), uintptr(unsafe.Pointer(&buffers[0])))
}
func (c *defaultContext) Enable(cap uint32) {
purego.SyscallN(c.gpEnable, uintptr(cap))
}
@ -501,6 +506,7 @@ func (c *defaultContext) LoadFunctions() error {
c.gpDeleteVertexArrays = g.get("glDeleteVertexArrays")
c.gpDisable = g.get("glDisable")
c.gpDisableVertexAttribArray = g.get("glDisableVertexAttribArray")
c.gpDrawBuffers = g.get("glDrawBuffers")
c.gpDrawElements = g.get("glDrawElements")
c.gpEnable = g.get("glEnable")
c.gpEnableVertexAttribArray = g.get("glEnableVertexAttribArray")

View File

@ -60,6 +60,7 @@ type Context interface {
Disable(cap uint32)
DisableVertexAttribArray(index uint32)
DrawElements(mode uint32, count int32, xtype uint32, offset int)
DrawBuffers(buffers []uint32)
Enable(cap uint32)
EnableVertexAttribArray(index uint32)
Flush()

View File

@ -16,38 +16,18 @@
package gl
// #cgo LDFLAGS: -ldl
//
// #include <dlfcn.h>
// #include <stdlib.h>
//
// static void* getProcAddressGL(void* libGL, const char* name) {
// static void*(*glXGetProcAddress)(const char*);
// if (!glXGetProcAddress) {
// glXGetProcAddress = dlsym(libGL, "glXGetProcAddress");
// if (!glXGetProcAddress) {
// glXGetProcAddress = dlsym(libGL, "glXGetProcAddressARB");
// }
// }
// return glXGetProcAddress(name);
// }
//
// static void* getProcAddressGLES(void* libGLES, const char* name) {
// return dlsym(libGLES, name);
// }
import "C"
import (
"fmt"
"os"
"runtime"
"strings"
"unsafe"
"github.com/ebitengine/purego"
)
var (
libGL unsafe.Pointer
libGLES unsafe.Pointer
libGL uintptr
libGLES uintptr
)
func (c *defaultContext) init() error {
@ -72,10 +52,8 @@ func (c *defaultContext) init() error {
// [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
}
@ -84,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
@ -99,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
}

View File

@ -198,18 +198,88 @@ func (g *Graphics) uniformVariableName(idx int) string {
return name
}
func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.ShaderImageCount]graphicsdriver.ImageID, shaderID graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
func (g *Graphics) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphicsdriver.ImageID, srcIDs [graphics.ShaderSrcImageCount]graphicsdriver.ImageID, shaderID graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
if shaderID == graphicsdriver.InvalidShaderID {
return fmt.Errorf("opengl: shader ID is invalid")
}
destination := g.images[dstID]
g.drawCalled = true
if err := destination.setViewport(); err != nil {
return err
targetCount := 0
firstTarget := -1
var dsts [graphics.ShaderDstImageCount]*Image
for i, dstID := range dstIDs {
if dstID == graphicsdriver.InvalidImageID {
continue
}
dst := g.images[dstIDs[i]]
if dst == nil {
continue
}
if firstTarget == -1 {
firstTarget = i
}
if err := dst.ensureFramebuffer(); err != nil {
return err
}
dsts[i] = dst
targetCount++
}
f := uint32(dsts[firstTarget].framebuffer.native)
// If the number of targets is more than one, or if the only target is the first one, then
// it is safe to assume that MRT is used.
// Also, it only matters in order to specify empty targets/viewports when not all slots are
// being filled.
usesMRT := firstTarget > 0 || targetCount > 1
if usesMRT {
f = uint32(g.context.mrtFramebuffer)
// Create the initial MRT framebuffer
if f == 0 {
f = g.context.ctx.CreateFramebuffer()
if f <= 0 {
return fmt.Errorf("opengl: creating framebuffer failed: the returned value is not positive but %d", f)
}
g.context.mrtFramebuffer = framebufferNative(f)
}
g.context.bindFramebuffer(framebufferNative(f))
// Reset color attachments
if s := g.context.ctx.CheckFramebufferStatus(gl.FRAMEBUFFER); s == gl.FRAMEBUFFER_COMPLETE {
g.context.ctx.Clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
}
for i, dst := range dsts {
if dst == nil {
continue
}
g.context.ctx.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0+uint32(i), gl.TEXTURE_2D, uint32(dst.texture), 0)
}
if s := g.context.ctx.CheckFramebufferStatus(gl.FRAMEBUFFER); s != gl.FRAMEBUFFER_COMPLETE {
if s != 0 {
return fmt.Errorf("opengl: creating framebuffer failed: %v", s)
}
if e := g.context.ctx.GetError(); e != gl.NO_ERROR {
return fmt.Errorf("opengl: creating framebuffer failed: (glGetError) %d", e)
}
return fmt.Errorf("opengl: creating framebuffer failed: unknown error")
}
// Color attachments
var attached []uint32
for i, dst := range dsts {
if dst == nil {
attached = append(attached, gl.NONE)
continue
}
attached = append(attached, uint32(gl.COLOR_ATTACHMENT0+i))
}
g.context.ctx.DrawBuffers(attached)
} else {
g.context.bindFramebuffer(framebufferNative(f))
}
w, h := dsts[firstTarget].viewportSize() //.framebuffer.viewportWidth, dsts[firstTarget].framebuffer.viewportHeight
g.context.setViewport(w, h, dsts[firstTarget].screen)
g.context.blend(blend)
shader := g.shaders[shaderID]
@ -232,7 +302,7 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
}
// In OpenGL, the NDC's Y direction is upward, so flip the Y direction for the final framebuffer.
if destination.screen {
if !usesMRT && dsts[firstTarget].screen {
const idx = graphics.ProjectionMatrixUniformVariableIndex
// Invert the sign bits as float32 values.
g.uniformVars[idx].value[1] ^= 1 << 31
@ -241,7 +311,7 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
g.uniformVars[idx].value[13] ^= 1 << 31
}
var imgs [graphics.ShaderImageCount]textureVariable
var imgs [graphics.ShaderSrcImageCount]textureVariable
for i, srcID := range srcIDs {
if srcID == graphicsdriver.InvalidImageID {
continue
@ -259,8 +329,8 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
}
g.uniformVars = g.uniformVars[:0]
if fillRule != graphicsdriver.FillAll {
if err := destination.ensureStencilBuffer(); err != nil {
if fillRule != graphicsdriver.FillRuleFillAll {
if err := dsts[firstTarget].ensureStencilBuffer(framebufferNative(f)); err != nil {
return err
}
g.context.ctx.Enable(gl.STENCIL_TEST)
@ -273,15 +343,17 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
int32(dstRegion.Region.Dx()),
int32(dstRegion.Region.Dy()),
)
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)
g.context.ctx.StencilOpSeparate(gl.BACK, gl.KEEP, gl.KEEP, gl.DECR_WRAP)
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)
@ -289,7 +361,7 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
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)
@ -298,10 +370,14 @@ func (g *Graphics) DrawTriangles(dstID graphicsdriver.ImageID, srcIDs [graphics.
indexOffset += dstRegion.IndexCount
}
if fillRule != graphicsdriver.FillAll {
if fillRule != graphicsdriver.FillRuleFillAll {
g.context.ctx.Disable(gl.STENCIL_TEST)
}
// Detach existing color attachments
//g.context.bindFramebuffer(fb)
//TODO:
return nil
}

View File

@ -60,14 +60,6 @@ func (i *Image) Dispose() {
i.graphics.removeImage(i)
}
func (i *Image) setViewport() error {
if err := i.ensureFramebuffer(); err != nil {
return err
}
i.graphics.context.setViewport(i.framebuffer)
return nil
}
func (i *Image) ReadPixels(args []graphicsdriver.PixelsArgs) error {
if err := i.ensureFramebuffer(); err != nil {
return err
@ -109,14 +101,14 @@ func (i *Image) ensureFramebuffer() error {
return nil
}
func (i *Image) ensureStencilBuffer() error {
func (i *Image) ensureStencilBuffer(f framebufferNative) error {
if i.stencil != 0 {
return nil
}
if err := i.ensureFramebuffer(); err != nil {
/*if err := i.ensureFramebuffer(); err != nil {
return err
}
}*/
r, err := i.graphics.context.newRenderbuffer(i.viewportSize())
if err != nil {
@ -124,7 +116,7 @@ func (i *Image) ensureStencilBuffer() error {
}
i.stencil = r
if err := i.graphics.context.bindStencilBuffer(i.framebuffer.native, i.stencil); err != nil {
if err := i.graphics.context.bindStencilBuffer(f, i.stencil); err != nil {
return err
}
return nil

View File

@ -259,7 +259,7 @@ func (g *Graphics) textureVariableName(idx int) string {
}
// useProgram uses the program (programTexture).
func (g *Graphics) useProgram(program program, uniforms []uniformVariable, textures [graphics.ShaderImageCount]textureVariable) error {
func (g *Graphics) useProgram(program program, uniforms []uniformVariable, textures [graphics.ShaderSrcImageCount]textureVariable) error {
if g.state.lastProgram != program {
g.context.ctx.UseProgram(uint32(program))

View File

@ -116,7 +116,7 @@ func (g *Graphics) NewShader(program *shaderir.Program) (graphicsdriver.Shader,
}, nil
}
func (g *Graphics) DrawTriangles(dst graphicsdriver.ImageID, srcs [graphics.ShaderImageCount]graphicsdriver.ImageID, shader graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
func (g *Graphics) DrawTriangles(dsts [graphics.ShaderDstImageCount]graphicsdriver.ImageID, srcs [graphics.ShaderSrcImageCount]graphicsdriver.ImageID, shader graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
return nil
}

View File

@ -0,0 +1,23 @@
// Copyright 2024 The Ebitengine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build playstation5
// This is a separated pure Go file so that the `shaderprecomp` package can use this without Cgo.
package playstation5
func RegisterPrecompiledShaders(source []byte, vertex, pixel []byte) {
// TODO: Implement this.
}

View File

@ -24,6 +24,7 @@ var (
uint8Array = js.Global().Get("Uint8Array")
float32Array = js.Global().Get("Float32Array")
int32Array = js.Global().Get("Int32Array")
uint32Array = js.Global().Get("Uint32Array")
)
var (
@ -40,8 +41,11 @@ var (
// temporaryFloat32Array is a Float32ArrayBuffer whose underlying buffer is always temporaryArrayBuffer.
temporaryFloat32Array = float32Array.New(temporaryArrayBuffer)
// temporaryInt32Array is a Float32ArrayBuffer whose underlying buffer is always temporaryArrayBuffer.
// temporaryInt32Array is a Int32ArrayBuffer whose underlying buffer is always temporaryArrayBuffer.
temporaryInt32Array = int32Array.New(temporaryArrayBuffer)
// temporaryUint32Array is a Uint32ArrayBuffer whose underlying buffer is always temporaryArrayBuffer.
temporaryUint32Array = uint32Array.New(temporaryArrayBuffer)
)
func ensureTemporaryArrayBufferSize(byteLength int) {
@ -54,6 +58,7 @@ func ensureTemporaryArrayBufferSize(byteLength int) {
temporaryUint8Array = uint8Array.New(temporaryArrayBuffer)
temporaryFloat32Array = float32Array.New(temporaryArrayBuffer)
temporaryInt32Array = int32Array.New(temporaryArrayBuffer)
temporaryUint32Array = uint32Array.New(temporaryArrayBuffer)
}
}
@ -101,3 +106,11 @@ func TemporaryInt32Array(minLength int, data []int32) js.Value {
copySliceToTemporaryArrayBuffer(data)
return temporaryInt32Array
}
// NewUint32Array returns a Uint32Array whose length is equal to the length of data.
func NewUint32Array(data []uint32) js.Value {
ensureTemporaryArrayBufferSize(len(data) * 4)
copySliceToTemporaryArrayBuffer(data)
a := temporaryUint32Array.Call("slice", 0, len(data))
return a
}

View File

@ -65,7 +65,7 @@ func (m *Mipmap) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byt
return m.orig.ReadPixels(graphicsDriver, pixels, region)
}
func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderImageCount]*Mipmap, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderImageCount]image.Rectangle, shader *atlas.Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, canSkipMipmap bool) {
func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Mipmap, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *atlas.Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, canSkipMipmap bool) {
if len(indices) == 0 {
return
}
@ -103,7 +103,7 @@ func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderImageCount]*Mipmap, vertices
}
}
var imgs [graphics.ShaderImageCount]*buffered.Image
var imgs [graphics.ShaderSrcImageCount]*buffered.Image
for i, src := range srcs {
if src == nil {
continue
@ -127,6 +127,81 @@ func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderImageCount]*Mipmap, vertices
m.deallocateMipmaps()
}
func DrawTrianglesMRT(dsts [graphics.ShaderDstImageCount]*Mipmap, srcs [graphics.ShaderSrcImageCount]*Mipmap, vertices []float32, indices []uint32, blend graphicsdriver.Blend, dstRegion image.Rectangle, srcRegions [graphics.ShaderSrcImageCount]image.Rectangle, shader *atlas.Shader, uniforms []uint32, fillRule graphicsdriver.FillRule, canSkipMipmap bool) {
if len(indices) == 0 {
return
}
level := 0
// TODO: Do we need to check all the sources' states of being volatile?
if !canSkipMipmap && srcs[0] != nil && canUseMipmap(srcs[0].imageType) {
level = math.MaxInt32
for i := 0; i < len(indices)/3; i++ {
const n = graphics.VertexFloatCount
dx0 := vertices[n*indices[3*i]+0]
dy0 := vertices[n*indices[3*i]+1]
sx0 := vertices[n*indices[3*i]+2]
sy0 := vertices[n*indices[3*i]+3]
dx1 := vertices[n*indices[3*i+1]+0]
dy1 := vertices[n*indices[3*i+1]+1]
sx1 := vertices[n*indices[3*i+1]+2]
sy1 := vertices[n*indices[3*i+1]+3]
dx2 := vertices[n*indices[3*i+2]+0]
dy2 := vertices[n*indices[3*i+2]+1]
sx2 := vertices[n*indices[3*i+2]+2]
sy2 := vertices[n*indices[3*i+2]+3]
if l := mipmapLevelFromDistance(dx0, dy0, dx1, dy1, sx0, sy0, sx1, sy1); level > l {
level = l
}
if l := mipmapLevelFromDistance(dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2); level > l {
level = l
}
if l := mipmapLevelFromDistance(dx2, dy2, dx0, dy0, sx2, sy2, sx0, sy0); level > l {
level = l
}
}
if level == math.MaxInt32 {
panic("mipmap: level must be calculated at least once but not")
}
}
var dstImgs [graphics.ShaderDstImageCount]*buffered.Image
for i, dst := range dsts {
if dst == nil {
continue
}
dstImgs[i] = dst.orig
}
var srcImgs [graphics.ShaderSrcImageCount]*buffered.Image
for i, src := range srcs {
if src == nil {
continue
}
if level != 0 {
if img := src.level(level); img != nil {
const n = graphics.VertexFloatCount
s := float32(pow2(level))
for i := 0; i < len(vertices)/n; i++ {
vertices[i*n+2] /= s
vertices[i*n+3] /= s
}
srcImgs[i] = img
continue
}
}
srcImgs[i] = src.orig
}
buffered.DrawTrianglesMRT(dstImgs, srcImgs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule)
for _, dst := range dsts {
if dst == nil {
continue
}
dst.deallocateMipmaps()
}
}
func (m *Mipmap) setImg(level int, img *buffered.Image) {
if m.imgs == nil {
m.imgs = map[int]*buffered.Image{}
@ -187,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.ShaderImageCount]*buffered.Image{src}, vs, is, graphicsdriver.BlendCopy, dstRegion, [graphics.ShaderImageCount]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]

View File

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

View File

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

View File

@ -59,7 +59,7 @@ func (g *Game) Update() error {
}
func (g *Game) Draw(screen *ebiten.Image) {
// Before the fix, some complex renderings with EvenOdd might cause a DirectX error like this (#2138):
// Before the fix, some complex renderings with FillRuleEvenOdd might cause a DirectX error like this (#2138):
// panic: directx: IDXGISwapChain4::Present failed: HRESULT(2289696773)
screen.DrawImage(debugCircleImage, nil)
@ -74,7 +74,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
p.Arc(100, 100, 6, 0, 2*math.Pi, vector.Clockwise)
filling, indicies := p.AppendVerticesAndIndicesForFilling(nil, nil)
screen.DrawTriangles(filling, indicies, whiteTextureImage, &ebiten.DrawTrianglesOptions{
FillRule: ebiten.EvenOdd,
FillRule: ebiten.FillRuleEvenOdd,
})
}

74
internal/processtest/testdata/shader.go vendored Normal file
View File

@ -0,0 +1,74 @@
// Copyright 2024 The Ebitengine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build ignore
package main
import (
"fmt"
"github.com/hajimehoshi/ebiten/v2"
)
// This test confirms that deallocation of a shader works correctly.
type Game struct {
count int
img *ebiten.Image
}
func (g *Game) Update() error {
if g.img == nil {
g.img = ebiten.NewImage(1, 1)
}
g.count++
s, err := ebiten.NewShader([]byte(fmt.Sprintf(`//kage:unit pixels
package main
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
return vec4(%d/255.0)
}
`, g.count)))
if err != nil {
return err
}
// Use the shader to ensure that the shader is actually allocated.
g.img.DrawRectShader(1, 1, s, nil)
s.Deallocate()
if g.count == 60 {
return ebiten.Termination
}
return nil
}
func (g *Game) Draw(screen *ebiten.Image) {
}
func (g *Game) Layout(w, h int) (int, int) {
return 320, 240
}
func main() {
if err := ebiten.RunGame(&Game{}); err != nil {
panic(err)
}
}

View File

@ -199,7 +199,7 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
return nil, nil, nil, false
}
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
}

View File

@ -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)
@ -215,6 +216,7 @@ func Compile(src []byte, vertexEntry, fragmentEntry string, textureCount int) (*
// TODO: Make a call graph and reorder the elements.
s.ir.TextureCount = textureCount
return &s.ir, nil
}
@ -741,8 +743,9 @@ func (cs *compileState) parseFuncParams(block *block, fname string, d *ast.FuncD
}
// If there is only one returning value, it is treated as a returning value.
// Only if not the fragment entrypoint.
// An array cannot be a returning value, especially for HLSL (#2923).
if len(out) == 1 && out[0].name == "" && out[0].typ.Main != shaderir.Array {
if fname != cs.fragmentEntry && len(out) == 1 && out[0].name == "" && out[0].typ.Main != shaderir.Array {
ret = out[0].typ
out = nil
}
@ -820,10 +823,15 @@ func (cs *compileState) parseFunc(block *block, d *ast.FuncDecl) (function, bool
return function{}, false
}
if len(outParams) != 0 || returnType.Main != shaderir.Vec4 {
cs.addError(d.Pos(), "fragment entry point must have one returning vec4 value for a color")
return function{}, false
// The first out-param is treated as fragColor0 in GLSL.
for i := range outParams {
if outParams[i].typ.Main != shaderir.Vec4 {
cs.addError(d.Pos(), "fragment entry point must only have vec4 return values for colors")
return function{}, false
}
}
// Adjust the number of textures to write to
cs.ir.ColorsOutCount = len(outParams)
if cs.varyingParsed {
checkVaryings(inParams[1:])

View File

@ -188,13 +188,13 @@ 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)
}
}
if tc.Metal != nil {
/*if tc.Metal != nil {
m := msl.Compile(s)
if got, want := metalNormalize(m), metalNormalize(string(tc.Metal)); got != want {
compare(t, "Metal", got, want)
@ -203,7 +203,7 @@ func TestCompile(t *testing.T) {
// Just check that Compile doesn't cause panic.
// TODO: Should the results be tested?
msl.Compile(s)
msl.Compile(s)*/
})
}
}

View File

@ -334,7 +334,8 @@ func (cs *compileState) parseStmt(block *block, fname string, stmt ast.Stmt, inP
case *ast.ReturnStmt:
if len(stmt.Results) != len(outParams) && len(stmt.Results) != 1 {
if !(len(stmt.Results) == 0 && len(outParams) > 0 && outParams[0].name != "") {
// Fragment function does not have to return a value due to discard
if fname != cs.fragmentEntry && !(len(stmt.Results) == 0 && len(outParams) > 0 && outParams[0].name != "") {
// TODO: Check variable shadowings.
// https://go.dev/ref/spec#Return_statements
cs.addError(stmt.Pos(), fmt.Sprintf("the number of returning variables must be %d but %d", len(outParams), len(stmt.Results)))

View File

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

View File

@ -3,31 +3,32 @@ uniform float U1;
uniform float U2;
int F0(in int l0);
vec4 F1(in vec4 l0);
void F1(in vec4 l0, out vec4 l1);
int F0(in int l0) {
return l0;
}
vec4 F1(in vec4 l0) {
int l1 = 0;
int l3 = 0;
l1 = 0;
for (int l2 = 0; l2 < 10; l2++) {
int l3 = 0;
l3 = F0(l2);
l1 = (l1) + (l3);
for (int l4 = 0; l4 < 10; l4++) {
int l5 = 0;
l5 = F0(l4);
l1 = (l1) + (l5);
void F1(in vec4 l0, out vec4 l1) {
int l2 = 0;
int l4 = 0;
l2 = 0;
for (int l3 = 0; l3 < 10; l3++) {
int l4 = 0;
l4 = F0(l3);
l2 = (l2) + (l4);
for (int l5 = 0; l5 < 10; l5++) {
int l6 = 0;
l6 = F0(l5);
l2 = (l2) + (l6);
}
}
l3 = 0;
l1 = (l1) + (l3);
return vec4(float(l1));
l4 = 0;
l2 = (l2) + (l4);
l1 = vec4(float(l2));
return;
}
void main(void) {
fragColor = F1(gl_FragCoord);
F1(gl_FragCoord, gl_FragData[0]);
}

View File

@ -1,12 +1,14 @@
vec4 F0(in vec4 l0);
void F0(in vec4 l0, out vec4 l1);
vec4 F0(in vec4 l0) {
void F0(in vec4 l0, out vec4 l1) {
if (true) {
return l0;
l1 = l0;
return;
}
return l0;
l1 = l0;
return;
}
void main(void) {
fragColor = F0(gl_FragCoord);
F0(gl_FragCoord, gl_FragData[0]);
}

View File

@ -1,13 +1,14 @@
vec4 F0(in vec4 l0);
void F0(in vec4 l0, out vec4 l1);
vec4 F0(in vec4 l0) {
vec4 l1 = vec4(0);
for (float l2 = 0.0; l2 < 4.0; l2++) {
(l1).x = ((l1).x) + ((l2) * (1.0000000000e-02));
void F0(in vec4 l0, out vec4 l1) {
vec4 l2 = vec4(0);
for (float l3 = 0.0; l3 < 4.0; l3++) {
(l2).x = ((l2).x) + ((l3) * (1.0000000000e-02));
}
return l1;
l1 = l2;
return;
}
void main(void) {
fragColor = F0(gl_FragCoord);
F0(gl_FragCoord, gl_FragData[0]);
}

View File

@ -1,6 +1,6 @@
void F2(void);
void F3(void);
vec4 F5(in vec4 l0);
void F5(in vec4 l0, out vec4 l1);
void F2(void) {
}
@ -9,11 +9,12 @@ void F3(void) {
F2();
}
vec4 F5(in vec4 l0) {
void F5(in vec4 l0, out vec4 l1) {
F3();
return vec4(0.0);
l1 = vec4(0.0);
return;
}
void main(void) {
fragColor = F5(gl_FragCoord);
F5(gl_FragCoord, gl_FragData[0]);
}

View File

@ -2,12 +2,13 @@ uniform vec2 U0;
in vec2 V0;
in vec4 V1;
vec4 F0(in vec4 l0, in vec2 l1, in vec4 l2);
void F0(in vec4 l0, in vec2 l1, in vec4 l2, out vec4 l3);
vec4 F0(in vec4 l0, in vec2 l1, in vec4 l2) {
return vec4((l0).x, (l1).y, (l2).z, 1.0);
void F0(in vec4 l0, in vec2 l1, in vec4 l2, out vec4 l3) {
l3 = vec4((l0).x, (l1).y, (l2).z, 1.0);
return;
}
void main(void) {
fragColor = F0(gl_FragCoord, V0, V1);
F0(gl_FragCoord, V0, V1, gl_FragData[0]);
}

View File

@ -86,9 +86,7 @@ precision highp int;
#define lowp
#define mediump
#define highp
#endif
out vec4 fragColor;`
#endif`
if version == GLSLVersionDefault {
prelude += "\n\n" + utilFunctions
}
@ -231,6 +229,12 @@ func Compile(p *shaderir.Program, version GLSLVersion) (vertexShader, fragmentSh
fslines = append(fslines, fmt.Sprintf("in %s;", c.varDecl(p, &t, fmt.Sprintf("V%d", i))))
}
}
// If ES300 out colors need to be defined explicitely
if version == GLSLVersionES300 {
for i := 0; i < p.ColorsOutCount; i++ {
fslines = append(fslines, fmt.Sprintf("layout(location = %d) out vec4 glFragColor%d;", i, i))
}
}
var funcs []*shaderir.Func
if p.VertexFunc.Block != nil {
@ -420,7 +424,10 @@ func (c *compileContext) localVariableName(p *shaderir.Program, topBlock *shader
case idx < nv+1:
return fmt.Sprintf("V%d", idx-1)
default:
return fmt.Sprintf("l%d", idx-(nv+1))
if c.version == GLSLVersionES300 {
return fmt.Sprintf("glFragColor%d", idx-(nv+1))
}
return fmt.Sprintf("gl_FragData[%d]", idx-(nv+1))
}
default:
return fmt.Sprintf("l%d", idx)
@ -595,7 +602,7 @@ func (c *compileContext) block(p *shaderir.Program, topBlock, block *shaderir.Bl
case shaderir.Return:
switch {
case topBlock == p.FragmentFunc.Block:
lines = append(lines, fmt.Sprintf("%sfragColor = %s;", idt, expr(&s.Exprs[0])))
lines = append(lines, fmt.Sprintf("%s%s;", idt, expr(&s.Exprs[0])))
// The 'return' statement is not required so far, as the fragment entrypoint has only one sentence so far. See adjustProgram implementation.
case len(s.Exprs) == 0:
lines = append(lines, idt+"return;")
@ -604,7 +611,7 @@ func (c *compileContext) block(p *shaderir.Program, topBlock, block *shaderir.Bl
}
case shaderir.Discard:
// 'discard' is invoked only in the fragment shader entry point.
lines = append(lines, idt+"discard;", idt+"return vec4(0.0);")
lines = append(lines, idt+"discard;") //, idt+"return vec4(0.0);")
default:
lines = append(lines, fmt.Sprintf("%s?(unexpected stmt: %d)", idt, s.Type))
}
@ -645,15 +652,20 @@ func adjustProgram(p *shaderir.Program) *shaderir.Program {
Main: shaderir.Vec4, // gl_FragCoord
}
copy(inParams[1:], newP.Varyings)
// Out parameters of a fragment func are colors
outParams := make([]shaderir.Type, p.ColorsOutCount)
for i := range outParams {
outParams[i] = shaderir.Type{
Main: shaderir.Vec4,
}
}
newP.FragmentFunc.Block.LocalVarIndexOffset += (p.ColorsOutCount - 1)
newP.Funcs = append(newP.Funcs, shaderir.Func{
Index: funcIdx,
InParams: inParams,
OutParams: nil,
Return: shaderir.Type{
Main: shaderir.Vec4,
},
Block: newP.FragmentFunc.Block,
OutParams: outParams,
Block: newP.FragmentFunc.Block,
})
// Create an AST to call the new function.
@ -663,7 +675,7 @@ func adjustProgram(p *shaderir.Program) *shaderir.Program {
Index: funcIdx,
},
}
for i := 0; i < 1+len(newP.Varyings); i++ {
for i := 0; i < 1+len(newP.Varyings)+p.ColorsOutCount; i++ {
call = append(call, shaderir.Expr{
Type: shaderir.LocalVariable,
Index: i,

View File

@ -86,8 +86,9 @@ 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{
unit: p.Unit,
@ -190,7 +191,15 @@ func Compile(p *shaderir.Program) (vertexShader, pixelShader string, offsets []i
}
if p.FragmentFunc.Block != nil && len(p.FragmentFunc.Block.Stmts) > 0 {
pslines = append(pslines, "")
pslines = append(pslines, fmt.Sprintf("float4 PSMain(Varyings %s) : SV_TARGET {", vsOut))
pslines = append(pslines, "struct PS_OUTPUT")
pslines = append(pslines, "{")
for i := 0; i < p.ColorsOutCount; i++ {
pslines = append(pslines, fmt.Sprintf("\tfloat4 Color%d: SV_Target%d;", i, i))
}
pslines = append(pslines, "};")
pslines = append(pslines, "")
pslines = append(pslines, fmt.Sprintf("PS_OUTPUT PSMain(Varyings %s) {", vsOut))
pslines = append(pslines, "\tPS_OUTPUT output;")
pslines = append(pslines, c.block(p, p.FragmentFunc.Block, p.FragmentFunc.Block, 0)...)
pslines = append(pslines, "}")
}
@ -353,7 +362,7 @@ func (c *compileContext) localVariableName(p *shaderir.Program, topBlock *shader
case idx < nv+1:
return fmt.Sprintf("%s.M%d", vsOut, idx-1)
default:
return fmt.Sprintf("l%d", idx-(nv+1))
return fmt.Sprintf("output.Color%d", idx-(nv+1))
}
default:
return fmt.Sprintf("l%d", idx)
@ -563,6 +572,10 @@ func (c *compileContext) block(p *shaderir.Program, topBlock, block *shaderir.Bl
switch {
case topBlock == p.VertexFunc.Block:
lines = append(lines, fmt.Sprintf("%sreturn %s;", idt, vsOut))
case topBlock == p.FragmentFunc.Block:
// Call to the pseudo fragment func based on out parameters
lines = append(lines, idt+expr(&s.Exprs[0])+";")
lines = append(lines, idt+"return output;")
case len(s.Exprs) == 0:
lines = append(lines, idt+"return;")
default:
@ -570,7 +583,7 @@ func (c *compileContext) block(p *shaderir.Program, topBlock, block *shaderir.Bl
}
case shaderir.Discard:
// 'discard' is invoked only in the fragment shader entry point.
lines = append(lines, idt+"discard;", idt+"return float4(0.0, 0.0, 0.0, 0.0);")
lines = append(lines, idt+"discard;")
default:
lines = append(lines, fmt.Sprintf("%s?(unexpected stmt: %d)", idt, s.Type))
}
@ -578,3 +591,86 @@ func (c *compileContext) block(p *shaderir.Program, topBlock, block *shaderir.Bl
return lines
}
func adjustProgram(p *shaderir.Program) *shaderir.Program {
if p.FragmentFunc.Block == nil {
return p
}
// Shallow-clone the program in order not to modify p itself.
newP := *p
// Create a new slice not to affect the original p.
newP.Funcs = make([]shaderir.Func, len(p.Funcs))
copy(newP.Funcs, p.Funcs)
// Create a new function whose body is the same is the fragment shader's entry point.
// Determine a unique index of the new function.
var funcIdx int
for _, f := range newP.Funcs {
if funcIdx <= f.Index {
funcIdx = f.Index + 1
}
}
// For parameters of a fragment func, see the comment in internal/shaderir/program.go.
inParams := make([]shaderir.Type, 1+len(newP.Varyings))
inParams[0] = shaderir.Type{
Main: shaderir.Vec4, // gl_FragCoord
}
copy(inParams[1:], newP.Varyings)
// Out parameters of a fragment func are colors
outParams := make([]shaderir.Type, p.ColorsOutCount)
for i := range outParams {
outParams[i] = shaderir.Type{
Main: shaderir.Vec4,
}
}
newP.FragmentFunc.Block.LocalVarIndexOffset += (p.ColorsOutCount - 1)
newP.Funcs = append(newP.Funcs, shaderir.Func{
Index: funcIdx,
InParams: inParams,
OutParams: outParams,
Block: newP.FragmentFunc.Block,
})
// Create an AST to call the new function.
call := []shaderir.Expr{
{
Type: shaderir.FunctionExpr,
Index: funcIdx,
},
}
for i := 0; i < 1+len(newP.Varyings)+p.ColorsOutCount; i++ {
call = append(call, shaderir.Expr{
Type: shaderir.LocalVariable,
Index: i,
})
}
// Replace the entry point with just calling the new function.
stmts := []shaderir.Stmt{
{
// Return: This will be replaced with a call to the new function.
// Then the output structure containing colors will be returned.
Type: shaderir.Return,
Exprs: []shaderir.Expr{
// The function call
{
Type: shaderir.Call,
Exprs: call,
},
},
},
}
newP.FragmentFunc = shaderir.FragmentFunc{
Block: &shaderir.Block{
LocalVars: nil,
LocalVarIndexOffset: 1 + len(newP.Varyings) + 1,
Stmts: stmts,
},
}
return &newP
}

View File

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

View File

@ -938,12 +938,12 @@ void F0(float l0, float l1, thread float& l2) {
},
Attributes: []shaderir.Type{
{Main: shaderir.Vec4},
{Main: shaderir.Float},
{Main: shaderir.Vec2},
{Main: shaderir.Vec4},
},
Varyings: []shaderir.Type{
{Main: shaderir.Float},
{Main: shaderir.Vec2},
{Main: shaderir.Vec4},
},
VertexFunc: shaderir.VertexFunc{
Block: block(
@ -967,10 +967,10 @@ void F0(float l0, float l1, thread float& l2) {
GlslVS: glslVertexPrelude + `
uniform float U0;
in vec4 A0;
in float A1;
in vec2 A2;
out float V0;
out vec2 V1;
in vec2 A1;
in vec4 A2;
out vec2 V0;
out vec4 V1;
void main(void) {
gl_Position = A0;
@ -979,8 +979,8 @@ void main(void) {
}`,
GlslFS: glslFragmentPrelude + `
uniform float U0;
in float V0;
in vec2 V1;`,
in vec2 V0;
in vec4 V1;`,
},
{
Name: "FragmentFunc",
@ -991,12 +991,12 @@ in vec2 V1;`,
},
Attributes: []shaderir.Type{
{Main: shaderir.Vec4},
{Main: shaderir.Float},
{Main: shaderir.Vec2},
{Main: shaderir.Vec4},
},
Varyings: []shaderir.Type{
{Main: shaderir.Float},
{Main: shaderir.Vec2},
{Main: shaderir.Vec4},
},
VertexFunc: shaderir.VertexFunc{
Block: block(
@ -1016,39 +1016,39 @@ in vec2 V1;`,
),
),
},
ColorsOutCount: 1,
FragmentFunc: shaderir.FragmentFunc{
Block: block(
[]shaderir.Type{
{Main: shaderir.Vec2},
{Main: shaderir.Vec4},
{Main: shaderir.Float},
},
3,
assignStmt(
localVariableExpr(3),
localVariableExpr(0),
),
3+1,
assignStmt(
localVariableExpr(4),
localVariableExpr(1),
),
returnStmt(
callExpr(
builtinFuncExpr(shaderir.Vec4F),
localVariableExpr(2),
localVariableExpr(1),
localVariableExpr(1),
),
assignStmt(
localVariableExpr(5),
localVariableExpr(2),
),
assignStmt(
localVariableExpr(3),
localVariableExpr(0),
),
shaderir.Stmt{
Type: shaderir.Return,
},
),
},
},
GlslVS: glslVertexPrelude + `
uniform float U0;
in vec4 A0;
in float A1;
in vec2 A2;
out float V0;
out vec2 V1;
in vec2 A1;
in vec4 A2;
out vec2 V0;
out vec4 V1;
void main(void) {
gl_Position = A0;
@ -1057,21 +1057,22 @@ void main(void) {
}`,
GlslFS: glslFragmentPrelude + `
uniform float U0;
in float V0;
in vec2 V1;
in vec2 V0;
in vec4 V1;
vec4 F0(in vec4 l0, in float l1, in vec2 l2);
void F0(in vec4 l0, in vec2 l1, in vec4 l2, out vec4 l3);
vec4 F0(in vec4 l0, in float l1, in vec2 l2) {
vec4 l3 = vec4(0);
float l4 = float(0);
l3 = l0;
void F0(in vec4 l0, in vec2 l1, in vec4 l2, out vec4 l3) {
vec2 l4 = vec2(0);
vec4 l5 = vec4(0);
l4 = l1;
return vec4(l2, l1, l1);
l5 = l2;
l3 = l0;
return;
}
void main(void) {
fragColor = F0(gl_FragCoord, V0, V1);
F0(gl_FragCoord, V0, V1, gl_FragData[0]);
}`,
},
}
@ -1093,14 +1094,14 @@ void main(void) {
t.Errorf("%s fragment: got: %s, want: %s", tc.Name, got, want)
}
}
m := msl.Compile(&tc.Program)
/*m := msl.Compile(&tc.Program)
if tc.Metal != "" {
got := m
want := tc.Metal + "\n"
if got != want {
t.Errorf("%s metal: got: %s, want: %s", tc.Name, got, want)
}
}
}*/
})
}
}

View File

@ -16,8 +16,10 @@
package shaderir
import (
"encoding/hex"
"go/constant"
"go/token"
"hash/fnv"
"sort"
"strings"
)
@ -29,16 +31,34 @@ 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
TextureCount int
Attributes []Type
Varyings []Type
Funcs []Func
VertexFunc VertexFunc
FragmentFunc FragmentFunc
Unit Unit
UniformNames []string
Uniforms []Type
TextureCount int
ColorsOutCount int
Attributes []Type
Varyings []Type
Funcs []Func
VertexFunc VertexFunc
FragmentFunc FragmentFunc
Unit Unit
SourceHash SourceHash
uniformFactors []uint32
}

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