From 7e50ae39c9326fcdefbe3ddf625cee8c219441f5 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Mon, 24 Dec 2018 16:02:08 +0900 Subject: [PATCH] graphicsdriver/opengl: Refactoring: Filter as a uniform value --- internal/graphicsdriver/opengl/program.go | 77 +++-------- internal/graphicsdriver/opengl/shader.go | 151 +++++++++++----------- 2 files changed, 95 insertions(+), 133 deletions(-) diff --git a/internal/graphicsdriver/opengl/program.go b/internal/graphicsdriver/opengl/program.go index 3f160abbc..0fdb41b74 100644 --- a/internal/graphicsdriver/opengl/program.go +++ b/internal/graphicsdriver/opengl/program.go @@ -119,13 +119,8 @@ type openGLState struct { // elementArrayBuffer is OpenGL's element array buffer (indices data). elementArrayBuffer buffer - // programNearest is OpenGL's program for rendering a texture with nearest filter. - programNearest program - - // programLinear is OpenGL's program for rendering a texture with linear filter. - programLinear program - - programScreen program + // program is OpenGL's program for rendering a texture. + program program lastProgram program lastViewportWidth int @@ -134,6 +129,7 @@ type openGLState struct { lastColorMatrixTranslation []float32 lastSourceWidth int lastSourceHeight int + lastFilter *graphics.Filter source *Image destination *Image @@ -162,18 +158,13 @@ func (s *openGLState) reset(context *context) error { s.lastColorMatrixTranslation = nil s.lastSourceWidth = 0 s.lastSourceHeight = 0 + s.lastFilter = nil // 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. // Let's delete them explicitly. - if s.programNearest != zeroProgram { - context.deleteProgram(s.programNearest) - } - if s.programLinear != zeroProgram { - context.deleteProgram(s.programLinear) - } - if s.programScreen != zeroProgram { - context.deleteProgram(s.programScreen) + if s.program != zeroProgram { + context.deleteProgram(s.program) } // On browsers (at least Chrome), buffers are already detached from the context @@ -193,43 +184,15 @@ func (s *openGLState) reset(context *context) error { } defer context.deleteShader(shaderVertexModelviewNative) - shaderFragmentNearestNative, err := context.newShader(fragmentShader, shaderStr(shaderFragmentNearest)) + shaderFragmentColorMatrixNative, err := context.newShader(fragmentShader, shaderStr(shaderFragmentColorMatrix)) if err != nil { panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err)) } - defer context.deleteShader(shaderFragmentNearestNative) + defer context.deleteShader(shaderFragmentColorMatrixNative) - shaderFragmentLinearNative, err := context.newShader(fragmentShader, shaderStr(shaderFragmentLinear)) - if err != nil { - panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err)) - } - defer context.deleteShader(shaderFragmentLinearNative) - - shaderFragmentScreenNative, err := context.newShader(fragmentShader, shaderStr(shaderFragmentScreen)) - if err != nil { - panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err)) - } - defer context.deleteShader(shaderFragmentScreenNative) - - s.programNearest, err = context.newProgram([]shader{ + s.program, err = context.newProgram([]shader{ shaderVertexModelviewNative, - shaderFragmentNearestNative, - }) - if err != nil { - return err - } - - s.programLinear, err = context.newProgram([]shader{ - shaderVertexModelviewNative, - shaderFragmentLinearNative, - }) - if err != nil { - return err - } - - s.programScreen, err = context.newProgram([]shader{ - shaderVertexModelviewNative, - shaderFragmentScreenNative, + shaderFragmentColorMatrixNative, }) if err != nil { return err @@ -277,18 +240,7 @@ func (d *Driver) useProgram(mode graphics.CompositeMode, colorM *affine.ColorM, d.context.blendFunc(mode) - var program program - switch filter { - case graphics.FilterNearest: - program = d.state.programNearest - case graphics.FilterLinear: - program = d.state.programLinear - case graphics.FilterScreen: - program = d.state.programScreen - default: - panic("not reached") - } - + program := d.state.program if d.state.lastProgram != program { d.context.useProgram(program) if d.state.lastProgram != zeroProgram { @@ -341,7 +293,12 @@ func (d *Driver) useProgram(mode graphics.CompositeMode, colorM *affine.ColorM, d.state.lastSourceHeight = sh } - if program == d.state.programScreen { + if d.state.lastFilter == nil || *d.state.lastFilter != filter { + d.context.uniformInt(program, "filter", int(filter)) + d.state.lastFilter = &filter + } + + if filter == graphics.FilterScreen { scale := float32(dstW) / float32(srcW) d.context.uniformFloat(program, "scale", scale) } diff --git a/internal/graphicsdriver/opengl/shader.go b/internal/graphicsdriver/opengl/shader.go index 7eb07b021..bde2c7daa 100644 --- a/internal/graphicsdriver/opengl/shader.go +++ b/internal/graphicsdriver/opengl/shader.go @@ -15,34 +15,37 @@ package opengl import ( + "fmt" "strings" + + "github.com/hajimehoshi/ebiten/internal/graphics" ) type shaderID int const ( shaderVertexModelview shaderID = iota - shaderFragmentNearest - shaderFragmentLinear - shaderFragmentScreen + shaderFragmentColorMatrix ) func shaderStr(id shaderID) string { - if id == shaderVertexModelview { - return shaderStrVertex - } - defs := []string{} switch id { - case shaderFragmentNearest: - defs = append(defs, "#define FILTER_NEAREST") - case shaderFragmentLinear: - defs = append(defs, "#define FILTER_LINEAR") - case shaderFragmentScreen: - defs = append(defs, "#define FILTER_SCREEN") + case shaderVertexModelview: + return shaderStrVertex + case shaderFragmentColorMatrix: + replaces := map[string]string{ + "{{.FilterNearest}}": fmt.Sprintf("%d", graphics.FilterNearest), + "{{.FilterLinear}}": fmt.Sprintf("%d", graphics.FilterLinear), + "{{.FilterScreen}}": fmt.Sprintf("%d", graphics.FilterScreen), + } + src := shaderStrFragment + for k, v := range replaces { + src = strings.Replace(src, k, v, -1) + } + return src default: panic("not reached") } - return strings.Replace(shaderStrFragment, "{{Definitions}}", strings.Join(defs, "\n"), -1) } const ( @@ -79,12 +82,15 @@ precision mediump float; #define highp #endif -{{Definitions}} +#define FILTER_NEAREST ({{.FilterNearest}}) +#define FILTER_LINEAR ({{.FilterLinear}}) +#define FILTER_SCREEN ({{.FilterScreen}}) uniform sampler2D texture; uniform mat4 color_matrix_body; uniform vec4 color_matrix_translation; +uniform int filter; uniform highp vec2 source_size; #if defined(FILTER_SCREEN) @@ -113,65 +119,64 @@ void main(void) { highp vec2 pos = varying_tex; highp vec2 texel_size = 1.0 / source_size; -#if defined(FILTER_NEAREST) - vec4 color = texture2D(texture, pos); - if (pos.x < varying_tex_region[0] || - pos.y < varying_tex_region[1] || - (varying_tex_region[2] - texel_size.x / 512.0) <= pos.x || - (varying_tex_region[3] - texel_size.y / 512.0) <= pos.y) { - color = vec4(0, 0, 0, 0); + vec4 color; + + if (filter == FILTER_NEAREST) { + color = texture2D(texture, pos); + if (pos.x < varying_tex_region[0] || + pos.y < varying_tex_region[1] || + (varying_tex_region[2] - texel_size.x / 512.0) <= pos.x || + (varying_tex_region[3] - texel_size.y / 512.0) <= pos.y) { + color = vec4(0, 0, 0, 0); + } + } else if (filter == FILTER_LINEAR) { + highp vec2 p0 = pos - texel_size / 2.0; + highp vec2 p1 = pos + texel_size / 2.0; + + 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); + 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 == 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); - - 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); - vec4 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); - vec4 color = mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y); -#endif // Un-premultiply alpha if (0.0 < color.a) {