diff --git a/graphics.go b/graphics.go index c52c2a6c9..0773c7a25 100644 --- a/graphics.go +++ b/graphics.go @@ -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. diff --git a/graphicscontext.go b/graphicscontext.go index dd1afbfcb..8af0bafe5 100644 --- a/graphicscontext.go +++ b/graphicscontext.go @@ -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 { diff --git a/internal/graphics/command.go b/internal/graphics/command.go index b2d9bc9bd..9383db6e0 100644 --- a/internal/graphics/command.go +++ b/internal/graphics/command.go @@ -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) diff --git a/internal/graphics/program.go b/internal/graphics/program.go index 5d2c5378a..dbad563ec 100644 --- a/internal/graphics/program.go +++ b/internal/graphics/program.go @@ -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 diff --git a/internal/graphics/shader.go b/internal/graphics/shader.go index 6a92dad1a..03227ac0e 100644 --- a/internal/graphics/shader.go +++ b/internal/graphics/shader.go @@ -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; diff --git a/internal/graphics/texture.go b/internal/graphics/texture.go index 0e2197827..1c07cd20d 100644 --- a/internal/graphics/texture.go +++ b/internal/graphics/texture.go @@ -24,6 +24,7 @@ const ( FilterDefault Filter = iota FilterNearest FilterLinear + FilterScreen ) // texture represents OpenGL's texture. diff --git a/internal/opengl/context_desktop.go b/internal/opengl/context_desktop.go index 425414890..32b3138a2 100644 --- a/internal/opengl/context_desktop.go +++ b/internal/opengl/context_desktop.go @@ -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)) diff --git a/internal/opengl/context_js.go b/internal/opengl/context_js.go index 416cd0fda..51f2b1105 100644 --- a/internal/opengl/context_js.go +++ b/internal/opengl/context_js.go @@ -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) diff --git a/internal/opengl/context_mobile.go b/internal/opengl/context_mobile.go index 02edd52e3..ad7e07c87 100644 --- a/internal/opengl/context_mobile.go +++ b/internal/opengl/context_mobile.go @@ -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))