diff --git a/image.go b/image.go index 3365c7a09..2551e12b9 100644 --- a/image.go +++ b/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. diff --git a/internal/graphicscommand/command.go b/internal/graphicscommand/command.go index 247785cb1..aac2ed4c6 100644 --- a/internal/graphicscommand/command.go +++ b/internal/graphicscommand/command.go @@ -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)} diff --git a/internal/graphicscommand/shader.go b/internal/graphicscommand/shader.go index e7065e5d8..55c51e20a 100644 --- a/internal/graphicscommand/shader.go +++ b/internal/graphicscommand/shader.go @@ -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, diff --git a/internal/graphicsdriver/directx/graphics_windows.go b/internal/graphicsdriver/directx/graphics_windows.go index aff77f0ff..bdefaa365 100644 --- a/internal/graphicsdriver/directx/graphics_windows.go +++ b/internal/graphicsdriver/directx/graphics_windows.go @@ -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())) diff --git a/internal/graphicsdriver/metal/graphics_darwin.go b/internal/graphicsdriver/metal/graphics_darwin.go index 30995c2f8..d5e955715 100644 --- a/internal/graphicsdriver/metal/graphics_darwin.go +++ b/internal/graphicsdriver/metal/graphics_darwin.go @@ -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) } diff --git a/internal/graphicsdriver/opengl/program.go b/internal/graphicsdriver/opengl/program.go index 2f7d2d3c9..8569e06ae 100644 --- a/internal/graphicsdriver/opengl/program.go +++ b/internal/graphicsdriver/opengl/program.go @@ -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. diff --git a/internal/shader/reachable_test.go b/internal/shader/reachable_test.go new file mode 100644 index 000000000..544934d9c --- /dev/null +++ b/internal/shader/reachable_test.go @@ -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) + } + } +} diff --git a/internal/shaderir/glsl/glsl.go b/internal/shaderir/glsl/glsl.go index d1c4deb59..b03c76f5a 100644 --- a/internal/shaderir/glsl/glsl.go +++ b/internal/shaderir/glsl/glsl.go @@ -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)) diff --git a/internal/shaderir/program.go b/internal/shaderir/program.go index 457c3b542..283803c51 100644 --- a/internal/shaderir/program.go +++ b/internal/shaderir/program.go @@ -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 +}