graphics: Separate GLSL programs for filters (#461)

This commit is contained in:
Hajime Hoshi 2017-12-21 00:24:27 +09:00
parent a8914ff245
commit 8e72906f3d
2 changed files with 113 additions and 73 deletions

View File

@ -114,14 +114,16 @@ type openGLState struct {
// elementArrayBuffer is OpenGL's element array buffer (indices data). // elementArrayBuffer is OpenGL's element array buffer (indices data).
elementArrayBuffer opengl.Buffer elementArrayBuffer opengl.Buffer
// programTexture is OpenGL's program for rendering a texture. // programNearest is OpenGL's program for rendering a texture with nearest filter.
programTexture opengl.Program programNearest opengl.Program
// programLinear is OpenGL's program for rendering a texture with linear filter.
programLinear opengl.Program
lastProgram opengl.Program lastProgram opengl.Program
lastProjectionMatrix []float32 lastProjectionMatrix []float32
lastColorMatrix []float32 lastColorMatrix []float32
lastColorMatrixTranslation []float32 lastColorMatrixTranslation []float32
lastFilterType Filter
lastSourceWidth int lastSourceWidth int
lastSourceHeight int lastSourceHeight int
} }
@ -153,15 +155,17 @@ func (s *openGLState) reset() error {
s.lastProjectionMatrix = nil s.lastProjectionMatrix = nil
s.lastColorMatrix = nil s.lastColorMatrix = nil
s.lastColorMatrixTranslation = nil s.lastColorMatrixTranslation = nil
s.lastFilterType = FilterNone
s.lastSourceWidth = 0 s.lastSourceWidth = 0
s.lastSourceHeight = 0 s.lastSourceHeight = 0
// 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.programTexture != zeroProgram { if s.programNearest != zeroProgram {
opengl.GetContext().DeleteProgram(s.programTexture) opengl.GetContext().DeleteProgram(s.programNearest)
}
if s.programLinear != zeroProgram {
opengl.GetContext().DeleteProgram(s.programLinear)
} }
if s.arrayBuffer != zeroBuffer { if s.arrayBuffer != zeroBuffer {
opengl.GetContext().DeleteBuffer(s.arrayBuffer) opengl.GetContext().DeleteBuffer(s.arrayBuffer)
@ -176,15 +180,29 @@ func (s *openGLState) reset() error {
} }
defer opengl.GetContext().DeleteShader(shaderVertexModelviewNative) defer opengl.GetContext().DeleteShader(shaderVertexModelviewNative)
shaderFragmentTextureNative, err := opengl.GetContext().NewShader(opengl.FragmentShader, shader(shaderFragmentTexture)) shaderFragmentNearestNative, err := opengl.GetContext().NewShader(opengl.FragmentShader, shader(shaderFragmentNearest))
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 opengl.GetContext().DeleteShader(shaderFragmentTextureNative) defer opengl.GetContext().DeleteShader(shaderFragmentNearestNative)
s.programTexture, err = opengl.GetContext().NewProgram([]opengl.Shader{ shaderFragmentLinearNative, err := opengl.GetContext().NewShader(opengl.FragmentShader, shader(shaderFragmentLinear))
if err != nil {
panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err))
}
defer opengl.GetContext().DeleteShader(shaderFragmentLinearNative)
s.programNearest, err = opengl.GetContext().NewProgram([]opengl.Shader{
shaderVertexModelviewNative, shaderVertexModelviewNative,
shaderFragmentTextureNative, shaderFragmentNearestNative,
})
if err != nil {
return err
}
s.programLinear, err = opengl.GetContext().NewProgram([]opengl.Shader{
shaderVertexModelviewNative,
shaderFragmentLinearNative,
}) })
if err != nil { if err != nil {
return err return err
@ -222,7 +240,16 @@ func areSameFloat32Array(a, b []float32) bool {
// useProgram uses the program (programTexture). // useProgram uses the program (programTexture).
func (s *openGLState) useProgram(proj []float32, texture opengl.Texture, sourceWidth, sourceHeight int, colorM affine.ColorM, filter Filter) { func (s *openGLState) useProgram(proj []float32, texture opengl.Texture, sourceWidth, sourceHeight int, colorM affine.ColorM, filter Filter) {
c := opengl.GetContext() c := opengl.GetContext()
program := s.programTexture
var program opengl.Program
switch filter {
case FilterNearest:
program = s.programNearest
case FilterLinear:
program = s.programLinear
default:
panic("not reached")
}
if s.lastProgram != program { if s.lastProgram != program {
c.UseProgram(program) c.UseProgram(program)
@ -231,7 +258,7 @@ func (s *openGLState) useProgram(proj []float32, texture opengl.Texture, sourceW
} }
theArrayBufferLayout.enable(program) theArrayBufferLayout.enable(program)
s.lastProgram = s.programTexture s.lastProgram = program
s.lastProjectionMatrix = nil s.lastProjectionMatrix = nil
s.lastColorMatrix = nil s.lastColorMatrix = nil
s.lastColorMatrixTranslation = nil s.lastColorMatrixTranslation = nil
@ -279,16 +306,13 @@ func (s *openGLState) useProgram(proj []float32, texture opengl.Texture, sourceW
copy(s.lastColorMatrixTranslation, colorMatrixTranslation) copy(s.lastColorMatrixTranslation, colorMatrixTranslation)
} }
if s.lastFilterType != filter { if program == s.programLinear {
c.UniformInt(program, "filter_type", int(filter)) if s.lastSourceWidth != sourceWidth || s.lastSourceHeight != sourceHeight {
s.lastFilterType = filter c.UniformFloats(program, "source_size",
} []float32{float32(sourceWidth), float32(sourceHeight)})
s.lastSourceWidth = sourceWidth
if s.lastSourceWidth != sourceWidth || s.lastSourceHeight != sourceHeight { s.lastSourceHeight = sourceHeight
c.UniformFloats(program, "source_size", }
[]float32{float32(sourceWidth), float32(sourceHeight)})
s.lastSourceWidth = sourceWidth
s.lastSourceHeight = sourceHeight
} }
// We don't have to call gl.ActiveTexture here: GL_TEXTURE0 is the default active texture // We don't have to call gl.ActiveTexture here: GL_TEXTURE0 is the default active texture

View File

@ -14,19 +14,34 @@
package graphics package graphics
import (
"strings"
)
type shaderID int type shaderID int
const ( const (
shaderVertexModelview shaderID = iota shaderVertexModelview shaderID = iota
shaderFragmentTexture shaderFragmentNearest
shaderFragmentLinear
) )
func shader(id shaderID) string { func shader(id shaderID) string {
return shaders[id] 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")
}
return strings.Replace(shaderStrFragment, "{{Definitions}}", strings.Join(defs, "\n"), -1)
} }
var shaders = map[shaderID]string{ const (
shaderVertexModelview: ` shaderStrVertex = `
uniform mat4 projection_matrix; uniform mat4 projection_matrix;
attribute vec2 vertex; attribute vec2 vertex;
attribute vec4 tex_coord; attribute vec4 tex_coord;
@ -50,8 +65,8 @@ void main(void) {
); );
gl_Position = projection_matrix * geo_matrix * vec4(vertex, 0, 1); gl_Position = projection_matrix * geo_matrix * vec4(vertex, 0, 1);
} }
`, `
shaderFragmentTexture: ` shaderStrFragment = `
#if defined(GL_ES) #if defined(GL_ES)
precision mediump float; precision mediump float;
#else #else
@ -60,11 +75,15 @@ precision mediump float;
#define highp #define highp
#endif #endif
{{Definitions}}
uniform sampler2D texture; uniform sampler2D texture;
uniform mat4 color_matrix; uniform mat4 color_matrix;
uniform vec4 color_matrix_translation; uniform vec4 color_matrix_translation;
#if defined(FILTER_LINEAR)
uniform highp vec2 source_size; uniform highp vec2 source_size;
uniform int filter_type; #endif
varying highp vec2 varying_tex_coord; varying highp vec2 varying_tex_coord;
varying highp vec2 varying_tex_coord_min; varying highp vec2 varying_tex_coord_min;
@ -80,51 +99,48 @@ highp vec2 roundTexel(highp vec2 p) {
} }
void main(void) { void main(void) {
vec4 color = vec4(0, 0, 0, 0);
highp vec2 pos = roundTexel(varying_tex_coord); highp vec2 pos = roundTexel(varying_tex_coord);
if (filter_type == 1) {
// Nearest neighbor
if (varying_tex_coord_min.x <= pos.x &&
varying_tex_coord_min.y <= pos.y &&
pos.x < varying_tex_coord_max.x &&
pos.y < varying_tex_coord_max.y) {
color = texture2D(texture, pos);
}
} else if (filter_type == 2) {
// Bi-linear
highp vec2 texel_size = 1.0 / source_size;
pos -= texel_size * 0.5;
highp vec2 p0 = pos; #if defined(FILTER_NEAREST)
highp vec2 p1 = pos + texel_size; vec4 color = texture2D(texture, pos);
vec4 c0 = texture2D(texture, p0); if (pos.x < varying_tex_coord_min.x ||
vec4 c1 = texture2D(texture, vec2(p1.x, p0.y)); pos.y < varying_tex_coord_min.y ||
vec4 c2 = texture2D(texture, vec2(p0.x, p1.y)); varying_tex_coord_max.x <= pos.x ||
vec4 c3 = texture2D(texture, p1); varying_tex_coord_max.y <= pos.y) {
if (p0.x < varying_tex_coord_min.x) { color = vec4(0, 0, 0, 0);
c0 = vec4(0, 0, 0, 0);
c2 = vec4(0, 0, 0, 0);
}
if (p0.y < varying_tex_coord_min.y) {
c0 = vec4(0, 0, 0, 0);
c1 = vec4(0, 0, 0, 0);
}
if (varying_tex_coord_max.x <= p1.x) {
c1 = vec4(0, 0, 0, 0);
c3 = vec4(0, 0, 0, 0);
}
if (varying_tex_coord_max.y <= p1.y) {
c2 = vec4(0, 0, 0, 0);
c3 = vec4(0, 0, 0, 0);
}
vec2 rate = fract(pos * source_size);
color = mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y);
} else {
// Error
color = vec4(1, 0, 0, 1);
} }
#endif
#if defined(FILTER_LINEAR)
highp vec2 texel_size = 1.0 / source_size;
pos -= texel_size * 0.5;
highp vec2 p0 = pos;
highp vec2 p1 = pos + texel_size;
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_coord_min.x) {
c0 = vec4(0, 0, 0, 0);
c2 = vec4(0, 0, 0, 0);
}
if (p0.y < varying_tex_coord_min.y) {
c0 = vec4(0, 0, 0, 0);
c1 = vec4(0, 0, 0, 0);
}
if (varying_tex_coord_max.x <= p1.x) {
c1 = vec4(0, 0, 0, 0);
c3 = vec4(0, 0, 0, 0);
}
if (varying_tex_coord_max.y <= p1.y) {
c2 = vec4(0, 0, 0, 0);
c3 = vec4(0, 0, 0, 0);
}
vec2 rate = fract(pos * source_size);
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) {
@ -138,5 +154,5 @@ void main(void) {
gl_FragColor = color; gl_FragColor = color;
} }
`, `
} )