diff --git a/examples/flappy/crt.go b/examples/flappy/crt.go index ff38b3c49..5f2d8d831 100644 --- a/examples/flappy/crt.go +++ b/examples/flappy/crt.go @@ -14,8 +14,6 @@ //go:build ignore -//ebitengine:embeddedshader - //kage:unit pixels // Reference: a public domain CRT effect diff --git a/examples/flappy/main.go b/examples/flappy/main.go index 7ca80406a..6b8ab9436 100644 --- a/examples/flappy/main.go +++ b/examples/flappy/main.go @@ -43,6 +43,8 @@ var flagCRT = flag.Bool("crt", false, "enable the CRT effect") //go:embed crt.go var crtGo []byte +//ebitengine:shaderfile crt.go + func floorDiv(x, y int) int { d := x / y if d*y == x || x >= 0 { diff --git a/examples/shader/chromaticaberration.go b/examples/shader/chromaticaberration.go index abbf49ced..f5689a99a 100644 --- a/examples/shader/chromaticaberration.go +++ b/examples/shader/chromaticaberration.go @@ -14,8 +14,6 @@ //go:build ignore -//ebitengine:embeddedshader - //kage:unit pixels package main diff --git a/examples/shader/default.go b/examples/shader/default.go index 4d5387a59..1c7f70197 100644 --- a/examples/shader/default.go +++ b/examples/shader/default.go @@ -14,8 +14,6 @@ //go:build ignore -//ebitengine:embeddedshader - //kage:unit pixels package main diff --git a/examples/shader/dissolve.go b/examples/shader/dissolve.go index 6388d0d19..006061a26 100644 --- a/examples/shader/dissolve.go +++ b/examples/shader/dissolve.go @@ -14,8 +14,6 @@ //go:build ignore -//ebitengine:embeddedshader - //kage:unit pixels package main diff --git a/examples/shader/lighting.go b/examples/shader/lighting.go index 9d6761cd0..32e905957 100644 --- a/examples/shader/lighting.go +++ b/examples/shader/lighting.go @@ -14,8 +14,6 @@ //go:build ignore -//ebitengine:embeddedshader - //kage:unit pixels package main diff --git a/examples/shader/main.go b/examples/shader/main.go index d8a75d98c..7aae6fe53 100644 --- a/examples/shader/main.go +++ b/examples/shader/main.go @@ -50,6 +50,17 @@ var ( water_go []byte ) +// These directives are used for an shader analyzer in the future. +// See also #3157. + +//ebitengine:shaderfile default.go +//ebitengine:shaderfile texel.go +//ebitengine:shaderfile lighting.go +//ebitengine:shaderfile radialblur.go +//ebitengine:shaderfile chromaticaberration.go +//ebitengine:shaderfile dissolve.go +//ebitengine:shaderfile water.go + const ( screenWidth = 640 screenHeight = 480 diff --git a/examples/shader/radialblur.go b/examples/shader/radialblur.go index 89f82cc72..f6e648e31 100644 --- a/examples/shader/radialblur.go +++ b/examples/shader/radialblur.go @@ -14,8 +14,6 @@ //go:build ignore -//ebitengine:embeddedshader - //kage:unit pixels package main diff --git a/examples/shader/texel.go b/examples/shader/texel.go index eaca326e5..7e06e0488 100644 --- a/examples/shader/texel.go +++ b/examples/shader/texel.go @@ -14,8 +14,6 @@ //go:build ignore -//ebitengine:embeddedshader - //kage:unit pixels package main diff --git a/examples/shader/water.go b/examples/shader/water.go index 564ad43b9..59685bdd7 100644 --- a/examples/shader/water.go +++ b/examples/shader/water.go @@ -14,8 +14,6 @@ //go:build ignore -//ebitengine:embeddedshader - //kage:unit pixels package main diff --git a/internal/shaderlister/main.go b/internal/shaderlister/main.go index 9799ccc4c..f744e9ec5 100644 --- a/internal/shaderlister/main.go +++ b/internal/shaderlister/main.go @@ -17,6 +17,7 @@ package main import ( "bufio" "encoding/json" + "errors" "flag" "fmt" "go/ast" @@ -25,6 +26,7 @@ import ( "go/types" "log/slog" "os" + "path/filepath" "regexp" "strings" @@ -94,7 +96,7 @@ func xmain() error { } pkgs, err := packages.Load(&packages.Config{ - Mode: packages.NeedName | packages.NeedImports | packages.NeedDeps | packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedEmbedFiles, + Mode: packages.NeedName | packages.NeedImports | packages.NeedDeps | packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo, }, flag.Args()...) if err != nil { return err @@ -180,16 +182,16 @@ func isStandardImportPath(path string) bool { } const ( - shaderSourceDirective = "ebitengine:shadersource" - embeddedShaderDirective = "ebitengine:embeddedshader" + shaderSourceDirective = "ebitengine:shadersource" + shaderFileDirective = "ebitengine:shaderfile" ) var ( - reShaderSourceDirective = regexp.MustCompile(`(?m)^\s*//` + regexp.QuoteMeta(shaderSourceDirective)) - reEmbeddedShaderDirective = regexp.MustCompile(`(?m)^\s*//` + regexp.QuoteMeta(embeddedShaderDirective)) + reShaderSourceDirective = regexp.MustCompile(`(?m)^\s*//` + regexp.QuoteMeta(shaderSourceDirective) + `$`) + reShaderFileDirective = regexp.MustCompile(`(?m)^\s*//` + regexp.QuoteMeta(shaderFileDirective) + ` (.+)$`) ) -func hasShaderDirectiveInComment(commentGroup *ast.CommentGroup) bool { +func hasShaderSourceDirectiveInComment(commentGroup *ast.CommentGroup) bool { for _, line := range commentGroup.List { if reShaderSourceDirective.MatchString(line.Text) { return true @@ -210,8 +212,54 @@ func appendShaderSources(shaders []Shader, pkg *packages.Package) ([]Shader, err return ok } - var genDeclStack []*ast.GenDecl + // Resolve ebitengine:shaderfile directives. + for _, f := range pkg.Syntax { + for _, c := range f.Comments { + for _, l := range c.List { + m := reShaderFileDirective.FindStringSubmatch(l.Text) + if len(m) == 0 { + continue + } + pattern := filepath.Join(pkg.Dir, filepath.FromSlash(m[1])) + stat, err := os.Stat(pattern) + if err == nil && stat.IsDir() { + // If the pattern is a directory, read all files in the directory recursively. + if err := filepath.WalkDir(pattern, func(path string, d os.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + shaders, err = appendShaderFromFile(shaders, pkg.PkgPath, path) + if err != nil { + return err + } + return nil + }); err != nil { + return nil, err + } + continue + } + if err != nil && !errors.Is(err, os.ErrNotExist) { + return nil, err + } + paths, err := filepath.Glob(pattern) + if err != nil { + return nil, err + } + for _, path := range paths { + shaders, err = appendShaderFromFile(shaders, pkg.PkgPath, path) + if err != nil { + return nil, err + } + } + } + } + } + // Resolve ebitengine:shadersource directives. + var genDeclStack []*ast.GenDecl in := inspector.New(pkg.Syntax) in.Nodes([]ast.Node{ (*ast.GenDecl)(nil), @@ -228,7 +276,7 @@ func appendShaderSources(shaders []Shader, pkg *packages.Package) ([]Shader, err // If the GenDecl is with parentheses (e.g. `const ( ... )`), check the GenDecl's comment. // The directive doesn't work, so if the directive is found, warn it. if genDecl.Lparen != token.NoPos { - if genDecl.Doc != nil && hasShaderDirectiveInComment(genDecl.Doc) { + if genDecl.Doc != nil && hasShaderSourceDirectiveInComment(genDecl.Doc) { pos := pkg.Fset.Position(genDecl.Doc.Pos()) slog.Warn(fmt.Sprintf("misplaced %s directive", shaderSourceDirective), "package", pkg.PkgPath, @@ -240,7 +288,7 @@ func appendShaderSources(shaders []Shader, pkg *packages.Package) ([]Shader, err if genDecl.Doc == nil { return false } - if !hasShaderDirectiveInComment(genDecl.Doc) { + if !hasShaderSourceDirectiveInComment(genDecl.Doc) { return false } } @@ -264,7 +312,7 @@ func appendShaderSources(shaders []Shader, pkg *packages.Package) ([]Shader, err if spec.Doc == nil { return false } - if !hasShaderDirectiveInComment(spec.Doc) { + if !hasShaderSourceDirectiveInComment(spec.Doc) { return false } } @@ -335,21 +383,19 @@ func appendShaderSources(shaders []Shader, pkg *packages.Package) ([]Shader, err } }) - for _, file := range pkg.EmbedFiles { - content, err := os.ReadFile(file) - if err != nil { - return nil, err - } - if !reEmbeddedShaderDirective.Match(content) { - continue - } - shaders = append(shaders, Shader{ - Package: pkg.PkgPath, - File: file, - Source: string(content), - }) - } + return shaders, nil +} +func appendShaderFromFile(shaders []Shader, pkgPath string, filePath string) ([]Shader, error) { + content, err := os.ReadFile(filePath) + if err != nil { + return nil, err + } + shaders = append(shaders, Shader{ + Package: pkgPath, + File: filePath, + Source: string(content), + }) return shaders, nil } diff --git a/internal/shaderlister/shaderlister_test.go b/internal/shaderlister/shaderlister_test.go index 6550ba24c..8bdbdf55d 100644 --- a/internal/shaderlister/shaderlister_test.go +++ b/internal/shaderlister/shaderlister_test.go @@ -82,7 +82,7 @@ func TestRun(t *testing.T) { }) if got, want := len(filteredShaders), 9; got != want { - t.Fatalf("len(shaders): got: %d, want: %d", got, want) + t.Errorf("len(shaders): got: %d, want: %d", got, want) } for i, s := range filteredShaders { diff --git a/internal/shaderlister/shaderlistertest/def.go b/internal/shaderlister/shaderlistertest/def.go index 4054d4ae2..aac55ae88 100644 --- a/internal/shaderlister/shaderlistertest/def.go +++ b/internal/shaderlister/shaderlistertest/def.go @@ -15,8 +15,6 @@ package shaderlistertest import ( - "embed" - "github.com/hajimehoshi/ebiten/v2/internal/shaderlister/shaderlistertest2" ) @@ -71,8 +69,7 @@ const ( _, _ = "ignored", "ignored again" // multiple consts are ignored to avoid confusion. ) -//go:embed *kage.go -var embed_go embed.FS +//ebitengine:shaderfile *_kage.go +//ebitengine:shaderfile resource -//go:embed resource -var embed2_go embed.FS +//ebitengine:shaderfile nonexistent.go diff --git a/internal/shaderlister/shaderlistertest/embed2_kage.go b/internal/shaderlister/shaderlistertest/dummy2_kage.go similarity index 95% rename from internal/shaderlister/shaderlistertest/embed2_kage.go rename to internal/shaderlister/shaderlistertest/dummy2_kage.go index 1bfd3ed9c..336ab1974 100644 --- a/internal/shaderlister/shaderlistertest/embed2_kage.go +++ b/internal/shaderlister/shaderlistertest/dummy2_kage.go @@ -14,8 +14,6 @@ //go:build ignore -//ebitengine:embeddedshader - package main // shader 8 diff --git a/internal/shaderlister/shaderlistertest/embed_kage.go b/internal/shaderlister/shaderlistertest/dummy_kage.go similarity index 95% rename from internal/shaderlister/shaderlistertest/embed_kage.go rename to internal/shaderlister/shaderlistertest/dummy_kage.go index 3a3a1b439..e309403d4 100644 --- a/internal/shaderlister/shaderlistertest/embed_kage.go +++ b/internal/shaderlister/shaderlistertest/dummy_kage.go @@ -14,8 +14,6 @@ //go:build ignore -//ebitengine:embeddedshader - package main // shader 7 diff --git a/internal/shaderlister/shaderlistertest/embed_notkage.go b/internal/shaderlister/shaderlistertest/dummy_notkage.go similarity index 83% rename from internal/shaderlister/shaderlistertest/embed_notkage.go rename to internal/shaderlister/shaderlistertest/dummy_notkage.go index 66bbc9971..99c4594de 100644 --- a/internal/shaderlister/shaderlistertest/embed_notkage.go +++ b/internal/shaderlister/shaderlistertest/dummy_notkage.go @@ -14,8 +14,6 @@ //go:build ignore -// This file lacks the directive `ebitengine:embeddedshader`, so this is not considered as a shader file by shaderlister. - package main // shader 0 diff --git a/internal/shaderlister/shaderlistertest/resource/embed_kage.go b/internal/shaderlister/shaderlistertest/resource/dummy_kage.go similarity index 95% rename from internal/shaderlister/shaderlistertest/resource/embed_kage.go rename to internal/shaderlister/shaderlistertest/resource/dummy_kage.go index 46bc1d959..846090733 100644 --- a/internal/shaderlister/shaderlistertest/resource/embed_kage.go +++ b/internal/shaderlister/shaderlistertest/resource/dummy_kage.go @@ -14,8 +14,6 @@ //go:build ignore -//ebitengine:embeddedshader - package main // shader 9 diff --git a/internal/shaderlister/shaderlistertest/resource/embed_notkage.go b/internal/shaderlister/shaderlistertest/resource/embed_notkage.go deleted file mode 100644 index 66bbc9971..000000000 --- a/internal/shaderlister/shaderlistertest/resource/embed_notkage.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2025 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 file lacks the directive `ebitengine:embeddedshader`, so this is not considered as a shader file by shaderlister. - -package main - -// shader 0