mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 11:18:54 +01:00
Reland(2): graphics: Appropriate rendering of edges on linear filter
This commit is contained in:
parent
362d1c417f
commit
723d153800
15
graphics.go
15
graphics.go
@ -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
|
||||||
|
|
||||||
|
7
image.go
7
image.go
@ -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
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
})
|
})
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
package opengl
|
package opengl
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Filter int
|
|
||||||
ShaderType int
|
ShaderType int
|
||||||
BufferType int
|
BufferType int
|
||||||
BufferUsage int
|
BufferUsage int
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user