mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 03:08:54 +01:00
graphics: Add 'screen' filter for fast rendering (#509)
This commit is contained in:
parent
092cb2f3f6
commit
591e0ad995
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -24,6 +24,7 @@ const (
|
||||
FilterDefault Filter = iota
|
||||
FilterNearest
|
||||
FilterLinear
|
||||
FilterScreen
|
||||
)
|
||||
|
||||
// texture represents OpenGL's texture.
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
|
Loading…
Reference in New Issue
Block a user