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
This commit is contained in:
Hajime Hoshi 2022-03-21 17:48:47 +09:00
parent e21636fbb9
commit 81b9f91f86
17 changed files with 279 additions and 684 deletions

View File

@ -398,13 +398,13 @@ func (i *Image) processSrc(src *Image) {
// 5: Color G // 5: Color G
// 6: Color B // 6: Color B
// 7: Color Y // 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() backendsM.Lock()
defer backendsM.Unlock() defer backendsM.Unlock()
i.drawTriangles(srcs, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, subimageOffsets, shader, uniforms, evenOdd, false) 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 { if i.disposed {
panic("atlas: the drawing target image must not be disposed (DrawTriangles)") panic("atlas: the drawing target image must not be disposed (DrawTriangles)")
} }

View File

@ -18,16 +18,15 @@ import (
"runtime" "runtime"
"github.com/hajimehoshi/ebiten/v2/internal/restorable" "github.com/hajimehoshi/ebiten/v2/internal/restorable"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
) )
type Shader struct { type Shader struct {
shader *restorable.Shader shader *restorable.Shader
} }
func NewShader(program *shaderir.Program) *Shader { func NewShader(src []byte) *Shader {
s := &Shader{ s := &Shader{
shader: restorable.NewShader(program), shader: restorable.NewShader(src),
} }
runtime.SetFinalizer(s, (*Shader).MarkDisposed) runtime.SetFinalizer(s, (*Shader).MarkDisposed)
return s return s

View File

@ -40,15 +40,12 @@ func TestShaderFillTwice(t *testing.T) {
Height: h, Height: h,
} }
g := ui.GraphicsDriverForTesting() g := ui.GraphicsDriverForTesting()
needsInvertY := g.FramebufferYDirection() != g.NDCYDirection() s0 := atlas.NewShader(etesting.ShaderProgramFill(0xff, 0xff, 0xff, 0xff))
p0 := etesting.ShaderProgramFill(needsInvertY, 0xff, 0xff, 0xff, 0xff)
s0 := atlas.NewShader(&p0)
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) 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) // Vertices must be recreated (#1755)
vs = quadVertices(w, h, 0, 0, 1) vs = quadVertices(w, h, 0, 0, 1)
p1 := etesting.ShaderProgramFill(needsInvertY, 0x80, 0x80, 0x80, 0xff) s1 := atlas.NewShader(etesting.ShaderProgramFill(0x80, 0x80, 0x80, 0xff))
s1 := atlas.NewShader(&p1)
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) 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) pix, err := dst.Pixels(g)

View File

@ -21,7 +21,6 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/atlas" "github.com/hajimehoshi/ebiten/v2/internal/atlas"
"github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
) )
type Image struct { 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. // DrawTriangles draws the src image with the given vertices.
// //
// Copying vertices and indices is the caller's responsibility. // 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 { for _, src := range srcs {
if i == src { if i == src {
panic("buffered: Image.DrawTriangles: source images must be different from the receiver") panic("buffered: Image.DrawTriangles: source images must be different from the receiver")
@ -273,9 +272,9 @@ type Shader struct {
shader *atlas.Shader shader *atlas.Shader
} }
func NewShader(program *shaderir.Program) *Shader { func NewShader(src []byte) *Shader {
return &Shader{ return &Shader{
shader: atlas.NewShader(program), shader: atlas.NewShader(src),
} }
} }

View File

@ -23,7 +23,6 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/debug" "github.com/hajimehoshi/ebiten/v2/internal/debug"
"github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
) )
// command represents a drawing command. // command represents a drawing command.
@ -138,7 +137,7 @@ func mustUseDifferentVertexBuffer(nextNumVertexFloats, nextNumIndices int) bool
} }
// EnqueueDrawTrianglesCommand enqueues a drawing-image command. // 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 { 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)) 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 dstRegion graphicsdriver.Region
srcRegion graphicsdriver.Region srcRegion graphicsdriver.Region
shader *Shader shader *Shader
uniforms [][]float32 uniforms map[string]interface{}
evenOdd bool evenOdd bool
} }
@ -461,6 +460,7 @@ func (c *drawTrianglesCommand) Exec(graphicsDriver graphicsdriver.Graphics, inde
var shaderID graphicsdriver.ShaderID = graphicsdriver.InvalidShaderID var shaderID graphicsdriver.ShaderID = graphicsdriver.InvalidShaderID
var imgs [graphics.ShaderImageNum]graphicsdriver.ImageID var imgs [graphics.ShaderImageNum]graphicsdriver.ImageID
var us [][]float32
if c.shader != nil { if c.shader != nil {
shaderID = c.shader.shader.ID() shaderID = c.shader.shader.ID()
for i, src := range c.srcs { for i, src := range c.srcs {
@ -470,11 +470,12 @@ func (c *drawTrianglesCommand) Exec(graphicsDriver graphicsdriver.Graphics, inde
} }
imgs[i] = src.image.ID() imgs[i] = src.image.ID()
} }
us = c.shader.convertUniforms(c.uniforms)
} else { } else {
imgs[0] = c.srcs[0].image.ID() 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 { func (c *drawTrianglesCommand) numVertices() int {
@ -706,7 +707,7 @@ func (c *newScreenFramebufferImageCommand) Exec(graphicsDriver graphicsdriver.Gr
// newShaderCommand is a command to create a shader. // newShaderCommand is a command to create a shader.
type newShaderCommand struct { type newShaderCommand struct {
result *Shader result *Shader
ir *shaderir.Program src []byte
} }
func (c *newShaderCommand) String() string { func (c *newShaderCommand) String() string {
@ -715,9 +716,19 @@ func (c *newShaderCommand) String() string {
// Exec executes a newShaderCommand. // Exec executes a newShaderCommand.
func (c *newShaderCommand) Exec(graphicsDriver graphicsdriver.Graphics, indexOffset int) error { func (c *newShaderCommand) Exec(graphicsDriver graphicsdriver.Graphics, indexOffset int) error {
var err error ir, err := compileShader(graphicsDriver, c.src)
c.result.shader, err = graphicsDriver.NewShader(c.ir) if err != nil {
return err 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. // InitializeGraphicsDriverState initialize the current graphics driver state.

View File

@ -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 // 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. // 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 { if shader == nil {
// Fast path for rendering without a shader (#1355). // Fast path for rendering without a shader (#1355).
img := srcs[0] img := srcs[0]

View File

@ -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) 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() g := ui.GraphicsDriverForTesting()
needsInvertY := g.FramebufferYDirection() != g.NDCYDirection() s := graphicscommand.NewShader(etesting.ShaderProgramFill(0xff, 0, 0, 0xff))
ir := etesting.ShaderProgramFill(needsInvertY, 0xff, 0, 0, 0xff)
s := graphicscommand.NewShader(&ir)
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) 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) pix := make([]byte, 4*w*h)

View File

@ -15,19 +15,162 @@
package graphicscommand package graphicscommand
import ( 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/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/shader"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir" "github.com/hajimehoshi/ebiten/v2/internal/shaderir"
) )
type Shader struct { var shaderSuffix string
shader graphicsdriver.Shader
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{} s := &Shader{}
c := &newShaderCommand{ c := &newShaderCommand{
result: s, result: s,
ir: ir, src: src,
} }
theCommandQueue.Enqueue(c) theCommandQueue.Enqueue(c)
return s return s
@ -39,3 +182,45 @@ func (s *Shader) Dispose() {
} }
theCommandQueue.Enqueue(c) 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
}

View File

@ -22,7 +22,6 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/buffered" "github.com/hajimehoshi/ebiten/v2/internal/buffered"
"github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" "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. // 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) 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 { if len(indices) == 0 {
return return
} }
@ -314,9 +313,9 @@ type Shader struct {
shader *buffered.Shader shader *buffered.Shader
} }
func NewShader(program *shaderir.Program) *Shader { func NewShader(src []byte) *Shader {
return &Shader{ return &Shader{
shader: buffered.NewShader(program), shader: buffered.NewShader(src),
} }
} }

View File

@ -76,7 +76,7 @@ type drawTrianglesHistoryItem struct {
dstRegion graphicsdriver.Region dstRegion graphicsdriver.Region
srcRegion graphicsdriver.Region srcRegion graphicsdriver.Region
shader *Shader shader *Shader
uniforms [][]float32 uniforms map[string]interface{}
evenOdd bool evenOdd bool
} }
@ -365,7 +365,7 @@ func (i *Image) ReplacePixels(pixels []byte, mask []byte, x, y, width, height in
// 5: Color G // 5: Color G
// 6: Color B // 6: Color B
// 7: Color Y // 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 { if i.priority {
panic("restorable: DrawTriangles cannot be called on a priority image") 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. // 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 { if i.stale || i.volatile || i.screen {
return return
} }

View File

@ -16,18 +16,17 @@ package restorable
import ( import (
"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand" "github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
) )
type Shader struct { type Shader struct {
shader *graphicscommand.Shader shader *graphicscommand.Shader
ir *shaderir.Program src []byte
} }
func NewShader(program *shaderir.Program) *Shader { func NewShader(src []byte) *Shader {
s := &Shader{ s := &Shader{
shader: graphicscommand.NewShader(program), shader: graphicscommand.NewShader(src),
ir: program, src: src,
} }
theImages.addShader(s) theImages.addShader(s)
return s return s
@ -37,9 +36,9 @@ func (s *Shader) Dispose() {
theImages.removeShader(s) theImages.removeShader(s)
s.shader.Dispose() s.shader.Dispose()
s.shader = nil s.shader = nil
s.ir = nil s.src = nil
} }
func (s *Shader) restore() { func (s *Shader) restore() {
s.shader = graphicscommand.NewShader(s.ir) s.shader = graphicscommand.NewShader(s.src)
} }

View File

@ -26,11 +26,6 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/ui" "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) { func clearImage(img *restorable.Image, w, h int) {
emptyImage := restorable.NewImage(3, 3) emptyImage := restorable.NewImage(3, 3)
defer emptyImage.Dispose() defer emptyImage.Dispose()
@ -63,8 +58,7 @@ func TestShader(t *testing.T) {
img := restorable.NewImage(1, 1) img := restorable.NewImage(1, 1)
defer img.Dispose() defer img.Dispose()
ir := etesting.ShaderProgramFill(needsInvertY(), 0xff, 0, 0, 0xff) s := restorable.NewShader(etesting.ShaderProgramFill(0xff, 0, 0, 0xff))
s := restorable.NewShader(&ir)
dr := graphicsdriver.Region{ dr := graphicsdriver.Region{
X: 0, X: 0,
Y: 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) imgs[0].ReplacePixels([]byte{0xff, 0, 0, 0xff}, nil, 0, 0, 1, 1)
ir := etesting.ShaderProgramImages(needsInvertY(), 1) s := restorable.NewShader(etesting.ShaderProgramImages(1))
s := restorable.NewShader(&ir)
for i := 0; i < num-1; i++ { for i := 0; i < num-1; i++ {
dr := graphicsdriver.Region{ dr := graphicsdriver.Region{
X: 0, X: 0,
@ -137,8 +130,7 @@ func TestShaderMultipleSources(t *testing.T) {
dst := restorable.NewImage(1, 1) dst := restorable.NewImage(1, 1)
ir := etesting.ShaderProgramImages(needsInvertY(), 3) s := restorable.NewShader(etesting.ShaderProgramImages(3))
s := restorable.NewShader(&ir)
var offsets [graphics.ShaderImageNum - 1][2]float32 var offsets [graphics.ShaderImageNum - 1][2]float32
dr := graphicsdriver.Region{ dr := graphicsdriver.Region{
X: 0, X: 0,
@ -176,8 +168,7 @@ func TestShaderMultipleSourcesOnOneTexture(t *testing.T) {
dst := restorable.NewImage(1, 1) dst := restorable.NewImage(1, 1)
ir := etesting.ShaderProgramImages(needsInvertY(), 3) s := restorable.NewShader(etesting.ShaderProgramImages(3))
s := restorable.NewShader(&ir)
offsets := [graphics.ShaderImageNum - 1][2]float32{ offsets := [graphics.ShaderImageNum - 1][2]float32{
{1, 0}, {1, 0},
{2, 0}, {2, 0},
@ -211,8 +202,7 @@ func TestShaderDispose(t *testing.T) {
img := restorable.NewImage(1, 1) img := restorable.NewImage(1, 1)
defer img.Dispose() defer img.Dispose()
ir := etesting.ShaderProgramFill(needsInvertY(), 0xff, 0, 0, 0xff) s := restorable.NewShader(etesting.ShaderProgramFill(0xff, 0, 0, 0xff))
s := restorable.NewShader(&ir)
dr := graphicsdriver.Region{ dr := graphicsdriver.Region{
X: 0, X: 0,
Y: 0, Y: 0,

View File

@ -15,437 +15,35 @@
package testing package testing
import ( import (
"go/constant" "fmt"
"strings"
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
) )
func projectionMatrix(invertY bool) shaderir.Expr { // ShaderProgramFill returns a shader source to fill the frambuffer.
m11s := 1 func ShaderProgramFill(r, g, b, a byte) []byte {
if invertY { return []byte(fmt.Sprintf(`package main
m11s = -1
}
m31 := -1
if invertY {
m31 = 1
}
return shaderir.Expr{ func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
Type: shaderir.Call, return vec4(%0.9f, %0.9f, %0.9f, %0.9f)
Exprs: []shaderir.Expr{ }
{ `, float64(r)/0xff, float64(g)/0xff, float64(b)/0xff, float64(a)/0xff))
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 vertexPosition() shaderir.Expr { // ShaderProgramImages returns a shader source to render the frambuffer with the given images.
return shaderir.Expr{ func ShaderProgramImages(numImages int) []byte {
Type: shaderir.Call, if numImages <= 0 {
Exprs: []shaderir.Expr{ panic("testing: numImages must be >= 1")
{
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,
},
},
} }
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
} }
`, strings.Join(exprs, " + ")))
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
} }

View File

@ -61,13 +61,11 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []f
} }
var s *mipmap.Shader var s *mipmap.Shader
var us [][]float32
if shader != nil { if shader != nil {
s = shader.shader 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) { func (i *Image) ReplacePixels(pix []byte, x, y, width, height int) {

View File

@ -15,200 +15,17 @@
package ui package ui
import ( 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/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 { type Shader struct {
shader *mipmap.Shader shader *mipmap.Shader
uniformNames []string
uniformTypes []shaderir.Type
} }
func NewShader(src []byte) (*Shader, error) { func NewShader(src []byte) *Shader {
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)
}
return &Shader{ return &Shader{
shader: mipmap.NewShader(s), shader: mipmap.NewShader(src),
uniformNames: s.UniformNames,
uniformTypes: s.Uniforms,
}, nil
}
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
} }
func (s *Shader) MarkDisposed() { func (s *Shader) MarkDisposed() {

View File

@ -27,16 +27,13 @@ type Shader struct {
// NewShader compiles a shader program in the shading language Kage, and retruns the result. // 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. // For the details about the shader, see https://ebiten.org/documents/shader.html.
func NewShader(src []byte) (*Shader, error) { func NewShader(src []byte) (*Shader, error) {
s, err := ui.NewShader(src)
if err != nil {
return nil, err
}
return &Shader{ return &Shader{
shader: s, shader: ui.NewShader(src),
}, nil }, nil
} }

View File

@ -192,10 +192,18 @@ func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
} }
func TestShaderNoMain(t *testing.T) { func TestShaderNoMain(t *testing.T) {
if _, err := ebiten.NewShader([]byte(`package main defer func() {
`)); err == nil { if r := recover(); r == nil {
t.Errorf("error must be non-nil but was 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) { func TestShaderUninitializedUniformVariables(t *testing.T) {