ebiten: compile shaders at NewShader

Closes #2035
This commit is contained in:
Hajime Hoshi 2022-04-04 02:15:33 +09:00
parent 02db3bad53
commit 6710808cd1
15 changed files with 261 additions and 237 deletions

View File

@ -497,7 +497,7 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
offsets[i][1] = -sy + float32(b.Min.Y) offsets[i][1] = -sy + float32(b.Min.Y)
} }
i.image.DrawTriangles(imgs, vs, is, affine.ColorMIdentity{}, mode, graphicsdriver.FilterNearest, graphicsdriver.AddressUnsafe, dstRegion, sr, offsets, shader.shader, convertUniforms(options.Uniforms), options.FillRule == EvenOdd, false) i.image.DrawTriangles(imgs, vs, is, affine.ColorMIdentity{}, mode, graphicsdriver.FilterNearest, graphicsdriver.AddressUnsafe, dstRegion, sr, offsets, shader.shader, shader.convertUniforms(options.Uniforms), options.FillRule == EvenOdd, false)
} }
// DrawRectShaderOptions represents options for DrawRectShader. // DrawRectShaderOptions represents options for DrawRectShader.
@ -608,7 +608,7 @@ func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawR
offsets[i][1] = -sy + float32(b.Min.Y) offsets[i][1] = -sy + float32(b.Min.Y)
} }
i.image.DrawTriangles(imgs, vs, is, affine.ColorMIdentity{}, mode, graphicsdriver.FilterNearest, graphicsdriver.AddressUnsafe, dstRegion, sr, offsets, shader.shader, convertUniforms(options.Uniforms), false, canSkipMipmap(options.GeoM, graphicsdriver.FilterNearest)) i.image.DrawTriangles(imgs, vs, is, affine.ColorMIdentity{}, mode, graphicsdriver.FilterNearest, graphicsdriver.AddressUnsafe, dstRegion, sr, offsets, shader.shader, shader.convertUniforms(options.Uniforms), false, canSkipMipmap(options.GeoM, graphicsdriver.FilterNearest))
} }
// SubImage returns an image representing the portion of the image p visible through r. // SubImage returns an image representing the portion of the image p visible through r.
@ -883,18 +883,3 @@ func colorMToScale(colorm affine.ColorM) (newColorM affine.ColorM, r, g, b, a fl
return affine.ColorMIdentity{}, r, g, b, a return affine.ColorMIdentity{}, r, g, b, a
} }
func convertUniforms(uniforms map[string]interface{}) map[string][]float32 {
us := map[string][]float32{}
for name, v := range uniforms {
switch v := v.(type) {
case float32:
us[name] = []float32{v}
case []float32:
us[name] = v
default:
panic(fmt.Sprintf("ebiten: unexpected uniform value type: %s, %T", name, v))
}
}
return us
}

View File

@ -404,13 +404,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 map[string][]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 [][]float32, 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 map[string][]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 [][]float32, 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,15 +18,16 @@ 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(src []byte) *Shader { func NewShader(ir *shaderir.Program) *Shader {
s := &Shader{ s := &Shader{
shader: restorable.NewShader(src), shader: restorable.NewShader(ir),
} }
runtime.SetFinalizer(s, (*Shader).MarkDisposed) runtime.SetFinalizer(s, (*Shader).MarkDisposed)
return s return s

View File

@ -21,6 +21,7 @@ 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 {
@ -228,7 +229,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 map[string][]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 [][]float32, 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")
@ -272,9 +273,9 @@ type Shader struct {
shader *atlas.Shader shader *atlas.Shader
} }
func NewShader(src []byte) *Shader { func NewShader(ir *shaderir.Program) *Shader {
return &Shader{ return &Shader{
shader: atlas.NewShader(src), shader: atlas.NewShader(ir),
} }
} }

140
internal/graphics/shader.go Normal file
View File

@ -0,0 +1,140 @@
// Copyright 2022 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package graphics
import (
"bytes"
"fmt"
"go/parser"
"go/token"
"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
}
`, ShaderImageNum, ShaderImageNum-1)
for i := 0; i < 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(src []byte) (*shaderir.Program, error) {
var buf bytes.Buffer
buf.Write(src)
buf.WriteString(shaderSuffix)
buf.WriteString(`var __projectionMatrix mat4
func __vertex(position vec2, texCoord vec2, color vec4) (vec4, vec2, vec4) {
return __projectionMatrix * 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, 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
}

View File

@ -23,6 +23,7 @@ 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.
@ -117,7 +118,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 map[string][]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 [][]float32, 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))
} }
@ -294,7 +295,7 @@ type drawTrianglesCommand struct {
dstRegion graphicsdriver.Region dstRegion graphicsdriver.Region
srcRegion graphicsdriver.Region srcRegion graphicsdriver.Region
shader *Shader shader *Shader
uniforms map[string][]float32 uniforms [][]float32
evenOdd bool evenOdd bool
} }
@ -392,7 +393,6 @@ 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 {
@ -402,12 +402,11 @@ 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, us, 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, c.uniforms, c.evenOdd)
} }
func (c *drawTrianglesCommand) numVertices() int { func (c *drawTrianglesCommand) numVertices() int {
@ -428,7 +427,7 @@ func (c *drawTrianglesCommand) addNumIndices(n int) {
// CanMergeWithDrawTrianglesCommand returns a boolean value indicating whether the other drawTrianglesCommand can be merged // CanMergeWithDrawTrianglesCommand returns a boolean value indicating whether the other drawTrianglesCommand can be merged
// with the drawTrianglesCommand c. // with the drawTrianglesCommand c.
func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageNum]*Image, vertices []float32, color affine.ColorM, mode graphicsdriver.CompositeMode, filter graphicsdriver.Filter, address graphicsdriver.Address, dstRegion, srcRegion graphicsdriver.Region, shader *Shader, uniforms map[string][]float32, evenOdd bool) bool { func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageNum]*Image, vertices []float32, color affine.ColorM, mode graphicsdriver.CompositeMode, filter graphicsdriver.Filter, address graphicsdriver.Address, dstRegion, srcRegion graphicsdriver.Region, shader *Shader, uniforms [][]float32, evenOdd bool) bool {
if c.shader != shader { if c.shader != shader {
return false return false
} }
@ -436,16 +435,12 @@ func (c *drawTrianglesCommand) CanMergeWithDrawTrianglesCommand(dst *Image, srcs
if len(c.uniforms) != len(uniforms) { if len(c.uniforms) != len(uniforms) {
return false return false
} }
for name, v0 := range c.uniforms { for i := range c.uniforms {
v1, ok := uniforms[name] if len(c.uniforms[i]) != len(uniforms[i]) {
if !ok {
return false return false
} }
if len(v0) != len(v1) { for j := range c.uniforms[i] {
return false if c.uniforms[i][j] != uniforms[i][j] {
}
for i := range v0 {
if v0[i] != v1[i] {
return false return false
} }
} }
@ -657,7 +652,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
src []byte ir *shaderir.Program
} }
func (c *newShaderCommand) String() string { func (c *newShaderCommand) String() string {
@ -666,18 +661,11 @@ 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 {
ir, err := compileShader(c.src) s, err := graphicsDriver.NewShader(c.ir)
if err != nil {
return err
}
s, err := graphicsDriver.NewShader(ir)
if err != nil { if err != nil {
return err return err
} }
c.result.shader = s c.result.shader = s
c.result.uniformNames = ir.UniformNames
c.result.uniformTypes = ir.Uniforms
return nil return nil
} }

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 map[string][]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 [][]float32, 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

@ -15,147 +15,19 @@
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"
) )
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)
}
}
func compileShader(src []byte) (*shaderir.Program, error) {
var buf bytes.Buffer
buf.Write(src)
buf.WriteString(shaderSuffix)
buf.WriteString(`var __projectionMatrix mat4
func __vertex(position vec2, texCoord vec2, color vec4) (vec4, vec2, vec4) {
return __projectionMatrix * 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 { type Shader struct {
shader graphicsdriver.Shader shader graphicsdriver.Shader
uniformNames []string
uniformTypes []shaderir.Type
uniformNameToIndex map[string]int
uniformNameToType map[string]shaderir.Type
} }
func NewShader(src []byte) *Shader { func NewShader(ir *shaderir.Program) *Shader {
s := &Shader{} s := &Shader{}
c := &newShaderCommand{ c := &newShaderCommand{
result: s, result: s,
src: src, ir: ir,
} }
theCommandQueue.Enqueue(c) theCommandQueue.Enqueue(c)
return s return s
@ -167,38 +39,3 @@ func (s *Shader) Dispose() {
} }
theCommandQueue.Enqueue(c) theCommandQueue.Enqueue(c)
} }
func (s *Shader) convertUniforms(uniforms map[string][]float32) [][]float32 {
if s.shader == nil {
panic("graphicscommand: shader is not compiled yet")
}
if s.uniformNameToIndex == nil {
s.uniformNameToIndex = map[string]int{}
s.uniformNameToType = map[string]shaderir.Type{}
var idx int
for i, n := range s.uniformNames {
if strings.HasPrefix(n, "__") {
continue
}
s.uniformNameToIndex[n] = idx
s.uniformNameToType[n] = s.uniformTypes[i]
idx++
}
}
us := make([][]float32, len(s.uniformNameToIndex))
for name, idx := range s.uniformNameToIndex {
if v, ok := uniforms[name]; ok {
us[idx] = v
continue
}
t := s.uniformNameToType[name]
us[idx] = make([]float32, t.FloatNum())
}
// TODO: Panic if uniforms include an invalid name
return us
}

View File

@ -22,6 +22,7 @@ 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.
@ -79,7 +80,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 map[string][]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 [][]float32, evenOdd bool, canSkipMipmap bool) {
if len(indices) == 0 { if len(indices) == 0 {
return return
} }
@ -313,9 +314,9 @@ type Shader struct {
shader *buffered.Shader shader *buffered.Shader
} }
func NewShader(src []byte) *Shader { func NewShader(ir *shaderir.Program) *Shader {
return &Shader{ return &Shader{
shader: buffered.NewShader(src), shader: buffered.NewShader(ir),
} }
} }

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 map[string][]float32 uniforms [][]float32
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 map[string][]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 [][]float32, 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 map[string][]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 [][]float32, evenOdd bool) {
if i.stale || i.volatile || i.screen { if i.stale || i.volatile || i.screen {
return return
} }

View File

@ -16,17 +16,18 @@ 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
src []byte ir *shaderir.Program
} }
func NewShader(src []byte) *Shader { func NewShader(ir *shaderir.Program) *Shader {
s := &Shader{ s := &Shader{
shader: graphicscommand.NewShader(src), shader: graphicscommand.NewShader(ir),
src: src, ir: ir,
} }
theImages.addShader(s) theImages.addShader(s)
return s return s
@ -36,9 +37,9 @@ func (s *Shader) Dispose() {
theImages.removeShader(s) theImages.removeShader(s)
s.shader.Dispose() s.shader.Dispose()
s.shader = nil s.shader = nil
s.src = nil s.ir = nil
} }
func (s *Shader) restore() { func (s *Shader) restore() {
s.shader = graphicscommand.NewShader(s.src) s.shader = graphicscommand.NewShader(s.ir)
} }

View File

@ -17,20 +17,27 @@ package testing
import ( import (
"fmt" "fmt"
"strings" "strings"
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
) )
// ShaderProgramFill returns a shader source to fill the frambuffer. // ShaderProgramFill returns a shader source to fill the frambuffer.
func ShaderProgramFill(r, g, b, a byte) []byte { func ShaderProgramFill(r, g, b, a byte) *shaderir.Program {
return []byte(fmt.Sprintf(`package main ir, err := graphics.CompileShader([]byte(fmt.Sprintf(`package main
func Fragment(position vec4, texCoord vec2, color vec4) vec4 { func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
return vec4(%0.9f, %0.9f, %0.9f, %0.9f) return vec4(%0.9f, %0.9f, %0.9f, %0.9f)
} }
`, float64(r)/0xff, float64(g)/0xff, float64(b)/0xff, float64(a)/0xff)) `, float64(r)/0xff, float64(g)/0xff, float64(b)/0xff, float64(a)/0xff)))
if err != nil {
panic(err)
}
return ir
} }
// ShaderProgramImages returns a shader source to render the frambuffer with the given images. // ShaderProgramImages returns a shader source to render the frambuffer with the given images.
func ShaderProgramImages(numImages int) []byte { func ShaderProgramImages(numImages int) *shaderir.Program {
if numImages <= 0 { if numImages <= 0 {
panic("testing: numImages must be >= 1") panic("testing: numImages must be >= 1")
} }
@ -40,10 +47,14 @@ func ShaderProgramImages(numImages int) []byte {
exprs = append(exprs, fmt.Sprintf("imageSrc%dUnsafeAt(texCoord)", i)) exprs = append(exprs, fmt.Sprintf("imageSrc%dUnsafeAt(texCoord)", i))
} }
return []byte(fmt.Sprintf(`package main ir, err := graphics.CompileShader([]byte(fmt.Sprintf(`package main
func Fragment(position vec4, texCoord vec2, color vec4) vec4 { func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
return %s return %s
} }
`, strings.Join(exprs, " + "))) `, strings.Join(exprs, " + "))))
if err != nil {
panic(err)
}
return ir
} }

View File

@ -59,7 +59,7 @@ func (i *Image) MarkDisposed() {
i.mipmap = nil i.mipmap = nil
} }
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][]float32, evenOdd bool, canSkipMipmap 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 [][]float32, evenOdd bool, canSkipMipmap bool) {
var srcMipmaps [graphics.ShaderImageNum]*mipmap.Mipmap var srcMipmaps [graphics.ShaderImageNum]*mipmap.Mipmap
for i, src := range srcs { for i, src := range srcs {
if src == nil { if src == nil {

View File

@ -16,15 +16,16 @@ package ui
import ( import (
"github.com/hajimehoshi/ebiten/v2/internal/mipmap" "github.com/hajimehoshi/ebiten/v2/internal/mipmap"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
) )
type Shader struct { type Shader struct {
shader *mipmap.Shader shader *mipmap.Shader
} }
func NewShader(src []byte) *Shader { func NewShader(ir *shaderir.Program) *Shader {
return &Shader{ return &Shader{
shader: mipmap.NewShader(src), shader: mipmap.NewShader(ir),
} }
} }

View File

@ -15,6 +15,11 @@
package ebiten package ebiten
import ( import (
"fmt"
"strings"
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
"github.com/hajimehoshi/ebiten/v2/internal/ui" "github.com/hajimehoshi/ebiten/v2/internal/ui"
) )
@ -23,17 +28,27 @@ import (
// 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.
type Shader struct { type Shader struct {
shader *ui.Shader shader *ui.Shader
uniformNames []string
uniformTypes []shaderir.Type
uniformNameToIndex map[string]int
uniformNameToType map[string]shaderir.Type
} }
// 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.
// //
// As of v2.3.0, the error value is always nil, and // If the compilation fails, NewShader returns an error.
// 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) {
ir, err := graphics.CompileShader(src)
if err != nil {
return nil, err
}
return &Shader{ return &Shader{
shader: ui.NewShader(src), shader: ui.NewShader(ir),
uniformNames: ir.UniformNames,
uniformTypes: ir.Uniforms,
}, nil }, nil
} }
@ -43,3 +58,46 @@ func (s *Shader) Dispose() {
s.shader.MarkDisposed() s.shader.MarkDisposed()
s.shader = nil s.shader = nil
} }
func (s *Shader) convertUniforms(uniforms map[string]interface{}) [][]float32 {
nameToF32s := map[string][]float32{}
for name, v := range uniforms {
switch v := v.(type) {
case float32:
nameToF32s[name] = []float32{v}
case []float32:
nameToF32s[name] = v
default:
panic(fmt.Sprintf("ebiten: unexpected uniform value type: %s, %T", name, v))
}
}
if s.uniformNameToIndex == nil {
s.uniformNameToIndex = map[string]int{}
s.uniformNameToType = map[string]shaderir.Type{}
var idx int
for i, n := range s.uniformNames {
if strings.HasPrefix(n, "__") {
continue
}
s.uniformNameToIndex[n] = idx
s.uniformNameToType[n] = s.uniformTypes[i]
idx++
}
}
us := make([][]float32, len(s.uniformNameToIndex))
for name, idx := range s.uniformNameToIndex {
if v, ok := nameToF32s[name]; ok {
us[idx] = v
continue
}
t := s.uniformNameToType[name]
us[idx] = make([]float32, t.FloatNum())
}
// TODO: Panic if uniforms include an invalid name
return us
}