opengl: Hide OpenGL context usages into internal/graphics package

This commit is contained in:
Hajime Hoshi 2017-05-31 02:09:27 +09:00
parent 62b364de2d
commit 7d181e3182
20 changed files with 131 additions and 190 deletions

View File

@ -16,12 +16,9 @@ package ebiten
import ( import (
"math" "math"
"sync/atomic"
"github.com/hajimehoshi/ebiten/internal/graphics" "github.com/hajimehoshi/ebiten/internal/graphics"
"github.com/hajimehoshi/ebiten/internal/opengl"
"github.com/hajimehoshi/ebiten/internal/restorable" "github.com/hajimehoshi/ebiten/internal/restorable"
"github.com/hajimehoshi/ebiten/internal/ui"
) )
func newGraphicsContext(f func(*Image) error) *graphicsContext { func newGraphicsContext(f func(*Image) error) *graphicsContext {
@ -36,17 +33,10 @@ type graphicsContext struct {
offscreen2 *Image // TODO: better name offscreen2 *Image // TODO: better name
screen *Image screen *Image
screenScale float64 screenScale float64
initialized int32 initialized bool
invalidated bool // browser only invalidated bool // browser only
} }
func (c *graphicsContext) GLContext() *opengl.Context {
if atomic.LoadInt32(&c.initialized) == 0 {
return nil
}
return ui.GLContext()
}
func (c *graphicsContext) Invalidate() { func (c *graphicsContext) Invalidate() {
// Note that this is called only on browsers so far. // Note that this is called only on browsers so far.
// TODO: On mobiles, this function is not called and instead IsTexture is called // TODO: On mobiles, this function is not called and instead IsTexture is called
@ -100,21 +90,21 @@ func (c *graphicsContext) SetSize(screenWidth, screenHeight int, screenScale flo
return nil return nil
} }
func (c *graphicsContext) initializeIfNeeded(context *opengl.Context) error { func (c *graphicsContext) initializeIfNeeded() error {
if atomic.LoadInt32(&c.initialized) == 0 { if !c.initialized {
if err := graphics.Reset(context); err != nil { if err := graphics.Reset(); err != nil {
return err return err
} }
atomic.StoreInt32(&c.initialized, 1) c.initialized = true
} }
r, err := c.needsRestoring(context) r, err := c.needsRestoring()
if err != nil { if err != nil {
return err return err
} }
if !r { if !r {
return nil return nil
} }
if err := c.restore(context); err != nil { if err := c.restore(); err != nil {
return err return err
} }
return nil return nil
@ -130,17 +120,17 @@ func drawWithFittingScale(dst *Image, src *Image) {
_ = dst.DrawImage(src, op) _ = dst.DrawImage(src, op)
} }
func (c *graphicsContext) drawToDefaultRenderTarget(context *opengl.Context) error { func (c *graphicsContext) drawToDefaultRenderTarget() error {
_ = c.screen.Clear() _ = c.screen.Clear()
drawWithFittingScale(c.screen, c.offscreen2) drawWithFittingScale(c.screen, c.offscreen2)
if err := graphics.FlushCommands(context); err != nil { if err := graphics.FlushCommands(); err != nil {
return err return err
} }
return nil return nil
} }
func (c *graphicsContext) UpdateAndDraw(context *opengl.Context, updateCount int) error { func (c *graphicsContext) UpdateAndDraw(updateCount int) error {
if err := c.initializeIfNeeded(context); err != nil { if err := c.initializeIfNeeded(); err != nil {
return err return err
} }
for i := 0; i < updateCount; i++ { for i := 0; i < updateCount; i++ {
@ -153,21 +143,21 @@ func (c *graphicsContext) UpdateAndDraw(context *opengl.Context, updateCount int
if 0 < updateCount { if 0 < updateCount {
drawWithFittingScale(c.offscreen2, c.offscreen) drawWithFittingScale(c.offscreen2, c.offscreen)
} }
if err := c.drawToDefaultRenderTarget(context); err != nil { if err := c.drawToDefaultRenderTarget(); err != nil {
return err return err
} }
// TODO: Add tests to check if this behavior is correct (#357) // TODO: Add tests to check if this behavior is correct (#357)
if err := restorable.ResolveStalePixels(context); err != nil { if err := restorable.ResolveStalePixels(); err != nil {
return err return err
} }
return nil return nil
} }
func (c *graphicsContext) restore(context *opengl.Context) error { func (c *graphicsContext) restore() error {
if err := graphics.Reset(context); err != nil { if err := graphics.Reset(); err != nil {
return err return err
} }
if err := restorable.Restore(context); err != nil { if err := restorable.Restore(); err != nil {
return err return err
} }
c.invalidated = false c.invalidated = false

View File

@ -16,10 +16,6 @@
package ebiten package ebiten
import ( func (c *graphicsContext) needsRestoring() (bool, error) {
"github.com/hajimehoshi/ebiten/internal/opengl"
)
func (c *graphicsContext) needsRestoring(context *opengl.Context) (bool, error) {
return c.invalidated, nil return c.invalidated, nil
} }

View File

@ -18,13 +18,12 @@ package ebiten
import ( import (
"github.com/hajimehoshi/ebiten/internal/graphics" "github.com/hajimehoshi/ebiten/internal/graphics"
"github.com/hajimehoshi/ebiten/internal/opengl"
) )
func (c *graphicsContext) needsRestoring(context *opengl.Context) (bool, error) { func (c *graphicsContext) needsRestoring() (bool, error) {
// FlushCommands is required because c.offscreen.impl might not have an actual texture. // FlushCommands is required because c.offscreen.impl might not have an actual texture.
if err := graphics.FlushCommands(context); err != nil { if err := graphics.FlushCommands(); err != nil {
return false, err return false, err
} }
return c.offscreen.restorable.IsInvalidated(context), nil return c.offscreen.restorable.IsInvalidated(), nil
} }

View File

@ -25,18 +25,6 @@ import (
"github.com/hajimehoshi/ebiten/internal/restorable" "github.com/hajimehoshi/ebiten/internal/restorable"
) )
func glContext() *opengl.Context {
// This is called from finalizers even when the context or the program is not set.
g, ok := theGraphicsContext.Load().(*graphicsContext)
if !ok {
return nil
}
if g == nil {
return nil
}
return g.GLContext()
}
// Image represents an image. // Image represents an image.
// The pixel format is alpha-premultiplied. // The pixel format is alpha-premultiplied.
// Image implements image.Image. // Image implements image.Image.
@ -166,7 +154,7 @@ func (i *Image) At(x, y int) color.Color {
return color.Transparent return color.Transparent
} }
// TODO: Error should be delayed until flushing. Do not panic here. // TODO: Error should be delayed until flushing. Do not panic here.
clr, err := i.restorable.At(x, y, glContext()) clr, err := i.restorable.At(x, y)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -27,7 +27,7 @@ import (
) )
type command interface { type command interface {
Exec(context *opengl.Context, indexOffsetInBytes int) error Exec(indexOffsetInBytes int) error
} }
type commandQueue struct { type commandQueue struct {
@ -113,11 +113,11 @@ func (q *commandQueue) commandGroups() [][]command {
return gs return gs
} }
func (q *commandQueue) Flush(context *opengl.Context) error { func (q *commandQueue) Flush() error {
q.m.Lock() q.m.Lock()
defer q.m.Unlock() defer q.m.Unlock()
// glViewport must be called at least at every frame on iOS. // glViewport must be called at least at every frame on iOS.
context.ResetViewportSize() opengl.GetContext().ResetViewportSize()
n := 0 n := 0
lastN := 0 lastN := 0
for _, g := range q.commandGroups() { for _, g := range q.commandGroups() {
@ -128,7 +128,7 @@ func (q *commandQueue) Flush(context *opengl.Context) error {
} }
} }
if 0 < n-lastN { if 0 < n-lastN {
context.BufferSubData(opengl.ArrayBuffer, q.vertices[lastN:n]) opengl.GetContext().BufferSubData(opengl.ArrayBuffer, q.vertices[lastN:n])
} }
// NOTE: WebGL doesn't seem to have Check gl.MAX_ELEMENTS_VERTICES or gl.MAX_ELEMENTS_INDICES so far. // NOTE: WebGL doesn't seem to have Check gl.MAX_ELEMENTS_VERTICES or gl.MAX_ELEMENTS_INDICES so far.
// Let's use them to compare to len(quads) in the future. // Let's use them to compare to len(quads) in the future.
@ -138,7 +138,7 @@ func (q *commandQueue) Flush(context *opengl.Context) error {
numc := len(g) numc := len(g)
indexOffsetInBytes := 0 indexOffsetInBytes := 0
for _, c := range g { for _, c := range g {
if err := c.Exec(context, indexOffsetInBytes); err != nil { if err := c.Exec(indexOffsetInBytes); err != nil {
return err return err
} }
if c, ok := c.(*drawImageCommand); ok { if c, ok := c.(*drawImageCommand); ok {
@ -148,7 +148,7 @@ func (q *commandQueue) Flush(context *opengl.Context) error {
} }
if 0 < numc { if 0 < numc {
// Call glFlush to prevent black flicking (especially on Android (#226) and iOS). // Call glFlush to prevent black flicking (especially on Android (#226) and iOS).
context.Flush() opengl.GetContext().Flush()
} }
lastN = n lastN = n
} }
@ -157,8 +157,8 @@ func (q *commandQueue) Flush(context *opengl.Context) error {
return nil return nil
} }
func FlushCommands(context *opengl.Context) error { func FlushCommands() error {
return theCommandQueue.Flush(context) return theCommandQueue.Flush()
} }
type fillCommand struct { type fillCommand struct {
@ -166,12 +166,12 @@ type fillCommand struct {
color color.RGBA color color.RGBA
} }
func (c *fillCommand) Exec(context *opengl.Context, indexOffsetInBytes int) error { func (c *fillCommand) Exec(indexOffsetInBytes int) error {
f, err := c.dst.createFramebufferIfNeeded(context) f, err := c.dst.createFramebufferIfNeeded()
if err != nil { if err != nil {
return err return err
} }
if err := f.setAsViewport(context); err != nil { if err := f.setAsViewport(); err != nil {
return err return err
} }
cr, cg, cb, ca := c.color.R, c.color.G, c.color.B, c.color.A cr, cg, cb, ca := c.color.R, c.color.G, c.color.B, c.color.A
@ -180,7 +180,7 @@ func (c *fillCommand) Exec(context *opengl.Context, indexOffsetInBytes int) erro
g := float64(cg) / max g := float64(cg) / max
b := float64(cb) / max b := float64(cb) / max
a := float64(ca) / max a := float64(ca) / max
return context.FillFramebuffer(r, g, b, a) return opengl.GetContext().FillFramebuffer(r, g, b, a)
} }
type drawImageCommand struct { type drawImageCommand struct {
@ -195,15 +195,15 @@ func QuadVertexSizeInBytes() int {
return 4 * theArrayBufferLayout.totalBytes() return 4 * theArrayBufferLayout.totalBytes()
} }
func (c *drawImageCommand) Exec(context *opengl.Context, indexOffsetInBytes int) error { func (c *drawImageCommand) Exec(indexOffsetInBytes int) error {
f, err := c.dst.createFramebufferIfNeeded(context) f, err := c.dst.createFramebufferIfNeeded()
if err != nil { if err != nil {
return err return err
} }
if err := f.setAsViewport(context); err != nil { if err := f.setAsViewport(); err != nil {
return err return err
} }
context.BlendFunc(c.mode) opengl.GetContext().BlendFunc(c.mode)
n := c.quadsNum() n := c.quadsNum()
if n == 0 { if n == 0 {
@ -214,7 +214,6 @@ func (c *drawImageCommand) Exec(context *opengl.Context, indexOffsetInBytes int)
p := &programContext{ p := &programContext{
state: &theOpenGLState, state: &theOpenGLState,
program: theOpenGLState.programTexture, program: theOpenGLState.programTexture,
context: context,
projectionMatrix: proj, projectionMatrix: proj,
texture: c.src.texture.native, texture: c.src.texture.native,
colorM: c.color, colorM: c.color,
@ -224,7 +223,7 @@ func (c *drawImageCommand) Exec(context *opengl.Context, indexOffsetInBytes int)
} }
// 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.
context.DrawElements(opengl.Triangles, 6*n, indexOffsetInBytes) opengl.GetContext().DrawElements(opengl.Triangles, 6*n, indexOffsetInBytes)
return nil return nil
} }
@ -263,29 +262,29 @@ type replacePixelsCommand struct {
pixels []uint8 pixels []uint8
} }
func (c *replacePixelsCommand) Exec(context *opengl.Context, indexOffsetInBytes int) error { func (c *replacePixelsCommand) Exec(indexOffsetInBytes int) error {
f, err := c.dst.createFramebufferIfNeeded(context) f, err := c.dst.createFramebufferIfNeeded()
if err != nil { if err != nil {
return err return err
} }
if err := f.setAsViewport(context); err != nil { if err := f.setAsViewport(); err != nil {
return err return err
} }
// Filling with non black or white color is required here for glTexSubImage2D. // Filling with non black or white color is required here for glTexSubImage2D.
// Very mysterious but this actually works (Issue #186). // Very mysterious but this actually works (Issue #186).
// This is needed even after fixing a shader bug at f537378f2a6a8ef56e1acf1c03034967b77c7b51. // This is needed even after fixing a shader bug at f537378f2a6a8ef56e1acf1c03034967b77c7b51.
if err := context.FillFramebuffer(0, 0, 0.5, 1); err != nil { if err := opengl.GetContext().FillFramebuffer(0, 0, 0.5, 1); err != nil {
return err return err
} }
// This is necessary on Android. We can't call glClear just before glTexSubImage2D without // This is necessary on Android. We can't call glClear just before glTexSubImage2D without
// glFlush. glTexSubImage2D didn't work without this hack at least on Nexus 5x (#211). // glFlush. glTexSubImage2D didn't work without this hack at least on Nexus 5x (#211).
// This also happens when a fillCommand precedes a replacePixelsCommand. // This also happens when a fillCommand precedes a replacePixelsCommand.
// TODO: Can we have a better way like optimizing commands? // TODO: Can we have a better way like optimizing commands?
context.Flush() opengl.GetContext().Flush()
if err := context.BindTexture(c.dst.texture.native); err != nil { if err := opengl.GetContext().BindTexture(c.dst.texture.native); err != nil {
return err return err
} }
context.TexSubImage2D(c.pixels, NextPowerOf2Int(c.dst.width), NextPowerOf2Int(c.dst.height)) opengl.GetContext().TexSubImage2D(c.pixels, NextPowerOf2Int(c.dst.width), NextPowerOf2Int(c.dst.height))
return nil return nil
} }
@ -293,12 +292,12 @@ type disposeCommand struct {
target *Image target *Image
} }
func (c *disposeCommand) Exec(context *opengl.Context, indexOffsetInBytes int) error { func (c *disposeCommand) Exec(indexOffsetInBytes int) error {
if c.target.framebuffer != nil { if c.target.framebuffer != nil {
context.DeleteFramebuffer(c.target.framebuffer.native) opengl.GetContext().DeleteFramebuffer(c.target.framebuffer.native)
} }
if c.target.texture != nil { if c.target.texture != nil {
context.DeleteTexture(c.target.texture.native) opengl.GetContext().DeleteTexture(c.target.texture.native)
} }
return nil return nil
} }
@ -309,7 +308,7 @@ type newImageFromImageCommand struct {
filter opengl.Filter filter opengl.Filter
} }
func (c *newImageFromImageCommand) Exec(context *opengl.Context, indexOffsetInBytes int) error { func (c *newImageFromImageCommand) Exec(indexOffsetInBytes int) error {
origSize := c.img.Bounds().Size() origSize := c.img.Bounds().Size()
if origSize.X < 1 { if origSize.X < 1 {
return errors.New("graphics: width must be equal or more than 1.") return errors.New("graphics: width must be equal or more than 1.")
@ -321,7 +320,7 @@ func (c *newImageFromImageCommand) Exec(context *opengl.Context, indexOffsetInBy
if c.img.Bounds() != image.Rect(0, 0, NextPowerOf2Int(w), NextPowerOf2Int(h)) { if c.img.Bounds() != image.Rect(0, 0, NextPowerOf2Int(w), 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 := context.NewTexture(w, h, c.img.Pix, c.filter) native, err := opengl.GetContext().NewTexture(w, h, c.img.Pix, c.filter)
if err != nil { if err != nil {
return err return err
} }
@ -338,7 +337,7 @@ type newImageCommand struct {
filter opengl.Filter filter opengl.Filter
} }
func (c *newImageCommand) Exec(context *opengl.Context, indexOffsetInBytes int) error { func (c *newImageCommand) Exec(indexOffsetInBytes int) error {
w := NextPowerOf2Int(c.width) w := NextPowerOf2Int(c.width)
h := NextPowerOf2Int(c.height) h := NextPowerOf2Int(c.height)
if w < 1 { if w < 1 {
@ -347,7 +346,7 @@ func (c *newImageCommand) Exec(context *opengl.Context, indexOffsetInBytes int)
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 := context.NewTexture(w, h, nil, c.filter) native, err := opengl.GetContext().NewTexture(w, h, nil, c.filter)
if err != nil { if err != nil {
return err return err
} }
@ -363,7 +362,7 @@ type newScreenFramebufferImageCommand struct {
height int height int
} }
func (c *newScreenFramebufferImageCommand) Exec(context *opengl.Context, indexOffsetInBytes int) error { func (c *newScreenFramebufferImageCommand) Exec(indexOffsetInBytes int) error {
if c.width < 1 { if c.width < 1 {
return errors.New("graphics: width must be equal or more than 1.") return errors.New("graphics: width must be equal or more than 1.")
} }
@ -371,7 +370,7 @@ func (c *newScreenFramebufferImageCommand) Exec(context *opengl.Context, indexOf
return errors.New("graphics: height must be equal or more than 1.") return errors.New("graphics: height must be equal or more than 1.")
} }
f := &framebuffer{ f := &framebuffer{
native: context.ScreenFramebuffer(), native: opengl.GetContext().ScreenFramebuffer(),
flipY: true, flipY: true,
} }
c.result.framebuffer = f c.result.framebuffer = f

View File

@ -38,8 +38,8 @@ type framebuffer struct {
proMatrix []float32 proMatrix []float32
} }
func newFramebufferFromTexture(context *opengl.Context, texture *texture) (*framebuffer, error) { func newFramebufferFromTexture(texture *texture) (*framebuffer, error) {
native, err := context.NewFramebuffer(opengl.Texture(texture.native)) native, err := opengl.GetContext().NewFramebuffer(opengl.Texture(texture.native))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -50,10 +50,10 @@ func newFramebufferFromTexture(context *opengl.Context, texture *texture) (*fram
const viewportSize = 4096 const viewportSize = 4096
func (f *framebuffer) setAsViewport(context *opengl.Context) error { func (f *framebuffer) setAsViewport() error {
width := viewportSize width := viewportSize
height := viewportSize height := viewportSize
return context.SetViewport(f.native, width, height) return opengl.GetContext().SetViewport(f.native, width, height)
} }
func (f *framebuffer) projectionMatrix(height int) []float32 { func (f *framebuffer) projectionMatrix(height int) []float32 {

View File

@ -145,16 +145,16 @@ func (i *Image) DrawImage(src *Image, vertices []float32, clr *affine.ColorM, mo
theCommandQueue.EnqueueDrawImageCommand(i, src, vertices, clr, mode) theCommandQueue.EnqueueDrawImageCommand(i, src, vertices, clr, mode)
} }
func (i *Image) Pixels(context *opengl.Context) ([]uint8, error) { func (i *Image) Pixels() ([]uint8, error) {
// Flush the enqueued commands so that pixels are certainly read. // Flush the enqueued commands so that pixels are certainly read.
if err := theCommandQueue.Flush(context); err != nil { if err := theCommandQueue.Flush(); err != nil {
return nil, err return nil, err
} }
f, err := i.createFramebufferIfNeeded(context) f, err := i.createFramebufferIfNeeded()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return context.FramebufferPixels(f.native, NextPowerOf2Int(i.width), NextPowerOf2Int(i.height)) return opengl.GetContext().FramebufferPixels(f.native, NextPowerOf2Int(i.width), NextPowerOf2Int(i.height))
} }
func (i *Image) ReplacePixels(p []uint8) { func (i *Image) ReplacePixels(p []uint8) {
@ -167,15 +167,15 @@ func (i *Image) ReplacePixels(p []uint8) {
theCommandQueue.Enqueue(c) theCommandQueue.Enqueue(c)
} }
func (i *Image) IsInvalidated(context *opengl.Context) bool { func (i *Image) IsInvalidated() bool {
return !context.IsTexture(i.texture.native) return !opengl.GetContext().IsTexture(i.texture.native)
} }
func (i *Image) createFramebufferIfNeeded(context *opengl.Context) (*framebuffer, error) { func (i *Image) createFramebufferIfNeeded() (*framebuffer, error) {
if i.framebuffer != nil { if i.framebuffer != nil {
return i.framebuffer, nil return i.framebuffer, nil
} }
f, err := newFramebufferFromTexture(context, i.texture) f, err := newFramebufferFromTexture(i.texture)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -46,26 +46,26 @@ func (a *arrayBufferLayout) totalBytes() int {
return a.total return a.total
} }
func (a *arrayBufferLayout) newArrayBuffer(c *opengl.Context) opengl.Buffer { func (a *arrayBufferLayout) newArrayBuffer() opengl.Buffer {
return c.NewBuffer(opengl.ArrayBuffer, a.totalBytes()*4*maxQuads, opengl.DynamicDraw) return opengl.GetContext().NewBuffer(opengl.ArrayBuffer, a.totalBytes()*4*maxQuads, opengl.DynamicDraw)
} }
func (a *arrayBufferLayout) enable(c *opengl.Context, program opengl.Program) { func (a *arrayBufferLayout) enable(program opengl.Program) {
for _, p := range a.parts { for _, p := range a.parts {
c.EnableVertexAttribArray(program, p.name) opengl.GetContext().EnableVertexAttribArray(program, p.name)
} }
total := a.totalBytes() total := a.totalBytes()
offset := 0 offset := 0
for _, p := range a.parts { for _, p := range a.parts {
c.VertexAttribPointer(program, p.name, p.num, p.dataType, p.normalize, total, offset) opengl.GetContext().VertexAttribPointer(program, p.name, p.num, p.dataType, p.normalize, total, offset)
offset += p.dataType.SizeInBytes() * p.num offset += p.dataType.SizeInBytes() * p.num
} }
} }
func (a *arrayBufferLayout) disable(c *opengl.Context, program opengl.Program) { func (a *arrayBufferLayout) disable(program opengl.Program) {
// TODO: Disabling should be done in reversed order? // TODO: Disabling should be done in reversed order?
for _, p := range a.parts { for _, p := range a.parts {
c.DisableVertexAttribArray(program, p.name) opengl.GetContext().DisableVertexAttribArray(program, p.name)
} }
} }
@ -125,12 +125,12 @@ const (
maxQuads = indicesNum / 6 maxQuads = indicesNum / 6
) )
func Reset(context *opengl.Context) error { func Reset() error {
return theOpenGLState.reset(context) return theOpenGLState.reset()
} }
func (s *openGLState) reset(context *opengl.Context) error { func (s *openGLState) reset() error {
if err := context.Reset(); err != nil { if err := opengl.GetContext().Reset(); err != nil {
return err return err
} }
s.lastProgram = zeroProgram s.lastProgram = zeroProgram
@ -138,19 +138,19 @@ func (s *openGLState) reset(context *opengl.Context) error {
s.lastColorMatrix = nil s.lastColorMatrix = nil
s.lastColorMatrixTranslation = nil s.lastColorMatrixTranslation = nil
shaderVertexModelviewNative, err := context.NewShader(opengl.VertexShader, shader(context, shaderVertexModelview)) shaderVertexModelviewNative, err := opengl.GetContext().NewShader(opengl.VertexShader, shader(shaderVertexModelview))
if err != nil { if err != nil {
panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err)) panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err))
} }
defer context.DeleteShader(shaderVertexModelviewNative) defer opengl.GetContext().DeleteShader(shaderVertexModelviewNative)
shaderFragmentTextureNative, err := context.NewShader(opengl.FragmentShader, shader(context, shaderFragmentTexture)) shaderFragmentTextureNative, err := opengl.GetContext().NewShader(opengl.FragmentShader, shader(shaderFragmentTexture))
if err != nil { if err != nil {
panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err)) panic(fmt.Sprintf("graphics: shader compiling error:\n%s", err))
} }
defer context.DeleteShader(shaderFragmentTextureNative) defer opengl.GetContext().DeleteShader(shaderFragmentTextureNative)
s.programTexture, err = context.NewProgram([]opengl.Shader{ s.programTexture, err = opengl.GetContext().NewProgram([]opengl.Shader{
shaderVertexModelviewNative, shaderVertexModelviewNative,
shaderFragmentTextureNative, shaderFragmentTextureNative,
}) })
@ -158,7 +158,7 @@ func (s *openGLState) reset(context *opengl.Context) error {
return err return err
} }
s.arrayBuffer = theArrayBufferLayout.newArrayBuffer(context) s.arrayBuffer = theArrayBufferLayout.newArrayBuffer()
indices := make([]uint16, 6*maxQuads) indices := make([]uint16, 6*maxQuads)
for i := uint16(0); i < maxQuads; i++ { for i := uint16(0); i < maxQuads; i++ {
@ -169,7 +169,7 @@ func (s *openGLState) reset(context *opengl.Context) error {
indices[6*i+4] = 4*i + 2 indices[6*i+4] = 4*i + 2
indices[6*i+5] = 4*i + 3 indices[6*i+5] = 4*i + 3
} }
s.indexBufferQuads = context.NewBuffer(opengl.ElementArrayBuffer, indices, opengl.StaticDraw) s.indexBufferQuads = opengl.GetContext().NewBuffer(opengl.ElementArrayBuffer, indices, opengl.StaticDraw)
return nil return nil
} }
@ -189,20 +189,19 @@ func areSameFloat32Array(a, b []float32) bool {
type programContext struct { type programContext struct {
state *openGLState state *openGLState
program opengl.Program program opengl.Program
context *opengl.Context
projectionMatrix []float32 projectionMatrix []float32
texture opengl.Texture texture opengl.Texture
colorM affine.ColorM colorM affine.ColorM
} }
func (p *programContext) begin() error { func (p *programContext) begin() error {
c := p.context c := opengl.GetContext()
if p.state.lastProgram != p.program { if p.state.lastProgram != p.program {
c.UseProgram(p.program) c.UseProgram(p.program)
if p.state.lastProgram != zeroProgram { if p.state.lastProgram != zeroProgram {
theArrayBufferLayout.disable(c, p.state.lastProgram) theArrayBufferLayout.disable(p.state.lastProgram)
} }
theArrayBufferLayout.enable(c, p.program) theArrayBufferLayout.enable(p.program)
p.state.lastProgram = p.state.programTexture p.state.lastProgram = p.state.programTexture
p.state.lastProjectionMatrix = nil p.state.lastProjectionMatrix = nil

View File

@ -27,9 +27,9 @@ const (
shaderFragmentTexture shaderFragmentTexture
) )
func shader(c *opengl.Context, id shaderId) string { func shader(id shaderId) string {
str := shaders[id] str := shaders[id]
if !c.GlslHighpSupported() { if !opengl.GetContext().GlslHighpSupported() {
str = strings.Replace(str, "highp ", "", -1) str = strings.Replace(str, "highp ", "", -1)
str = strings.Replace(str, "lowp ", "", -1) str = strings.Replace(str, "lowp ", "", -1)
} }

View File

@ -18,7 +18,6 @@ import (
"errors" "errors"
"time" "time"
"github.com/hajimehoshi/ebiten/internal/opengl"
"github.com/hajimehoshi/ebiten/internal/sync" "github.com/hajimehoshi/ebiten/internal/sync"
"github.com/hajimehoshi/ebiten/internal/ui" "github.com/hajimehoshi/ebiten/internal/ui"
) )
@ -76,7 +75,7 @@ func (c *runContext) updateFPS(fps float64) {
type GraphicsContext interface { type GraphicsContext interface {
SetSize(width, height int, scale float64) error SetSize(width, height int, scale float64) error
UpdateAndDraw(context *opengl.Context, updateCount int) error UpdateAndDraw(updateCount int) error
Invalidate() Invalidate()
} }
@ -150,7 +149,7 @@ func (c *runContext) render(g GraphicsContext) error {
if tt == 0 && (int64(time.Second)/int64(fps)-int64(5*time.Millisecond)) < t { if tt == 0 && (int64(time.Second)/int64(fps)-int64(5*time.Millisecond)) < t {
tt = 1 tt = 1
} }
if err := g.UpdateAndDraw(ui.GLContext(), tt); err != nil { if err := g.UpdateAndDraw(tt); err != nil {
return err return err
} }
c.lastUpdated += int64(tt) * int64(time.Second) / int64(fps) c.lastUpdated += int64(tt) * int64(time.Second) / int64(fps)

View File

@ -47,6 +47,12 @@ type Context struct {
context context
} }
var theContext *Context
func GetContext() *Context {
return theContext
}
func (c *Context) BindTexture(t Texture) error { func (c *Context) BindTexture(t Texture) error {
if c.lastTexture == t { if c.lastTexture == t {
return nil return nil

View File

@ -73,10 +73,10 @@ type context struct {
runOnMainThread func(func() error) error runOnMainThread func(func() error) error
} }
func NewContext(runOnMainThread func(func() error) error) (*Context, error) { func Init(runOnMainThread func(func() error) error) {
c := &Context{} c := &Context{}
c.runOnMainThread = runOnMainThread c.runOnMainThread = runOnMainThread
return c, nil theContext = c
} }
func (c *Context) runOnContextThread(f func() error) error { func (c *Context) runOnContextThread(f func() error) error {

View File

@ -91,7 +91,7 @@ type context struct {
lastProgramID programID lastProgramID programID
} }
func NewContext() (*Context, error) { func Init() error {
var gl *webgl.Context var gl *webgl.Context
if js.Global.Get("require") == js.Undefined { if js.Global.Get("require") == js.Undefined {
@ -103,7 +103,7 @@ func NewContext() (*Context, error) {
PremultipliedAlpha: true, PremultipliedAlpha: true,
}) })
if err != nil { if err != nil {
return nil, err return err
} }
} else { } else {
// TODO: Now Ebiten with headless-gl doesn't work well (#141). // TODO: Now Ebiten with headless-gl doesn't work well (#141).
@ -126,7 +126,8 @@ func NewContext() (*Context, error) {
c.loseContext.Call("loseContext") c.loseContext.Call("loseContext")
}) })
} }
return c, nil theContext = c
return nil
} }
func (c *Context) Reset() error { func (c *Context) Reset() error {

View File

@ -72,10 +72,10 @@ type context struct {
worker mgl.Worker worker mgl.Worker
} }
func NewContext() (*Context, error) { func Init() {
c := &Context{} c := &Context{}
c.gl, c.worker = mgl.NewContext() c.gl, c.worker = mgl.NewContext()
return c, nil theContext = c
} }
func (c *Context) DoWork(chError <-chan error, chDone <-chan struct{}) error { func (c *Context) DoWork(chError <-chan error, chDone <-chan struct{}) error {

View File

@ -167,14 +167,14 @@ func (p *Image) appendDrawImageHistory(image *Image, vertices []float32, colorm
// At returns a color value at (x, y). // At returns a color value at (x, y).
// //
// Note that this must not be called until context is available. // Note that this must not be called until context is available.
func (p *Image) At(x, y int, context *opengl.Context) (color.RGBA, error) { func (p *Image) At(x, y int) (color.RGBA, error) {
w, h := p.image.Size() w, h := p.image.Size()
w2, h2 := graphics.NextPowerOf2Int(w), graphics.NextPowerOf2Int(h) w2, h2 := graphics.NextPowerOf2Int(w), graphics.NextPowerOf2Int(h)
if x < 0 || y < 0 || w2 <= x || h2 <= y { if x < 0 || y < 0 || w2 <= x || h2 <= y {
return color.RGBA{}, nil return color.RGBA{}, nil
} }
if p.basePixels == nil || p.drawImageHistory != nil || p.stale { if p.basePixels == nil || p.drawImageHistory != nil || p.stale {
if err := p.readPixelsFromGPU(p.image, context); err != nil { if err := p.readPixelsFromGPU(p.image); err != nil {
return color.RGBA{}, err return color.RGBA{}, err
} }
} }
@ -195,9 +195,9 @@ func (p *Image) makeStaleIfDependingOn(target *Image) {
} }
} }
func (p *Image) readPixelsFromGPU(image *graphics.Image, context *opengl.Context) error { func (p *Image) readPixelsFromGPU(image *graphics.Image) error {
var err error var err error
p.basePixels, err = image.Pixels(context) p.basePixels, err = image.Pixels()
if err != nil { if err != nil {
return err return err
} }
@ -207,14 +207,14 @@ func (p *Image) readPixelsFromGPU(image *graphics.Image, context *opengl.Context
return nil return nil
} }
func (p *Image) resolveStalePixels(context *opengl.Context) error { func (p *Image) resolveStalePixels() error {
if p.volatile { if p.volatile {
return nil return nil
} }
if !p.stale { if !p.stale {
return nil return nil
} }
return p.readPixelsFromGPU(p.image, context) return p.readPixelsFromGPU(p.image)
} }
func (p *Image) hasDependency() bool { func (p *Image) hasDependency() bool {
@ -225,7 +225,7 @@ func (p *Image) hasDependency() bool {
} }
// Restore restores *graphics.Image from the pixels using its state. // Restore restores *graphics.Image from the pixels using its state.
func (p *Image) restore(context *opengl.Context) error { func (p *Image) restore() error {
w, h := p.image.Size() w, h := p.image.Size()
if p.screen { if p.screen {
// The screen image should also be recreated because framebuffer might // The screen image should also be recreated because framebuffer might
@ -273,7 +273,7 @@ func (p *Image) restore(context *opengl.Context) error {
p.image = gimg p.image = gimg
var err error var err error
p.basePixels, err = gimg.Pixels(context) p.basePixels, err = gimg.Pixels()
if err != nil { if err != nil {
return err return err
} }
@ -295,6 +295,6 @@ func (p *Image) Dispose() {
runtime.SetFinalizer(p, nil) runtime.SetFinalizer(p, nil)
} }
func (p *Image) IsInvalidated(context *opengl.Context) bool { func (p *Image) IsInvalidated() bool {
return p.image.IsInvalidated(context) return p.image.IsInvalidated()
} }

View File

@ -15,7 +15,6 @@
package restorable package restorable
import ( import (
"github.com/hajimehoshi/ebiten/internal/opengl"
"github.com/hajimehoshi/ebiten/internal/sync" "github.com/hajimehoshi/ebiten/internal/sync"
) )
@ -29,12 +28,12 @@ var theImages = &images{
images: map[*Image]struct{}{}, images: map[*Image]struct{}{},
} }
func ResolveStalePixels(context *opengl.Context) error { func ResolveStalePixels() error {
return theImages.resolveStalePixels(context) return theImages.resolveStalePixels()
} }
func Restore(context *opengl.Context) error { func Restore() error {
return theImages.restore(context) return theImages.restore()
} }
func ClearVolatileImages() { func ClearVolatileImages() {
@ -53,12 +52,12 @@ func (i *images) remove(img *Image) {
delete(i.images, img) delete(i.images, img)
} }
func (i *images) resolveStalePixels(context *opengl.Context) error { func (i *images) resolveStalePixels() error {
i.m.Lock() i.m.Lock()
defer i.m.Unlock() defer i.m.Unlock()
i.lastChecked = nil i.lastChecked = nil
for img := range i.images { for img := range i.images {
if err := img.resolveStalePixels(context); err != nil { if err := img.resolveStalePixels(); err != nil {
return err return err
} }
} }
@ -86,7 +85,7 @@ func (i *images) resetPixelsIfDependingOn(target *Image) {
i.m.Unlock() i.m.Unlock()
} }
func (i *images) restore(context *opengl.Context) error { func (i *images) restore() error {
i.m.Lock() i.m.Lock()
defer i.m.Unlock() defer i.m.Unlock()
// Framebuffers/textures cannot be disposed since framebuffers/textures that // Framebuffers/textures cannot be disposed since framebuffers/textures that
@ -102,12 +101,12 @@ func (i *images) restore(context *opengl.Context) error {
} }
// Images depending on other images should be processed first. // Images depending on other images should be processed first.
for _, img := range imagesWithoutDependency { for _, img := range imagesWithoutDependency {
if err := img.restore(context); err != nil { if err := img.restore(); err != nil {
return err return err
} }
} }
for _, img := range imagesWithDependency { for _, img := range imagesWithDependency {
if err := img.restore(context); err != nil { if err := img.restore(); err != nil {
return err return err
} }
} }

View File

@ -1,25 +0,0 @@
// Copyright 2016 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ui
import (
"github.com/hajimehoshi/ebiten/internal/opengl"
)
var glContext *opengl.Context
func GLContext() *opengl.Context {
return glContext
}

View File

@ -178,11 +178,7 @@ func Run(width, height int, scale float64, title string, g GraphicsContext) erro
u := currentUI u := currentUI
// GLContext must be created before setting the screen size, which requires // GLContext must be created before setting the screen size, which requires
// swapping buffers. // swapping buffers.
var err error opengl.Init(currentUI.runOnMainThread)
glContext, err = opengl.NewContext(currentUI.runOnMainThread)
if err != nil {
return err
}
if err := u.runOnMainThread(func() error { if err := u.runOnMainThread(func() error {
m := glfw.GetPrimaryMonitor() m := glfw.GetPrimaryMonitor()
v := m.GetVideoMode() v := m.GetVideoMode()
@ -275,7 +271,7 @@ func (u *userInterface) loop(g GraphicsContext) error {
return err return err
} }
// The bound framebuffer must be the default one (0) before swapping buffers. // The bound framebuffer must be the default one (0) before swapping buffers.
if err := glContext.BindScreenFramebuffer(); err != nil { if err := opengl.GetContext().BindScreenFramebuffer(); err != nil {
return err return err
} }
_ = u.runOnMainThread(func() error { _ = u.runOnMainThread(func() error {

View File

@ -72,8 +72,8 @@ func (u *userInterface) update(g GraphicsContext) error {
if !u.windowFocus { if !u.windowFocus {
return nil return nil
} }
if glContext.IsContextLost() { if opengl.GetContext().IsContextLost() {
glContext.RestoreContext() opengl.GetContext().RestoreContext()
g.Invalidate() g.Invalidate()
} }
currentInput.updateGamepads() currentInput.updateGamepads()
@ -280,9 +280,7 @@ func Run(width, height int, scale float64, title string, g GraphicsContext) erro
doc.Set("title", title) doc.Set("title", title)
u.setScreenSize(width, height, scale) u.setScreenSize(width, height, scale)
canvas.Call("focus") canvas.Call("focus")
var err error if err := opengl.Init(); err != nil {
glContext, err = opengl.NewContext()
if err != nil {
return err return err
} }
return u.loop(g) return u.loop(g)

View File

@ -38,7 +38,7 @@ func Render(chError <-chan error) error {
// TODO: Check this is called on the rendering thread // TODO: Check this is called on the rendering thread
select { select {
case chRender <- struct{}{}: case chRender <- struct{}{}:
return glContext.DoWork(chError, chRenderEnd) return opengl.GetContext().DoWork(chError, chRenderEnd)
case <-time.After(500 * time.Millisecond): case <-time.After(500 * time.Millisecond):
// This function must not be blocked. We need to break for timeout. // This function must not be blocked. We need to break for timeout.
return nil return nil
@ -66,11 +66,7 @@ func Run(width, height int, scale float64, title string, g GraphicsContext) erro
u.height = height u.height = height
u.scale = scale u.scale = scale
// title is ignored? // title is ignored?
var err error opengl.Init()
glContext, err = opengl.NewContext()
if err != nil {
return err
}
for { for {
if err := u.update(g); err != nil { if err := u.update(g); err != nil {
return err return err