graphicsdriver/opengl: Reduce 'if' in shader programs

Fixes #812
This commit is contained in:
Hajime Hoshi 2019-02-12 11:00:18 +09:00
parent 6c4260d0e1
commit c2c3579cde
2 changed files with 154 additions and 128 deletions

View File

@ -107,6 +107,11 @@ func init() {
} }
} }
type programKey struct {
filter graphics.Filter
address graphics.Address
}
// openGLState is a state for // openGLState is a state for
type openGLState struct { type openGLState struct {
// arrayBuffer is OpenGL's array buffer (vertices data). // arrayBuffer is OpenGL's array buffer (vertices data).
@ -115,8 +120,8 @@ type openGLState struct {
// elementArrayBuffer is OpenGL's element array buffer (indices data). // elementArrayBuffer is OpenGL's element array buffer (indices data).
elementArrayBuffer buffer elementArrayBuffer buffer
// program is OpenGL's program for rendering a texture. // programs is OpenGL's program for rendering a texture.
program program programs map[programKey]program
lastProgram program lastProgram program
lastViewportWidth int lastViewportWidth int
@ -161,8 +166,13 @@ func (s *openGLState) reset(context *context) error {
// When context lost happens, deleting programs or buffers is not necessary. // When context lost happens, deleting programs or buffers is not necessary.
// However, it is not assumed that reset is called only when context lost happens. // However, it is not assumed that reset is called only when context lost happens.
// Let's delete them explicitly. // Let's delete them explicitly.
if s.program != zeroProgram { if s.programs == nil {
context.deleteProgram(s.program) s.programs = map[programKey]program{}
} else {
for k, p := range s.programs {
context.deleteProgram(p)
delete(s.programs, k)
}
} }
// On browsers (at least Chrome), buffers are already detached from the context // On browsers (at least Chrome), buffers are already detached from the context
@ -176,24 +186,39 @@ func (s *openGLState) reset(context *context) error {
} }
} }
shaderVertexModelviewNative, err := context.newShader(vertexShader, shaderStr(shaderVertexModelview)) shaderVertexModelviewNative, err := context.newShader(vertexShader, vertexShaderStr())
if err != nil { if err != nil {
panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err)) panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err))
} }
defer context.deleteShader(shaderVertexModelviewNative) defer context.deleteShader(shaderVertexModelviewNative)
shaderFragmentColorMatrixNative, err := context.newShader(fragmentShader, shaderStr(shaderFragmentColorMatrix)) for _, a := range []graphics.Address{
if err != nil { graphics.AddressClampToZero,
panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err)) graphics.AddressRepeat,
} } {
defer context.deleteShader(shaderFragmentColorMatrixNative) for _, f := range []graphics.Filter{
graphics.FilterNearest,
graphics.FilterLinear,
graphics.FilterScreen,
} {
shaderFragmentColorMatrixNative, err := context.newShader(fragmentShader, fragmentShaderStr(f, a))
if err != nil {
panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err))
}
defer context.deleteShader(shaderFragmentColorMatrixNative)
s.program, err = context.newProgram([]shader{ program, err := context.newProgram([]shader{
shaderVertexModelviewNative, shaderVertexModelviewNative,
shaderFragmentColorMatrixNative, shaderFragmentColorMatrixNative,
}) })
if err != nil { if err != nil {
return err return err
}
s.programs[programKey{
filter: f,
address: a,
}] = program
}
} }
s.arrayBuffer = theArrayBufferLayout.newArrayBuffer(context) s.arrayBuffer = theArrayBufferLayout.newArrayBuffer(context)
@ -238,7 +263,10 @@ func (d *Driver) useProgram(mode graphics.CompositeMode, colorM *affine.ColorM,
d.context.blendFunc(mode) d.context.blendFunc(mode)
program := d.state.program program := d.state.programs[programKey{
filter: filter,
address: address,
}]
if d.state.lastProgram != program { if d.state.lastProgram != program {
d.context.useProgram(program) d.context.useProgram(program)
if d.state.lastProgram != zeroProgram { if d.state.lastProgram != zeroProgram {
@ -291,15 +319,6 @@ func (d *Driver) useProgram(mode graphics.CompositeMode, colorM *affine.ColorM,
d.state.lastSourceHeight = sh d.state.lastSourceHeight = sh
} }
if d.state.lastFilter == nil || *d.state.lastFilter != filter {
d.context.uniformInt(program, "filter_type", int(filter))
d.state.lastFilter = &filter
}
if d.state.lastAddress == nil || *d.state.lastAddress != address {
d.context.uniformInt(program, "address", int(address))
d.state.lastAddress = &address
}
if filter == graphics.FilterScreen { if filter == graphics.FilterScreen {
scale := float32(dstW) / float32(srcW) scale := float32(dstW) / float32(srcW)
d.context.uniformFloat(program, "scale", scale) d.context.uniformFloat(program, "scale", scale)

View File

@ -22,13 +22,6 @@ import (
"github.com/hajimehoshi/ebiten/internal/graphics" "github.com/hajimehoshi/ebiten/internal/graphics"
) )
type shaderID int
const (
shaderVertexModelview shaderID = iota
shaderFragmentColorMatrix
)
// glslReservedKeywords is a set of reserved keywords that cannot be used as an indentifier on some environments. // glslReservedKeywords is a set of reserved keywords that cannot be used as an indentifier on some environments.
// See https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.60.pdf. // See https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.60.pdf.
var glslReservedKeywords = map[string]struct{}{ var glslReservedKeywords = map[string]struct{}{
@ -62,26 +55,43 @@ func checkGLSL(src string) {
} }
} }
func shaderStr(id shaderID) string { func vertexShaderStr() string {
src := "" src := shaderStrVertex
switch id { checkGLSL(src)
case shaderVertexModelview: return src
src = shaderStrVertex }
case shaderFragmentColorMatrix:
replaces := map[string]string{ func fragmentShaderStr(filter graphics.Filter, address graphics.Address) string {
"{{.FilterNearest}}": fmt.Sprintf("%d", graphics.FilterNearest), replaces := map[string]string{
"{{.FilterLinear}}": fmt.Sprintf("%d", graphics.FilterLinear), "{{.AddressClampToZero}}": fmt.Sprintf("%d", graphics.AddressClampToZero),
"{{.FilterScreen}}": fmt.Sprintf("%d", graphics.FilterScreen), "{{.AddressRepeat}}": fmt.Sprintf("%d", graphics.AddressRepeat),
"{{.AddressClampToZero}}": fmt.Sprintf("%d", graphics.AddressClampToZero),
"{{.AddressRepeat}}": fmt.Sprintf("%d", graphics.AddressRepeat),
}
src = shaderStrFragment
for k, v := range replaces {
src = strings.Replace(src, k, v, -1)
}
default:
panic(fmt.Sprintf("opengl: invalid shader id: %d", id))
} }
src := shaderStrFragment
for k, v := range replaces {
src = strings.Replace(src, k, v, -1)
}
var defs []string
switch filter {
case graphics.FilterNearest:
defs = append(defs, "#define FILTER_NEAREST")
case graphics.FilterLinear:
defs = append(defs, "#define FILTER_LINEAR")
case graphics.FilterScreen:
defs = append(defs, "#define FILTER_SCREEN")
default:
panic(fmt.Sprintf("opengl: invalid filter: %d", filter))
}
switch address {
case graphics.AddressClampToZero:
defs = append(defs, "#define ADDRESS_CLAMP_TO_ZERO")
case graphics.AddressRepeat:
defs = append(defs, "#define ADDRESS_REPEAT")
default:
panic(fmt.Sprintf("opengl: invalid address: %d", address))
}
src = strings.Replace(src, "{{.Definitions}}", strings.Join(defs, "\n"), -1)
checkGLSL(src) checkGLSL(src)
return src return src
@ -121,19 +131,13 @@ precision mediump float;
#define highp #define highp
#endif #endif
#define FILTER_NEAREST ({{.FilterNearest}}) {{.Definitions}}
#define FILTER_LINEAR ({{.FilterLinear}})
#define FILTER_SCREEN ({{.FilterScreen}})
#define ADDRESS_CLAMP_TO_ZERO ({{.AddressClampToZero}})
#define ADDRESS_REPEAT ({{.AddressRepeat}})
uniform sampler2D texture; uniform sampler2D texture;
uniform mat4 color_matrix_body; uniform mat4 color_matrix_body;
uniform vec4 color_matrix_translation; uniform vec4 color_matrix_translation;
uniform int filter_type;
uniform highp vec2 source_size; uniform highp vec2 source_size;
uniform int address;
#if defined(FILTER_SCREEN) #if defined(FILTER_SCREEN)
uniform highp float scale; uniform highp float scale;
@ -164,17 +168,16 @@ highp float floorMod(highp float x, highp float y) {
return x - y * floor(x/y); return x - y * floor(x/y);
} }
highp vec2 adjustTexelByAddress(highp vec2 p, highp vec4 tex_region, int address) { highp vec2 adjustTexelByAddress(highp vec2 p, highp vec4 tex_region) {
if (address == ADDRESS_CLAMP_TO_ZERO) { #if defined(ADDRESS_CLAMP_TO_ZERO)
return p; return p;
} #endif
if (address == ADDRESS_REPEAT) {
highp vec2 o = vec2(tex_region[0], tex_region[1]); #if defined(ADDRESS_REPEAT)
highp vec2 size = vec2(tex_region[2] - tex_region[0], tex_region[3] - tex_region[1]); highp vec2 o = vec2(tex_region[0], tex_region[1]);
return vec2(floorMod((p.x - o.x), size.x) + o.x, floorMod((p.y - o.y), size.y) + o.y); highp vec2 size = vec2(tex_region[2] - tex_region[0], tex_region[3] - tex_region[1]);
} return vec2(floorMod((p.x - o.x), size.x) + o.x, floorMod((p.y - o.y), size.y) + o.y);
// Not reached. #endif
return vec2(0.0);
} }
void main(void) { void main(void) {
@ -183,65 +186,69 @@ void main(void) {
vec4 color; vec4 color;
if (filter_type == FILTER_NEAREST) { #if defined(FILTER_NEAREST)
pos = adjustTexelByAddress(pos, varying_tex_region, address); pos = adjustTexelByAddress(pos, varying_tex_region);
color = texture2D(texture, pos); color = texture2D(texture, pos);
if (pos.x < varying_tex_region[0] || if (pos.x < varying_tex_region[0] ||
pos.y < varying_tex_region[1] || pos.y < varying_tex_region[1] ||
(varying_tex_region[2] - texel_size.x / 512.0) <= pos.x || (varying_tex_region[2] - texel_size.x / 512.0) <= pos.x ||
(varying_tex_region[3] - texel_size.y / 512.0) <= pos.y) { (varying_tex_region[3] - texel_size.y / 512.0) <= pos.y) {
color = vec4(0, 0, 0, 0); color = vec4(0, 0, 0, 0);
}
} else if (filter_type == FILTER_LINEAR) {
highp vec2 p0 = pos - texel_size / 2.0;
highp vec2 p1 = pos + texel_size / 2.0;
p1 = adjustTexel(p0, p1);
p0 = adjustTexelByAddress(p0, varying_tex_region, address);
p1 = adjustTexelByAddress(p1, varying_tex_region, address);
vec4 c0 = texture2D(texture, p0);
vec4 c1 = texture2D(texture, vec2(p1.x, p0.y));
vec4 c2 = texture2D(texture, vec2(p0.x, p1.y));
vec4 c3 = texture2D(texture, p1);
if (p0.x < varying_tex_region[0]) {
c0 = vec4(0, 0, 0, 0);
c2 = vec4(0, 0, 0, 0);
}
if (p0.y < varying_tex_region[1]) {
c0 = vec4(0, 0, 0, 0);
c1 = vec4(0, 0, 0, 0);
}
if ((varying_tex_region[2] - texel_size.x / 512.0) <= p1.x) {
c1 = vec4(0, 0, 0, 0);
c3 = vec4(0, 0, 0, 0);
}
if ((varying_tex_region[3] - texel_size.y / 512.0) <= p1.y) {
c2 = vec4(0, 0, 0, 0);
c3 = vec4(0, 0, 0, 0);
}
vec2 rate = fract(p0 * source_size);
color = mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y);
} else if (filter_type == FILTER_SCREEN) {
highp vec2 p0 = pos - texel_size / 2.0 / scale;
highp vec2 p1 = pos + texel_size / 2.0 / scale;
p1 = adjustTexel(p0, p1);
vec4 c0 = texture2D(texture, p0);
vec4 c1 = texture2D(texture, vec2(p1.x, p0.y));
vec4 c2 = texture2D(texture, vec2(p0.x, p1.y));
vec4 c3 = texture2D(texture, p1);
// Texels must be in the source rect, so it is not necessary to check that like linear filter.
vec2 rateCenter = vec2(1.0, 1.0) - texel_size / 2.0 / scale;
vec2 rate = clamp(((fract(p0 * source_size) - rateCenter) * scale) + rateCenter, 0.0, 1.0);
color = mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y);
} else {
// Not reached.
discard;
} }
#endif
#if defined(FILTER_LINEAR)
highp vec2 p0 = pos - texel_size / 2.0;
highp vec2 p1 = pos + texel_size / 2.0;
p1 = adjustTexel(p0, p1);
p0 = adjustTexelByAddress(p0, varying_tex_region);
p1 = adjustTexelByAddress(p1, varying_tex_region);
vec4 c0 = texture2D(texture, p0);
vec4 c1 = texture2D(texture, vec2(p1.x, p0.y));
vec4 c2 = texture2D(texture, vec2(p0.x, p1.y));
vec4 c3 = texture2D(texture, p1);
if (p0.x < varying_tex_region[0]) {
c0 = vec4(0, 0, 0, 0);
c2 = vec4(0, 0, 0, 0);
}
if (p0.y < varying_tex_region[1]) {
c0 = vec4(0, 0, 0, 0);
c1 = vec4(0, 0, 0, 0);
}
if ((varying_tex_region[2] - texel_size.x / 512.0) <= p1.x) {
c1 = vec4(0, 0, 0, 0);
c3 = vec4(0, 0, 0, 0);
}
if ((varying_tex_region[3] - texel_size.y / 512.0) <= p1.y) {
c2 = vec4(0, 0, 0, 0);
c3 = vec4(0, 0, 0, 0);
}
vec2 rate = fract(p0 * source_size);
color = mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y);
#endif
#if defined(FILTER_SCREEN)
highp vec2 p0 = pos - texel_size / 2.0 / scale;
highp vec2 p1 = pos + texel_size / 2.0 / scale;
// Prevent this variable from being optimized out.
p0 += varying_tex_region.xy - varying_tex_region.xy;
p1 = adjustTexel(p0, p1);
vec4 c0 = texture2D(texture, p0);
vec4 c1 = texture2D(texture, vec2(p1.x, p0.y));
vec4 c2 = texture2D(texture, vec2(p0.x, p1.y));
vec4 c3 = texture2D(texture, p1);
// Texels must be in the source rect, so it is not necessary to check that like linear filter.
vec2 rateCenter = vec2(1.0, 1.0) - texel_size / 2.0 / scale;
vec2 rate = clamp(((fract(p0 * source_size) - rateCenter) * scale) + rateCenter, 0.0, 1.0);
color = mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y);
#endif
// Un-premultiply alpha // Un-premultiply alpha
if (0.0 < color.a) { if (0.0 < color.a) {