graphicsdriver/opengl: Refactoring: Filter as a uniform value

This commit is contained in:
Hajime Hoshi 2018-12-24 16:02:08 +09:00
parent 529cd3376b
commit 7e50ae39c9
2 changed files with 95 additions and 133 deletions

View File

@ -119,13 +119,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
// programNearest is OpenGL's program for rendering a texture with nearest filter. // program is OpenGL's program for rendering a texture.
programNearest program program program
// programLinear is OpenGL's program for rendering a texture with linear filter.
programLinear program
programScreen program
lastProgram program lastProgram program
lastViewportWidth int lastViewportWidth int
@ -134,6 +129,7 @@ type openGLState struct {
lastColorMatrixTranslation []float32 lastColorMatrixTranslation []float32
lastSourceWidth int lastSourceWidth int
lastSourceHeight int lastSourceHeight int
lastFilter *graphics.Filter
source *Image source *Image
destination *Image destination *Image
@ -162,18 +158,13 @@ func (s *openGLState) reset(context *context) error {
s.lastColorMatrixTranslation = nil s.lastColorMatrixTranslation = nil
s.lastSourceWidth = 0 s.lastSourceWidth = 0
s.lastSourceHeight = 0 s.lastSourceHeight = 0
s.lastFilter = nil
// 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.programNearest != zeroProgram { if s.program != zeroProgram {
context.deleteProgram(s.programNearest) context.deleteProgram(s.program)
}
if s.programLinear != zeroProgram {
context.deleteProgram(s.programLinear)
}
if s.programScreen != zeroProgram {
context.deleteProgram(s.programScreen)
} }
// On browsers (at least Chrome), buffers are already detached from the context // 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) defer context.deleteShader(shaderVertexModelviewNative)
shaderFragmentNearestNative, err := context.newShader(fragmentShader, shaderStr(shaderFragmentNearest)) shaderFragmentColorMatrixNative, err := context.newShader(fragmentShader, shaderStr(shaderFragmentColorMatrix))
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(shaderFragmentNearestNative) defer context.deleteShader(shaderFragmentColorMatrixNative)
shaderFragmentLinearNative, err := context.newShader(fragmentShader, shaderStr(shaderFragmentLinear)) s.program, err = context.newProgram([]shader{
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{
shaderVertexModelviewNative, shaderVertexModelviewNative,
shaderFragmentNearestNative, shaderFragmentColorMatrixNative,
})
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,
}) })
if err != nil { if err != nil {
return err return err
@ -277,18 +240,7 @@ func (d *Driver) useProgram(mode graphics.CompositeMode, colorM *affine.ColorM,
d.context.blendFunc(mode) d.context.blendFunc(mode)
var program program program := d.state.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")
}
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 {
@ -341,7 +293,12 @@ func (d *Driver) useProgram(mode graphics.CompositeMode, colorM *affine.ColorM,
d.state.lastSourceHeight = sh 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) scale := float32(dstW) / float32(srcW)
d.context.uniformFloat(program, "scale", scale) d.context.uniformFloat(program, "scale", scale)
} }

View File

@ -15,34 +15,37 @@
package opengl package opengl
import ( import (
"fmt"
"strings" "strings"
"github.com/hajimehoshi/ebiten/internal/graphics"
) )
type shaderID int type shaderID int
const ( const (
shaderVertexModelview shaderID = iota shaderVertexModelview shaderID = iota
shaderFragmentNearest shaderFragmentColorMatrix
shaderFragmentLinear
shaderFragmentScreen
) )
func shaderStr(id shaderID) string { func shaderStr(id shaderID) string {
if id == shaderVertexModelview {
return shaderStrVertex
}
defs := []string{}
switch id { switch id {
case shaderFragmentNearest: case shaderVertexModelview:
defs = append(defs, "#define FILTER_NEAREST") return shaderStrVertex
case shaderFragmentLinear: case shaderFragmentColorMatrix:
defs = append(defs, "#define FILTER_LINEAR") replaces := map[string]string{
case shaderFragmentScreen: "{{.FilterNearest}}": fmt.Sprintf("%d", graphics.FilterNearest),
defs = append(defs, "#define FILTER_SCREEN") "{{.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: default:
panic("not reached") panic("not reached")
} }
return strings.Replace(shaderStrFragment, "{{Definitions}}", strings.Join(defs, "\n"), -1)
} }
const ( const (
@ -79,12 +82,15 @@ precision mediump float;
#define highp #define highp
#endif #endif
{{Definitions}} #define FILTER_NEAREST ({{.FilterNearest}})
#define FILTER_LINEAR ({{.FilterLinear}})
#define FILTER_SCREEN ({{.FilterScreen}})
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;
uniform highp vec2 source_size; uniform highp vec2 source_size;
#if defined(FILTER_SCREEN) #if defined(FILTER_SCREEN)
@ -113,65 +119,64 @@ void main(void) {
highp vec2 pos = varying_tex; highp vec2 pos = varying_tex;
highp vec2 texel_size = 1.0 / source_size; highp vec2 texel_size = 1.0 / source_size;
#if defined(FILTER_NEAREST) vec4 color;
vec4 color = texture2D(texture, pos);
if (pos.x < varying_tex_region[0] || if (filter == FILTER_NEAREST) {
pos.y < varying_tex_region[1] || color = texture2D(texture, pos);
(varying_tex_region[2] - texel_size.x / 512.0) <= pos.x || if (pos.x < varying_tex_region[0] ||
(varying_tex_region[3] - texel_size.y / 512.0) <= pos.y) { pos.y < varying_tex_region[1] ||
color = vec4(0, 0, 0, 0); (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 // Un-premultiply alpha
if (0.0 < color.a) { if (0.0 < color.a) {