From 81b9f91f86630edf80c7f552ce7a544924c352b2 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Mon, 21 Mar 2022 17:48:47 +0900 Subject: [PATCH] internal/graphicscommand: compile shaders lazily With DirectX, the graphics driver cannot be determined until the main loop starts, as a transparent window cannot be treated with DirectX so far. On the other hand, compiling shaders requires a graphics driver as it requires information about Y directions of NDCs and framebuffers. This change delays compiling shaders until the graphics commands are actually executed in the main loop. Updates #1007 Updates #2019 --- internal/atlas/image.go | 4 +- internal/atlas/shader.go | 5 +- internal/atlas/shader_test.go | 7 +- internal/buffered/image.go | 7 +- internal/graphicscommand/command.go | 27 +- internal/graphicscommand/image.go | 2 +- internal/graphicscommand/image_test.go | 4 +- internal/graphicscommand/shader.go | 193 ++++++++++- internal/mipmap/mipmap.go | 7 +- internal/restorable/image.go | 6 +- internal/restorable/shader.go | 13 +- internal/restorable/shader_test.go | 20 +- internal/testing/shader.go | 450 ++----------------------- internal/ui/image.go | 4 +- internal/ui/shader.go | 189 +---------- shader.go | 9 +- shader_test.go | 16 +- 17 files changed, 279 insertions(+), 684 deletions(-) diff --git a/internal/atlas/image.go b/internal/atlas/image.go index 124f4b61e..a3e3385f1 100644 --- a/internal/atlas/image.go +++ b/internal/atlas/image.go @@ -398,13 +398,13 @@ func (i *Image) processSrc(src *Image) { // 5: Color G // 6: Color B // 7: Color Y -func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm affine.ColorM, mode graphicsdriver.CompositeMode, filter graphicsdriver.Filter, address graphicsdriver.Address, dstRegion, srcRegion graphicsdriver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms [][]float32, evenOdd bool) { +func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm affine.ColorM, mode graphicsdriver.CompositeMode, filter graphicsdriver.Filter, address graphicsdriver.Address, dstRegion, srcRegion graphicsdriver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms map[string]interface{}, evenOdd bool) { backendsM.Lock() defer backendsM.Unlock() i.drawTriangles(srcs, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, subimageOffsets, shader, uniforms, evenOdd, false) } -func (i *Image) drawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm affine.ColorM, mode graphicsdriver.CompositeMode, filter graphicsdriver.Filter, address graphicsdriver.Address, dstRegion, srcRegion graphicsdriver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms [][]float32, evenOdd bool, keepOnAtlas bool) { +func (i *Image) drawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm affine.ColorM, mode graphicsdriver.CompositeMode, filter graphicsdriver.Filter, address graphicsdriver.Address, dstRegion, srcRegion graphicsdriver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms map[string]interface{}, evenOdd bool, keepOnAtlas bool) { if i.disposed { panic("atlas: the drawing target image must not be disposed (DrawTriangles)") } diff --git a/internal/atlas/shader.go b/internal/atlas/shader.go index 3b75f382e..e71c2f7c2 100644 --- a/internal/atlas/shader.go +++ b/internal/atlas/shader.go @@ -18,16 +18,15 @@ import ( "runtime" "github.com/hajimehoshi/ebiten/v2/internal/restorable" - "github.com/hajimehoshi/ebiten/v2/internal/shaderir" ) type Shader struct { shader *restorable.Shader } -func NewShader(program *shaderir.Program) *Shader { +func NewShader(src []byte) *Shader { s := &Shader{ - shader: restorable.NewShader(program), + shader: restorable.NewShader(src), } runtime.SetFinalizer(s, (*Shader).MarkDisposed) return s diff --git a/internal/atlas/shader_test.go b/internal/atlas/shader_test.go index 3c9eb24a6..d9c8dfb0c 100644 --- a/internal/atlas/shader_test.go +++ b/internal/atlas/shader_test.go @@ -40,15 +40,12 @@ func TestShaderFillTwice(t *testing.T) { Height: h, } g := ui.GraphicsDriverForTesting() - needsInvertY := g.FramebufferYDirection() != g.NDCYDirection() - p0 := etesting.ShaderProgramFill(needsInvertY, 0xff, 0xff, 0xff, 0xff) - s0 := atlas.NewShader(&p0) + s0 := atlas.NewShader(etesting.ShaderProgramFill(0xff, 0xff, 0xff, 0xff)) dst.DrawTriangles([graphics.ShaderImageNum]*atlas.Image{}, vs, is, affine.ColorMIdentity{}, graphicsdriver.CompositeModeCopy, graphicsdriver.FilterNearest, graphicsdriver.AddressUnsafe, dr, graphicsdriver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, s0, nil, false) // Vertices must be recreated (#1755) vs = quadVertices(w, h, 0, 0, 1) - p1 := etesting.ShaderProgramFill(needsInvertY, 0x80, 0x80, 0x80, 0xff) - s1 := atlas.NewShader(&p1) + s1 := atlas.NewShader(etesting.ShaderProgramFill(0x80, 0x80, 0x80, 0xff)) dst.DrawTriangles([graphics.ShaderImageNum]*atlas.Image{}, vs, is, affine.ColorMIdentity{}, graphicsdriver.CompositeModeCopy, graphicsdriver.FilterNearest, graphicsdriver.AddressUnsafe, dr, graphicsdriver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, s1, nil, false) pix, err := dst.Pixels(g) diff --git a/internal/buffered/image.go b/internal/buffered/image.go index a52340f6e..df9df6656 100644 --- a/internal/buffered/image.go +++ b/internal/buffered/image.go @@ -21,7 +21,6 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/atlas" "github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" - "github.com/hajimehoshi/ebiten/v2/internal/shaderir" ) type Image struct { @@ -229,7 +228,7 @@ func (img *Image) replacePendingPixels(pix []byte, x, y, width, height int) { // DrawTriangles draws the src image with the given vertices. // // Copying vertices and indices is the caller's responsibility. -func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm affine.ColorM, mode graphicsdriver.CompositeMode, filter graphicsdriver.Filter, address graphicsdriver.Address, dstRegion, srcRegion graphicsdriver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms [][]float32, evenOdd bool) { +func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm affine.ColorM, mode graphicsdriver.CompositeMode, filter graphicsdriver.Filter, address graphicsdriver.Address, dstRegion, srcRegion graphicsdriver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms map[string]interface{}, evenOdd bool) { for _, src := range srcs { if i == src { panic("buffered: Image.DrawTriangles: source images must be different from the receiver") @@ -273,9 +272,9 @@ type Shader struct { shader *atlas.Shader } -func NewShader(program *shaderir.Program) *Shader { +func NewShader(src []byte) *Shader { return &Shader{ - shader: atlas.NewShader(program), + shader: atlas.NewShader(src), } } diff --git a/internal/graphicscommand/command.go b/internal/graphicscommand/command.go index c3ed44a9d..845303e11 100644 --- a/internal/graphicscommand/command.go +++ b/internal/graphicscommand/command.go @@ -23,7 +23,6 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/debug" "github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" - "github.com/hajimehoshi/ebiten/v2/internal/shaderir" ) // command represents a drawing command. @@ -138,7 +137,7 @@ func mustUseDifferentVertexBuffer(nextNumVertexFloats, nextNumIndices int) bool } // EnqueueDrawTrianglesCommand enqueues a drawing-image command. -func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, color affine.ColorM, mode graphicsdriver.CompositeMode, filter graphicsdriver.Filter, address graphicsdriver.Address, dstRegion, srcRegion graphicsdriver.Region, shader *Shader, uniforms [][]float32, evenOdd bool) { +func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, color affine.ColorM, mode graphicsdriver.CompositeMode, filter graphicsdriver.Filter, address graphicsdriver.Address, dstRegion, srcRegion graphicsdriver.Region, shader *Shader, uniforms map[string]interface{}, evenOdd bool) { if len(indices) > graphics.IndicesNum { panic(fmt.Sprintf("graphicscommand: len(indices) must be <= graphics.IndicesNum but not at EnqueueDrawTrianglesCommand: len(indices): %d, graphics.IndicesNum: %d", len(indices), graphics.IndicesNum)) } @@ -363,7 +362,7 @@ type drawTrianglesCommand struct { dstRegion graphicsdriver.Region srcRegion graphicsdriver.Region shader *Shader - uniforms [][]float32 + uniforms map[string]interface{} evenOdd bool } @@ -461,6 +460,7 @@ func (c *drawTrianglesCommand) Exec(graphicsDriver graphicsdriver.Graphics, inde var shaderID graphicsdriver.ShaderID = graphicsdriver.InvalidShaderID var imgs [graphics.ShaderImageNum]graphicsdriver.ImageID + var us [][]float32 if c.shader != nil { shaderID = c.shader.shader.ID() for i, src := range c.srcs { @@ -470,11 +470,12 @@ func (c *drawTrianglesCommand) Exec(graphicsDriver graphicsdriver.Graphics, inde } imgs[i] = src.image.ID() } + us = c.shader.convertUniforms(c.uniforms) } else { imgs[0] = c.srcs[0].image.ID() } - return graphicsDriver.DrawTriangles(c.dst.image.ID(), imgs, c.offsets, shaderID, c.nindices, indexOffset, c.mode, c.color, c.filter, c.address, c.dstRegion, c.srcRegion, c.uniforms, c.evenOdd) + return graphicsDriver.DrawTriangles(c.dst.image.ID(), imgs, c.offsets, shaderID, c.nindices, indexOffset, c.mode, c.color, c.filter, c.address, c.dstRegion, c.srcRegion, us, c.evenOdd) } func (c *drawTrianglesCommand) numVertices() int { @@ -706,7 +707,7 @@ func (c *newScreenFramebufferImageCommand) Exec(graphicsDriver graphicsdriver.Gr // newShaderCommand is a command to create a shader. type newShaderCommand struct { result *Shader - ir *shaderir.Program + src []byte } func (c *newShaderCommand) String() string { @@ -715,9 +716,19 @@ func (c *newShaderCommand) String() string { // Exec executes a newShaderCommand. func (c *newShaderCommand) Exec(graphicsDriver graphicsdriver.Graphics, indexOffset int) error { - var err error - c.result.shader, err = graphicsDriver.NewShader(c.ir) - return err + ir, err := compileShader(graphicsDriver, c.src) + if err != nil { + return err + } + + s, err := graphicsDriver.NewShader(ir) + if err != nil { + return err + } + c.result.shader = s + c.result.uniformNames = ir.UniformNames + c.result.uniformTypes = ir.Uniforms + return nil } // InitializeGraphicsDriverState initialize the current graphics driver state. diff --git a/internal/graphicscommand/image.go b/internal/graphicscommand/image.go index 3fdf24aa6..e8b66b5ca 100644 --- a/internal/graphicscommand/image.go +++ b/internal/graphicscommand/image.go @@ -141,7 +141,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.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, clr affine.ColorM, mode graphicsdriver.CompositeMode, filter graphicsdriver.Filter, address graphicsdriver.Address, dstRegion, srcRegion graphicsdriver.Region, shader *Shader, uniforms [][]float32, evenOdd bool) { +func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, clr affine.ColorM, mode graphicsdriver.CompositeMode, filter graphicsdriver.Filter, address graphicsdriver.Address, dstRegion, srcRegion graphicsdriver.Region, shader *Shader, uniforms map[string]interface{}, evenOdd bool) { if shader == nil { // Fast path for rendering without a shader (#1355). img := srcs[0] diff --git a/internal/graphicscommand/image_test.go b/internal/graphicscommand/image_test.go index 54adb065b..e1f0adc71 100644 --- a/internal/graphicscommand/image_test.go +++ b/internal/graphicscommand/image_test.go @@ -154,9 +154,7 @@ func TestShader(t *testing.T) { dst.DrawTriangles([graphics.ShaderImageNum]*graphicscommand.Image{clr}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, affine.ColorMIdentity{}, graphicsdriver.CompositeModeClear, graphicsdriver.FilterNearest, graphicsdriver.AddressUnsafe, dr, graphicsdriver.Region{}, nil, nil, false) g := ui.GraphicsDriverForTesting() - needsInvertY := g.FramebufferYDirection() != g.NDCYDirection() - ir := etesting.ShaderProgramFill(needsInvertY, 0xff, 0, 0, 0xff) - s := graphicscommand.NewShader(&ir) + s := graphicscommand.NewShader(etesting.ShaderProgramFill(0xff, 0, 0, 0xff)) dst.DrawTriangles([graphics.ShaderImageNum]*graphicscommand.Image{}, [graphics.ShaderImageNum - 1][2]float32{}, vs, is, affine.ColorMIdentity{}, graphicsdriver.CompositeModeSourceOver, graphicsdriver.FilterNearest, graphicsdriver.AddressUnsafe, dr, graphicsdriver.Region{}, s, nil, false) pix := make([]byte, 4*w*h) diff --git a/internal/graphicscommand/shader.go b/internal/graphicscommand/shader.go index e7065e5d8..c91b8905b 100644 --- a/internal/graphicscommand/shader.go +++ b/internal/graphicscommand/shader.go @@ -15,19 +15,162 @@ package graphicscommand import ( + "bytes" + "fmt" + "go/parser" + "go/token" + "strings" + + "github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" + "github.com/hajimehoshi/ebiten/v2/internal/shader" "github.com/hajimehoshi/ebiten/v2/internal/shaderir" ) -type Shader struct { - shader graphicsdriver.Shader +var shaderSuffix string + +func init() { + shaderSuffix = ` +var __imageDstTextureSize vec2 + +// imageSrcTextureSize returns the destination image's texture size in pixels. +func imageDstTextureSize() vec2 { + return __imageDstTextureSize +} +` + + shaderSuffix += fmt.Sprintf(` +var __textureSizes [%[1]d]vec2 + +// imageSrcTextureSize returns the source image's texture size in pixels. +// As an image is a part of internal texture, the texture is usually bigger than the image. +// The texture's size is useful when you want to calculate pixels from texels. +func imageSrcTextureSize() vec2 { + return __textureSizes[0] } -func NewShader(ir *shaderir.Program) *Shader { +// The unit is the source texture's texel. +var __textureDestinationRegionOrigin vec2 + +// The unit is the source texture's texel. +var __textureDestinationRegionSize vec2 + +// imageDstRegionOnTexture returns the destination image's region (the origin and the size) on its texture. +// The unit is the source texture's texel. +// +// As an image is a part of internal texture, the image can be located at an arbitrary position on the texture. +func imageDstRegionOnTexture() (vec2, vec2) { + return __textureDestinationRegionOrigin, __textureDestinationRegionSize +} + +// The unit is the source texture's texel. +var __textureSourceOffsets [%[2]d]vec2 + +// The unit is the source texture's texel. +var __textureSourceRegionOrigin vec2 + +// The unit is the source texture's texel. +var __textureSourceRegionSize vec2 + +// imageSrcRegionOnTexture returns the source image's region (the origin and the size) on its texture. +// The unit is the source texture's texel. +// +// As an image is a part of internal texture, the image can be located at an arbitrary position on the texture. +func imageSrcRegionOnTexture() (vec2, vec2) { + return __textureSourceRegionOrigin, __textureSourceRegionSize +} +`, graphics.ShaderImageNum, graphics.ShaderImageNum-1) + + for i := 0; i < graphics.ShaderImageNum; i++ { + pos := "pos" + if i >= 1 { + // Convert the position in texture0's texels to the target texture texels. + pos = fmt.Sprintf("(pos + __textureSourceOffsets[%d]) * __textureSizes[0] / __textureSizes[%d]", i-1, i) + } + // __t%d is a special variable for a texture variable. + shaderSuffix += fmt.Sprintf(` +func imageSrc%[1]dUnsafeAt(pos vec2) vec4 { + // pos is the position in texels of the source texture (= 0th image's texture). + return texture2D(__t%[1]d, %[2]s) +} + +func imageSrc%[1]dAt(pos vec2) vec4 { + // pos is the position in texels of the source texture (= 0th image's texture). + return texture2D(__t%[1]d, %[2]s) * + step(__textureSourceRegionOrigin.x, pos.x) * + (1 - step(__textureSourceRegionOrigin.x + __textureSourceRegionSize.x, pos.x)) * + step(__textureSourceRegionOrigin.y, pos.y) * + (1 - step(__textureSourceRegionOrigin.y + __textureSourceRegionSize.y, pos.y)) +} +`, i, pos) + } +} + +func compileShader(graphicsDriver graphicsdriver.Graphics, src []byte) (*shaderir.Program, error) { + var buf bytes.Buffer + buf.Write(src) + buf.WriteString(shaderSuffix) + if graphicsDriver.FramebufferYDirection() != graphicsDriver.NDCYDirection() { + buf.WriteString(` +func __vertex(position vec2, texCoord vec2, color vec4) (vec4, vec2, vec4) { + return mat4( + 2/__imageDstTextureSize.x, 0, 0, 0, + 0, -2/__imageDstTextureSize.y, 0, 0, + 0, 0, 1, 0, + -1, 1, 0, 1, + ) * vec4(position, 0, 1), texCoord, color +} +`) + } else { + buf.WriteString(` +func __vertex(position vec2, texCoord vec2, color vec4) (vec4, vec2, vec4) { + return mat4( + 2/__imageDstTextureSize.x, 0, 0, 0, + 0, 2/__imageDstTextureSize.y, 0, 0, + 0, 0, 1, 0, + -1, -1, 0, 1, + ) * vec4(position, 0, 1), texCoord, color +} +`) + } + + fs := token.NewFileSet() + f, err := parser.ParseFile(fs, "", buf.Bytes(), parser.AllErrors) + if err != nil { + return nil, err + } + + const ( + vert = "__vertex" + frag = "Fragment" + ) + ir, err := shader.Compile(fs, f, vert, frag, graphics.ShaderImageNum) + if err != nil { + return nil, err + } + + if ir.VertexFunc.Block == nil { + return nil, fmt.Errorf("graphicscommand: vertex shader entry point '%s' is missing", vert) + } + if ir.FragmentFunc.Block == nil { + return nil, fmt.Errorf("graphicscommand: fragment shader entry point '%s' is missing", frag) + } + + return ir, nil +} + +type Shader struct { + shader graphicsdriver.Shader + + uniformNames []string + uniformTypes []shaderir.Type +} + +func NewShader(src []byte) *Shader { s := &Shader{} c := &newShaderCommand{ result: s, - ir: ir, + src: src, } theCommandQueue.Enqueue(c) return s @@ -39,3 +182,45 @@ func (s *Shader) Dispose() { } theCommandQueue.Enqueue(c) } + +func (s *Shader) convertUniforms(uniforms map[string]interface{}) [][]float32 { + type index struct { + resultIndex int + shaderUniformIndex int + } + + names := map[string]index{} + var idx int + for i, n := range s.uniformNames { + if strings.HasPrefix(n, "__") { + continue + } + names[n] = index{ + resultIndex: idx, + shaderUniformIndex: i, + } + idx++ + } + + us := make([][]float32, len(names)) + for name, idx := range names { + if v, ok := uniforms[name]; ok { + switch v := v.(type) { + case float32: + us[idx.resultIndex] = []float32{v} + case []float32: + us[idx.resultIndex] = v + default: + panic(fmt.Sprintf("ebiten: unexpected uniform value type: %s, %T", name, v)) + } + continue + } + + t := s.uniformTypes[idx.shaderUniformIndex] + us[idx.resultIndex] = make([]float32, t.FloatNum()) + } + + // TODO: Panic if uniforms include an invalid name + + return us +} diff --git a/internal/mipmap/mipmap.go b/internal/mipmap/mipmap.go index cae3adf5e..ebd04f7a4 100644 --- a/internal/mipmap/mipmap.go +++ b/internal/mipmap/mipmap.go @@ -22,7 +22,6 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/buffered" "github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" - "github.com/hajimehoshi/ebiten/v2/internal/shaderir" ) // Mipmap is a set of buffered.Image sorted by the order of mipmap level. @@ -80,7 +79,7 @@ func (m *Mipmap) At(graphicsDriver graphicsdriver.Graphics, x, y int) (r, g, b, return m.orig.At(graphicsDriver, x, y) } -func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderImageNum]*Mipmap, vertices []float32, indices []uint16, colorm affine.ColorM, mode graphicsdriver.CompositeMode, filter graphicsdriver.Filter, address graphicsdriver.Address, dstRegion, srcRegion graphicsdriver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms [][]float32, evenOdd bool, canSkipMipmap bool) { +func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderImageNum]*Mipmap, vertices []float32, indices []uint16, colorm affine.ColorM, mode graphicsdriver.CompositeMode, filter graphicsdriver.Filter, address graphicsdriver.Address, dstRegion, srcRegion graphicsdriver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms map[string]interface{}, evenOdd bool, canSkipMipmap bool) { if len(indices) == 0 { return } @@ -314,9 +313,9 @@ type Shader struct { shader *buffered.Shader } -func NewShader(program *shaderir.Program) *Shader { +func NewShader(src []byte) *Shader { return &Shader{ - shader: buffered.NewShader(program), + shader: buffered.NewShader(src), } } diff --git a/internal/restorable/image.go b/internal/restorable/image.go index 7117593b1..0352bd391 100644 --- a/internal/restorable/image.go +++ b/internal/restorable/image.go @@ -76,7 +76,7 @@ type drawTrianglesHistoryItem struct { dstRegion graphicsdriver.Region srcRegion graphicsdriver.Region shader *Shader - uniforms [][]float32 + uniforms map[string]interface{} evenOdd bool } @@ -365,7 +365,7 @@ func (i *Image) ReplacePixels(pixels []byte, mask []byte, x, y, width, height in // 5: Color G // 6: Color B // 7: Color Y -func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, colorm affine.ColorM, mode graphicsdriver.CompositeMode, filter graphicsdriver.Filter, address graphicsdriver.Address, dstRegion, srcRegion graphicsdriver.Region, shader *Shader, uniforms [][]float32, evenOdd bool) { +func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, colorm affine.ColorM, mode graphicsdriver.CompositeMode, filter graphicsdriver.Filter, address graphicsdriver.Address, dstRegion, srcRegion graphicsdriver.Region, shader *Shader, uniforms map[string]interface{}, evenOdd bool) { if i.priority { panic("restorable: DrawTriangles cannot be called on a priority image") } @@ -410,7 +410,7 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [gra } // appendDrawTrianglesHistory appends a draw-image history item to the image. -func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, colorm affine.ColorM, mode graphicsdriver.CompositeMode, filter graphicsdriver.Filter, address graphicsdriver.Address, dstRegion, srcRegion graphicsdriver.Region, shader *Shader, uniforms [][]float32, evenOdd bool) { +func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, colorm affine.ColorM, mode graphicsdriver.CompositeMode, filter graphicsdriver.Filter, address graphicsdriver.Address, dstRegion, srcRegion graphicsdriver.Region, shader *Shader, uniforms map[string]interface{}, evenOdd bool) { if i.stale || i.volatile || i.screen { return } diff --git a/internal/restorable/shader.go b/internal/restorable/shader.go index 512dc387d..33aa2bef0 100644 --- a/internal/restorable/shader.go +++ b/internal/restorable/shader.go @@ -16,18 +16,17 @@ package restorable import ( "github.com/hajimehoshi/ebiten/v2/internal/graphicscommand" - "github.com/hajimehoshi/ebiten/v2/internal/shaderir" ) type Shader struct { shader *graphicscommand.Shader - ir *shaderir.Program + src []byte } -func NewShader(program *shaderir.Program) *Shader { +func NewShader(src []byte) *Shader { s := &Shader{ - shader: graphicscommand.NewShader(program), - ir: program, + shader: graphicscommand.NewShader(src), + src: src, } theImages.addShader(s) return s @@ -37,9 +36,9 @@ func (s *Shader) Dispose() { theImages.removeShader(s) s.shader.Dispose() s.shader = nil - s.ir = nil + s.src = nil } func (s *Shader) restore() { - s.shader = graphicscommand.NewShader(s.ir) + s.shader = graphicscommand.NewShader(s.src) } diff --git a/internal/restorable/shader_test.go b/internal/restorable/shader_test.go index 77214a837..f00df4113 100644 --- a/internal/restorable/shader_test.go +++ b/internal/restorable/shader_test.go @@ -26,11 +26,6 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/ui" ) -func needsInvertY() bool { - g := ui.GraphicsDriverForTesting() - return g.FramebufferYDirection() != g.NDCYDirection() -} - func clearImage(img *restorable.Image, w, h int) { emptyImage := restorable.NewImage(3, 3) defer emptyImage.Dispose() @@ -63,8 +58,7 @@ func TestShader(t *testing.T) { img := restorable.NewImage(1, 1) defer img.Dispose() - ir := etesting.ShaderProgramFill(needsInvertY(), 0xff, 0, 0, 0xff) - s := restorable.NewShader(&ir) + s := restorable.NewShader(etesting.ShaderProgramFill(0xff, 0, 0, 0xff)) dr := graphicsdriver.Region{ X: 0, Y: 0, @@ -98,8 +92,7 @@ func TestShaderChain(t *testing.T) { imgs[0].ReplacePixels([]byte{0xff, 0, 0, 0xff}, nil, 0, 0, 1, 1) - ir := etesting.ShaderProgramImages(needsInvertY(), 1) - s := restorable.NewShader(&ir) + s := restorable.NewShader(etesting.ShaderProgramImages(1)) for i := 0; i < num-1; i++ { dr := graphicsdriver.Region{ X: 0, @@ -137,8 +130,7 @@ func TestShaderMultipleSources(t *testing.T) { dst := restorable.NewImage(1, 1) - ir := etesting.ShaderProgramImages(needsInvertY(), 3) - s := restorable.NewShader(&ir) + s := restorable.NewShader(etesting.ShaderProgramImages(3)) var offsets [graphics.ShaderImageNum - 1][2]float32 dr := graphicsdriver.Region{ X: 0, @@ -176,8 +168,7 @@ func TestShaderMultipleSourcesOnOneTexture(t *testing.T) { dst := restorable.NewImage(1, 1) - ir := etesting.ShaderProgramImages(needsInvertY(), 3) - s := restorable.NewShader(&ir) + s := restorable.NewShader(etesting.ShaderProgramImages(3)) offsets := [graphics.ShaderImageNum - 1][2]float32{ {1, 0}, {2, 0}, @@ -211,8 +202,7 @@ func TestShaderDispose(t *testing.T) { img := restorable.NewImage(1, 1) defer img.Dispose() - ir := etesting.ShaderProgramFill(needsInvertY(), 0xff, 0, 0, 0xff) - s := restorable.NewShader(&ir) + s := restorable.NewShader(etesting.ShaderProgramFill(0xff, 0, 0, 0xff)) dr := graphicsdriver.Region{ X: 0, Y: 0, diff --git a/internal/testing/shader.go b/internal/testing/shader.go index a46a9ac87..006d8ea0d 100644 --- a/internal/testing/shader.go +++ b/internal/testing/shader.go @@ -15,437 +15,35 @@ package testing import ( - "go/constant" - - "github.com/hajimehoshi/ebiten/v2/internal/graphics" - "github.com/hajimehoshi/ebiten/v2/internal/shaderir" + "fmt" + "strings" ) -func projectionMatrix(invertY bool) shaderir.Expr { - m11s := 1 - if invertY { - m11s = -1 - } - m31 := -1 - if invertY { - m31 = 1 - } +// ShaderProgramFill returns a shader source to fill the frambuffer. +func ShaderProgramFill(r, g, b, a byte) []byte { + return []byte(fmt.Sprintf(`package main - return shaderir.Expr{ - Type: shaderir.Call, - Exprs: []shaderir.Expr{ - { - Type: shaderir.BuiltinFuncExpr, - BuiltinFunc: shaderir.Mat4F, - }, - { - Type: shaderir.Binary, - Op: shaderir.Div, - Exprs: []shaderir.Expr{ - { - Type: shaderir.NumberExpr, - Const: constant.MakeFloat64(2), - ConstType: shaderir.ConstTypeFloat, - }, - { - Type: shaderir.FieldSelector, - Exprs: []shaderir.Expr{ - { - Type: shaderir.UniformVariable, - Index: 0, - }, - { - Type: shaderir.SwizzlingExpr, - Swizzling: "x", - }, - }, - }, - }, - }, - { - Type: shaderir.NumberExpr, - Const: constant.MakeFloat64(0), - ConstType: shaderir.ConstTypeFloat, - }, - { - Type: shaderir.NumberExpr, - Const: constant.MakeFloat64(0), - ConstType: shaderir.ConstTypeFloat, - }, - { - Type: shaderir.NumberExpr, - Const: constant.MakeFloat64(0), - ConstType: shaderir.ConstTypeFloat, - }, - { - Type: shaderir.NumberExpr, - Const: constant.MakeFloat64(0), - ConstType: shaderir.ConstTypeFloat, - }, - { - Type: shaderir.Binary, - Op: shaderir.Div, - Exprs: []shaderir.Expr{ - { - Type: shaderir.NumberExpr, - Const: constant.MakeFloat64(float64(m11s) * 2), - ConstType: shaderir.ConstTypeFloat, - }, - { - Type: shaderir.FieldSelector, - Exprs: []shaderir.Expr{ - { - Type: shaderir.UniformVariable, - Index: 0, - }, - { - Type: shaderir.SwizzlingExpr, - Swizzling: "y", - }, - }, - }, - }, - }, - { - Type: shaderir.NumberExpr, - Const: constant.MakeFloat64(0), - ConstType: shaderir.ConstTypeFloat, - }, - { - Type: shaderir.NumberExpr, - Const: constant.MakeFloat64(0), - ConstType: shaderir.ConstTypeFloat, - }, - { - Type: shaderir.NumberExpr, - Const: constant.MakeFloat64(0), - ConstType: shaderir.ConstTypeFloat, - }, - { - Type: shaderir.NumberExpr, - Const: constant.MakeFloat64(0), - ConstType: shaderir.ConstTypeFloat, - }, - { - Type: shaderir.NumberExpr, - Const: constant.MakeFloat64(1), - ConstType: shaderir.ConstTypeFloat, - }, - { - Type: shaderir.NumberExpr, - Const: constant.MakeFloat64(0), - ConstType: shaderir.ConstTypeFloat, - }, - { - Type: shaderir.NumberExpr, - Const: constant.MakeFloat64(-1), - ConstType: shaderir.ConstTypeFloat, - }, - { - Type: shaderir.NumberExpr, - Const: constant.MakeFloat64(float64(m31)), - ConstType: shaderir.ConstTypeFloat, - }, - { - Type: shaderir.NumberExpr, - Const: constant.MakeFloat64(0), - ConstType: shaderir.ConstTypeFloat, - }, - { - Type: shaderir.NumberExpr, - Const: constant.MakeFloat64(1), - ConstType: shaderir.ConstTypeFloat, - }, - }, - } +func Fragment(position vec4, texCoord vec2, color vec4) vec4 { + return vec4(%0.9f, %0.9f, %0.9f, %0.9f) +} +`, float64(r)/0xff, float64(g)/0xff, float64(b)/0xff, float64(a)/0xff)) } -func vertexPosition() shaderir.Expr { - return shaderir.Expr{ - Type: shaderir.Call, - Exprs: []shaderir.Expr{ - { - Type: shaderir.BuiltinFuncExpr, - BuiltinFunc: shaderir.Vec4F, - }, - { - Type: shaderir.LocalVariable, - Index: 0, - }, - { - Type: shaderir.NumberExpr, - Const: constant.MakeFloat64(0), - ConstType: shaderir.ConstTypeFloat, - }, - { - Type: shaderir.NumberExpr, - Const: constant.MakeFloat64(1), - ConstType: shaderir.ConstTypeFloat, - }, - }, +// ShaderProgramImages returns a shader source to render the frambuffer with the given images. +func ShaderProgramImages(numImages int) []byte { + if numImages <= 0 { + panic("testing: numImages must be >= 1") } + + var exprs []string + for i := 0; i < numImages; i++ { + exprs = append(exprs, fmt.Sprintf("imageSrc%dUnsafeAt(texCoord)", i)) + } + + return []byte(fmt.Sprintf(`package main + +func Fragment(position vec4, texCoord vec2, color vec4) vec4 { + return %s } - -func defaultVertexFunc(invertY bool) shaderir.VertexFunc { - return shaderir.VertexFunc{ - Block: &shaderir.Block{ - LocalVarIndexOffset: 4 + 1, - Stmts: []shaderir.Stmt{ - { - Type: shaderir.Assign, - Exprs: []shaderir.Expr{ - { - Type: shaderir.LocalVariable, - Index: 4, // the varying variable - }, - { - Type: shaderir.LocalVariable, - Index: 1, // the 2nd attribute variable - }, - }, - }, - { - Type: shaderir.Assign, - Exprs: []shaderir.Expr{ - { - Type: shaderir.LocalVariable, - Index: 3, // gl_Position in GLSL - }, - { - Type: shaderir.Binary, - Op: shaderir.MatrixMul, - Exprs: []shaderir.Expr{ - projectionMatrix(invertY), - vertexPosition(), - }, - }, - }, - }, - }, - }, - } -} - -func defaultProgram(invertY bool) shaderir.Program { - p := shaderir.Program{ - Attributes: []shaderir.Type{ - {Main: shaderir.Vec2}, // Local var (0) in the vertex shader - {Main: shaderir.Vec2}, // Local var (1) in the vertex shader - {Main: shaderir.Vec4}, // Local var (2) in the vertex shader - }, - Varyings: []shaderir.Type{ - {Main: shaderir.Vec2}, // Local var (4) in the vertex shader, (1) in the fragment shader - }, - VertexFunc: defaultVertexFunc(invertY), - } - - p.Uniforms = make([]shaderir.Type, graphics.PreservedUniformVariablesNum) - // Destination texture size - p.Uniforms[0] = shaderir.Type{Main: shaderir.Vec2} - // Source texture sizes - p.Uniforms[1] = shaderir.Type{ - Main: shaderir.Array, - Length: graphics.ShaderImageNum, - Sub: []shaderir.Type{{Main: shaderir.Vec2}}, - } - // Destination region origin - p.Uniforms[2] = shaderir.Type{Main: shaderir.Vec2} - // Destination region size - p.Uniforms[3] = shaderir.Type{Main: shaderir.Vec2} - // Source texture offsets - p.Uniforms[4] = shaderir.Type{ - Main: shaderir.Array, - Length: graphics.ShaderImageNum - 1, - Sub: []shaderir.Type{{Main: shaderir.Vec2}}, - } - // Source region origin - p.Uniforms[5] = shaderir.Type{Main: shaderir.Vec2} - // Source region size - p.Uniforms[6] = shaderir.Type{Main: shaderir.Vec2} - return p -} - -// ShaderProgramFill returns a shader intermediate representation to fill the frambuffer. -// -// Uniform variable's index and its value are: -// -// 0: the framebuffer size (Vec2) -func ShaderProgramFill(invertY bool, r, g, b, a byte) shaderir.Program { - clr := shaderir.Expr{ - Type: shaderir.Call, - Exprs: []shaderir.Expr{ - { - Type: shaderir.BuiltinFuncExpr, - BuiltinFunc: shaderir.Vec4F, - }, - { - Type: shaderir.NumberExpr, - Const: constant.MakeFloat64(float64(r) / 0xff), - ConstType: shaderir.ConstTypeFloat, - }, - { - Type: shaderir.NumberExpr, - Const: constant.MakeFloat64(float64(g) / 0xff), - ConstType: shaderir.ConstTypeFloat, - }, - { - Type: shaderir.NumberExpr, - Const: constant.MakeFloat64(float64(b) / 0xff), - ConstType: shaderir.ConstTypeFloat, - }, - { - Type: shaderir.NumberExpr, - Const: constant.MakeFloat64(float64(a) / 0xff), - ConstType: shaderir.ConstTypeFloat, - }, - }, - } - - p := defaultProgram(invertY) - p.FragmentFunc = shaderir.FragmentFunc{ - Block: &shaderir.Block{ - LocalVarIndexOffset: 2 + 1, - Stmts: []shaderir.Stmt{ - { - Type: shaderir.Assign, - Exprs: []shaderir.Expr{ - { - Type: shaderir.LocalVariable, - Index: 2, - }, - clr, - }, - }, - }, - }, - } - - return p -} - -// ShaderProgramImages returns a shader intermediate representation to render the frambuffer with the given images. -// -// Uniform variables's indices and their values are: -// -// 0: the framebuffer size (Vec2) -// -// The size and region values are actually not used in this shader so far. -func ShaderProgramImages(invertY bool, imageNum int) shaderir.Program { - if imageNum <= 0 { - panic("testing: imageNum must be >= 1") - } - - p := defaultProgram(invertY) - p.TextureNum = imageNum - - // In the fragment shader, local variables are: - // - // 0: gl_FragCoord - // 1: Varying variables (vec2) - // 2: gl_FragColor - // 3: Actual local variables in the main function - - local := shaderir.Expr{ - Type: shaderir.LocalVariable, - Index: 3, - } - fragColor := shaderir.Expr{ - Type: shaderir.LocalVariable, - Index: 2, - } - texPos := shaderir.Expr{ - Type: shaderir.LocalVariable, - Index: 1, - } - - var stmts []shaderir.Stmt - for i := 0; i < imageNum; i++ { - var rhs shaderir.Expr - if i == 0 { - rhs = shaderir.Expr{ - Type: shaderir.Call, - Exprs: []shaderir.Expr{ - { - Type: shaderir.BuiltinFuncExpr, - BuiltinFunc: shaderir.Texture2DF, - }, - { - Type: shaderir.TextureVariable, - Index: 0, - }, - texPos, - }, - } - } else { - texPos2 := shaderir.Expr{ - Type: shaderir.Binary, - Op: shaderir.Add, - Exprs: []shaderir.Expr{ - texPos, - { - Type: shaderir.Index, - Exprs: []shaderir.Expr{ - { - Type: shaderir.UniformVariable, - Index: graphics.TextureSourceOffsetsUniformVariableIndex, - }, - { - Type: shaderir.NumberExpr, - Const: constant.MakeInt64(int64(i - 1)), - ConstType: shaderir.ConstTypeInt, - }, - }, - }, - }, - } - rhs = shaderir.Expr{ - Type: shaderir.Binary, - Op: shaderir.Add, - Exprs: []shaderir.Expr{ - local, - { - Type: shaderir.Call, - Exprs: []shaderir.Expr{ - { - Type: shaderir.BuiltinFuncExpr, - BuiltinFunc: shaderir.Texture2DF, - }, - { - Type: shaderir.TextureVariable, - Index: i, - }, - texPos2, - }, - }, - }, - } - } - stmts = append(stmts, shaderir.Stmt{ - Type: shaderir.Assign, - Exprs: []shaderir.Expr{ - local, - rhs, - }, - }) - } - - stmts = append(stmts, shaderir.Stmt{ - Type: shaderir.Assign, - Exprs: []shaderir.Expr{ - fragColor, - local, - }, - }) - - p.FragmentFunc = shaderir.FragmentFunc{ - Block: &shaderir.Block{ - LocalVars: []shaderir.Type{ - {Main: shaderir.Vec4}, - }, - LocalVarIndexOffset: 2 + 1, - Stmts: stmts, - }, - } - - return p +`, strings.Join(exprs, " + "))) } diff --git a/internal/ui/image.go b/internal/ui/image.go index 3ef2e9cf7..156c27898 100644 --- a/internal/ui/image.go +++ b/internal/ui/image.go @@ -61,13 +61,11 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []f } var s *mipmap.Shader - var us [][]float32 if shader != nil { s = shader.shader - us = shader.convertUniforms(uniforms) } - i.mipmap.DrawTriangles(srcMipmaps, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, subimageOffsets, s, us, evenOdd, canSkipMipmap) + i.mipmap.DrawTriangles(srcMipmaps, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, subimageOffsets, s, uniforms, evenOdd, canSkipMipmap) } func (i *Image) ReplacePixels(pix []byte, x, y, width, height int) { diff --git a/internal/ui/shader.go b/internal/ui/shader.go index def268577..0a03957d8 100644 --- a/internal/ui/shader.go +++ b/internal/ui/shader.go @@ -15,200 +15,17 @@ package ui import ( - "bytes" - "fmt" - "go/parser" - "go/token" - "strings" - - "github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/mipmap" - "github.com/hajimehoshi/ebiten/v2/internal/shader" - "github.com/hajimehoshi/ebiten/v2/internal/shaderir" ) -var shaderSuffix string - -func init() { - shaderSuffix = ` -var __imageDstTextureSize vec2 - -// imageSrcTextureSize returns the destination image's texture size in pixels. -func imageDstTextureSize() vec2 { - return __imageDstTextureSize -} -` - - shaderSuffix += fmt.Sprintf(` -var __textureSizes [%[1]d]vec2 - -// imageSrcTextureSize returns the source image's texture size in pixels. -// As an image is a part of internal texture, the texture is usually bigger than the image. -// The texture's size is useful when you want to calculate pixels from texels. -func imageSrcTextureSize() vec2 { - return __textureSizes[0] -} - -// The unit is the source texture's texel. -var __textureDestinationRegionOrigin vec2 - -// The unit is the source texture's texel. -var __textureDestinationRegionSize vec2 - -// imageDstRegionOnTexture returns the destination image's region (the origin and the size) on its texture. -// The unit is the source texture's texel. -// -// As an image is a part of internal texture, the image can be located at an arbitrary position on the texture. -func imageDstRegionOnTexture() (vec2, vec2) { - return __textureDestinationRegionOrigin, __textureDestinationRegionSize -} - -// The unit is the source texture's texel. -var __textureSourceOffsets [%[2]d]vec2 - -// The unit is the source texture's texel. -var __textureSourceRegionOrigin vec2 - -// The unit is the source texture's texel. -var __textureSourceRegionSize vec2 - -// imageSrcRegionOnTexture returns the source image's region (the origin and the size) on its texture. -// The unit is the source texture's texel. -// -// As an image is a part of internal texture, the image can be located at an arbitrary position on the texture. -func imageSrcRegionOnTexture() (vec2, vec2) { - return __textureSourceRegionOrigin, __textureSourceRegionSize -} -`, graphics.ShaderImageNum, graphics.ShaderImageNum-1) - - for i := 0; i < graphics.ShaderImageNum; i++ { - pos := "pos" - if i >= 1 { - // Convert the position in texture0's texels to the target texture texels. - pos = fmt.Sprintf("(pos + __textureSourceOffsets[%d]) * __textureSizes[0] / __textureSizes[%d]", i-1, i) - } - // __t%d is a special variable for a texture variable. - shaderSuffix += fmt.Sprintf(` -func imageSrc%[1]dUnsafeAt(pos vec2) vec4 { - // pos is the position in texels of the source texture (= 0th image's texture). - return texture2D(__t%[1]d, %[2]s) -} - -func imageSrc%[1]dAt(pos vec2) vec4 { - // pos is the position in texels of the source texture (= 0th image's texture). - return texture2D(__t%[1]d, %[2]s) * - step(__textureSourceRegionOrigin.x, pos.x) * - (1 - step(__textureSourceRegionOrigin.x + __textureSourceRegionSize.x, pos.x)) * - step(__textureSourceRegionOrigin.y, pos.y) * - (1 - step(__textureSourceRegionOrigin.y + __textureSourceRegionSize.y, pos.y)) -} -`, i, pos) - } -} - type Shader struct { - shader *mipmap.Shader - uniformNames []string - uniformTypes []shaderir.Type + shader *mipmap.Shader } -func NewShader(src []byte) (*Shader, error) { - var buf bytes.Buffer - buf.Write(src) - buf.WriteString(shaderSuffix) - if graphicsDriver().FramebufferYDirection() != graphicsDriver().NDCYDirection() { - buf.WriteString(` -func __vertex(position vec2, texCoord vec2, color vec4) (vec4, vec2, vec4) { - return mat4( - 2/__imageDstTextureSize.x, 0, 0, 0, - 0, -2/__imageDstTextureSize.y, 0, 0, - 0, 0, 1, 0, - -1, 1, 0, 1, - ) * vec4(position, 0, 1), texCoord, color -} -`) - } else { - buf.WriteString(` -func __vertex(position vec2, texCoord vec2, color vec4) (vec4, vec2, vec4) { - return mat4( - 2/__imageDstTextureSize.x, 0, 0, 0, - 0, 2/__imageDstTextureSize.y, 0, 0, - 0, 0, 1, 0, - -1, -1, 0, 1, - ) * vec4(position, 0, 1), texCoord, color -} -`) - } - - fs := token.NewFileSet() - f, err := parser.ParseFile(fs, "", buf.Bytes(), parser.AllErrors) - if err != nil { - return nil, err - } - - const ( - vert = "__vertex" - frag = "Fragment" - ) - s, err := shader.Compile(fs, f, vert, frag, graphics.ShaderImageNum) - if err != nil { - return nil, err - } - - if s.VertexFunc.Block == nil { - return nil, fmt.Errorf("ui: vertex shader entry point '%s' is missing", vert) - } - if s.FragmentFunc.Block == nil { - return nil, fmt.Errorf("ui: fragment shader entry point '%s' is missing", frag) - } - +func NewShader(src []byte) *Shader { return &Shader{ - shader: mipmap.NewShader(s), - uniformNames: s.UniformNames, - uniformTypes: s.Uniforms, - }, nil -} - -func (s *Shader) convertUniforms(uniforms map[string]interface{}) [][]float32 { - type index struct { - resultIndex int - shaderUniformIndex int + shader: mipmap.NewShader(src), } - - names := map[string]index{} - var idx int - for i, n := range s.uniformNames { - if strings.HasPrefix(n, "__") { - continue - } - names[n] = index{ - resultIndex: idx, - shaderUniformIndex: i, - } - idx++ - } - - us := make([][]float32, len(names)) - for name, idx := range names { - if v, ok := uniforms[name]; ok { - switch v := v.(type) { - case float32: - us[idx.resultIndex] = []float32{v} - case []float32: - us[idx.resultIndex] = v - default: - panic(fmt.Sprintf("ebiten: unexpected uniform value type: %s, %T", name, v)) - } - continue - } - - t := s.uniformTypes[idx.shaderUniformIndex] - us[idx.resultIndex] = make([]float32, t.FloatNum()) - } - - // TODO: Panic if uniforms include an invalid name - - return us } func (s *Shader) MarkDisposed() { diff --git a/shader.go b/shader.go index 1b4baab2c..68acd01ad 100644 --- a/shader.go +++ b/shader.go @@ -27,16 +27,13 @@ type Shader struct { // NewShader compiles a shader program in the shading language Kage, and retruns the result. // -// If the compilation fails, NewShader returns an error. +// As of v2.3.0, the error value is always nil, and +// the actual complation happens lazily after the main loop starts. // // For the details about the shader, see https://ebiten.org/documents/shader.html. func NewShader(src []byte) (*Shader, error) { - s, err := ui.NewShader(src) - if err != nil { - return nil, err - } return &Shader{ - shader: s, + shader: ui.NewShader(src), }, nil } diff --git a/shader_test.go b/shader_test.go index 98596aa52..66832557f 100644 --- a/shader_test.go +++ b/shader_test.go @@ -192,10 +192,18 @@ func Fragment(position vec4, texCoord vec2, color vec4) vec4 { } func TestShaderNoMain(t *testing.T) { - if _, err := ebiten.NewShader([]byte(`package main -`)); err == nil { - t.Errorf("error must be non-nil but was nil") - } + defer func() { + if r := recover(); r == nil { + t.Errorf("At must panic but not") + } + }() + + const w, h = 16, 16 + s, _ := ebiten.NewShader([]byte(`package main +`)) + dst := ebiten.NewImage(w, h) + dst.DrawRectShader(w, h, s, nil) + dst.At(0, 0) } func TestShaderUninitializedUniformVariables(t *testing.T) {