internal/graphicscommand: remove unused uniform variables

This improves possibility of merging graphics commands by reducing
uniform variables.

Updates #2232
This commit is contained in:
Hajime Hoshi 2022-11-03 18:48:59 +09:00
parent 86e694941f
commit 384dee7160
9 changed files with 299 additions and 78 deletions

View File

@ -249,7 +249,7 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
})
}
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedRegion(), graphicsdriver.Region{}, [graphics.ShaderImageCount - 1][2]float32{}, shader.shader, uniforms, false, canSkipMipmap(options.GeoM, filter), false)
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedRegion(), img.adjustedRegion(), [graphics.ShaderImageCount - 1][2]float32{}, shader.shader, uniforms, false, canSkipMipmap(options.GeoM, filter), false)
}
// Vertex represents a vertex passed to DrawTriangles.
@ -424,11 +424,6 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
}
address := builtinshader.Address(options.Address)
var sr graphicsdriver.Region
if address != builtinshader.AddressUnsafe {
sr = img.adjustedRegion()
}
filter := builtinshader.Filter(options.Filter)
colorm, cr, cg, cb, ca := colorMToScale(options.ColorM.affineColorM())
@ -480,7 +475,7 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
})
}
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedRegion(), sr, [graphics.ShaderImageCount - 1][2]float32{}, shader.shader, uniforms, options.FillRule == EvenOdd, filter != builtinshader.FilterLinear, options.AntiAlias)
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedRegion(), img.adjustedRegion(), [graphics.ShaderImageCount - 1][2]float32{}, shader.shader, uniforms, options.FillRule == EvenOdd, filter != builtinshader.FilterLinear, options.AntiAlias)
}
// DrawTrianglesShaderOptions represents options for DrawTrianglesShader.

View File

@ -110,6 +110,20 @@ func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.Sh
uniforms = prependPreservedUniforms(uniforms, dst, srcs, offsets, dstRegion, srcRegion)
// Remove unused uniform variables so that more commands can be merged.
uvs := map[int]struct{}{}
for _, i := range shader.ir.ReachableUniformVariablesFromBlock(shader.ir.VertexFunc.Block) {
uvs[i] = struct{}{}
}
for _, i := range shader.ir.ReachableUniformVariablesFromBlock(shader.ir.FragmentFunc.Block) {
uvs[i] = struct{}{}
}
for i := range uniforms {
if _, ok := uvs[i]; !ok {
uniforms[i] = nil
}
}
// TODO: If dst is the screen, reorder the command to be the last.
if !split && 0 < len(q.commands) {
if last, ok := q.commands[len(q.commands)-1].(*drawTrianglesCommand); ok {
@ -575,7 +589,6 @@ func prependPreservedUniforms(uniforms [][]float32, dst *Image, srcs [graphics.S
uniforms[graphics.TextureSourceSizesUniformVariableIndex] = usizes
// Set the destination region.
// TODO: Set them only when the shader refers this (#2232).
uniforms[graphics.TextureDestinationRegionOriginUniformVariableIndex] = []float32{float32(dstRegion.X) / float32(dw), float32(dstRegion.Y) / float32(dh)}
uniforms[graphics.TextureDestinationRegionSizeUniformVariableIndex] = []float32{float32(dstRegion.Width) / float32(dw), float32(dstRegion.Height) / float32(dh)}
@ -600,7 +613,6 @@ func prependPreservedUniforms(uniforms [][]float32, dst *Image, srcs [graphics.S
uniforms[graphics.TextureSourceOffsetsUniformVariableIndex] = uoffsets
// Set the source region of texture0.
// TODO: Set them only when the shader refers this (#2232).
uniforms[graphics.TextureSourceRegionOriginUniformVariableIndex] = []float32{float32(srcRegion.X), float32(srcRegion.Y)}
uniforms[graphics.TextureSourceRegionSizeUniformVariableIndex] = []float32{float32(srcRegion.Width), float32(srcRegion.Height)}

View File

@ -21,10 +21,13 @@ import (
type Shader struct {
shader graphicsdriver.Shader
ir *shaderir.Program
}
func NewShader(ir *shaderir.Program) *Shader {
s := &Shader{}
s := &Shader{
ir: ir,
}
c := &newShaderCommand{
result: s,
ir: ir,

View File

@ -1763,53 +1763,116 @@ func (s *Shader) uniformsToFloat32s(uniforms [][]float32) []float32 {
t := s.uniformTypes[i]
switch t.Main {
case shaderir.Float, shaderir.Vec2, shaderir.Vec3, shaderir.Vec4:
case shaderir.Float:
if u != nil {
fs = append(fs, u...)
} else {
fs = append(fs, 0)
}
case shaderir.Vec2:
if u != nil {
fs = append(fs, u...)
} else {
fs = append(fs, 0, 0)
}
case shaderir.Vec3:
if u != nil {
fs = append(fs, u...)
} else {
fs = append(fs, 0, 0, 0)
}
case shaderir.Vec4:
if u != nil {
fs = append(fs, u...)
} else {
fs = append(fs, 0, 0, 0, 0)
}
case shaderir.Mat2:
if u != nil {
fs = append(fs,
u[0], u[2], 0, 0,
u[1], u[3],
)
} else {
fs = append(fs,
0, 0, 0, 0,
0, 0,
)
}
case shaderir.Mat3:
if u != nil {
fs = append(fs,
u[0], u[3], u[6], 0,
u[1], u[4], u[7], 0,
u[2], u[5], u[8],
)
} else {
fs = append(fs,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0,
)
}
case shaderir.Mat4:
if u != nil {
fs = append(fs,
u[0], u[4], u[8], u[12],
u[1], u[5], u[9], u[13],
u[2], u[6], u[10], u[14],
u[3], u[7], u[11], u[15],
)
} else {
fs = append(fs,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
)
}
case shaderir.Array:
// Each element is aligned to the boundary.
switch t.Sub[0].Main {
case shaderir.Float:
if u != nil {
for j := 0; j < t.Length; j++ {
fs = append(fs, u[j])
if j < t.Length-1 {
fs = append(fs, 0, 0, 0)
}
}
} else {
fs = append(fs, make([]float32, (t.Length-1)*4+1)...)
}
case shaderir.Vec2:
if u != nil {
for j := 0; j < t.Length; j++ {
fs = append(fs, u[2*j:2*(j+1)]...)
if j < t.Length-1 {
fs = append(fs, 0, 0)
}
}
} else {
fs = append(fs, make([]float32, (t.Length-1)*4+2)...)
}
case shaderir.Vec3:
if u != nil {
for j := 0; j < t.Length; j++ {
fs = append(fs, u[3*j:3*(j+1)]...)
if j < t.Length-1 {
fs = append(fs, 0)
}
}
} else {
fs = append(fs, make([]float32, (t.Length-1)*4+3)...)
}
case shaderir.Vec4:
if u != nil {
fs = append(fs, u...)
} else {
fs = append(fs, make([]float32, t.Length*4)...)
}
case shaderir.Mat2:
if u != nil {
for j := 0; j < t.Length; j++ {
u1 := u[4*j : 4*(j+1)]
fs = append(fs,
@ -1820,7 +1883,11 @@ func (s *Shader) uniformsToFloat32s(uniforms [][]float32) []float32 {
if t.Length > 0 {
fs = fs[:len(fs)-2]
}
} else {
fs = append(fs, make([]float32, (t.Length-1)*8+6)...)
}
case shaderir.Mat3:
if u != nil {
for j := 0; j < t.Length; j++ {
u1 := u[9*j : 9*(j+1)]
fs = append(fs,
@ -1832,7 +1899,11 @@ func (s *Shader) uniformsToFloat32s(uniforms [][]float32) []float32 {
if t.Length > 0 {
fs = fs[:len(fs)-1]
}
} else {
fs = append(fs, make([]float32, (t.Length-1)*12+11)...)
}
case shaderir.Mat4:
if u != nil {
for j := 0; j < t.Length; j++ {
u1 := u[16*j : 16*(j+1)]
fs = append(fs,
@ -1842,6 +1913,9 @@ func (s *Shader) uniformsToFloat32s(uniforms [][]float32) []float32 {
u1[3], u1[7], u1[11], u1[15],
)
}
} else {
fs = append(fs, make([]float32, t.Length*16)...)
}
default:
panic(fmt.Sprintf("directx: not implemented type for uniform variables: %s", t.String()))
}

View File

@ -484,6 +484,9 @@ func (g *Graphics) draw(rps mtl.RenderPipelineState, dst *Image, dstRegion graph
g.rce.SetVertexBuffer(g.vb, 0, 0)
for i, u := range uniforms {
if u == nil {
continue
}
g.rce.SetVertexBytes(unsafe.Pointer(&u[0]), unsafe.Sizeof(u[0])*uintptr(len(u)), i+1)
g.rce.SetFragmentBytes(unsafe.Pointer(&u[0]), unsafe.Sizeof(u[0])*uintptr(len(u)), i+1)
}

View File

@ -219,6 +219,9 @@ func (g *Graphics) useProgram(program program, uniforms []uniformVariable, textu
}
for _, u := range uniforms {
if u.value == nil {
continue
}
if got, expected := len(u.value), u.typ.FloatCount(); got != expected {
// Copy a shaderir.Type value once. Do not pass u.typ directly to fmt.Errorf arguments, or
// the value u would be allocated on heap.

View File

@ -0,0 +1,107 @@
// Copyright 2022 The Ebitengine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package shader_test
import (
"testing"
)
func areIntSlicesEqual(a, b []int) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func TestReachableUniformVariablesFromBlock(t *testing.T) {
src0 := `package main
var U0 float
var U1 float
func F0() float {
return U0
}
func F1() {
a := U0
_ = a
}
func F2() {
}
func F3() float {
return F0()
}
func F4() float {
return F0() + U1
}
func neverCalled() float {
return U0 + U1
}
`
cases := []struct {
source string
index int
expected []int
}{
{
source: src0,
index: 0,
expected: []int{0},
},
{
source: src0,
index: 1,
expected: []int{0},
},
{
source: src0,
index: 2,
expected: []int{},
},
{
source: src0,
index: 3,
expected: []int{0},
},
{
source: src0,
index: 4,
expected: []int{0, 1},
},
}
for _, c := range cases {
ir, err := compileToIR([]byte(c.source))
if err != nil {
t.Fatal(err)
}
got := ir.ReachableUniformVariablesFromBlock(ir.Funcs[c.index].Block)
want := c.expected
if !areIntSlicesEqual(got, want) {
t.Errorf("test: %v, got: %v, want: %v", c, got, want)
}
}
}

View File

@ -129,7 +129,7 @@ func Compile(p *shaderir.Program, version GLSLVersion) (vertexShader, fragmentSh
var funcs []*shaderir.Func
if p.VertexFunc.Block != nil {
funcs = p.ReachableFuncsFromVertexShader()
funcs = p.ReachableFuncsFromBlock(p.VertexFunc.Block)
} else {
// When a vertex entry point is not defined, allow to put all the functions. This is useful for testing.
funcs = make([]*shaderir.Func, 0, len(p.Funcs))
@ -222,7 +222,7 @@ func Compile(p *shaderir.Program, version GLSLVersion) (vertexShader, fragmentSh
var funcs []*shaderir.Func
if p.VertexFunc.Block != nil {
funcs = p.ReachableFuncsFromFragmentShader()
funcs = p.ReachableFuncsFromBlock(p.FragmentFunc.Block)
} else {
// When a fragment entry point is not defined, allow to put all the functions. This is useful for testing.
funcs = make([]*shaderir.Func, 0, len(p.Funcs))

View File

@ -364,15 +364,7 @@ func IsValidSwizzling(s string) bool {
return false
}
func (p *Program) ReachableFuncsFromVertexShader() []*Func {
return p.reachableFuncsFromBlockEntryPoint(p.VertexFunc.Block)
}
func (p *Program) ReachableFuncsFromFragmentShader() []*Func {
return p.reachableFuncsFromBlockEntryPoint(p.FragmentFunc.Block)
}
func (p *Program) reachableFuncsFromBlockEntryPoint(block *Block) []*Func {
func (p *Program) ReachableFuncsFromBlock(block *Block) []*Func {
indexToFunc := map[int]*Func{}
for _, f := range p.Funcs {
f := f
@ -427,3 +419,35 @@ func walkExprsInExpr(f func(expr *Expr), expr *Expr) {
walkExprsInExpr(f, &e)
}
}
func (p *Program) ReachableUniformVariablesFromBlock(block *Block) []int {
indexToFunc := map[int]*Func{}
for _, f := range p.Funcs {
f := f
indexToFunc[f.Index] = &f
}
visitedFuncs := map[int]struct{}{}
indices := map[int]struct{}{}
var f func(expr *Expr)
f = func(expr *Expr) {
switch expr.Type {
case UniformVariable:
indices[expr.Index] = struct{}{}
case FunctionExpr:
if _, ok := visitedFuncs[expr.Index]; ok {
return
}
visitedFuncs[expr.Index] = struct{}{}
walkExprs(f, indexToFunc[expr.Index].Block)
}
}
walkExprs(f, block)
is := make([]int, 0, len(indices))
for i := range indices {
is = append(is, i)
}
sort.Ints(is)
return is
}