mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-24 10:48:53 +01:00
internal/graphicscommand: remove unused uniform variables
This improves possibility of merging graphics commands by reducing uniform variables. Updates #2232
This commit is contained in:
parent
86e694941f
commit
384dee7160
9
image.go
9
image.go
@ -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.
|
||||
|
@ -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)}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -1763,84 +1763,158 @@ func (s *Shader) uniformsToFloat32s(uniforms [][]float32) []float32 {
|
||||
|
||||
t := s.uniformTypes[i]
|
||||
switch t.Main {
|
||||
case shaderir.Float, shaderir.Vec2, shaderir.Vec3, shaderir.Vec4:
|
||||
fs = append(fs, u...)
|
||||
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:
|
||||
fs = append(fs,
|
||||
u[0], u[2], 0, 0,
|
||||
u[1], u[3],
|
||||
)
|
||||
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:
|
||||
fs = append(fs,
|
||||
u[0], u[3], u[6], 0,
|
||||
u[1], u[4], u[7], 0,
|
||||
u[2], u[5], u[8],
|
||||
)
|
||||
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:
|
||||
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],
|
||||
)
|
||||
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:
|
||||
for j := 0; j < t.Length; j++ {
|
||||
fs = append(fs, u[j])
|
||||
if j < t.Length-1 {
|
||||
fs = append(fs, 0, 0, 0)
|
||||
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:
|
||||
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)
|
||||
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:
|
||||
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)
|
||||
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:
|
||||
fs = append(fs, u...)
|
||||
case shaderir.Mat2:
|
||||
for j := 0; j < t.Length; j++ {
|
||||
u1 := u[4*j : 4*(j+1)]
|
||||
fs = append(fs,
|
||||
u1[0], u1[2], 0, 0,
|
||||
u1[1], u1[3], 0, 0,
|
||||
)
|
||||
if u != nil {
|
||||
fs = append(fs, u...)
|
||||
} else {
|
||||
fs = append(fs, make([]float32, t.Length*4)...)
|
||||
}
|
||||
if t.Length > 0 {
|
||||
fs = fs[:len(fs)-2]
|
||||
case shaderir.Mat2:
|
||||
if u != nil {
|
||||
for j := 0; j < t.Length; j++ {
|
||||
u1 := u[4*j : 4*(j+1)]
|
||||
fs = append(fs,
|
||||
u1[0], u1[2], 0, 0,
|
||||
u1[1], u1[3], 0, 0,
|
||||
)
|
||||
}
|
||||
if t.Length > 0 {
|
||||
fs = fs[:len(fs)-2]
|
||||
}
|
||||
} else {
|
||||
fs = append(fs, make([]float32, (t.Length-1)*8+6)...)
|
||||
}
|
||||
case shaderir.Mat3:
|
||||
for j := 0; j < t.Length; j++ {
|
||||
u1 := u[9*j : 9*(j+1)]
|
||||
fs = append(fs,
|
||||
u1[0], u1[3], u1[6], 0,
|
||||
u1[1], u1[4], u1[7], 0,
|
||||
u1[2], u1[5], u1[8], 0,
|
||||
)
|
||||
}
|
||||
if t.Length > 0 {
|
||||
fs = fs[:len(fs)-1]
|
||||
if u != nil {
|
||||
for j := 0; j < t.Length; j++ {
|
||||
u1 := u[9*j : 9*(j+1)]
|
||||
fs = append(fs,
|
||||
u1[0], u1[3], u1[6], 0,
|
||||
u1[1], u1[4], u1[7], 0,
|
||||
u1[2], u1[5], u1[8], 0,
|
||||
)
|
||||
}
|
||||
if t.Length > 0 {
|
||||
fs = fs[:len(fs)-1]
|
||||
}
|
||||
} else {
|
||||
fs = append(fs, make([]float32, (t.Length-1)*12+11)...)
|
||||
}
|
||||
case shaderir.Mat4:
|
||||
for j := 0; j < t.Length; j++ {
|
||||
u1 := u[16*j : 16*(j+1)]
|
||||
fs = append(fs,
|
||||
u1[0], u1[4], u1[8], u1[12],
|
||||
u1[1], u1[5], u1[9], u1[13],
|
||||
u1[2], u1[6], u1[10], u1[14],
|
||||
u1[3], u1[7], u1[11], u1[15],
|
||||
)
|
||||
if u != nil {
|
||||
for j := 0; j < t.Length; j++ {
|
||||
u1 := u[16*j : 16*(j+1)]
|
||||
fs = append(fs,
|
||||
u1[0], u1[4], u1[8], u1[12],
|
||||
u1[1], u1[5], u1[9], u1[13],
|
||||
u1[2], u1[6], u1[10], u1[14],
|
||||
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()))
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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.
|
||||
|
107
internal/shader/reachable_test.go
Normal file
107
internal/shader/reachable_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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))
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user