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 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.

View File

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

View File

@ -247,12 +247,9 @@ func (c *drawImageCommand) Exec(indexOffsetInBytes int) error {
if n == 0 {
return nil
}
sw, sh := c.src.Size()
sw = emath.NextPowerOf2Int(sw)
sh = emath.NextPowerOf2Int(sh)
_, dh := c.dst.Size()
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?
// The buffer is already bound at begin() but it is counterintuitive.
opengl.GetContext().DrawElements(opengl.Triangles, 6*n, indexOffsetInBytes)

View File

@ -18,6 +18,7 @@ import (
"fmt"
"github.com/hajimehoshi/ebiten/internal/affine"
emath "github.com/hajimehoshi/ebiten/internal/math"
"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 opengl.Program
programScreen opengl.Program
lastProgram opengl.Program
lastProjectionMatrix []float32
lastColorMatrix []float32
@ -157,6 +160,9 @@ func (s *openGLState) reset() error {
if s.programLinear != zeroProgram {
opengl.GetContext().DeleteProgram(s.programLinear)
}
if s.programScreen != zeroProgram {
opengl.GetContext().DeleteProgram(s.programScreen)
}
if s.arrayBuffer != zeroBuffer {
opengl.GetContext().DeleteBuffer(s.arrayBuffer)
}
@ -182,6 +188,12 @@ func (s *openGLState) reset() error {
}
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{
shaderVertexModelviewNative,
shaderFragmentNearestNative,
@ -198,6 +210,14 @@ func (s *openGLState) reset() error {
return err
}
s.programScreen, err = opengl.GetContext().NewProgram([]opengl.Shader{
shaderVertexModelviewNative,
shaderFragmentScreenNative,
})
if err != nil {
return err
}
s.arrayBuffer = theArrayBufferLayout.newArrayBuffer()
indices := make([]uint16, 6*maxQuads)
@ -228,7 +248,7 @@ func areSameFloat32Array(a, b []float32) bool {
}
// 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()
var program opengl.Program
@ -237,6 +257,8 @@ func (s *openGLState) useProgram(proj []float32, texture opengl.Texture, sourceW
program = s.programNearest
case FilterLinear:
program = s.programLinear
case FilterScreen:
program = s.programScreen
default:
panic("not reached")
}
@ -287,14 +309,23 @@ func (s *openGLState) useProgram(proj []float32, texture opengl.Texture, sourceW
s.lastColorMatrixTranslation = esTranslate
}
if program == s.programLinear {
if s.lastSourceWidth != sourceWidth || s.lastSourceHeight != sourceHeight {
c.UniformFloats(program, "source_size",
[]float32{float32(sourceWidth), float32(sourceHeight)})
s.lastSourceWidth = sourceWidth
s.lastSourceHeight = sourceHeight
if program == s.programLinear || program == s.programScreen {
sw, sh := src.Size()
sw = emath.NextPowerOf2Int(sw)
sh = emath.NextPowerOf2Int(sh)
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
// See also: https://www.opengl.org/sdk/docs/man2/xhtml/glActiveTexture.xml

View File

@ -24,6 +24,7 @@ const (
shaderVertexModelview shaderID = iota
shaderFragmentNearest
shaderFragmentLinear
shaderFragmentScreen
)
func shader(id shaderID) string {
@ -36,6 +37,8 @@ func shader(id shaderID) string {
defs = append(defs, "#define FILTER_NEAREST")
case shaderFragmentLinear:
defs = append(defs, "#define FILTER_LINEAR")
case shaderFragmentScreen:
defs = append(defs, "#define FILTER_SCREEN")
default:
panic("not reached")
}
@ -73,10 +76,14 @@ uniform sampler2D texture;
uniform mat4 color_matrix;
uniform vec4 color_matrix_translation;
#if defined(FILTER_LINEAR)
#if defined(FILTER_LINEAR) || defined(FILTER_SCREEN)
uniform highp vec2 source_size;
#endif
#if defined(FILTER_SCREEN)
uniform highp float scale;
#endif
varying highp vec2 varying_tex_coord;
varying highp vec2 varying_tex_coord_min;
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);
#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
if (0.0 < color.a) {
color.rgb /= color.a;

View File

@ -24,6 +24,7 @@ const (
FilterDefault Filter = iota
FilterNearest
FilterLinear
FilterScreen
)
// 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) {
_ = c.runOnContextThread(func() error {
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)
}
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) {
gl := c.gl
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)
}
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) {
gl := c.gl
l := mgl.Uniform(c.locationCache.GetUniformLocation(c, p, location))