Revert 'graphics: Appropriate rendering of edges on linear filter (Reland)' (#458)

This commit is contained in:
Hajime Hoshi 2017-12-11 20:28:57 +09:00
parent 7b74a8f29f
commit 362d1c417f
15 changed files with 86 additions and 178 deletions

View File

@ -15,7 +15,6 @@
package ebiten
import (
"github.com/hajimehoshi/ebiten/internal/graphics"
"github.com/hajimehoshi/ebiten/internal/opengl"
)
@ -24,12 +23,22 @@ type Filter int
const (
// FilterNearest represents nearest (crisp-edged) filter
FilterNearest Filter = Filter(graphics.FilterNearest)
FilterNearest Filter = iota
// FilterLinear represents linear filter
FilterLinear Filter = Filter(graphics.FilterLinear)
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.
type CompositeMode int

View File

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

View File

@ -25,7 +25,6 @@ import (
"testing"
. "github.com/hajimehoshi/ebiten"
"github.com/hajimehoshi/ebiten/ebitenutil"
emath "github.com/hajimehoshi/ebiten/internal/math"
)
@ -596,28 +595,3 @@ func BenchmarkDrawImage(b *testing.B) {
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,18 +242,12 @@ 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.src.texture.filter)
_, h := c.dst.Size()
proj := f.projectionMatrix(h)
theOpenGLState.useProgram(proj, c.src.texture.native, c.color)
// 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)
// This is necessary at least on MacBook Pro (a smilar problem at #419)
opengl.GetContext().Flush()
return nil
}
@ -344,7 +338,7 @@ func (c *disposeCommand) Exec(indexOffsetInBytes int) error {
type newImageFromImageCommand struct {
result *Image
img *image.RGBA
filter Filter
filter opengl.Filter
}
// Exec executes the newImageFromImageCommand.
@ -360,13 +354,12 @@ func (c *newImageFromImageCommand) Exec(indexOffsetInBytes int) error {
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()))
}
native, err := opengl.GetContext().NewTexture(w, h, c.img.Pix)
native, err := opengl.GetContext().NewTexture(w, h, c.img.Pix, c.filter)
if err != nil {
return err
}
c.result.texture = &texture{
native: native,
filter: c.filter,
}
return nil
}
@ -376,7 +369,7 @@ type newImageCommand struct {
result *Image
width int
height int
filter Filter
filter opengl.Filter
}
// Exec executes a newImageCommand.
@ -389,13 +382,12 @@ func (c *newImageCommand) Exec(indexOffsetInBytes int) error {
if h < 1 {
return errors.New("graphics: height must be equal or more than 1.")
}
native, err := opengl.GetContext().NewTexture(w, h, nil)
native, err := opengl.GetContext().NewTexture(w, h, nil, c.filter)
if err != nil {
return err
}
c.result.texture = &texture{
native: native,
filter: c.filter,
}
return nil
}

View File

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

View File

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

View File

@ -63,52 +63,17 @@ precision mediump float;
uniform sampler2D texture;
uniform mat4 color_matrix;
uniform vec4 color_matrix_translation;
uniform int filter_type;
uniform vec2 source_size;
varying vec2 varying_tex_coord;
varying vec2 varying_tex_coord_min;
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) {
vec4 color = vec4(0, 0, 0, 0);
vec2 pos = roundTexel(varying_tex_coord);
if (filter_type == 1) {
// Nearest neighbor
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);
if (varying_tex_coord_min.x <= varying_tex_coord.x &&
varying_tex_coord_min.y <= varying_tex_coord.y &&
varying_tex_coord.x < varying_tex_coord_max.x &&
varying_tex_coord.y < varying_tex_coord_max.y) {
color = texture2D(texture, varying_tex_coord);
}
// Un-premultiply alpha

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,6 +15,7 @@
package opengl
type (
Filter int
ShaderType int
BufferType 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.
type Image struct {
image *graphics.Image
filter graphics.Filter
filter opengl.Filter
// baseImage and baseColor are exclusive.
basePixels []uint8
@ -84,7 +84,7 @@ type Image struct {
}
// NewImage creates an empty image with the given size and filter.
func NewImage(width, height int, filter graphics.Filter, volatile bool) *Image {
func NewImage(width, height int, filter opengl.Filter, volatile bool) *Image {
i := &Image{
image: graphics.NewImage(width, height, filter),
filter: filter,
@ -96,7 +96,7 @@ func NewImage(width, height int, filter graphics.Filter, volatile bool) *Image {
}
// NewImageFromImage creates an image with source image.
func NewImageFromImage(source image.Image, filter graphics.Filter) *Image {
func NewImageFromImage(source image.Image, filter opengl.Filter) *Image {
size := source.Bounds().Size()
width, height := size.X, size.Y
rgbaImg := CopyImage(source)

View File

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