mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2025-01-11 19:48:54 +01:00
init
This commit is contained in:
commit
b8a1a1806c
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*~
|
10
game/game.go
Normal file
10
game/game.go
Normal file
@ -0,0 +1,10 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
_ "../graphics"
|
||||
)
|
||||
|
||||
type Game interface {
|
||||
Update()
|
||||
Draw()
|
||||
}
|
112
graphics/affine_matrix.go
Normal file
112
graphics/affine_matrix.go
Normal file
@ -0,0 +1,112 @@
|
||||
package graphics
|
||||
|
||||
type AffineMatrixElement float64
|
||||
|
||||
type AffineMatrix struct {
|
||||
elements []AffineMatrixElement
|
||||
dimension int
|
||||
}
|
||||
|
||||
func NewAffineMatrix(dimension int) *AffineMatrix {
|
||||
if dimension < 0 {
|
||||
panic("invalid dimension")
|
||||
}
|
||||
matrix := &AffineMatrix{}
|
||||
elementsNumber := dimension * (dimension - 1)
|
||||
matrix.elements = make([]AffineMatrixElement, elementsNumber)
|
||||
matrix.dimension = dimension
|
||||
return matrix
|
||||
}
|
||||
|
||||
func IdentityAffineMatrix(dimension int) *AffineMatrix {
|
||||
if dimension < 0 {
|
||||
panic("invalid dimension")
|
||||
}
|
||||
matrix := NewAffineMatrix(dimension)
|
||||
for i := 0; i < dimension-1; i++ {
|
||||
for j := 0; j < dimension; j++ {
|
||||
if i == j {
|
||||
matrix.elements[i*dimension+j] = 1
|
||||
} else {
|
||||
matrix.elements[i*dimension+j] = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
return matrix
|
||||
}
|
||||
|
||||
func (matrix *AffineMatrix) Clone() *AffineMatrix {
|
||||
result := NewAffineMatrix(matrix.dimension)
|
||||
copy(result.elements, matrix.elements)
|
||||
return result
|
||||
}
|
||||
|
||||
func (matrix *AffineMatrix) Element(i, j int) AffineMatrixElement {
|
||||
dimension := matrix.dimension
|
||||
if i < 0 || dimension <= i {
|
||||
panic("out of range index i")
|
||||
}
|
||||
if j < 0 || dimension <= j {
|
||||
panic("out of range index j")
|
||||
}
|
||||
if i == dimension-1 {
|
||||
if j == dimension-1 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return matrix.elements[i*dimension+j]
|
||||
}
|
||||
|
||||
func (matrix *AffineMatrix) SetElement(i, j int, element AffineMatrixElement) {
|
||||
dimension := matrix.dimension
|
||||
if i < 0 || dimension-1 <= i {
|
||||
panic("out of range index i")
|
||||
}
|
||||
if j < 0 || dimension <= j {
|
||||
panic("out of range index j")
|
||||
}
|
||||
matrix.elements[i*dimension+j] = element
|
||||
}
|
||||
|
||||
func (matrix *AffineMatrix) IsIdentity() bool {
|
||||
dimension := matrix.dimension
|
||||
for i := 0; i < dimension-1; i++ {
|
||||
for j := 0; j < dimension; j++ {
|
||||
element := matrix.elements[i*dimension+j]
|
||||
if i == j && element != 1 {
|
||||
return false
|
||||
} else if i != j && element != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: The arguments' names are strange even though they are not wrong.
|
||||
*/
|
||||
func (rhs *AffineMatrix) Concat(lhs *AffineMatrix) *AffineMatrix {
|
||||
dimension := lhs.dimension
|
||||
if dimension != rhs.dimension {
|
||||
panic("diffrent-sized matrices can't be concatenated")
|
||||
}
|
||||
result := NewAffineMatrix(dimension)
|
||||
|
||||
for i := 0; i < dimension-1; i++ {
|
||||
for j := 0; j < dimension; j++ {
|
||||
var element AffineMatrixElement = 0.0
|
||||
for k := 0; k < dimension-1; k++ {
|
||||
element += lhs.elements[i*dimension+k] *
|
||||
rhs.elements[k*dimension+j]
|
||||
}
|
||||
if j == dimension-1 {
|
||||
element += lhs.elements[i*dimension+dimension-1]
|
||||
}
|
||||
result.elements[i*dimension+j] = element
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
179
graphics/affine_matrix_test.go
Normal file
179
graphics/affine_matrix_test.go
Normal file
@ -0,0 +1,179 @@
|
||||
package graphics_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
. "."
|
||||
)
|
||||
|
||||
func setElements(matrix *AffineMatrix, elements [][]AffineMatrixElement) {
|
||||
dimension := len(elements) + 1
|
||||
for i := 0; i < dimension-1; i++ {
|
||||
for j := 0; j < dimension; j++ {
|
||||
matrix.SetElement(i, j, elements[i][j])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAffineMatrixElement(t *testing.T) {
|
||||
matrix := NewAffineMatrix(4)
|
||||
matrix.SetElement(0, 0, 1)
|
||||
matrix.SetElement(0, 1, 2)
|
||||
matrix.SetElement(0, 2, 3)
|
||||
expected := [][]AffineMatrixElement{
|
||||
{1, 2, 3, 0},
|
||||
{0, 0, 0, 0},
|
||||
{0, 0, 0, 0},
|
||||
{0, 0, 0, 1},
|
||||
}
|
||||
for i := 0; i < 4; i++ {
|
||||
for j := 0; j < 4; j++ {
|
||||
got := matrix.Element(i, j)
|
||||
want := expected[i][j]
|
||||
if want != got {
|
||||
t.Errorf("matrix.Element(%d, %d) = %f, want %f",
|
||||
i, j, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matrix.SetElement(1, 0, 4)
|
||||
matrix.SetElement(1, 1, 5)
|
||||
matrix.SetElement(2, 3, 6)
|
||||
expected = [][]AffineMatrixElement{
|
||||
{1, 2, 3, 0},
|
||||
{4, 5, 0, 0},
|
||||
{0, 0, 0, 6},
|
||||
{0, 0, 0, 1},
|
||||
}
|
||||
for i := 0; i < 4; i++ {
|
||||
for j := 0; j < 4; j++ {
|
||||
got := matrix.Element(i, j)
|
||||
want := expected[i][j]
|
||||
if want != got {
|
||||
t.Errorf("matrix.Element(%d, %d) = %f, want %f",
|
||||
i, j, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAffineMatrixIsIdentity(t *testing.T) {
|
||||
matrix := NewAffineMatrix(4)
|
||||
matrix.SetElement(0, 0, 1)
|
||||
matrix.SetElement(1, 1, 1)
|
||||
matrix.SetElement(2, 2, 1)
|
||||
got := matrix.IsIdentity()
|
||||
want := true
|
||||
if want != got {
|
||||
t.Errorf("matrix.IsIdentity() = %t, want %t", got, want)
|
||||
}
|
||||
|
||||
matrix2 := matrix.Clone()
|
||||
got = matrix2.IsIdentity()
|
||||
want = true
|
||||
if want != got {
|
||||
t.Errorf("matrix2.IsIdentity() = %t, want %t", got, want)
|
||||
}
|
||||
|
||||
matrix2.SetElement(0, 1, 1)
|
||||
got = matrix2.IsIdentity()
|
||||
want = false
|
||||
if want != got {
|
||||
t.Errorf("matrix2.IsIdentity() = %t, want %t", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAffineMatrixConcat(t *testing.T) {
|
||||
matrix1 := IdentityAffineMatrix(3)
|
||||
matrix2 := IdentityAffineMatrix(3)
|
||||
setElements(matrix1, [][]AffineMatrixElement{
|
||||
{2, 0, 0},
|
||||
{0, 2, 0},
|
||||
})
|
||||
setElements(matrix2, [][]AffineMatrixElement{
|
||||
{1, 0, 1},
|
||||
{0, 1, 1},
|
||||
})
|
||||
|
||||
// TODO: 'matrix1x2' may not be a good name.
|
||||
matrix1x2 := matrix1.Concat(matrix2)
|
||||
expected := [][]AffineMatrixElement{
|
||||
{2, 0, 1},
|
||||
{0, 2, 1},
|
||||
{0, 0, 1},
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
for j := 0; j < 3; j++ {
|
||||
got := matrix1x2.Element(i, j)
|
||||
want := expected[i][j]
|
||||
if want != got {
|
||||
t.Errorf("matrix1x2.Element(%d, %d) = %f, want %f",
|
||||
i, j, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matrix2x1 := matrix2.Concat(matrix1)
|
||||
expected = [][]AffineMatrixElement{
|
||||
{2, 0, 2},
|
||||
{0, 2, 2},
|
||||
{0, 0, 1},
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
for j := 0; j < 3; j++ {
|
||||
got := matrix2x1.Element(i, j)
|
||||
want := expected[i][j]
|
||||
if want != got {
|
||||
t.Errorf("matrix2x1.Element(%d, %d) = %f, want %f",
|
||||
i, j, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matrix3 := NewAffineMatrix(4)
|
||||
matrix4 := NewAffineMatrix(4)
|
||||
setElements(matrix3, [][]AffineMatrixElement{
|
||||
{1, 2, 3, 4},
|
||||
{5, 6, 7, 8},
|
||||
{9, 10, 11, 12},
|
||||
})
|
||||
setElements(matrix4, [][]AffineMatrixElement{
|
||||
{13, 14, 15, 16},
|
||||
{17, 18, 19, 20},
|
||||
{21, 22, 23, 24},
|
||||
})
|
||||
|
||||
matrix3x4 := matrix3.Concat(matrix4)
|
||||
expected = [][]AffineMatrixElement{
|
||||
{218, 260, 302, 360},
|
||||
{278, 332, 386, 460},
|
||||
{338, 404, 470, 560},
|
||||
{0, 0, 0, 1}}
|
||||
for i := 0; i < 4; i++ {
|
||||
for j := 0; j < 4; j++ {
|
||||
got := matrix3x4.Element(i, j)
|
||||
want := expected[i][j]
|
||||
if want != got {
|
||||
t.Errorf("matrix3x4.Element(%d, %d) = %f, want %f",
|
||||
i, j, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matrix4x3 := matrix4.Concat(matrix3)
|
||||
expected = [][]AffineMatrixElement{
|
||||
{110, 116, 122, 132},
|
||||
{314, 332, 350, 376},
|
||||
{518, 548, 578, 620},
|
||||
{0, 0, 0, 1}}
|
||||
for i := 0; i < 4; i++ {
|
||||
for j := 0; j < 4; j++ {
|
||||
got := matrix4x3.Element(i, j)
|
||||
want := expected[i][j]
|
||||
if want != got {
|
||||
t.Errorf("matrix4x3.Element(%d, %d) = %f, want %f",
|
||||
i, j, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
graphics/color_matrix.go
Normal file
19
graphics/color_matrix.go
Normal file
@ -0,0 +1,19 @@
|
||||
package graphics
|
||||
|
||||
type ColorMatrix struct {
|
||||
AffineMatrix
|
||||
}
|
||||
|
||||
const colorMatrixDimension = 5
|
||||
|
||||
func NewColorMatrix() *ColorMatrix {
|
||||
return &ColorMatrix{*NewAffineMatrix(colorMatrixDimension)}
|
||||
}
|
||||
|
||||
func IdentityColorMatrix() *ColorMatrix {
|
||||
return &ColorMatrix{*IdentityAffineMatrix(colorMatrixDimension)}
|
||||
}
|
||||
|
||||
func (matrix *ColorMatrix) Clone() *ColorMatrix {
|
||||
return &ColorMatrix{*(matrix.AffineMatrix.Clone())}
|
||||
}
|
53
graphics/device.go
Normal file
53
graphics/device.go
Normal file
@ -0,0 +1,53 @@
|
||||
package graphics
|
||||
|
||||
// #cgo LDFLAGS: -framework OpenGL
|
||||
//
|
||||
// #include <OpenGL/gl.h>
|
||||
// #include <stdlib.h>
|
||||
import "C"
|
||||
|
||||
type device struct {
|
||||
screenWidth int
|
||||
screenHeight int
|
||||
screenScale int
|
||||
graphicsContext *GraphicsContext
|
||||
offscreenTexture *Texture
|
||||
drawFunc func(*GraphicsContext, *Texture)
|
||||
}
|
||||
|
||||
// This method should be called on the UI thread??
|
||||
func newDevice(screenWidth, screenHeight, screenScale int, drawFunc func(*GraphicsContext, *Texture)) *device {
|
||||
return &device{
|
||||
screenWidth: screenWidth,
|
||||
screenHeight: screenHeight,
|
||||
screenScale: screenScale,
|
||||
graphicsContext: newGraphicsContext(screenWidth, screenHeight, screenScale),
|
||||
offscreenTexture: NewTexture(screenWidth, screenHeight),
|
||||
drawFunc: drawFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// This method should be called on the UI thread??
|
||||
func (d *device) Update() {
|
||||
g := d.graphicsContext
|
||||
// g.initialize()
|
||||
C.glEnable(C.GL_TEXTURE_2D)
|
||||
C.glTexParameteri(C.GL_TEXTURE_2D, C.GL_TEXTURE_MIN_FILTER, C.GL_NEAREST)
|
||||
C.glTexParameteri(C.GL_TEXTURE_2D, C.GL_TEXTURE_MAG_FILTER, C.GL_NEAREST)
|
||||
g.SetOffscreen(d.offscreenTexture)
|
||||
g.Clear()
|
||||
d.drawFunc(g, d.offscreenTexture)
|
||||
g.flush()
|
||||
C.glTexParameteri(C.GL_TEXTURE_2D, C.GL_TEXTURE_MIN_FILTER, C.GL_LINEAR)
|
||||
C.glTexParameteri(C.GL_TEXTURE_2D, C.GL_TEXTURE_MAG_FILTER, C.GL_LINEAR)
|
||||
g.resetOffscreen()
|
||||
g.Clear()
|
||||
|
||||
geometryMatrix := IdentityGeometryMatrix()
|
||||
geometryMatrix.SetA(AffineMatrixElement(g.screenScale))
|
||||
geometryMatrix.SetD(AffineMatrixElement(g.screenScale))
|
||||
g.DrawTexture(d.offscreenTexture,
|
||||
0, 0, d.screenWidth, d.screenHeight,
|
||||
geometryMatrix, IdentityColorMatrix())
|
||||
g.flush()
|
||||
}
|
67
graphics/geometry_matrix.go
Normal file
67
graphics/geometry_matrix.go
Normal file
@ -0,0 +1,67 @@
|
||||
package graphics
|
||||
|
||||
type GeometryMatrix struct {
|
||||
AffineMatrix
|
||||
}
|
||||
|
||||
const geometryMatrixDimension = 3
|
||||
|
||||
func NewGeometryMatrix() *GeometryMatrix {
|
||||
return &GeometryMatrix{*NewAffineMatrix(geometryMatrixDimension)}
|
||||
}
|
||||
|
||||
func IdentityGeometryMatrix() *GeometryMatrix {
|
||||
return &GeometryMatrix{*IdentityAffineMatrix(geometryMatrixDimension)}
|
||||
}
|
||||
|
||||
func (matrix *GeometryMatrix) Clone() *GeometryMatrix {
|
||||
return &GeometryMatrix{*(matrix.AffineMatrix.Clone())}
|
||||
}
|
||||
|
||||
func (matrix *GeometryMatrix) A() AffineMatrixElement {
|
||||
return matrix.Element(0, 0)
|
||||
}
|
||||
|
||||
func (matrix *GeometryMatrix) B() AffineMatrixElement {
|
||||
return matrix.Element(0, 1)
|
||||
}
|
||||
|
||||
func (matrix *GeometryMatrix) C() AffineMatrixElement {
|
||||
return matrix.Element(1, 0)
|
||||
}
|
||||
|
||||
func (matrix *GeometryMatrix) D() AffineMatrixElement {
|
||||
return matrix.Element(1, 1)
|
||||
}
|
||||
|
||||
func (matrix *GeometryMatrix) Tx() AffineMatrixElement {
|
||||
return matrix.Element(0, 2)
|
||||
}
|
||||
|
||||
func (matrix *GeometryMatrix) Ty() AffineMatrixElement {
|
||||
return matrix.Element(1, 2)
|
||||
}
|
||||
|
||||
func (matrix *GeometryMatrix) SetA(a AffineMatrixElement) {
|
||||
matrix.SetElement(0, 0, a)
|
||||
}
|
||||
|
||||
func (matrix *GeometryMatrix) SetB(b AffineMatrixElement) {
|
||||
matrix.SetElement(0, 1, b)
|
||||
}
|
||||
|
||||
func (matrix *GeometryMatrix) SetC(c AffineMatrixElement) {
|
||||
matrix.SetElement(1, 0, c)
|
||||
}
|
||||
|
||||
func (matrix *GeometryMatrix) SetD(d AffineMatrixElement) {
|
||||
matrix.SetElement(1, 1, d)
|
||||
}
|
||||
|
||||
func (matrix *GeometryMatrix) SetTx(tx AffineMatrixElement) {
|
||||
matrix.SetElement(0, 2, tx)
|
||||
}
|
||||
|
||||
func (matrix *GeometryMatrix) SetTy(ty AffineMatrixElement) {
|
||||
matrix.SetElement(1, 2, ty)
|
||||
}
|
61
graphics/geometry_matrix_test.go
Normal file
61
graphics/geometry_matrix_test.go
Normal file
@ -0,0 +1,61 @@
|
||||
package graphics_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
. "."
|
||||
)
|
||||
|
||||
func TestGeometryMatrixElements(t *testing.T) {
|
||||
matrix := NewGeometryMatrix()
|
||||
matrix.SetA(1)
|
||||
matrix.SetB(2)
|
||||
matrix.SetC(3)
|
||||
matrix.SetD(4)
|
||||
matrix.SetTx(5)
|
||||
matrix.SetTy(6)
|
||||
|
||||
got := matrix.A()
|
||||
want := AffineMatrixElement(1)
|
||||
if want != got {
|
||||
t.Errorf("matrix.A() = %f, want %f", got, want)
|
||||
}
|
||||
|
||||
got = matrix.B()
|
||||
want = AffineMatrixElement(2)
|
||||
if want != got {
|
||||
t.Errorf("matrix.B() = %f, want %f", got, want)
|
||||
}
|
||||
|
||||
got = matrix.C()
|
||||
want = AffineMatrixElement(3)
|
||||
if want != got {
|
||||
t.Errorf("matrix.C() = %f, want %f", got, want)
|
||||
}
|
||||
|
||||
got = matrix.D()
|
||||
want = AffineMatrixElement(4)
|
||||
if want != got {
|
||||
t.Errorf("matrix.D() = %f, want %f", got, want)
|
||||
}
|
||||
|
||||
got = matrix.Tx()
|
||||
want = AffineMatrixElement(5)
|
||||
if want != got {
|
||||
t.Errorf("matrix.Tx() = %f, want %f", got, want)
|
||||
}
|
||||
|
||||
got = matrix.Ty()
|
||||
want = AffineMatrixElement(6)
|
||||
if want != got {
|
||||
t.Errorf("matrix.Ty() = %f, want %f", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeometryIdentity(t *testing.T) {
|
||||
matrix := IdentityGeometryMatrix()
|
||||
got := matrix.IsIdentity()
|
||||
want := true
|
||||
if want != got {
|
||||
t.Errorf("matrix.IsIdentity() = %t, want %t", got, want)
|
||||
}
|
||||
}
|
260
graphics/graphics_context.go
Normal file
260
graphics/graphics_context.go
Normal file
@ -0,0 +1,260 @@
|
||||
package graphics
|
||||
|
||||
// #cgo LDFLAGS: -framework OpenGL
|
||||
//
|
||||
// #include <OpenGL/gl.h>
|
||||
// #include <stdlib.h>
|
||||
import "C"
|
||||
import (
|
||||
"image/color"
|
||||
"math"
|
||||
"unsafe"
|
||||
"../ui"
|
||||
)
|
||||
|
||||
type GraphicsContext struct {
|
||||
screenWidth int
|
||||
screenHeight int
|
||||
screenScale int
|
||||
mainFramebuffer C.GLuint
|
||||
projectionMatrix [16]float32
|
||||
currentShaderProgram C.GLuint
|
||||
framebuffers map[C.GLuint]C.GLuint
|
||||
}
|
||||
|
||||
// This method should be called on the UI thread.
|
||||
func newGraphicsContext(screenWidth, screenHeight, screenScale int) *GraphicsContext {
|
||||
context := &GraphicsContext{
|
||||
screenWidth: screenWidth,
|
||||
screenHeight: screenHeight,
|
||||
screenScale: screenScale,
|
||||
mainFramebuffer: 0,
|
||||
framebuffers: map[C.GLuint]C.GLuint{},
|
||||
}
|
||||
// main framebuffer should be created sooner than any other framebuffers!
|
||||
mainFramebuffer := C.GLint(0)
|
||||
C.glGetIntegerv(C.GL_FRAMEBUFFER_BINDING, &mainFramebuffer)
|
||||
context.mainFramebuffer = C.GLuint(mainFramebuffer)
|
||||
return context
|
||||
}
|
||||
|
||||
func (context *GraphicsContext) Clear() {
|
||||
ui.ExecuteOnUIThread(func() {
|
||||
C.glClearColor(0, 0, 0, 1)
|
||||
C.glClear(C.GL_COLOR_BUFFER_BIT)
|
||||
})
|
||||
}
|
||||
|
||||
func (context *GraphicsContext) Fill(color color.Color) {
|
||||
r, g, b, a := color.RGBA()
|
||||
ui.ExecuteOnUIThread(func() {
|
||||
max := 65535.0
|
||||
C.glClearColor(
|
||||
C.GLclampf(float64(r) / max),
|
||||
C.GLclampf(float64(g) / max),
|
||||
C.GLclampf(float64(b) / max),
|
||||
C.GLclampf(float64(a) / max))
|
||||
C.glClear(C.GL_COLOR_BUFFER_BIT)
|
||||
})
|
||||
}
|
||||
|
||||
func (context *GraphicsContext) DrawRect(x, y, width, height int, color color.Color) {
|
||||
}
|
||||
|
||||
func (context *GraphicsContext) DrawTexture(texture *Texture,
|
||||
srcX, srcY, srcWidth, srcHeight int,
|
||||
geometryMatrix *GeometryMatrix, colorMatrix *ColorMatrix) {
|
||||
geometryMatrix = geometryMatrix.Clone()
|
||||
colorMatrix = colorMatrix.Clone()
|
||||
|
||||
ui.ExecuteOnUIThread(func() {
|
||||
context.setShaderProgram(geometryMatrix, colorMatrix)
|
||||
C.glBindTexture(C.GL_TEXTURE_2D, texture.id)
|
||||
|
||||
x1 := float32(0)
|
||||
x2 := float32(srcWidth)
|
||||
y1 := float32(0)
|
||||
y2 := float32(srcHeight)
|
||||
vertex := [...]float32{
|
||||
x1, y1,
|
||||
x2, y1,
|
||||
x1, y2,
|
||||
x2, y2,
|
||||
}
|
||||
|
||||
tu1 := float32(srcX) / float32(texture.TextureWidth)
|
||||
tu2 := float32(srcX + srcWidth) / float32(texture.TextureWidth)
|
||||
tv1 := float32(srcY) / float32(texture.TextureHeight)
|
||||
tv2 := float32(srcY + srcHeight) / float32(texture.TextureHeight)
|
||||
texCoord := [...]float32{
|
||||
tu1, tv1,
|
||||
tu2, tv1,
|
||||
tu1, tv2,
|
||||
tu2, tv2,
|
||||
}
|
||||
|
||||
vertexAttrLocation := getAttributeLocation(context.currentShaderProgram, "vertex")
|
||||
textureAttrLocation := getAttributeLocation(context.currentShaderProgram, "texture")
|
||||
|
||||
C.glEnableClientState(C.GL_VERTEX_ARRAY)
|
||||
C.glEnableClientState(C.GL_TEXTURE_COORD_ARRAY)
|
||||
C.glEnableVertexAttribArray(C.GLuint(vertexAttrLocation))
|
||||
C.glEnableVertexAttribArray(C.GLuint(textureAttrLocation))
|
||||
C.glVertexAttribPointer(C.GLuint(vertexAttrLocation), 2, C.GL_FLOAT, C.GL_FALSE,
|
||||
0, unsafe.Pointer(&vertex[0]))
|
||||
C.glVertexAttribPointer(C.GLuint(textureAttrLocation), 2, C.GL_FLOAT, C.GL_FALSE,
|
||||
0, unsafe.Pointer(&texCoord[0]))
|
||||
C.glDrawArrays(C.GL_TRIANGLE_STRIP, 0, 4)
|
||||
C.glDisableVertexAttribArray(C.GLuint(textureAttrLocation))
|
||||
C.glDisableVertexAttribArray(C.GLuint(vertexAttrLocation))
|
||||
C.glDisableClientState(C.GL_TEXTURE_COORD_ARRAY)
|
||||
C.glDisableClientState(C.GL_VERTEX_ARRAY)
|
||||
})
|
||||
}
|
||||
|
||||
func (context *GraphicsContext) SetOffscreen(texture *Texture) {
|
||||
ui.ExecuteOnUIThread(func() {
|
||||
framebuffer := C.GLuint(0)
|
||||
if texture != nil {
|
||||
framebuffer = context.getFramebuffer(texture)
|
||||
} else {
|
||||
framebuffer = context.mainFramebuffer
|
||||
}
|
||||
C.glBindFramebuffer(C.GL_FRAMEBUFFER, framebuffer)
|
||||
// TODO: assert glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE
|
||||
C.glEnable(C.GL_BLEND)
|
||||
C.glBlendFunc(C.GL_SRC_ALPHA, C.GL_ONE_MINUS_SRC_ALPHA)
|
||||
|
||||
width, height, tx, ty := 0, 0, 0, 0
|
||||
if framebuffer != context.mainFramebuffer {
|
||||
width = texture.TextureWidth
|
||||
height = texture.TextureHeight
|
||||
tx = -1
|
||||
ty = -1
|
||||
} else {
|
||||
width = context.screenWidth * context.screenScale
|
||||
height = -1 * context.screenHeight * context.screenScale
|
||||
tx = -1
|
||||
ty = 1
|
||||
}
|
||||
C.glViewport(0, 0,
|
||||
C.GLsizei(math.Abs(float64(width))),
|
||||
C.GLsizei(math.Abs(float64(height))))
|
||||
e11 := float32(2.0 / width)
|
||||
e22 := float32(2.0 / height)
|
||||
e41 := float32(tx)
|
||||
e42 := float32(ty)
|
||||
context.projectionMatrix = [...]float32{
|
||||
e11, 0, 0, 0,
|
||||
0, e22, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
e41, e42, 0, 1,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (context *GraphicsContext) resetOffscreen() {
|
||||
context.SetOffscreen(nil)
|
||||
}
|
||||
|
||||
// This method should be called on the UI thread.
|
||||
func (context *GraphicsContext) flush() {
|
||||
C.glFlush()
|
||||
}
|
||||
|
||||
// This method should be called on the UI thread.
|
||||
func (context *GraphicsContext) setShaderProgram(
|
||||
geometryMatrix *GeometryMatrix, colorMatrix *ColorMatrix) {
|
||||
program := C.GLuint(0)
|
||||
if colorMatrix.IsIdentity() {
|
||||
program = regularShaderProgram
|
||||
} else {
|
||||
program = colorMatrixShaderProgram
|
||||
}
|
||||
// TODO: cache and skip?
|
||||
C.glUseProgram(program)
|
||||
context.currentShaderProgram = program
|
||||
|
||||
C.glUniformMatrix4fv(getUniformLocation(program, "projection_matrix"),
|
||||
1, C.GL_FALSE,
|
||||
(*C.GLfloat)(&context.projectionMatrix[0]))
|
||||
|
||||
a := float32(geometryMatrix.A())
|
||||
b := float32(geometryMatrix.B())
|
||||
c := float32(geometryMatrix.C())
|
||||
d := float32(geometryMatrix.D())
|
||||
tx := float32(geometryMatrix.Tx())
|
||||
ty := float32(geometryMatrix.Ty())
|
||||
glModelviewMatrix := [...]float32{
|
||||
a, c, 0, 0,
|
||||
b, d, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
tx, ty, 0, 1,
|
||||
}
|
||||
C.glUniformMatrix4fv(getUniformLocation(program, "modelview_matrix"),
|
||||
1, C.GL_FALSE,
|
||||
(*C.GLfloat)(&glModelviewMatrix[0]))
|
||||
|
||||
C.glUniform1i(getUniformLocation(program, "texture"), 0)
|
||||
|
||||
if program != colorMatrixShaderProgram {
|
||||
return
|
||||
}
|
||||
|
||||
e := [4][5]float32{}
|
||||
for i := 0; i < 4; i++ {
|
||||
for j := 0; j < 5; j++ {
|
||||
e[i][j] = float32(colorMatrix.Element(i, j))
|
||||
}
|
||||
}
|
||||
|
||||
glColorMatrix := [...]float32{
|
||||
e[0][0], e[0][1], e[0][2], e[0][3],
|
||||
e[1][0], e[1][1], e[1][2], e[1][3],
|
||||
e[2][0], e[2][1], e[2][2], e[2][3],
|
||||
e[3][0], e[3][1], e[3][2], e[3][3],
|
||||
}
|
||||
C.glUniformMatrix4fv(getUniformLocation(program, "color_matrix"),
|
||||
1, C.GL_FALSE, (*C.GLfloat)(&glColorMatrix[0]))
|
||||
|
||||
glColorMatrixTranslation := [...]float32{
|
||||
e[0][4], e[1][4], e[2][4], e[3][4],
|
||||
}
|
||||
C.glUniform4fv(getUniformLocation(program, "color_matrix_translation"),
|
||||
1, (*C.GLfloat)(&glColorMatrixTranslation[0]))
|
||||
}
|
||||
|
||||
// This method should be called on the UI thread.
|
||||
func (context *GraphicsContext) getFramebuffer(texture *Texture) C.GLuint{
|
||||
framebuffer, ok := context.framebuffers[texture.id]
|
||||
if ok {
|
||||
return framebuffer
|
||||
}
|
||||
|
||||
newFramebuffer := C.GLuint(0)
|
||||
C.glGenFramebuffers(1, &newFramebuffer)
|
||||
|
||||
origFramebuffer := C.GLint(0)
|
||||
C.glGetIntegerv(C.GL_FRAMEBUFFER_BINDING, &origFramebuffer)
|
||||
C.glBindFramebuffer(C.GL_FRAMEBUFFER, newFramebuffer)
|
||||
C.glFramebufferTexture2D(C.GL_FRAMEBUFFER, C.GL_COLOR_ATTACHMENT0,
|
||||
C.GL_TEXTURE_2D, texture.id, 0)
|
||||
C.glBindFramebuffer(C.GL_FRAMEBUFFER, C.GLuint(origFramebuffer))
|
||||
if C.glCheckFramebufferStatus(C.GL_FRAMEBUFFER) != C.GL_FRAMEBUFFER_COMPLETE {
|
||||
panic("creating framebuffer failed")
|
||||
}
|
||||
|
||||
context.framebuffers[texture.id] = newFramebuffer
|
||||
return newFramebuffer
|
||||
}
|
||||
|
||||
// This method should be called on the UI thread.
|
||||
func (context *GraphicsContext) deleteFramebuffer(texture *Texture) {
|
||||
framebuffer, ok := context.framebuffers[texture.id]
|
||||
if !ok {
|
||||
// TODO: panic?
|
||||
return
|
||||
}
|
||||
C.glDeleteFramebuffers(1, &framebuffer)
|
||||
delete(context.framebuffers, texture.id)
|
||||
}
|
180
graphics/shader.go
Normal file
180
graphics/shader.go
Normal file
@ -0,0 +1,180 @@
|
||||
package graphics
|
||||
|
||||
// #cgo LDFLAGS: -framework OpenGL
|
||||
//
|
||||
// #include <OpenGL/gl.h>
|
||||
// #include <stdlib.h>
|
||||
import "C"
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type shader struct {
|
||||
id C.GLuint
|
||||
name string
|
||||
source string
|
||||
}
|
||||
|
||||
var (
|
||||
vertexShader = &shader{
|
||||
id: 0,
|
||||
name: "vertex_shader",
|
||||
source: `
|
||||
attribute /*highp*/ vec2 vertex;
|
||||
attribute /*highp*/ vec2 texture;
|
||||
uniform /*highp*/ mat4 projection_matrix;
|
||||
uniform /*highp*/ mat4 modelview_matrix;
|
||||
varying /*highp*/ vec2 tex_coord;
|
||||
|
||||
void main(void) {
|
||||
tex_coord = texture;
|
||||
gl_Position = projection_matrix * modelview_matrix * vec4(vertex, 0, 1);
|
||||
}
|
||||
`,
|
||||
}
|
||||
fragmentShader = &shader{
|
||||
id: 0,
|
||||
name: "fragment_shader",
|
||||
source: `
|
||||
uniform /*lowp*/ sampler2D texture;
|
||||
varying /*highp*/ vec2 tex_coord;
|
||||
|
||||
void main(void) {
|
||||
gl_FragColor = texture2D(texture, tex_coord);
|
||||
}
|
||||
`,
|
||||
}
|
||||
colorMatrixShader = &shader{
|
||||
id: 0,
|
||||
name: "color_matrix_shader",
|
||||
source: `
|
||||
uniform /*highp*/ sampler2D texture;
|
||||
uniform /*lowp*/ mat4 color_matrix;
|
||||
uniform /*lowp*/ vec4 color_matrix_translation;
|
||||
varying /*highp*/ vec2 tex_coord;
|
||||
|
||||
void main(void) {
|
||||
/*lowp*/ vec4 color = texture2D(texture, tex_coord);
|
||||
gl_FragColor = (color_matrix * color) + color_matrix_translation;
|
||||
}
|
||||
`,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
regularShaderProgram = C.GLuint(0)
|
||||
colorMatrixShaderProgram = C.GLuint(0)
|
||||
)
|
||||
|
||||
func (s *shader) compile() {
|
||||
csource := (*C.GLchar)(C.CString(s.source))
|
||||
// TODO: defer?
|
||||
//defer C.free(unsafe.Pointer(csource))
|
||||
|
||||
C.glShaderSource(s.id, 1, &csource, nil)
|
||||
C.glCompileShader(s.id)
|
||||
|
||||
compiled := C.GLint(C.GL_FALSE)
|
||||
C.glGetShaderiv(s.id, C.GL_COMPILE_STATUS, &compiled)
|
||||
if compiled == C.GL_FALSE {
|
||||
s.showShaderLog()
|
||||
panic("shader compile failed: " + s.name)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *shader) showShaderLog() {
|
||||
logSize := C.GLint(0)
|
||||
C.glGetShaderiv(s.id, C.GL_INFO_LOG_LENGTH, &logSize)
|
||||
if logSize == 0 {
|
||||
return
|
||||
}
|
||||
length := C.GLsizei(0)
|
||||
buffer := make([]C.GLchar, logSize)
|
||||
C.glGetShaderInfoLog(s.id, C.GLsizei(logSize), &length, &buffer[0])
|
||||
|
||||
message := string(C.GoBytes(unsafe.Pointer(&buffer[0]), C.int(length)))
|
||||
print("shader error (", s.name, "):\n", message)
|
||||
}
|
||||
|
||||
func createProgram(shaders ...*shader) C.GLuint {
|
||||
program := C.glCreateProgram()
|
||||
for _, shader := range shaders {
|
||||
C.glAttachShader(program, shader.id)
|
||||
}
|
||||
C.glLinkProgram(program)
|
||||
linked := C.GLint(C.GL_FALSE)
|
||||
C.glGetProgramiv(program, C.GL_LINK_STATUS, &linked)
|
||||
if linked == C.GL_FALSE {
|
||||
panic("program error")
|
||||
}
|
||||
return program
|
||||
}
|
||||
|
||||
func initializeShaders() {
|
||||
// TODO: when should this function be called?
|
||||
vertexShader.id = C.glCreateShader(C.GL_VERTEX_SHADER)
|
||||
if vertexShader.id == 0 {
|
||||
panic("creating shader failed")
|
||||
}
|
||||
fragmentShader.id = C.glCreateShader(C.GL_FRAGMENT_SHADER)
|
||||
if fragmentShader.id == 0 {
|
||||
panic("creating shader failed")
|
||||
}
|
||||
colorMatrixShader.id = C.glCreateShader(C.GL_FRAGMENT_SHADER)
|
||||
if colorMatrixShader.id == 0 {
|
||||
panic("creating shader failed")
|
||||
}
|
||||
|
||||
vertexShader.compile()
|
||||
fragmentShader.compile()
|
||||
colorMatrixShader.compile()
|
||||
|
||||
regularShaderProgram = createProgram(vertexShader, fragmentShader)
|
||||
colorMatrixShaderProgram = createProgram(vertexShader, colorMatrixShader)
|
||||
|
||||
C.glDeleteShader(vertexShader.id)
|
||||
C.glDeleteShader(fragmentShader.id)
|
||||
C.glDeleteShader(colorMatrixShader.id)
|
||||
}
|
||||
|
||||
func isOpenGLES2() bool {
|
||||
// TODO: Implement!
|
||||
return false
|
||||
}
|
||||
|
||||
const (
|
||||
qualifierVariableTypeAttribute = iota
|
||||
qualifierVariableTypeUniform
|
||||
)
|
||||
|
||||
// This method should be called on the UI thread.
|
||||
// TODO: Can we cache the locations?
|
||||
func getLocation(program C.GLuint, name string, qualifierVariableType int) C.GLint {
|
||||
locationName := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(locationName))
|
||||
|
||||
location := C.GLint(-1)
|
||||
switch qualifierVariableType {
|
||||
case qualifierVariableTypeAttribute:
|
||||
location = C.glGetAttribLocation(program, (*C.GLchar)(locationName))
|
||||
case qualifierVariableTypeUniform:
|
||||
location = C.glGetUniformLocation(program, (*C.GLchar)(locationName))
|
||||
default:
|
||||
panic("no reach")
|
||||
}
|
||||
if location == -1 {
|
||||
panic("glGetUniformLocation failed")
|
||||
}
|
||||
|
||||
return location
|
||||
}
|
||||
|
||||
// This method should be called on the UI thread.
|
||||
func getAttributeLocation(program C.GLuint, name string) C.GLint {
|
||||
return getLocation(program, name, qualifierVariableTypeAttribute)
|
||||
}
|
||||
|
||||
// This method should be called on the UI thread.
|
||||
func getUniformLocation(program C.GLuint, name string) C.GLint {
|
||||
return getLocation(program, name, qualifierVariableTypeUniform)
|
||||
}
|
96
graphics/texture.go
Normal file
96
graphics/texture.go
Normal file
@ -0,0 +1,96 @@
|
||||
package graphics
|
||||
|
||||
// #cgo LDFLAGS: -framework OpenGL
|
||||
//
|
||||
// #include <OpenGL/gl.h>
|
||||
import "C"
|
||||
import (
|
||||
"image"
|
||||
"unsafe"
|
||||
"../ui"
|
||||
)
|
||||
|
||||
func Clp2(x uint64) uint64 {
|
||||
x -= 1
|
||||
x |= (x >> 1)
|
||||
x |= (x >> 2)
|
||||
x |= (x >> 4)
|
||||
x |= (x >> 8)
|
||||
x |= (x >> 16)
|
||||
x |= (x >> 32)
|
||||
return x + 1
|
||||
}
|
||||
|
||||
type Texture struct {
|
||||
id C.GLuint
|
||||
Width int
|
||||
Height int
|
||||
TextureWidth int
|
||||
TextureHeight int
|
||||
}
|
||||
|
||||
func createTexture(width, height int, pixels []uint8) *Texture{
|
||||
textureWidth := int(Clp2(uint64(width)))
|
||||
textureHeight := int(Clp2(uint64(height)))
|
||||
if width != textureWidth {
|
||||
panic("sorry, but width should be power of 2")
|
||||
}
|
||||
if height != textureHeight {
|
||||
panic("sorry, but height should be power of 2")
|
||||
}
|
||||
texture := &Texture{
|
||||
id: 0,
|
||||
Width: width,
|
||||
Height: height,
|
||||
TextureWidth: textureWidth,
|
||||
TextureHeight: textureHeight,
|
||||
}
|
||||
|
||||
ch := make(chan C.GLuint)
|
||||
ui.ExecuteOnUIThread(func() {
|
||||
textureID := C.GLuint(0)
|
||||
C.glGenTextures(1, (*C.GLuint)(&textureID))
|
||||
C.glPixelStorei(C.GL_UNPACK_ALIGNMENT, 4)
|
||||
C.glBindTexture(C.GL_TEXTURE_2D, C.GLuint(textureID))
|
||||
if textureID != 0 {
|
||||
panic("glBindTexture failed")
|
||||
}
|
||||
|
||||
ptr := unsafe.Pointer(nil)
|
||||
if pixels != nil {
|
||||
ptr = unsafe.Pointer(&pixels[0])
|
||||
}
|
||||
C.glTexImage2D(C.GL_TEXTURE_2D, 0, C.GL_RGBA,
|
||||
C.GLsizei(textureWidth), C.GLsizei(textureHeight),
|
||||
0, C.GL_RGBA, C.GL_UNSIGNED_BYTE, ptr)
|
||||
|
||||
C.glTexParameteri(C.GL_TEXTURE_2D, C.GL_TEXTURE_MAG_FILTER, C.GL_LINEAR)
|
||||
C.glTexParameteri(C.GL_TEXTURE_2D, C.GL_TEXTURE_MIN_FILTER, C.GL_LINEAR)
|
||||
C.glBindTexture(C.GL_TEXTURE_2D, 0)
|
||||
|
||||
ch<- textureID
|
||||
close(ch)
|
||||
})
|
||||
// TODO: should wait?
|
||||
go func() {
|
||||
texture.id = <-ch
|
||||
}()
|
||||
|
||||
return texture
|
||||
}
|
||||
|
||||
func NewTexture(width, height int) *Texture {
|
||||
return createTexture(width, height, nil)
|
||||
}
|
||||
|
||||
func NewTextureFromRGBA(image *image.RGBA) *Texture {
|
||||
return createTexture(image.Rect.Size().X, image.Rect.Size().Y, image.Pix)
|
||||
}
|
||||
|
||||
func (texture *Texture) IsAvailable() bool {
|
||||
return texture.id != 0
|
||||
}
|
||||
|
||||
func init() {
|
||||
// TODO: Initialize OpenGL here?
|
||||
}
|
27
graphics/texture_test.go
Normal file
27
graphics/texture_test.go
Normal file
@ -0,0 +1,27 @@
|
||||
package graphics_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
. "."
|
||||
)
|
||||
|
||||
func TestClp2(t *testing.T) {
|
||||
testCases := []struct {
|
||||
expected uint64
|
||||
arg uint64
|
||||
}{
|
||||
{256, 255},
|
||||
{256, 256},
|
||||
{512, 257},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
got := Clp2(testCase.arg)
|
||||
wanted := testCase.expected
|
||||
if wanted != got {
|
||||
t.Errorf("Clp(%d) = %d, wanted %d",
|
||||
testCase.arg, got, wanted)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user