Reland(2): graphics: Appropriate rendering of edges on linear filter

This commit is contained in:
Hajime Hoshi 2017-12-11 23:07:01 +09:00
parent 362d1c417f
commit 723d153800
15 changed files with 180 additions and 86 deletions

View File

@ -15,6 +15,7 @@
package ebiten package ebiten
import ( import (
"github.com/hajimehoshi/ebiten/internal/graphics"
"github.com/hajimehoshi/ebiten/internal/opengl" "github.com/hajimehoshi/ebiten/internal/opengl"
) )
@ -23,22 +24,12 @@ type Filter int
const ( const (
// FilterNearest represents nearest (crisp-edged) filter // FilterNearest represents nearest (crisp-edged) filter
FilterNearest Filter = iota FilterNearest Filter = Filter(graphics.FilterNearest)
// FilterLinear represents linear filter // FilterLinear represents linear filter
FilterLinear FilterLinear Filter = Filter(graphics.FilterLinear)
) )
func glFilter(filter Filter) opengl.Filter {
switch filter {
case FilterNearest:
return opengl.Nearest
case FilterLinear:
return opengl.Linear
}
panic("not reach")
}
// CompositeMode represents Porter-Duff composition mode. // CompositeMode represents Porter-Duff composition mode.
type CompositeMode int type CompositeMode int

View File

@ -20,6 +20,7 @@ import (
"image/color" "image/color"
"runtime" "runtime"
"github.com/hajimehoshi/ebiten/internal/graphics"
"github.com/hajimehoshi/ebiten/internal/math" "github.com/hajimehoshi/ebiten/internal/math"
"github.com/hajimehoshi/ebiten/internal/opengl" "github.com/hajimehoshi/ebiten/internal/opengl"
"github.com/hajimehoshi/ebiten/internal/restorable" "github.com/hajimehoshi/ebiten/internal/restorable"
@ -244,7 +245,7 @@ type DrawImageOptions struct {
// Error returned by NewImage is always nil as of 1.5.0-alpha. // Error returned by NewImage is always nil as of 1.5.0-alpha.
func NewImage(width, height int, filter Filter) (*Image, error) { func NewImage(width, height int, filter Filter) (*Image, error) {
checkSize(width, height) checkSize(width, height)
r := restorable.NewImage(width, height, glFilter(filter), false) r := restorable.NewImage(width, height, graphics.Filter(filter), false)
r.Fill(0, 0, 0, 0) r.Fill(0, 0, 0, 0)
i := &Image{r} i := &Image{r}
runtime.SetFinalizer(i, (*Image).Dispose) runtime.SetFinalizer(i, (*Image).Dispose)
@ -268,7 +269,7 @@ func NewImage(width, height int, filter Filter) (*Image, error) {
// Error returned by newVolatileImage is always nil as of 1.5.0-alpha. // Error returned by newVolatileImage is always nil as of 1.5.0-alpha.
func newVolatileImage(width, height int, filter Filter) *Image { func newVolatileImage(width, height int, filter Filter) *Image {
checkSize(width, height) checkSize(width, height)
r := restorable.NewImage(width, height, glFilter(filter), true) r := restorable.NewImage(width, height, graphics.Filter(filter), true)
r.Fill(0, 0, 0, 0) r.Fill(0, 0, 0, 0)
i := &Image{r} i := &Image{r}
runtime.SetFinalizer(i, (*Image).Dispose) runtime.SetFinalizer(i, (*Image).Dispose)
@ -283,7 +284,7 @@ func newVolatileImage(width, height int, filter Filter) *Image {
func NewImageFromImage(source image.Image, filter Filter) (*Image, error) { func NewImageFromImage(source image.Image, filter Filter) (*Image, error) {
size := source.Bounds().Size() size := source.Bounds().Size()
checkSize(size.X, size.Y) checkSize(size.X, size.Y)
r := restorable.NewImageFromImage(source, glFilter(filter)) r := restorable.NewImageFromImage(source, graphics.Filter(filter))
i := &Image{r} i := &Image{r}
runtime.SetFinalizer(i, (*Image).Dispose) runtime.SetFinalizer(i, (*Image).Dispose)
return i, nil return i, nil

View File

@ -25,6 +25,7 @@ import (
"testing" "testing"
. "github.com/hajimehoshi/ebiten" . "github.com/hajimehoshi/ebiten"
"github.com/hajimehoshi/ebiten/ebitenutil"
emath "github.com/hajimehoshi/ebiten/internal/math" emath "github.com/hajimehoshi/ebiten/internal/math"
) )
@ -595,3 +596,28 @@ func BenchmarkDrawImage(b *testing.B) {
img0.DrawImage(img1, op) img0.DrawImage(img1, op)
} }
} }
func TestImageLinear(t *testing.T) {
src, _ := NewImage(32, 32, FilterLinear)
dst, _ := NewImage(64, 64, FilterNearest)
src.Fill(color.RGBA{0, 0xff, 0, 0xff})
ebitenutil.DrawRect(src, 8, 8, 16, 16, color.RGBA{0xff, 0, 0, 0xff})
op := &DrawImageOptions{}
op.GeoM.Translate(8, 8)
op.GeoM.Scale(2, 2)
r := image.Rect(8, 8, 24, 24)
op.SourceRect = &r
dst.DrawImage(src, op)
for j := 0; j < 64; j++ {
for i := 0; i < 64; i++ {
c := color.RGBAModel.Convert(dst.At(i, j)).(color.RGBA)
got := c.G
want := uint8(0)
if got != want {
t.Errorf("dst At(%d, %d).G: got %#v, want: %#v", i, j, got, want)
}
}
}
}

View File

@ -242,12 +242,18 @@ func (c *drawImageCommand) Exec(indexOffsetInBytes int) error {
if n == 0 { if n == 0 {
return nil return nil
} }
_, h := c.dst.Size() sw, sh := c.src.Size()
proj := f.projectionMatrix(h) sw = emath.NextPowerOf2Int(sw)
theOpenGLState.useProgram(proj, c.src.texture.native, c.color) sh = emath.NextPowerOf2Int(sh)
_, dh := c.dst.Size()
proj := f.projectionMatrix(dh)
theOpenGLState.useProgram(proj, c.src.texture.native, sw, sh, c.color, c.src.texture.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)
// This is necessary at least on MacBook Pro (a smilar problem at #419)
opengl.GetContext().Flush()
return nil return nil
} }
@ -338,7 +344,7 @@ func (c *disposeCommand) Exec(indexOffsetInBytes int) error {
type newImageFromImageCommand struct { type newImageFromImageCommand struct {
result *Image result *Image
img *image.RGBA img *image.RGBA
filter opengl.Filter filter Filter
} }
// Exec executes the newImageFromImageCommand. // Exec executes the newImageFromImageCommand.
@ -354,12 +360,13 @@ func (c *newImageFromImageCommand) Exec(indexOffsetInBytes int) error {
if c.img.Bounds() != image.Rect(0, 0, emath.NextPowerOf2Int(w), emath.NextPowerOf2Int(h)) { if c.img.Bounds() != image.Rect(0, 0, emath.NextPowerOf2Int(w), emath.NextPowerOf2Int(h)) {
panic(fmt.Sprintf("graphics: invalid image bounds: %v", c.img.Bounds())) panic(fmt.Sprintf("graphics: invalid image bounds: %v", c.img.Bounds()))
} }
native, err := opengl.GetContext().NewTexture(w, h, c.img.Pix, c.filter) native, err := opengl.GetContext().NewTexture(w, h, c.img.Pix)
if err != nil { if err != nil {
return err return err
} }
c.result.texture = &texture{ c.result.texture = &texture{
native: native, native: native,
filter: c.filter,
} }
return nil return nil
} }
@ -369,7 +376,7 @@ type newImageCommand struct {
result *Image result *Image
width int width int
height int height int
filter opengl.Filter filter Filter
} }
// Exec executes a newImageCommand. // Exec executes a newImageCommand.
@ -382,12 +389,13 @@ func (c *newImageCommand) Exec(indexOffsetInBytes int) error {
if h < 1 { if h < 1 {
return errors.New("graphics: height must be equal or more than 1.") return errors.New("graphics: height must be equal or more than 1.")
} }
native, err := opengl.GetContext().NewTexture(w, h, nil, c.filter) native, err := opengl.GetContext().NewTexture(w, h, nil)
if err != nil { if err != nil {
return err return err
} }
c.result.texture = &texture{ c.result.texture = &texture{
native: native, native: native,
filter: c.filter,
} }
return nil return nil
} }

View File

@ -33,7 +33,7 @@ type Image struct {
// MaxImageSize is the maximum of width/height of an image. // MaxImageSize is the maximum of width/height of an image.
const MaxImageSize = defaultViewportSize const MaxImageSize = defaultViewportSize
func NewImage(width, height int, filter opengl.Filter) *Image { func NewImage(width, height int, filter Filter) *Image {
i := &Image{ i := &Image{
width: width, width: width,
height: height, height: height,
@ -48,7 +48,7 @@ func NewImage(width, height int, filter opengl.Filter) *Image {
return i return i
} }
func NewImageFromImage(img *image.RGBA, width, height int, filter opengl.Filter) *Image { func NewImageFromImage(img *image.RGBA, width, height int, filter Filter) *Image {
i := &Image{ i := &Image{
width: width, width: width,
height: height, height: height,

View File

@ -121,6 +121,9 @@ type openGLState struct {
lastProjectionMatrix []float32 lastProjectionMatrix []float32
lastColorMatrix []float32 lastColorMatrix []float32
lastColorMatrixTranslation []float32 lastColorMatrixTranslation []float32
lastFilterType Filter
lastSourceWidth int
lastSourceHeight int
} }
var ( var (
@ -150,6 +153,9 @@ 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.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.
@ -214,7 +220,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, colorM affine.ColorM) { 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 program := s.programTexture
@ -273,6 +279,18 @@ func (s *openGLState) useProgram(proj []float32, texture opengl.Texture, colorM
copy(s.lastColorMatrixTranslation, colorMatrixTranslation) copy(s.lastColorMatrixTranslation, colorMatrixTranslation)
} }
if s.lastFilterType != filter {
c.UniformInt(program, "filter_type", int(filter))
s.lastFilterType = filter
}
if s.lastSourceWidth != sourceWidth || 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
// See also: https://www.opengl.org/sdk/docs/man2/xhtml/glActiveTexture.xml // See also: https://www.opengl.org/sdk/docs/man2/xhtml/glActiveTexture.xml
c.BindTexture(texture) c.BindTexture(texture)

View File

@ -63,17 +63,54 @@ precision mediump float;
uniform sampler2D texture; uniform sampler2D texture;
uniform mat4 color_matrix; uniform mat4 color_matrix;
uniform vec4 color_matrix_translation; uniform vec4 color_matrix_translation;
uniform int filter_type;
uniform vec2 source_size;
varying vec2 varying_tex_coord; varying vec2 varying_tex_coord;
varying vec2 varying_tex_coord_min; varying vec2 varying_tex_coord_min;
varying vec2 varying_tex_coord_max; varying vec2 varying_tex_coord_max;
vec2 roundTexel(vec2 p) {
vec2 factor = 1.0 / (source_size * 256.0);
if (factor.x > 0.0) {
p.x -= mod(p.x + factor.x * 0.5, factor.x) - factor.x * 0.5;
}
if (factor.y > 0.0) {
p.y -= mod(p.y + factor.y * 0.5, factor.y) - factor.y * 0.5;
}
return p;
}
vec4 getColorAt(vec2 pos) {
if (pos.x < varying_tex_coord_min.x ||
pos.y < varying_tex_coord_min.y ||
varying_tex_coord_max.x <= pos.x ||
varying_tex_coord_max.y <= pos.y) {
return vec4(0, 0, 0, 0);
}
return texture2D(texture, pos);
}
void main(void) { void main(void) {
vec4 color = vec4(0, 0, 0, 0); vec4 color = vec4(0, 0, 0, 0);
if (varying_tex_coord_min.x <= varying_tex_coord.x &&
varying_tex_coord_min.y <= varying_tex_coord.y && vec2 pos = roundTexel(varying_tex_coord);
varying_tex_coord.x < varying_tex_coord_max.x && if (filter_type == 1) {
varying_tex_coord.y < varying_tex_coord_max.y) { // Nearest neighbor
color = texture2D(texture, varying_tex_coord); color = getColorAt(pos);
} else if (filter_type == 2) {
// Bi-linear
vec2 texel_size = 1.0 / source_size;
pos -= texel_size * 0.5;
vec4 c0 = getColorAt(pos);
vec4 c1 = getColorAt(pos + vec2(texel_size.x, 0));
vec4 c2 = getColorAt(pos + vec2(0, texel_size.y));
vec4 c3 = getColorAt(pos + texel_size);
float rateX = fract(pos.x * source_size.x);
float rateY = fract(pos.y * source_size.y);
color = mix(mix(c0, c1, rateX), mix(c2, c3, rateX), rateY);
} else {
color = vec4(1, 0, 0, 1);
} }
// Un-premultiply alpha // Un-premultiply alpha

View File

@ -18,7 +18,16 @@ import (
"github.com/hajimehoshi/ebiten/internal/opengl" "github.com/hajimehoshi/ebiten/internal/opengl"
) )
type Filter int
const (
FilterNone Filter = iota
FilterNearest
FilterLinear
)
// texture represents OpenGL's texture. // texture represents OpenGL's texture.
type texture struct { type texture struct {
native opengl.Texture native opengl.Texture
filter Filter
} }

View File

@ -38,8 +38,6 @@ func adjustForClearColor(x float32) float32 {
} }
var ( var (
Nearest Filter
Linear Filter
VertexShader ShaderType VertexShader ShaderType
FragmentShader ShaderType FragmentShader ShaderType
ArrayBuffer BufferType ArrayBuffer BufferType

View File

@ -26,11 +26,13 @@ import (
"github.com/go-gl/gl/v2.1/gl" "github.com/go-gl/gl/v2.1/gl"
) )
type Texture uint32 type (
type Framebuffer uint32 Texture uint32
type Shader uint32 Framebuffer uint32
type Program uint32 Shader uint32
type Buffer uint32 Program uint32
Buffer uint32
)
func (t Texture) equals(other Texture) bool { func (t Texture) equals(other Texture) bool {
return t == other return t == other
@ -40,8 +42,10 @@ func (f Framebuffer) equals(other Framebuffer) bool {
return f == other return f == other
} }
type uniformLocation int32 type (
type attribLocation int32 uniformLocation int32
attribLocation int32
)
type programID uint32 type programID uint32
@ -55,8 +59,6 @@ func (p Program) id() programID {
} }
func init() { func init() {
Nearest = gl.NEAREST
Linear = gl.LINEAR
VertexShader = gl.VERTEX_SHADER VertexShader = gl.VERTEX_SHADER
FragmentShader = gl.FRAGMENT_SHADER FragmentShader = gl.FRAGMENT_SHADER
ArrayBuffer = gl.ARRAY_BUFFER ArrayBuffer = gl.ARRAY_BUFFER
@ -137,7 +139,7 @@ func (c *Context) BlendFunc(mode CompositeMode) {
}) })
} }
func (c *Context) NewTexture(width, height int, pixels []uint8, filter Filter) (Texture, error) { func (c *Context) NewTexture(width, height int, pixels []uint8) (Texture, error) {
var texture Texture var texture Texture
if err := c.runOnContextThread(func() error { if err := c.runOnContextThread(func() error {
var t uint32 var t uint32
@ -154,10 +156,8 @@ func (c *Context) NewTexture(width, height int, pixels []uint8, filter Filter) (
} }
c.BindTexture(texture) c.BindTexture(texture)
_ = c.runOnContextThread(func() error { _ = c.runOnContextThread(func() error {
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, int32(filter)) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, int32(filter)) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
//gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP)
//gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP)
var p interface{} var p interface{}
if pixels != nil { if pixels != nil {
@ -408,12 +408,14 @@ 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))
switch len(v) { switch len(v) {
case 2:
gl.Uniform2fv(l, 1, (*float32)(gl.Ptr(v)))
case 4: case 4:
gl.Uniform4fv(l, 1, (*float32)(gl.Ptr(v))) gl.Uniform4fv(l, 1, (*float32)(gl.Ptr(v)))
case 16: case 16:
gl.UniformMatrix4fv(l, 1, false, (*float32)(gl.Ptr(v))) gl.UniformMatrix4fv(l, 1, false, (*float32)(gl.Ptr(v)))
default: default:
panic("not reach") panic("not reached")
} }
return nil return nil
}) })

View File

@ -77,8 +77,6 @@ func (p Program) id() programID {
func init() { func init() {
// Accessing the prototype is rquired on Safari. // Accessing the prototype is rquired on Safari.
c := js.Global.Get("WebGLRenderingContext").Get("prototype") c := js.Global.Get("WebGLRenderingContext").Get("prototype")
Nearest = Filter(c.Get("NEAREST").Int())
Linear = Filter(c.Get("LINEAR").Int())
VertexShader = ShaderType(c.Get("VERTEX_SHADER").Int()) VertexShader = ShaderType(c.Get("VERTEX_SHADER").Int())
FragmentShader = ShaderType(c.Get("FRAGMENT_SHADER").Int()) FragmentShader = ShaderType(c.Get("FRAGMENT_SHADER").Int())
ArrayBuffer = BufferType(c.Get("ARRAY_BUFFER").Int()) ArrayBuffer = BufferType(c.Get("ARRAY_BUFFER").Int())
@ -159,7 +157,7 @@ func (c *Context) BlendFunc(mode CompositeMode) {
gl.BlendFunc(int(s), int(d)) gl.BlendFunc(int(s), int(d))
} }
func (c *Context) NewTexture(width, height int, pixels []uint8, filter Filter) (Texture, error) { func (c *Context) NewTexture(width, height int, pixels []uint8) (Texture, error) {
gl := c.gl gl := c.gl
t := gl.CreateTexture() t := gl.CreateTexture()
if t == nil { if t == nil {
@ -168,8 +166,8 @@ func (c *Context) NewTexture(width, height int, pixels []uint8, filter Filter) (
gl.PixelStorei(gl.UNPACK_ALIGNMENT, 4) gl.PixelStorei(gl.UNPACK_ALIGNMENT, 4)
c.BindTexture(Texture{t}) c.BindTexture(Texture{t})
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, int(filter)) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, int(filter)) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
// TODO: Can we use glTexSubImage2D with linear filtering? // TODO: Can we use glTexSubImage2D with linear filtering?
@ -348,12 +346,14 @@ 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)
switch len(v) { switch len(v) {
case 2:
gl.Call("uniform2fv", l.Object, v)
case 4: case 4:
gl.Call("uniform4fv", l.Object, v) gl.Call("uniform4fv", l.Object, v)
case 16: case 16:
gl.UniformMatrix4fv(l.Object, false, v) gl.UniformMatrix4fv(l.Object, false, v)
default: default:
panic("not reach") panic("not reached")
} }
} }

View File

@ -25,11 +25,13 @@ import (
mgl "golang.org/x/mobile/gl" mgl "golang.org/x/mobile/gl"
) )
type Texture mgl.Texture type (
type Framebuffer mgl.Framebuffer Texture mgl.Texture
type Shader mgl.Shader Framebuffer mgl.Framebuffer
type Program mgl.Program Shader mgl.Shader
type Buffer mgl.Buffer Program mgl.Program
Buffer mgl.Buffer
)
func (t Texture) equals(other Texture) bool { func (t Texture) equals(other Texture) bool {
return t == other return t == other
@ -39,8 +41,10 @@ func (f Framebuffer) equals(other Framebuffer) bool {
return f == other return f == other
} }
type uniformLocation mgl.Uniform type (
type attribLocation mgl.Attrib uniformLocation mgl.Uniform
attribLocation mgl.Attrib
)
type programID uint32 type programID uint32
@ -54,8 +58,6 @@ func (p Program) id() programID {
} }
func init() { func init() {
Nearest = mgl.NEAREST
Linear = mgl.LINEAR
VertexShader = mgl.VERTEX_SHADER VertexShader = mgl.VERTEX_SHADER
FragmentShader = mgl.FRAGMENT_SHADER FragmentShader = mgl.FRAGMENT_SHADER
ArrayBuffer = mgl.ARRAY_BUFFER ArrayBuffer = mgl.ARRAY_BUFFER
@ -127,7 +129,7 @@ func (c *Context) BlendFunc(mode CompositeMode) {
gl.BlendFunc(mgl.Enum(s), mgl.Enum(d)) gl.BlendFunc(mgl.Enum(s), mgl.Enum(d))
} }
func (c *Context) NewTexture(width, height int, pixels []uint8, filter Filter) (Texture, error) { func (c *Context) NewTexture(width, height int, pixels []uint8) (Texture, error) {
gl := c.gl gl := c.gl
t := gl.CreateTexture() t := gl.CreateTexture()
if t.Value <= 0 { if t.Value <= 0 {
@ -136,8 +138,8 @@ func (c *Context) NewTexture(width, height int, pixels []uint8, filter Filter) (
gl.PixelStorei(mgl.UNPACK_ALIGNMENT, 4) gl.PixelStorei(mgl.UNPACK_ALIGNMENT, 4)
c.BindTexture(Texture(t)) c.BindTexture(Texture(t))
gl.TexParameteri(mgl.TEXTURE_2D, mgl.TEXTURE_MAG_FILTER, int(filter)) gl.TexParameteri(mgl.TEXTURE_2D, mgl.TEXTURE_MAG_FILTER, mgl.NEAREST)
gl.TexParameteri(mgl.TEXTURE_2D, mgl.TEXTURE_MIN_FILTER, int(filter)) gl.TexParameteri(mgl.TEXTURE_2D, mgl.TEXTURE_MIN_FILTER, mgl.NEAREST)
var p []uint8 var p []uint8
if pixels != nil { if pixels != nil {
@ -317,12 +319,14 @@ 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))
switch len(v) { switch len(v) {
case 2:
gl.Uniform2fv(l, v)
case 4: case 4:
gl.Uniform4fv(l, v) gl.Uniform4fv(l, v)
case 16: case 16:
gl.UniformMatrix4fv(l, v) gl.UniformMatrix4fv(l, v)
default: default:
panic("not reach") panic("not reached")
} }
} }

View File

@ -15,7 +15,6 @@
package opengl package opengl
type ( type (
Filter int
ShaderType int ShaderType int
BufferType int BufferType int
BufferUsage int BufferUsage int

View File

@ -60,7 +60,7 @@ func (d *drawImageHistoryItem) canMerge(image *Image, colorm *affine.ColorM, mod
// Image represents an image that can be restored when GL context is lost. // Image represents an image that can be restored when GL context is lost.
type Image struct { type Image struct {
image *graphics.Image image *graphics.Image
filter opengl.Filter filter graphics.Filter
// baseImage and baseColor are exclusive. // baseImage and baseColor are exclusive.
basePixels []uint8 basePixels []uint8
@ -84,7 +84,7 @@ type Image struct {
} }
// NewImage creates an empty image with the given size and filter. // NewImage creates an empty image with the given size and filter.
func NewImage(width, height int, filter opengl.Filter, volatile bool) *Image { func NewImage(width, height int, filter graphics.Filter, volatile bool) *Image {
i := &Image{ i := &Image{
image: graphics.NewImage(width, height, filter), image: graphics.NewImage(width, height, filter),
filter: filter, filter: filter,
@ -96,7 +96,7 @@ func NewImage(width, height int, filter opengl.Filter, volatile bool) *Image {
} }
// NewImageFromImage creates an image with source image. // NewImageFromImage creates an image with source image.
func NewImageFromImage(source image.Image, filter opengl.Filter) *Image { func NewImageFromImage(source image.Image, filter graphics.Filter) *Image {
size := source.Bounds().Size() size := source.Bounds().Size()
width, height := size.X, size.Y width, height := size.X, size.Y
rgbaImg := CopyImage(source) rgbaImg := CopyImage(source)

View File

@ -23,6 +23,7 @@ import (
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten"
"github.com/hajimehoshi/ebiten/internal/affine" "github.com/hajimehoshi/ebiten/internal/affine"
"github.com/hajimehoshi/ebiten/internal/graphics"
"github.com/hajimehoshi/ebiten/internal/opengl" "github.com/hajimehoshi/ebiten/internal/opengl"
. "github.com/hajimehoshi/ebiten/internal/restorable" . "github.com/hajimehoshi/ebiten/internal/restorable"
) )
@ -47,7 +48,7 @@ func uint8SliceToColor(b []uint8, index int) color.RGBA {
} }
func TestRestore(t *testing.T) { func TestRestore(t *testing.T) {
img0 := NewImage(1, 1, opengl.Nearest, false) img0 := NewImage(1, 1, graphics.FilterNearest, false)
// Clear images explicitly. // Clear images explicitly.
// In this 'restorable' layer, reused texture might not be cleared. // In this 'restorable' layer, reused texture might not be cleared.
img0.Fill(0, 0, 0, 0) img0.Fill(0, 0, 0, 0)
@ -89,7 +90,7 @@ func TestRestoreChain(t *testing.T) {
const num = 10 const num = 10
imgs := []*Image{} imgs := []*Image{}
for i := 0; i < num; i++ { for i := 0; i < num; i++ {
img := NewImage(1, 1, opengl.Nearest, false) img := NewImage(1, 1, graphics.FilterNearest, false)
img.Fill(0, 0, 0, 0) img.Fill(0, 0, 0, 0)
imgs = append(imgs, img) imgs = append(imgs, img)
} }
@ -119,13 +120,13 @@ func TestRestoreChain(t *testing.T) {
} }
func TestRestoreOverrideSource(t *testing.T) { func TestRestoreOverrideSource(t *testing.T) {
img0 := NewImage(1, 1, opengl.Nearest, false) img0 := NewImage(1, 1, graphics.FilterNearest, false)
img0.Fill(0, 0, 0, 0) img0.Fill(0, 0, 0, 0)
img1 := NewImage(1, 1, opengl.Nearest, false) img1 := NewImage(1, 1, graphics.FilterNearest, false)
img1.Fill(0, 0, 0, 0) img1.Fill(0, 0, 0, 0)
img2 := NewImage(1, 1, opengl.Nearest, false) img2 := NewImage(1, 1, graphics.FilterNearest, false)
img2.Fill(0, 0, 0, 0) img2.Fill(0, 0, 0, 0)
img3 := NewImage(1, 1, opengl.Nearest, false) img3 := NewImage(1, 1, graphics.FilterNearest, false)
img3.Fill(0, 0, 0, 0) img3.Fill(0, 0, 0, 0)
defer func() { defer func() {
img3.Dispose() img3.Dispose()
@ -194,18 +195,18 @@ func TestRestoreComplexGraph(t *testing.T) {
base.Pix[1] = 0xff base.Pix[1] = 0xff
base.Pix[2] = 0xff base.Pix[2] = 0xff
base.Pix[3] = 0xff base.Pix[3] = 0xff
img0 := NewImageFromImage(base, opengl.Nearest) img0 := NewImageFromImage(base, graphics.FilterNearest)
img1 := NewImageFromImage(base, opengl.Nearest) img1 := NewImageFromImage(base, graphics.FilterNearest)
img2 := NewImageFromImage(base, opengl.Nearest) img2 := NewImageFromImage(base, graphics.FilterNearest)
img3 := NewImage(4, 1, opengl.Nearest, false) img3 := NewImage(4, 1, graphics.FilterNearest, false)
img3.Fill(0, 0, 0, 0) img3.Fill(0, 0, 0, 0)
img4 := NewImage(4, 1, opengl.Nearest, false) img4 := NewImage(4, 1, graphics.FilterNearest, false)
img4.Fill(0, 0, 0, 0) img4.Fill(0, 0, 0, 0)
img5 := NewImage(4, 1, opengl.Nearest, false) img5 := NewImage(4, 1, graphics.FilterNearest, false)
img5.Fill(0, 0, 0, 0) img5.Fill(0, 0, 0, 0)
img6 := NewImage(4, 1, opengl.Nearest, false) img6 := NewImage(4, 1, graphics.FilterNearest, false)
img6.Fill(0, 0, 0, 0) img6.Fill(0, 0, 0, 0)
img7 := NewImage(4, 1, opengl.Nearest, false) img7 := NewImage(4, 1, graphics.FilterNearest, false)
img7.Fill(0, 0, 0, 0) img7.Fill(0, 0, 0, 0)
defer func() { defer func() {
img7.Dispose() img7.Dispose()
@ -298,8 +299,8 @@ func TestRestoreRecursive(t *testing.T) {
base.Pix[1] = 0xff base.Pix[1] = 0xff
base.Pix[2] = 0xff base.Pix[2] = 0xff
base.Pix[3] = 0xff base.Pix[3] = 0xff
img0 := NewImageFromImage(base, opengl.Nearest) img0 := NewImageFromImage(base, graphics.FilterNearest)
img1 := NewImage(4, 1, opengl.Nearest, false) img1 := NewImage(4, 1, graphics.FilterNearest, false)
img1.Fill(0, 0, 0, 0) img1.Fill(0, 0, 0, 0)
defer func() { defer func() {
img1.Dispose() img1.Dispose()