graphics: Add 'screen' filter for fast rendering (#509)

This commit is contained in:
Hajime Hoshi 2018-02-22 11:46:46 +09:00
parent 092cb2f3f6
commit 591e0ad995
9 changed files with 90 additions and 28 deletions

View File

@ -31,6 +31,9 @@ const (
// FilterLinear represents linear filter // FilterLinear represents linear filter
FilterLinear Filter = Filter(graphics.FilterLinear) FilterLinear Filter = Filter(graphics.FilterLinear)
// filterScreen represents a special filter for screen. Inner usage only.
filterScreen Filter = Filter(graphics.FilterScreen)
) )
// CompositeMode represents Porter-Duff composition mode. // CompositeMode represents Porter-Duff composition mode.

View File

@ -15,8 +15,6 @@
package ebiten package ebiten
import ( import (
"math"
"github.com/hajimehoshi/ebiten/internal/clock" "github.com/hajimehoshi/ebiten/internal/clock"
"github.com/hajimehoshi/ebiten/internal/hooks" "github.com/hajimehoshi/ebiten/internal/hooks"
"github.com/hajimehoshi/ebiten/internal/restorable" "github.com/hajimehoshi/ebiten/internal/restorable"
@ -33,7 +31,6 @@ func newGraphicsContext(f func(*Image) error) *graphicsContext {
type graphicsContext struct { type graphicsContext struct {
f func(*Image) error f func(*Image) error
offscreen *Image offscreen *Image
offscreen2 *Image // TODO: better name
screen *Image screen *Image
initialized bool initialized bool
invalidated bool // browser only invalidated bool // browser only
@ -54,24 +51,15 @@ func (c *graphicsContext) SetSize(screenWidth, screenHeight int, screenScale flo
if c.offscreen != nil { if c.offscreen != nil {
_ = c.offscreen.Dispose() _ = c.offscreen.Dispose()
} }
if c.offscreen2 != nil {
_ = c.offscreen2.Dispose()
}
offscreen := newVolatileImage(screenWidth, screenHeight, FilterDefault) offscreen := newVolatileImage(screenWidth, screenHeight, FilterDefault)
intScreenScale := int(math.Ceil(screenScale)) w := int(float64(screenWidth) * screenScale)
w := screenWidth * intScreenScale h := int(float64(screenHeight) * screenScale)
h := screenHeight * intScreenScale
offscreen2 := newVolatileImage(w, h, FilterDefault)
w = int(float64(screenWidth) * screenScale)
h = int(float64(screenHeight) * screenScale)
ox, oy := ui.ScreenOffset() ox, oy := ui.ScreenOffset()
c.screen = newImageWithScreenFramebuffer(w, h, ox, oy) c.screen = newImageWithScreenFramebuffer(w, h, ox, oy)
_ = c.screen.Clear() _ = c.screen.Clear()
c.offscreen = offscreen c.offscreen = offscreen
c.offscreen2 = offscreen2
} }
func (c *graphicsContext) initializeIfNeeded() error { func (c *graphicsContext) initializeIfNeeded() error {
@ -117,8 +105,7 @@ func (c *graphicsContext) Update(afterFrameUpdate func()) error {
afterFrameUpdate() afterFrameUpdate()
} }
if 0 < updateCount { if 0 < updateCount {
drawWithFittingScale(c.offscreen2, c.offscreen, FilterNearest) drawWithFittingScale(c.screen, c.offscreen, filterScreen)
drawWithFittingScale(c.screen, c.offscreen2, FilterLinear)
} }
if err := restorable.ResolveStaleImages(); err != nil { if err := restorable.ResolveStaleImages(); err != nil {

View File

@ -247,12 +247,9 @@ func (c *drawImageCommand) Exec(indexOffsetInBytes int) error {
if n == 0 { if n == 0 {
return nil return nil
} }
sw, sh := c.src.Size()
sw = emath.NextPowerOf2Int(sw)
sh = emath.NextPowerOf2Int(sh)
_, dh := c.dst.Size() _, dh := c.dst.Size()
proj := f.projectionMatrix(dh) proj := f.projectionMatrix(dh)
theOpenGLState.useProgram(proj, c.src.texture.native, sw, sh, c.color, c.filter) theOpenGLState.useProgram(proj, c.src.texture.native, c.dst, c.src, c.color, c.filter)
// TODO: We should call glBindBuffer here? // TODO: We should call glBindBuffer here?
// The buffer is already bound at begin() but it is counterintuitive. // The buffer is already bound at begin() but it is counterintuitive.
opengl.GetContext().DrawElements(opengl.Triangles, 6*n, indexOffsetInBytes) opengl.GetContext().DrawElements(opengl.Triangles, 6*n, indexOffsetInBytes)

View File

@ -18,6 +18,7 @@ import (
"fmt" "fmt"
"github.com/hajimehoshi/ebiten/internal/affine" "github.com/hajimehoshi/ebiten/internal/affine"
emath "github.com/hajimehoshi/ebiten/internal/math"
"github.com/hajimehoshi/ebiten/internal/opengl" "github.com/hajimehoshi/ebiten/internal/opengl"
) )
@ -110,6 +111,8 @@ type openGLState struct {
// programLinear is OpenGL's program for rendering a texture with linear filter. // programLinear is OpenGL's program for rendering a texture with linear filter.
programLinear opengl.Program programLinear opengl.Program
programScreen opengl.Program
lastProgram opengl.Program lastProgram opengl.Program
lastProjectionMatrix []float32 lastProjectionMatrix []float32
lastColorMatrix []float32 lastColorMatrix []float32
@ -157,6 +160,9 @@ func (s *openGLState) reset() error {
if s.programLinear != zeroProgram { if s.programLinear != zeroProgram {
opengl.GetContext().DeleteProgram(s.programLinear) opengl.GetContext().DeleteProgram(s.programLinear)
} }
if s.programScreen != zeroProgram {
opengl.GetContext().DeleteProgram(s.programScreen)
}
if s.arrayBuffer != zeroBuffer { if s.arrayBuffer != zeroBuffer {
opengl.GetContext().DeleteBuffer(s.arrayBuffer) opengl.GetContext().DeleteBuffer(s.arrayBuffer)
} }
@ -182,6 +188,12 @@ func (s *openGLState) reset() error {
} }
defer opengl.GetContext().DeleteShader(shaderFragmentLinearNative) defer opengl.GetContext().DeleteShader(shaderFragmentLinearNative)
shaderFragmentScreenNative, err := opengl.GetContext().NewShader(opengl.FragmentShader, shader(shaderFragmentScreen))
if err != nil {
panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err))
}
defer opengl.GetContext().DeleteShader(shaderFragmentScreenNative)
s.programNearest, err = opengl.GetContext().NewProgram([]opengl.Shader{ s.programNearest, err = opengl.GetContext().NewProgram([]opengl.Shader{
shaderVertexModelviewNative, shaderVertexModelviewNative,
shaderFragmentNearestNative, shaderFragmentNearestNative,
@ -198,6 +210,14 @@ func (s *openGLState) reset() error {
return err return err
} }
s.programScreen, err = opengl.GetContext().NewProgram([]opengl.Shader{
shaderVertexModelviewNative,
shaderFragmentScreenNative,
})
if err != nil {
return err
}
s.arrayBuffer = theArrayBufferLayout.newArrayBuffer() s.arrayBuffer = theArrayBufferLayout.newArrayBuffer()
indices := make([]uint16, 6*maxQuads) indices := make([]uint16, 6*maxQuads)
@ -228,7 +248,7 @@ 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, dst, src *Image, colorM affine.ColorM, filter Filter) {
c := opengl.GetContext() c := opengl.GetContext()
var program opengl.Program var program opengl.Program
@ -237,6 +257,8 @@ func (s *openGLState) useProgram(proj []float32, texture opengl.Texture, sourceW
program = s.programNearest program = s.programNearest
case FilterLinear: case FilterLinear:
program = s.programLinear program = s.programLinear
case FilterScreen:
program = s.programScreen
default: default:
panic("not reached") panic("not reached")
} }
@ -287,14 +309,23 @@ func (s *openGLState) useProgram(proj []float32, texture opengl.Texture, sourceW
s.lastColorMatrixTranslation = esTranslate s.lastColorMatrixTranslation = esTranslate
} }
if program == s.programLinear { if program == s.programLinear || program == s.programScreen {
if s.lastSourceWidth != sourceWidth || s.lastSourceHeight != sourceHeight { sw, sh := src.Size()
c.UniformFloats(program, "source_size", sw = emath.NextPowerOf2Int(sw)
[]float32{float32(sourceWidth), float32(sourceHeight)}) sh = emath.NextPowerOf2Int(sh)
s.lastSourceWidth = sourceWidth
s.lastSourceHeight = sourceHeight if s.lastSourceWidth != sw || s.lastSourceHeight != sh {
c.UniformFloats(program, "source_size", []float32{float32(sw), float32(sh)})
s.lastSourceWidth = sw
s.lastSourceHeight = sh
} }
} }
if program == s.programScreen {
sw, _ := src.Size()
dw, _ := dst.Size()
scale := float32(dw) / float32(sw)
c.UniformFloat(program, "scale", scale)
}
// 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
// See also: https://www.opengl.org/sdk/docs/man2/xhtml/glActiveTexture.xml // See also: https://www.opengl.org/sdk/docs/man2/xhtml/glActiveTexture.xml

View File

@ -24,6 +24,7 @@ const (
shaderVertexModelview shaderID = iota shaderVertexModelview shaderID = iota
shaderFragmentNearest shaderFragmentNearest
shaderFragmentLinear shaderFragmentLinear
shaderFragmentScreen
) )
func shader(id shaderID) string { func shader(id shaderID) string {
@ -36,6 +37,8 @@ func shader(id shaderID) string {
defs = append(defs, "#define FILTER_NEAREST") defs = append(defs, "#define FILTER_NEAREST")
case shaderFragmentLinear: case shaderFragmentLinear:
defs = append(defs, "#define FILTER_LINEAR") defs = append(defs, "#define FILTER_LINEAR")
case shaderFragmentScreen:
defs = append(defs, "#define FILTER_SCREEN")
default: default:
panic("not reached") panic("not reached")
} }
@ -73,10 +76,14 @@ 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) #if defined(FILTER_LINEAR) || defined(FILTER_SCREEN)
uniform highp vec2 source_size; uniform highp vec2 source_size;
#endif #endif
#if defined(FILTER_SCREEN)
uniform highp float scale;
#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;
varying highp vec2 varying_tex_coord_max; varying highp vec2 varying_tex_coord_max;
@ -142,6 +149,23 @@ void main(void) {
vec4 color = mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y); vec4 color = mix(mix(c0, c1, rate.x), mix(c2, c3, rate.x), rate.y);
#endif #endif
#if defined(FILTER_SCREEN)
pos = roundTexel(pos);
highp vec2 texel_size = 1.0 / source_size;
pos -= texel_size * 0.5 / scale;
highp vec2 p0 = pos;
highp vec2 p1 = pos + texel_size / scale;
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 rate = min(max(1.0 - ((1.0 - fract(pos * source_size)) * scale), 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) {
color.rgb /= color.a; color.rgb /= color.a;

View File

@ -24,6 +24,7 @@ const (
FilterDefault Filter = iota FilterDefault Filter = iota
FilterNearest FilterNearest
FilterLinear FilterLinear
FilterScreen
) )
// texture represents OpenGL's texture. // texture represents OpenGL's texture.

View File

@ -398,6 +398,14 @@ func (c *Context) UniformInt(p Program, location string, v int) {
}) })
} }
func (c *Context) UniformFloat(p Program, location string, v float32) {
_ = c.runOnContextThread(func() error {
l := int32(c.locationCache.GetUniformLocation(c, p, location))
gl.Uniform1f(l, v)
return nil
})
}
func (c *Context) UniformFloats(p Program, location string, v []float32) { func (c *Context) UniformFloats(p Program, location string, v []float32) {
_ = c.runOnContextThread(func() error { _ = c.runOnContextThread(func() error {
l := int32(c.locationCache.GetUniformLocation(c, p, location)) l := int32(c.locationCache.GetUniformLocation(c, p, location))

View File

@ -316,6 +316,12 @@ func (c *Context) UniformInt(p Program, location string, v int) {
gl.Uniform1i(l.(*js.Object), v) gl.Uniform1i(l.(*js.Object), v)
} }
func (c *Context) UniformFloat(p Program, location string, v float32) {
gl := c.gl
l := c.locationCache.GetUniformLocation(c, p, location)
gl.Uniform1f(l.(*js.Object), v)
}
func (c *Context) UniformFloats(p Program, location string, v []float32) { func (c *Context) UniformFloats(p Program, location string, v []float32) {
gl := c.gl gl := c.gl
l := c.locationCache.GetUniformLocation(c, p, location) l := c.locationCache.GetUniformLocation(c, p, location)

View File

@ -309,6 +309,11 @@ func (c *Context) UniformInt(p Program, location string, v int) {
gl.Uniform1i(mgl.Uniform(c.locationCache.GetUniformLocation(c, p, location)), v) gl.Uniform1i(mgl.Uniform(c.locationCache.GetUniformLocation(c, p, location)), v)
} }
func (c *Context) UniformFloat(p Program, location string, v float32) {
gl := c.gl
gl.Uniform1f(mgl.Uniform(c.locationCache.GetUniformLocation(c, p, location)), v)
}
func (c *Context) UniformFloats(p Program, location string, v []float32) { func (c *Context) UniformFloats(p Program, location string, v []float32) {
gl := c.gl gl := c.gl
l := mgl.Uniform(c.locationCache.GetUniformLocation(c, p, location)) l := mgl.Uniform(c.locationCache.GetUniformLocation(c, p, location))