From 713eee11172d1fd044735d4c9637e4b98361411f Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Tue, 13 Oct 2020 02:12:02 +0900 Subject: [PATCH] Revert "graphicsdriver/metal, graphicsdriver/opengl: Remove the thread usages for performance" This reverts commit 2942f10d9d3ae2e784265e67bd6fe90e2531ac4f. Reason: Compile error on mobiles and runtime error on browsers --- internal/driver/graphics.go | 2 + internal/graphicscommand/command.go | 27 +- internal/graphicsdriver/metal/graphics.go | 623 ++++++++++-------- internal/graphicsdriver/opengl/context.go | 3 + .../graphicsdriver/opengl/context_desktop.go | 570 ++++++++++------ internal/graphicsdriver/opengl/context_js.go | 4 +- .../graphicsdriver/opengl/context_mobile.go | 4 +- internal/graphicsdriver/opengl/graphics.go | 5 + internal/graphicsdriver/opengl/image.go | 5 +- internal/uidriver/glfw/ui.go | 3 +- 10 files changed, 733 insertions(+), 513 deletions(-) diff --git a/internal/driver/graphics.go b/internal/driver/graphics.go index 9330dda6a..bbe5c709c 100644 --- a/internal/driver/graphics.go +++ b/internal/driver/graphics.go @@ -20,6 +20,7 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/affine" "github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/shaderir" + "github.com/hajimehoshi/ebiten/v2/internal/thread" ) type Region struct { @@ -30,6 +31,7 @@ type Region struct { } type Graphics interface { + SetThread(thread *thread.Thread) Begin() End() SetTransparent(transparent bool) diff --git a/internal/graphicscommand/command.go b/internal/graphicscommand/command.go index 161498371..ef80c10eb 100644 --- a/internal/graphicscommand/command.go +++ b/internal/graphicscommand/command.go @@ -23,7 +23,6 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/driver" "github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/shaderir" - "github.com/hajimehoshi/ebiten/v2/internal/thread" ) var theGraphicsDriver driver.Graphics @@ -32,10 +31,6 @@ func SetGraphicsDriver(driver driver.Graphics) { theGraphicsDriver = driver } -func SetThread(thread *thread.Thread) { - theCommandQueue.thread = thread -} - func NeedsRestoring() bool { if theGraphicsDriver == nil { // This happens on initialization. @@ -91,8 +86,6 @@ type commandQueue struct { tmpNumIndices int nextIndex int - thread *thread.Thread - err error } @@ -207,19 +200,10 @@ func (q *commandQueue) Enqueue(command command) { // Flush flushes the command queue. func (q *commandQueue) Flush() error { - return q.thread.Call(func() error { - return q.flush() - }) -} - -// flush must be called the main thread. -func (q *commandQueue) flush() error { if len(q.commands) == 0 { return nil } - // TODO: Use thread.Call here! - es := q.indices vs := q.vertices if recordLog() { @@ -732,17 +716,10 @@ func (c *newShaderCommand) CanMergeWithDrawTrianglesCommand(dst *Image, src [gra // ResetGraphicsDriverState resets or initializes the current graphics driver state. func ResetGraphicsDriverState() error { - return theCommandQueue.thread.Call(func() error { - return theGraphicsDriver.Reset() - }) + return theGraphicsDriver.Reset() } // MaxImageSize returns the maximum size of an image. func MaxImageSize() int { - var size int - _ = theCommandQueue.thread.Call(func() error { - size = theGraphicsDriver.MaxImageSize() - return nil - }) - return size + return theGraphicsDriver.MaxImageSize() } diff --git a/internal/graphicsdriver/metal/graphics.go b/internal/graphicsdriver/metal/graphics.go index e8799a190..36cd0e655 100644 --- a/internal/graphicsdriver/metal/graphics.go +++ b/internal/graphicsdriver/metal/graphics.go @@ -27,6 +27,7 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/ca" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/metal/mtl" "github.com/hajimehoshi/ebiten/v2/internal/shaderir" + "github.com/hajimehoshi/ebiten/v2/internal/thread" ) // #cgo CFLAGS: -x objective-c @@ -327,6 +328,8 @@ type Graphics struct { maxImageSize int tmpTexture mtl.Texture + t *thread.Thread + pool unsafe.Pointer } @@ -336,23 +339,36 @@ func Get() *Graphics { return &theGraphics } +func (g *Graphics) SetThread(thread *thread.Thread) { + g.t = thread +} + func (g *Graphics) Begin() { - // NSAutoreleasePool is required to release drawable correctly (#847). - // https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/Drawables.html - g.pool = C.allocAutoreleasePool() + g.t.Call(func() error { + // NSAutoreleasePool is required to release drawable correctly (#847). + // https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/Drawables.html + g.pool = C.allocAutoreleasePool() + return nil + }) } func (g *Graphics) End() { g.flushIfNeeded(false, true) - g.screenDrawable = ca.MetalDrawable{} - C.releaseAutoreleasePool(g.pool) - g.pool = nil + g.t.Call(func() error { + g.screenDrawable = ca.MetalDrawable{} + C.releaseAutoreleasePool(g.pool) + g.pool = nil + return nil + }) } func (g *Graphics) SetWindow(window uintptr) { - // Note that [NSApp mainWindow] returns nil when the window is borderless. - // Then the window is needed to be given explicitly. - g.view.setWindow(window) + g.t.Call(func() error { + // Note that [NSApp mainWindow] returns nil when the window is borderless. + // Then the window is needed to be given explicitly. + g.view.setWindow(window) + return nil + }) } func (g *Graphics) SetUIView(uiview uintptr) { @@ -361,30 +377,37 @@ func (g *Graphics) SetUIView(uiview uintptr) { } func (g *Graphics) SetVertices(vertices []float32, indices []uint16) { - if g.vb != (mtl.Buffer{}) { - g.vb.Release() - } - if g.ib != (mtl.Buffer{}) { - g.ib.Release() - } - g.vb = g.view.getMTLDevice().MakeBufferWithBytes(unsafe.Pointer(&vertices[0]), unsafe.Sizeof(vertices[0])*uintptr(len(vertices)), resourceStorageMode) - g.ib = g.view.getMTLDevice().MakeBufferWithBytes(unsafe.Pointer(&indices[0]), unsafe.Sizeof(indices[0])*uintptr(len(indices)), resourceStorageMode) + g.t.Call(func() error { + if g.vb != (mtl.Buffer{}) { + g.vb.Release() + } + if g.ib != (mtl.Buffer{}) { + g.ib.Release() + } + g.vb = g.view.getMTLDevice().MakeBufferWithBytes(unsafe.Pointer(&vertices[0]), unsafe.Sizeof(vertices[0])*uintptr(len(vertices)), resourceStorageMode) + g.ib = g.view.getMTLDevice().MakeBufferWithBytes(unsafe.Pointer(&indices[0]), unsafe.Sizeof(indices[0])*uintptr(len(indices)), resourceStorageMode) + return nil + }) } func (g *Graphics) flushIfNeeded(wait bool, present bool) { - if g.cb == (mtl.CommandBuffer{}) { - return - } + g.t.Call(func() error { + if g.cb == (mtl.CommandBuffer{}) { + return nil + } - if present && g.screenDrawable != (ca.MetalDrawable{}) { - g.cb.PresentDrawable(g.screenDrawable) - } - g.cb.Commit() - if wait { - g.cb.WaitUntilCompleted() - } + if present && g.screenDrawable != (ca.MetalDrawable{}) { + g.cb.PresentDrawable(g.screenDrawable) + } + g.cb.Commit() + if wait { + g.cb.WaitUntilCompleted() + } - g.cb = mtl.CommandBuffer{} + g.cb = mtl.CommandBuffer{} + + return nil + }) } func (g *Graphics) checkSize(width, height int) { @@ -429,7 +452,11 @@ func (g *Graphics) NewImage(width, height int) (driver.Image, error) { StorageMode: storageMode, Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget, } - t := g.view.getMTLDevice().MakeTexture(td) + var t mtl.Texture + g.t.Call(func() error { + t = g.view.getMTLDevice().MakeTexture(td) + return nil + }) i := &Image{ id: g.genNextImageID(), graphics: g, @@ -442,7 +469,10 @@ func (g *Graphics) NewImage(width, height int) (driver.Image, error) { } func (g *Graphics) NewScreenFramebufferImage(width, height int) (driver.Image, error) { - g.view.setDrawableSize(width, height) + g.t.Call(func() error { + g.view.setDrawableSize(width, height) + return nil + }) i := &Image{ id: g.genNextImageID(), graphics: g, @@ -494,120 +524,126 @@ func operationToBlendFactor(c driver.Operation) mtl.BlendFactor { } func (g *Graphics) Reset() error { - if g.cq != (mtl.CommandQueue{}) { - g.cq.Release() - g.cq = mtl.CommandQueue{} - } + if err := g.t.Call(func() error { + if g.cq != (mtl.CommandQueue{}) { + g.cq.Release() + g.cq = mtl.CommandQueue{} + } - // TODO: Release existing rpss - if g.rpss == nil { - g.rpss = map[rpsKey]mtl.RenderPipelineState{} - } + // TODO: Release existing rpss + if g.rpss == nil { + g.rpss = map[rpsKey]mtl.RenderPipelineState{} + } - if err := g.view.reset(); err != nil { - return err - } - if g.transparent { - g.view.ml.SetOpaque(false) - } + if err := g.view.reset(); err != nil { + return err + } + if g.transparent { + g.view.ml.SetOpaque(false) + } - replaces := map[string]string{ - "{{.FilterNearest}}": fmt.Sprintf("%d", driver.FilterNearest), - "{{.FilterLinear}}": fmt.Sprintf("%d", driver.FilterLinear), - "{{.FilterScreen}}": fmt.Sprintf("%d", driver.FilterScreen), - "{{.AddressClampToZero}}": fmt.Sprintf("%d", driver.AddressClampToZero), - "{{.AddressRepeat}}": fmt.Sprintf("%d", driver.AddressRepeat), - "{{.AddressUnsafe}}": fmt.Sprintf("%d", driver.AddressUnsafe), - } - src := source - for k, v := range replaces { - src = strings.Replace(src, k, v, -1) - } + replaces := map[string]string{ + "{{.FilterNearest}}": fmt.Sprintf("%d", driver.FilterNearest), + "{{.FilterLinear}}": fmt.Sprintf("%d", driver.FilterLinear), + "{{.FilterScreen}}": fmt.Sprintf("%d", driver.FilterScreen), + "{{.AddressClampToZero}}": fmt.Sprintf("%d", driver.AddressClampToZero), + "{{.AddressRepeat}}": fmt.Sprintf("%d", driver.AddressRepeat), + "{{.AddressUnsafe}}": fmt.Sprintf("%d", driver.AddressUnsafe), + } + src := source + for k, v := range replaces { + src = strings.Replace(src, k, v, -1) + } - lib, err := g.view.getMTLDevice().MakeLibrary(src, mtl.CompileOptions{}) - if err != nil { - return err - } - vs, err := lib.MakeFunction("VertexShader") - if err != nil { - return err - } - fs, err := lib.MakeFunction( - fmt.Sprintf("FragmentShader_%d_%d_%d", 0, driver.FilterScreen, driver.AddressUnsafe)) - if err != nil { - return err - } - rpld := mtl.RenderPipelineDescriptor{ - VertexFunction: vs, - FragmentFunction: fs, - } - rpld.ColorAttachments[0].PixelFormat = g.view.colorPixelFormat() - rpld.ColorAttachments[0].BlendingEnabled = true - rpld.ColorAttachments[0].DestinationAlphaBlendFactor = mtl.BlendFactorZero - rpld.ColorAttachments[0].DestinationRGBBlendFactor = mtl.BlendFactorZero - rpld.ColorAttachments[0].SourceAlphaBlendFactor = mtl.BlendFactorOne - rpld.ColorAttachments[0].SourceRGBBlendFactor = mtl.BlendFactorOne - rps, err := g.view.getMTLDevice().MakeRenderPipelineState(rpld) - if err != nil { - return err - } - g.screenRPS = rps + lib, err := g.view.getMTLDevice().MakeLibrary(src, mtl.CompileOptions{}) + if err != nil { + return err + } + vs, err := lib.MakeFunction("VertexShader") + if err != nil { + return err + } + fs, err := lib.MakeFunction( + fmt.Sprintf("FragmentShader_%d_%d_%d", 0, driver.FilterScreen, driver.AddressUnsafe)) + if err != nil { + return err + } + rpld := mtl.RenderPipelineDescriptor{ + VertexFunction: vs, + FragmentFunction: fs, + } + rpld.ColorAttachments[0].PixelFormat = g.view.colorPixelFormat() + rpld.ColorAttachments[0].BlendingEnabled = true + rpld.ColorAttachments[0].DestinationAlphaBlendFactor = mtl.BlendFactorZero + rpld.ColorAttachments[0].DestinationRGBBlendFactor = mtl.BlendFactorZero + rpld.ColorAttachments[0].SourceAlphaBlendFactor = mtl.BlendFactorOne + rpld.ColorAttachments[0].SourceRGBBlendFactor = mtl.BlendFactorOne + rps, err := g.view.getMTLDevice().MakeRenderPipelineState(rpld) + if err != nil { + return err + } + g.screenRPS = rps - for _, screen := range []bool{false, true} { - for _, cm := range []bool{false, true} { - for _, a := range []driver.Address{ - driver.AddressClampToZero, - driver.AddressRepeat, - driver.AddressUnsafe, - } { - for _, f := range []driver.Filter{ - driver.FilterNearest, - driver.FilterLinear, + for _, screen := range []bool{false, true} { + for _, cm := range []bool{false, true} { + for _, a := range []driver.Address{ + driver.AddressClampToZero, + driver.AddressRepeat, + driver.AddressUnsafe, } { - for c := driver.CompositeModeSourceOver; c <= driver.CompositeModeMax; c++ { - cmi := 0 - if cm { - cmi = 1 - } - fs, err := lib.MakeFunction(fmt.Sprintf("FragmentShader_%d_%d_%d", cmi, f, a)) - if err != nil { - return err - } - rpld := mtl.RenderPipelineDescriptor{ - VertexFunction: vs, - FragmentFunction: fs, - } + for _, f := range []driver.Filter{ + driver.FilterNearest, + driver.FilterLinear, + } { + for c := driver.CompositeModeSourceOver; c <= driver.CompositeModeMax; c++ { + cmi := 0 + if cm { + cmi = 1 + } + fs, err := lib.MakeFunction(fmt.Sprintf("FragmentShader_%d_%d_%d", cmi, f, a)) + if err != nil { + return err + } + rpld := mtl.RenderPipelineDescriptor{ + VertexFunction: vs, + FragmentFunction: fs, + } - pix := mtl.PixelFormatRGBA8UNorm - if screen { - pix = g.view.colorPixelFormat() - } - rpld.ColorAttachments[0].PixelFormat = pix - rpld.ColorAttachments[0].BlendingEnabled = true + pix := mtl.PixelFormatRGBA8UNorm + if screen { + pix = g.view.colorPixelFormat() + } + rpld.ColorAttachments[0].PixelFormat = pix + rpld.ColorAttachments[0].BlendingEnabled = true - src, dst := c.Operations() - rpld.ColorAttachments[0].DestinationAlphaBlendFactor = operationToBlendFactor(dst) - rpld.ColorAttachments[0].DestinationRGBBlendFactor = operationToBlendFactor(dst) - rpld.ColorAttachments[0].SourceAlphaBlendFactor = operationToBlendFactor(src) - rpld.ColorAttachments[0].SourceRGBBlendFactor = operationToBlendFactor(src) - rps, err := g.view.getMTLDevice().MakeRenderPipelineState(rpld) - if err != nil { - return err + src, dst := c.Operations() + rpld.ColorAttachments[0].DestinationAlphaBlendFactor = operationToBlendFactor(dst) + rpld.ColorAttachments[0].DestinationRGBBlendFactor = operationToBlendFactor(dst) + rpld.ColorAttachments[0].SourceAlphaBlendFactor = operationToBlendFactor(src) + rpld.ColorAttachments[0].SourceRGBBlendFactor = operationToBlendFactor(src) + rps, err := g.view.getMTLDevice().MakeRenderPipelineState(rpld) + if err != nil { + return err + } + g.rpss[rpsKey{ + screen: screen, + useColorM: cm, + filter: f, + address: a, + compositeMode: c, + }] = rps } - g.rpss[rpsKey{ - screen: screen, - useColorM: cm, - filter: f, - address: a, - compositeMode: c, - }] = rps } } } } + + g.cq = g.view.getMTLDevice().MakeCommandQueue() + return nil + }); err != nil { + return err } - g.cq = g.view.getMTLDevice().MakeCommandQueue() return nil } @@ -697,32 +733,37 @@ func (g *Graphics) Draw(dstID, srcID driver.ImageID, indexLen int, indexOffset i }] } - w, h := dst.internalSize() - sourceSize := []float32{0, 0} - if filter != driver.FilterNearest { - w, h := srcs[0].internalSize() - sourceSize[0] = float32(w) - sourceSize[1] = float32(h) - } - esBody, esTranslate := colorM.UnsafeElements() - scale := float32(0) - if filter == driver.FilterScreen { - scale = float32(dst.width) / float32(srcs[0].width) - } - uniforms := []interface{}{ - []float32{float32(w), float32(h)}, - sourceSize, - esBody, - esTranslate, - scale, - []float32{ - sourceRegion.X, - sourceRegion.Y, - sourceRegion.X + sourceRegion.Width, - sourceRegion.Y + sourceRegion.Height, - }, - } - if err := g.draw(rps, dst, srcs, indexLen, indexOffset, uniforms); err != nil { + if err := g.t.Call(func() error { + w, h := dst.internalSize() + sourceSize := []float32{0, 0} + if filter != driver.FilterNearest { + w, h := srcs[0].internalSize() + sourceSize[0] = float32(w) + sourceSize[1] = float32(h) + } + esBody, esTranslate := colorM.UnsafeElements() + scale := float32(0) + if filter == driver.FilterScreen { + scale = float32(dst.width) / float32(srcs[0].width) + } + uniforms := []interface{}{ + []float32{float32(w), float32(h)}, + sourceSize, + esBody, + esTranslate, + scale, + []float32{ + sourceRegion.X, + sourceRegion.Y, + sourceRegion.X + sourceRegion.Width, + sourceRegion.Y + sourceRegion.Height, + }, + } + if err := g.draw(rps, dst, srcs, indexLen, indexOffset, uniforms); err != nil { + return err + } + return nil + }); err != nil { return err } return nil @@ -749,40 +790,52 @@ func (g *Graphics) HasHighPrecisionFloat() bool { } func (g *Graphics) MaxImageSize() int { - if g.maxImageSize == 0 { - g.maxImageSize = 4096 - // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf - switch { - case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily5_v1): - g.maxImageSize = 16384 - case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily4_v1): - g.maxImageSize = 16384 - case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily3_v1): - g.maxImageSize = 16384 - case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily2_v2): - g.maxImageSize = 8192 - case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily2_v1): + m := 0 + g.t.Call(func() error { + if g.maxImageSize == 0 { g.maxImageSize = 4096 - case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily1_v2): - g.maxImageSize = 8192 - case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily1_v1): - g.maxImageSize = 4096 - case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_tvOS_GPUFamily2_v1): - g.maxImageSize = 16384 - case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_tvOS_GPUFamily1_v1): - g.maxImageSize = 8192 - case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_macOS_GPUFamily1_v1): - g.maxImageSize = 16384 - default: - panic("metal: there is no supported feature set") + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf + switch { + case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily5_v1): + g.maxImageSize = 16384 + case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily4_v1): + g.maxImageSize = 16384 + case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily3_v1): + g.maxImageSize = 16384 + case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily2_v2): + g.maxImageSize = 8192 + case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily2_v1): + g.maxImageSize = 4096 + case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily1_v2): + g.maxImageSize = 8192 + case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_iOS_GPUFamily1_v1): + g.maxImageSize = 4096 + case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_tvOS_GPUFamily2_v1): + g.maxImageSize = 16384 + case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_tvOS_GPUFamily1_v1): + g.maxImageSize = 8192 + case g.view.getMTLDevice().SupportsFeatureSet(mtl.FeatureSet_macOS_GPUFamily1_v1): + g.maxImageSize = 16384 + default: + panic("metal: there is no supported feature set") + } } - } - return g.maxImageSize + m = g.maxImageSize + return nil + }) + return m } func (g *Graphics) NewShader(program *shaderir.Program) (driver.Shader, error) { - s, err := newShader(g.view.getMTLDevice(), g.genNextShaderID(), program) - if err != nil { + var s *Shader + if err := g.t.Call(func() error { + var err error + s, err = newShader(g.view.getMTLDevice(), g.genNextShaderID(), program) + if err != nil { + return err + } + return nil + }); err != nil { return nil, err } g.addShader(s) @@ -824,10 +877,13 @@ func (i *Image) internalSize() (int, int) { } func (i *Image) Dispose() { - if i.texture != (mtl.Texture{}) { - i.texture.Release() - i.texture = mtl.Texture{} - } + i.graphics.t.Call(func() error { + if i.texture != (mtl.Texture{}) { + i.texture.Release() + i.texture = mtl.Texture{} + } + return nil + }) i.graphics.removeImage(i) } @@ -841,16 +897,19 @@ func (i *Image) IsInvalidated() bool { func (i *Image) syncTexture() { // Calling SynchronizeTexture is ignored on iOS (see mtl.m), but it looks like committing BliCommandEncoder // is necessary (#1337). - if i.graphics.cb != (mtl.CommandBuffer{}) { - panic("metal: command buffer must be empty at syncTexture: flushIfNeeded is not called yet?") - } + i.graphics.t.Call(func() error { + if i.graphics.cb != (mtl.CommandBuffer{}) { + panic("metal: command buffer must be empty at syncTexture: flushIfNeeded is not called yet?") + } - cb := i.graphics.cq.MakeCommandBuffer() - bce := cb.MakeBlitCommandEncoder() - bce.SynchronizeTexture(i.texture, 0, 0) - bce.EndEncoding() - cb.Commit() - cb.WaitUntilCompleted() + cb := i.graphics.cq.MakeCommandBuffer() + bce := cb.MakeBlitCommandEncoder() + bce.SynchronizeTexture(i.texture, 0, 0) + bce.EndEncoding() + cb.Commit() + cb.WaitUntilCompleted() + return nil + }) } func (i *Image) Pixels() ([]byte, error) { @@ -858,9 +917,12 @@ func (i *Image) Pixels() ([]byte, error) { i.syncTexture() b := make([]byte, 4*i.width*i.height) - i.texture.GetBytes(&b[0], uintptr(4*i.width), mtl.Region{ - Size: mtl.Size{Width: i.width, Height: i.height, Depth: 1}, - }, 0) + i.graphics.t.Call(func() error { + i.texture.GetBytes(&b[0], uintptr(4*i.width), mtl.Region{ + Size: mtl.Size{Width: i.width, Height: i.height, Depth: 1}, + }, 0) + return nil + }) return b, nil } @@ -870,50 +932,58 @@ func (i *Image) ReplacePixels(args []*driver.ReplacePixelsArgs) { // If the memory is shared (e.g., iOS), texture data doen't have to be synced. Send the data directly. if storageMode == mtl.StorageModeShared { - for _, a := range args { - i.texture.ReplaceRegion(mtl.Region{ - Origin: mtl.Origin{X: a.X, Y: a.Y, Z: 0}, - Size: mtl.Size{Width: a.Width, Height: a.Height, Depth: 1}, - }, 0, unsafe.Pointer(&a.Pixels[0]), 4*a.Width) - } + g.t.Call(func() error { + for _, a := range args { + i.texture.ReplaceRegion(mtl.Region{ + Origin: mtl.Origin{X: a.X, Y: a.Y, Z: 0}, + Size: mtl.Size{Width: a.Width, Height: a.Height, Depth: 1}, + }, 0, unsafe.Pointer(&a.Pixels[0]), 4*a.Width) + } + return nil + }) + return } // If the memory is managed (e.g., macOS), texture data cannot be sent to the destination directly because // this requires synchronizing data between CPU and GPU. As synchronizing is inefficient, let's send the // data to a temporary texture once, and then copy it in GPU. - w, h := i.texture.Width(), i.texture.Height() - if g.tmpTexture == (mtl.Texture{}) || w > g.tmpTexture.Width() || h > g.tmpTexture.Height() { - if g.tmpTexture != (mtl.Texture{}) { - g.tmpTexture.Release() + g.t.Call(func() error { + w, h := i.texture.Width(), i.texture.Height() + if g.tmpTexture == (mtl.Texture{}) || w > g.tmpTexture.Width() || h > g.tmpTexture.Height() { + if g.tmpTexture != (mtl.Texture{}) { + g.tmpTexture.Release() + } + td := mtl.TextureDescriptor{ + TextureType: mtl.TextureType2D, + PixelFormat: mtl.PixelFormatRGBA8UNorm, + Width: w, + Height: h, + StorageMode: storageMode, + Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget, + } + g.tmpTexture = g.view.getMTLDevice().MakeTexture(td) } - td := mtl.TextureDescriptor{ - TextureType: mtl.TextureType2D, - PixelFormat: mtl.PixelFormatRGBA8UNorm, - Width: w, - Height: h, - StorageMode: storageMode, - Usage: mtl.TextureUsageShaderRead | mtl.TextureUsageRenderTarget, + + for _, a := range args { + g.tmpTexture.ReplaceRegion(mtl.Region{ + Origin: mtl.Origin{X: a.X, Y: a.Y, Z: 0}, + Size: mtl.Size{Width: a.Width, Height: a.Height, Depth: 1}, + }, 0, unsafe.Pointer(&a.Pixels[0]), 4*a.Width) } - g.tmpTexture = g.view.getMTLDevice().MakeTexture(td) - } - for _, a := range args { - g.tmpTexture.ReplaceRegion(mtl.Region{ - Origin: mtl.Origin{X: a.X, Y: a.Y, Z: 0}, - Size: mtl.Size{Width: a.Width, Height: a.Height, Depth: 1}, - }, 0, unsafe.Pointer(&a.Pixels[0]), 4*a.Width) - } + if g.cb == (mtl.CommandBuffer{}) { + g.cb = i.graphics.cq.MakeCommandBuffer() + } + bce := g.cb.MakeBlitCommandEncoder() + for _, a := range args { + o := mtl.Origin{X: a.X, Y: a.Y, Z: 0} + s := mtl.Size{Width: a.Width, Height: a.Height, Depth: 1} + bce.CopyFromTexture(g.tmpTexture, 0, 0, o, s, i.texture, 0, 0, o) + } + bce.EndEncoding() - if g.cb == (mtl.CommandBuffer{}) { - g.cb = i.graphics.cq.MakeCommandBuffer() - } - bce := g.cb.MakeBlitCommandEncoder() - for _, a := range args { - o := mtl.Origin{X: a.X, Y: a.Y, Z: 0} - s := mtl.Size{Width: a.Width, Height: a.Height, Depth: 1} - bce.CopyFromTexture(g.tmpTexture, 0, 0, o, s, i.texture, 0, 0, o) - } - bce.EndEncoding() + return nil + }) } func (g *Graphics) DrawShader(dstID driver.ImageID, srcIDs [graphics.ShaderImageNum]driver.ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shader driver.ShaderID, indexLen int, indexOffset int, sourceRegion driver.Region, mode driver.CompositeMode, uniforms []interface{}) error { @@ -923,51 +993,56 @@ func (g *Graphics) DrawShader(dstID driver.ImageID, srcIDs [graphics.ShaderImage srcs[i] = g.images[srcID] } - rps, err := g.shaders[shader].RenderPipelineState(g.view.getMTLDevice(), mode) - if err != nil { - return err - } - - us := make([]interface{}, graphics.PreservedUniformVariablesNum+len(uniforms)) - - // Set the destination texture size. - dw, dh := dst.internalSize() - us[graphics.DestinationTextureSizeUniformVariableIndex] = []float32{float32(dw), float32(dh)} - - // Set the source texture sizes. - usizes := make([]float32, 2*len(srcs)) - for i, src := range srcs { - if src != nil { - w, h := src.internalSize() - usizes[2*i] = float32(w) - usizes[2*i+1] = float32(h) + if err := g.t.Call(func() error { + rps, err := g.shaders[shader].RenderPipelineState(g.view.getMTLDevice(), mode) + if err != nil { + return err } - } - us[graphics.TextureSizesUniformVariableIndex] = usizes - // Set the source offsets. - uoffsets := make([]float32, 2*len(offsets)) - for i, offset := range offsets { - uoffsets[2*i] = offset[0] - uoffsets[2*i+1] = offset[1] - } - us[graphics.TextureSourceOffsetsUniformVariableIndex] = uoffsets + us := make([]interface{}, graphics.PreservedUniformVariablesNum+len(uniforms)) - // Set the source region's origin of texture0. - uorigin := []float32{float32(sourceRegion.X), float32(sourceRegion.Y)} - us[graphics.TextureSourceRegionOriginUniformVariableIndex] = uorigin + // Set the destination texture size. + dw, dh := dst.internalSize() + us[graphics.DestinationTextureSizeUniformVariableIndex] = []float32{float32(dw), float32(dh)} - // Set the source region's size of texture0. - ussize := []float32{float32(sourceRegion.Width), float32(sourceRegion.Height)} - us[graphics.TextureSourceRegionSizeUniformVariableIndex] = ussize + // Set the source texture sizes. + usizes := make([]float32, 2*len(srcs)) + for i, src := range srcs { + if src != nil { + w, h := src.internalSize() + usizes[2*i] = float32(w) + usizes[2*i+1] = float32(h) + } + } + us[graphics.TextureSizesUniformVariableIndex] = usizes - // Set the additional uniform variables. - for i, v := range uniforms { - const offset = graphics.PreservedUniformVariablesNum - us[offset+i] = v - } + // Set the source offsets. + uoffsets := make([]float32, 2*len(offsets)) + for i, offset := range offsets { + uoffsets[2*i] = offset[0] + uoffsets[2*i+1] = offset[1] + } + us[graphics.TextureSourceOffsetsUniformVariableIndex] = uoffsets - if err := g.draw(rps, dst, srcs, indexLen, indexOffset, us); err != nil { + // Set the source region's origin of texture0. + uorigin := []float32{float32(sourceRegion.X), float32(sourceRegion.Y)} + us[graphics.TextureSourceRegionOriginUniformVariableIndex] = uorigin + + // Set the source region's size of texture0. + ussize := []float32{float32(sourceRegion.Width), float32(sourceRegion.Height)} + us[graphics.TextureSourceRegionSizeUniformVariableIndex] = ussize + + // Set the additional uniform variables. + for i, v := range uniforms { + const offset = graphics.PreservedUniformVariablesNum + us[offset+i] = v + } + + if err := g.draw(rps, dst, srcs, indexLen, indexOffset, us); err != nil { + return err + } + return nil + }); err != nil { return err } return nil diff --git a/internal/graphicsdriver/opengl/context.go b/internal/graphicsdriver/opengl/context.go index f5186970c..2b70eb356 100644 --- a/internal/graphicsdriver/opengl/context.go +++ b/internal/graphicsdriver/opengl/context.go @@ -19,6 +19,7 @@ import ( "sync" "github.com/hajimehoshi/ebiten/v2/internal/driver" + "github.com/hajimehoshi/ebiten/v2/internal/thread" ) func convertOperation(op driver.Operation) operation { @@ -55,6 +56,8 @@ type context struct { highp bool highpOnce sync.Once + t *thread.Thread + contextImpl } diff --git a/internal/graphicsdriver/opengl/context_desktop.go b/internal/graphicsdriver/opengl/context_desktop.go index 4701dc8dc..31c63952f 100644 --- a/internal/graphicsdriver/opengl/context_desktop.go +++ b/internal/graphicsdriver/opengl/context_desktop.go @@ -101,90 +101,131 @@ type contextImpl struct { } func (c *context) reset() error { - if !c.init { + if err := c.t.Call(func() error { + if c.init { + return nil + } // Note that this initialization must be done after Loop is called. if err := gl.Init(); err != nil { return fmt.Errorf("opengl: initializing error %v", err) } c.init = true + return nil + }); err != nil { + return err } - c.locationCache = newLocationCache() c.lastTexture = invalidTexture c.lastFramebuffer = invalidFramebuffer c.lastViewportWidth = 0 c.lastViewportHeight = 0 c.lastCompositeMode = driver.CompositeModeUnknown - gl.Enable(gl.BLEND) - + _ = c.t.Call(func() error { + gl.Enable(gl.BLEND) + return nil + }) c.blendFunc(driver.CompositeModeSourceOver) - - f := int32(0) - gl.GetIntegerv(gl.FRAMEBUFFER_BINDING, &f) - c.screenFramebuffer = framebufferNative(f) + _ = c.t.Call(func() error { + f := int32(0) + gl.GetIntegerv(gl.FRAMEBUFFER_BINDING, &f) + c.screenFramebuffer = framebufferNative(f) + return nil + }) return nil } func (c *context) blendFunc(mode driver.CompositeMode) { - if c.lastCompositeMode == mode { - return - } - c.lastCompositeMode = mode - s, d := mode.Operations() - s2, d2 := convertOperation(s), convertOperation(d) - gl.BlendFunc(uint32(s2), uint32(d2)) + _ = c.t.Call(func() error { + if c.lastCompositeMode == mode { + return nil + } + c.lastCompositeMode = mode + s, d := mode.Operations() + s2, d2 := convertOperation(s), convertOperation(d) + gl.BlendFunc(uint32(s2), uint32(d2)) + return nil + }) } func (c *context) newTexture(width, height int) (textureNative, error) { - var t uint32 - gl.GenTextures(1, &t) - // TODO: Use gl.IsTexture - if t <= 0 { - return 0, errors.New("opengl: creating texture failed") + var texture textureNative + if err := c.t.Call(func() error { + var t uint32 + gl.GenTextures(1, &t) + // TODO: Use gl.IsTexture + if t <= 0 { + return errors.New("opengl: creating texture failed") + } + gl.PixelStorei(gl.UNPACK_ALIGNMENT, 4) + texture = textureNative(t) + return nil + }); err != nil { + return 0, err } - gl.PixelStorei(gl.UNPACK_ALIGNMENT, 4) - texture := textureNative(t) - c.bindTexture(texture) - gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) - gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) - gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) - gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) - // If data is nil, this just allocates memory and the content is undefined. - // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glTexImage2D.xhtml - gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, int32(width), int32(height), 0, gl.RGBA, gl.UNSIGNED_BYTE, nil) + _ = c.t.Call(func() error { + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) + // If data is nil, this just allocates memory and the content is undefined. + // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glTexImage2D.xhtml + gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, int32(width), int32(height), 0, gl.RGBA, gl.UNSIGNED_BYTE, nil) + return nil + }) return texture, nil } func (c *context) bindFramebufferImpl(f framebufferNative) { - gl.BindFramebufferEXT(gl.FRAMEBUFFER, uint32(f)) + _ = c.t.Call(func() error { + gl.BindFramebufferEXT(gl.FRAMEBUFFER, uint32(f)) + return nil + }) } -func (c *context) framebufferPixels(f *framebuffer, width, height int) []byte { - gl.Flush() +func (c *context) framebufferPixels(f *framebuffer, width, height int) ([]byte, error) { + var pixels []byte + _ = c.t.Call(func() error { + gl.Flush() + return nil + }) c.bindFramebuffer(f.native) - pixels := make([]byte, 4*width*height) - gl.ReadPixels(0, 0, int32(width), int32(height), gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(pixels)) - return pixels + if err := c.t.Call(func() error { + pixels = make([]byte, 4*width*height) + gl.ReadPixels(0, 0, int32(width), int32(height), gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(pixels)) + return nil + }); err != nil { + return nil, err + } + return pixels, nil } func (c *context) activeTexture(idx int) { - gl.ActiveTexture(gl.TEXTURE0 + uint32(idx)) + _ = c.t.Call(func() error { + gl.ActiveTexture(gl.TEXTURE0 + uint32(idx)) + return nil + }) } func (c *context) bindTextureImpl(t textureNative) { - gl.BindTexture(gl.TEXTURE_2D, uint32(t)) + _ = c.t.Call(func() error { + gl.BindTexture(gl.TEXTURE_2D, uint32(t)) + return nil + }) } func (c *context) deleteTexture(t textureNative) { - tt := uint32(t) - if !gl.IsTexture(tt) { - return - } - if c.lastTexture == t { - c.lastTexture = invalidTexture - } - gl.DeleteTextures(1, &tt) + _ = c.t.Call(func() error { + tt := uint32(t) + if !gl.IsTexture(tt) { + return nil + } + if c.lastTexture == t { + c.lastTexture = invalidTexture + } + gl.DeleteTextures(1, &tt) + return nil + }) } func (c *context) isTexture(t textureNative) bool { @@ -192,114 +233,155 @@ func (c *context) isTexture(t textureNative) bool { } func (c *context) newFramebuffer(texture textureNative) (framebufferNative, error) { + var framebuffer framebufferNative var f uint32 - gl.GenFramebuffersEXT(1, &f) - // TODO: Use gl.IsFramebuffer - if f <= 0 { - return 0, errors.New("opengl: creating framebuffer failed: gl.IsFramebuffer returns false") + if err := c.t.Call(func() error { + gl.GenFramebuffersEXT(1, &f) + // TODO: Use gl.IsFramebuffer + if f <= 0 { + return errors.New("opengl: creating framebuffer failed: gl.IsFramebuffer returns false") + } + return nil + }); err != nil { + return 0, err } c.bindFramebuffer(framebufferNative(f)) - gl.FramebufferTexture2DEXT(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, uint32(texture), 0) - s := gl.CheckFramebufferStatusEXT(gl.FRAMEBUFFER) - if s != gl.FRAMEBUFFER_COMPLETE { - if s != 0 { - return 0, fmt.Errorf("opengl: creating framebuffer failed: %v", s) + if err := c.t.Call(func() error { + gl.FramebufferTexture2DEXT(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, uint32(texture), 0) + s := gl.CheckFramebufferStatusEXT(gl.FRAMEBUFFER) + if s != gl.FRAMEBUFFER_COMPLETE { + if s != 0 { + return fmt.Errorf("opengl: creating framebuffer failed: %v", s) + } + if e := gl.GetError(); e != gl.NO_ERROR { + return fmt.Errorf("opengl: creating framebuffer failed: (glGetError) %d", e) + } + return fmt.Errorf("opengl: creating framebuffer failed: unknown error") } - if e := gl.GetError(); e != gl.NO_ERROR { - return 0, fmt.Errorf("opengl: creating framebuffer failed: (glGetError) %d", e) - } - return 0, fmt.Errorf("opengl: creating framebuffer failed: unknown error") + framebuffer = framebufferNative(f) + return nil + }); err != nil { + return 0, err } - return framebufferNative(f), nil + return framebuffer, nil } func (c *context) setViewportImpl(width, height int) { - gl.Viewport(0, 0, int32(width), int32(height)) + _ = c.t.Call(func() error { + gl.Viewport(0, 0, int32(width), int32(height)) + return nil + }) } func (c *context) deleteFramebuffer(f framebufferNative) { - ff := uint32(f) - if !gl.IsFramebufferEXT(ff) { - return - } - if c.lastFramebuffer == f { - c.lastFramebuffer = invalidFramebuffer - c.lastViewportWidth = 0 - c.lastViewportHeight = 0 - } - gl.DeleteFramebuffersEXT(1, &ff) + _ = c.t.Call(func() error { + ff := uint32(f) + if !gl.IsFramebufferEXT(ff) { + return nil + } + if c.lastFramebuffer == f { + c.lastFramebuffer = invalidFramebuffer + c.lastViewportWidth = 0 + c.lastViewportHeight = 0 + } + gl.DeleteFramebuffersEXT(1, &ff) + return nil + }) } func (c *context) newShader(shaderType shaderType, source string) (shader, error) { - s := gl.CreateShader(uint32(shaderType)) - if s == 0 { - return 0, fmt.Errorf("opengl: glCreateShader failed: shader type: %d", shaderType) - } - cSources, free := gl.Strs(source + "\x00") - gl.ShaderSource(uint32(s), 1, cSources, nil) - free() - gl.CompileShader(s) - - var v int32 - gl.GetShaderiv(s, gl.COMPILE_STATUS, &v) - if v == gl.FALSE { - var l int32 - var log []byte - gl.GetShaderiv(uint32(s), gl.INFO_LOG_LENGTH, &l) - if l != 0 { - log = make([]byte, l) - gl.GetShaderInfoLog(s, l, nil, (*uint8)(gl.Ptr(log))) + var sh shader + if err := c.t.Call(func() error { + s := gl.CreateShader(uint32(shaderType)) + if s == 0 { + return fmt.Errorf("opengl: glCreateShader failed: shader type: %d", shaderType) } - return 0, fmt.Errorf("opengl: shader compile failed: %s", log) + cSources, free := gl.Strs(source + "\x00") + gl.ShaderSource(uint32(s), 1, cSources, nil) + free() + gl.CompileShader(s) + + var v int32 + gl.GetShaderiv(s, gl.COMPILE_STATUS, &v) + if v == gl.FALSE { + var l int32 + var log []byte + gl.GetShaderiv(uint32(s), gl.INFO_LOG_LENGTH, &l) + if l != 0 { + log = make([]byte, l) + gl.GetShaderInfoLog(s, l, nil, (*uint8)(gl.Ptr(log))) + } + return fmt.Errorf("opengl: shader compile failed: %s", log) + } + sh = shader(s) + return nil + }); err != nil { + return 0, err } - return shader(s), nil + return sh, nil } func (c *context) deleteShader(s shader) { - gl.DeleteShader(uint32(s)) + _ = c.t.Call(func() error { + gl.DeleteShader(uint32(s)) + return nil + }) } func (c *context) newProgram(shaders []shader, attributes []string) (program, error) { - p := gl.CreateProgram() - if p == 0 { - return 0, errors.New("opengl: glCreateProgram failed") - } - - for _, shader := range shaders { - gl.AttachShader(p, uint32(shader)) - } - - for i, name := range attributes { - l, free := gl.Strs(name + "\x00") - gl.BindAttribLocation(p, uint32(i), *l) - free() - } - - gl.LinkProgram(p) - var v int32 - gl.GetProgramiv(p, gl.LINK_STATUS, &v) - if v == gl.FALSE { - var l int32 - var log []byte - gl.GetProgramiv(p, gl.INFO_LOG_LENGTH, &l) - if l != 0 { - log = make([]byte, l) - gl.GetProgramInfoLog(p, l, nil, (*uint8)(gl.Ptr(log))) + var pr program + if err := c.t.Call(func() error { + p := gl.CreateProgram() + if p == 0 { + return errors.New("opengl: glCreateProgram failed") } - return 0, fmt.Errorf("opengl: program error: %s", log) + + for _, shader := range shaders { + gl.AttachShader(p, uint32(shader)) + } + + for i, name := range attributes { + l, free := gl.Strs(name + "\x00") + gl.BindAttribLocation(p, uint32(i), *l) + free() + } + + gl.LinkProgram(p) + var v int32 + gl.GetProgramiv(p, gl.LINK_STATUS, &v) + if v == gl.FALSE { + var l int32 + var log []byte + gl.GetProgramiv(p, gl.INFO_LOG_LENGTH, &l) + if l != 0 { + log = make([]byte, l) + gl.GetProgramInfoLog(p, l, nil, (*uint8)(gl.Ptr(log))) + } + return fmt.Errorf("opengl: program error: %s", log) + } + pr = program(p) + return nil + }); err != nil { + return 0, err } - return program(p), nil + return pr, nil } func (c *context) useProgram(p program) { - gl.UseProgram(uint32(p)) + _ = c.t.Call(func() error { + gl.UseProgram(uint32(p)) + return nil + }) } func (c *context) deleteProgram(p program) { - if !gl.IsProgram(uint32(p)) { - return - } - gl.DeleteProgram(uint32(p)) + _ = c.t.Call(func() error { + if !gl.IsProgram(uint32(p)) { + return nil + } + gl.DeleteProgram(uint32(p)) + return nil + }) } func (c *context) getUniformLocationImpl(p program, location string) uniformLocation { @@ -310,110 +392,164 @@ func (c *context) getUniformLocationImpl(p program, location string) uniformLoca } func (c *context) uniformInt(p program, location string, v int) bool { - l := int32(c.locationCache.GetUniformLocation(c, p, location)) - if l == invalidUniform { - return false - } - gl.Uniform1i(l, int32(v)) - return true + var r bool + _ = c.t.Call(func() error { + l := int32(c.locationCache.GetUniformLocation(c, p, location)) + if l == invalidUniform { + return nil + } + r = true + gl.Uniform1i(l, int32(v)) + return nil + }) + return r } func (c *context) uniformFloat(p program, location string, v float32) bool { - l := int32(c.locationCache.GetUniformLocation(c, p, location)) - if l == invalidUniform { - return false - } - gl.Uniform1f(l, v) - return true + var r bool + _ = c.t.Call(func() error { + l := int32(c.locationCache.GetUniformLocation(c, p, location)) + if l == invalidUniform { + return nil + } + r = true + gl.Uniform1f(l, v) + return nil + }) + return r } func (c *context) uniformFloats(p program, location string, v []float32, typ shaderir.Type) bool { - l := int32(c.locationCache.GetUniformLocation(c, p, location)) - if l == invalidUniform { - return false - } + var r bool + _ = c.t.Call(func() error { + l := int32(c.locationCache.GetUniformLocation(c, p, location)) + if l == invalidUniform { + return nil + } + r = true - base := typ.Main - len := int32(1) - if base == shaderir.Array { - base = typ.Sub[0].Main - len = int32(typ.Length) - } + base := typ.Main + len := int32(1) + if base == shaderir.Array { + base = typ.Sub[0].Main + len = int32(typ.Length) + } - switch base { - case shaderir.Float: - gl.Uniform1fv(l, len, (*float32)(gl.Ptr(v))) - case shaderir.Vec2: - gl.Uniform2fv(l, len, (*float32)(gl.Ptr(v))) - case shaderir.Vec3: - gl.Uniform3fv(l, len, (*float32)(gl.Ptr(v))) - case shaderir.Vec4: - gl.Uniform4fv(l, len, (*float32)(gl.Ptr(v))) - case shaderir.Mat2: - gl.UniformMatrix2fv(l, len, false, (*float32)(gl.Ptr(v))) - case shaderir.Mat3: - gl.UniformMatrix3fv(l, len, false, (*float32)(gl.Ptr(v))) - case shaderir.Mat4: - gl.UniformMatrix4fv(l, len, false, (*float32)(gl.Ptr(v))) - default: - panic(fmt.Sprintf("opengl: unexpected type: %s", typ.String())) - } - return true + switch base { + case shaderir.Float: + gl.Uniform1fv(l, len, (*float32)(gl.Ptr(v))) + case shaderir.Vec2: + gl.Uniform2fv(l, len, (*float32)(gl.Ptr(v))) + case shaderir.Vec3: + gl.Uniform3fv(l, len, (*float32)(gl.Ptr(v))) + case shaderir.Vec4: + gl.Uniform4fv(l, len, (*float32)(gl.Ptr(v))) + case shaderir.Mat2: + gl.UniformMatrix2fv(l, len, false, (*float32)(gl.Ptr(v))) + case shaderir.Mat3: + gl.UniformMatrix3fv(l, len, false, (*float32)(gl.Ptr(v))) + case shaderir.Mat4: + gl.UniformMatrix4fv(l, len, false, (*float32)(gl.Ptr(v))) + default: + panic(fmt.Sprintf("opengl: unexpected type: %s", typ.String())) + } + return nil + }) + return r } func (c *context) vertexAttribPointer(p program, index int, size int, dataType dataType, stride int, offset int) { - gl.VertexAttribPointer(uint32(index), int32(size), uint32(dataType), false, int32(stride), uintptr(offset)) + _ = c.t.Call(func() error { + gl.VertexAttribPointer(uint32(index), int32(size), uint32(dataType), false, int32(stride), uintptr(offset)) + return nil + }) } func (c *context) enableVertexAttribArray(p program, index int) { - gl.EnableVertexAttribArray(uint32(index)) + _ = c.t.Call(func() error { + gl.EnableVertexAttribArray(uint32(index)) + return nil + }) } func (c *context) disableVertexAttribArray(p program, index int) { - gl.DisableVertexAttribArray(uint32(index)) + _ = c.t.Call(func() error { + gl.DisableVertexAttribArray(uint32(index)) + return nil + }) } func (c *context) newArrayBuffer(size int) buffer { - var b uint32 - gl.GenBuffers(1, &b) - gl.BindBuffer(uint32(arrayBuffer), b) - gl.BufferData(uint32(arrayBuffer), size, nil, uint32(dynamicDraw)) - return buffer(b) + var bf buffer + _ = c.t.Call(func() error { + var b uint32 + gl.GenBuffers(1, &b) + gl.BindBuffer(uint32(arrayBuffer), b) + gl.BufferData(uint32(arrayBuffer), size, nil, uint32(dynamicDraw)) + bf = buffer(b) + return nil + }) + return bf } func (c *context) newElementArrayBuffer(size int) buffer { - var b uint32 - gl.GenBuffers(1, &b) - gl.BindBuffer(uint32(elementArrayBuffer), b) - gl.BufferData(uint32(elementArrayBuffer), size, nil, uint32(dynamicDraw)) - return buffer(b) + var bf buffer + _ = c.t.Call(func() error { + var b uint32 + gl.GenBuffers(1, &b) + gl.BindBuffer(uint32(elementArrayBuffer), b) + gl.BufferData(uint32(elementArrayBuffer), size, nil, uint32(dynamicDraw)) + bf = buffer(b) + return nil + }) + return bf } func (c *context) bindBuffer(bufferType bufferType, b buffer) { - gl.BindBuffer(uint32(bufferType), uint32(b)) + _ = c.t.Call(func() error { + gl.BindBuffer(uint32(bufferType), uint32(b)) + return nil + }) } func (c *context) arrayBufferSubData(data []float32) { - gl.BufferSubData(uint32(arrayBuffer), 0, len(data)*4, gl.Ptr(data)) + _ = c.t.Call(func() error { + gl.BufferSubData(uint32(arrayBuffer), 0, len(data)*4, gl.Ptr(data)) + return nil + }) } func (c *context) elementArrayBufferSubData(data []uint16) { - gl.BufferSubData(uint32(elementArrayBuffer), 0, len(data)*2, gl.Ptr(data)) + _ = c.t.Call(func() error { + gl.BufferSubData(uint32(elementArrayBuffer), 0, len(data)*2, gl.Ptr(data)) + return nil + }) } func (c *context) deleteBuffer(b buffer) { - bb := uint32(b) - gl.DeleteBuffers(1, &bb) + _ = c.t.Call(func() error { + bb := uint32(b) + gl.DeleteBuffers(1, &bb) + return nil + }) } func (c *context) drawElements(len int, offsetInBytes int) { - gl.DrawElements(gl.TRIANGLES, int32(len), gl.UNSIGNED_SHORT, uintptr(offsetInBytes)) + _ = c.t.Call(func() error { + gl.DrawElements(gl.TRIANGLES, int32(len), gl.UNSIGNED_SHORT, uintptr(offsetInBytes)) + return nil + }) } func (c *context) maxTextureSizeImpl() int { - s := int32(0) - gl.GetIntegerv(gl.MAX_TEXTURE_SIZE, &s) - return int(s) + size := 0 + _ = c.t.Call(func() error { + s := int32(0) + gl.GetIntegerv(gl.MAX_TEXTURE_SIZE, &s) + size = int(s) + return nil + }) + return size } func (c *context) getShaderPrecisionFormatPrecision() int { @@ -423,7 +559,10 @@ func (c *context) getShaderPrecisionFormatPrecision() int { } func (c *context) flush() { - gl.Flush() + _ = c.t.Call(func() error { + gl.Flush() + return nil + }) } func (c *context) needsRestoring() bool { @@ -431,37 +570,54 @@ func (c *context) needsRestoring() bool { } func (c *context) canUsePBO() bool { - return isPBOAvailable() + var available bool + _ = c.t.Call(func() error { + available = isPBOAvailable() + return nil + }) + + return available } func (c *context) texSubImage2D(t textureNative, width, height int, args []*driver.ReplacePixelsArgs) { c.bindTexture(t) - for _, a := range args { - gl.TexSubImage2D(gl.TEXTURE_2D, 0, int32(a.X), int32(a.Y), int32(a.Width), int32(a.Height), gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(a.Pixels)) - } + _ = c.t.Call(func() error { + for _, a := range args { + gl.TexSubImage2D(gl.TEXTURE_2D, 0, int32(a.X), int32(a.Y), int32(a.Width), int32(a.Height), gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(a.Pixels)) + } + return nil + }) } func (c *context) newPixelBufferObject(width, height int) buffer { - var b uint32 - gl.GenBuffers(1, &b) - gl.BindBuffer(gl.PIXEL_UNPACK_BUFFER, b) - gl.BufferData(gl.PIXEL_UNPACK_BUFFER, 4*width*height, nil, gl.STREAM_DRAW) - gl.BindBuffer(gl.PIXEL_UNPACK_BUFFER, 0) - return buffer(b) + var bf buffer + _ = c.t.Call(func() error { + var b uint32 + gl.GenBuffers(1, &b) + gl.BindBuffer(gl.PIXEL_UNPACK_BUFFER, b) + gl.BufferData(gl.PIXEL_UNPACK_BUFFER, 4*width*height, nil, gl.STREAM_DRAW) + gl.BindBuffer(gl.PIXEL_UNPACK_BUFFER, 0) + bf = buffer(b) + return nil + }) + return bf } func (c *context) replacePixelsWithPBO(buffer buffer, t textureNative, width, height int, args []*driver.ReplacePixelsArgs) { c.bindTexture(t) - gl.BindBuffer(gl.PIXEL_UNPACK_BUFFER, uint32(buffer)) + _ = c.t.Call(func() error { + gl.BindBuffer(gl.PIXEL_UNPACK_BUFFER, uint32(buffer)) - stride := 4 * width - for _, a := range args { - offset := 4 * (a.Y*width + a.X) - for j := 0; j < a.Height; j++ { - gl.BufferSubData(gl.PIXEL_UNPACK_BUFFER, offset+stride*j, 4*a.Width, gl.Ptr(a.Pixels[4*a.Width*j:4*a.Width*(j+1)])) + stride := 4 * width + for _, a := range args { + offset := 4 * (a.Y*width + a.X) + for j := 0; j < a.Height; j++ { + gl.BufferSubData(gl.PIXEL_UNPACK_BUFFER, offset+stride*j, 4*a.Width, gl.Ptr(a.Pixels[4*a.Width*j:4*a.Width*(j+1)])) + } } - } - gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, int32(width), int32(height), gl.RGBA, gl.UNSIGNED_BYTE, nil) - gl.BindBuffer(gl.PIXEL_UNPACK_BUFFER, 0) + gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, int32(width), int32(height), gl.RGBA, gl.UNSIGNED_BYTE, nil) + gl.BindBuffer(gl.PIXEL_UNPACK_BUFFER, 0) + return nil + }) } diff --git a/internal/graphicsdriver/opengl/context_js.go b/internal/graphicsdriver/opengl/context_js.go index 676b5d13f..ae7361100 100644 --- a/internal/graphicsdriver/opengl/context_js.go +++ b/internal/graphicsdriver/opengl/context_js.go @@ -273,7 +273,7 @@ func (c *context) bindFramebufferImpl(f framebufferNative) { gl.Call("bindFramebuffer", framebuffer_, js.Value(f)) } -func (c *context) framebufferPixels(f *framebuffer, width, height int) []byte { +func (c *context) framebufferPixels(f *framebuffer, width, height int) ([]byte, error) { c.ensureGL() gl := c.gl @@ -282,7 +282,7 @@ func (c *context) framebufferPixels(f *framebuffer, width, height int) []byte { p := jsutil.TemporaryUint8Array(4 * width * height) gl.Call("readPixels", 0, 0, width, height, rgba, unsignedByte, p) - return jsutil.Uint8ArrayToSlice(p) + return jsutil.Uint8ArrayToSlice(p), nil } func (c *context) activeTexture(idx int) { diff --git a/internal/graphicsdriver/opengl/context_mobile.go b/internal/graphicsdriver/opengl/context_mobile.go index b7b6a43ab..1cdb4023d 100644 --- a/internal/graphicsdriver/opengl/context_mobile.go +++ b/internal/graphicsdriver/opengl/context_mobile.go @@ -148,7 +148,7 @@ func (c *context) bindFramebufferImpl(f framebufferNative) { gl.BindFramebuffer(mgl.FRAMEBUFFER, mgl.Framebuffer(f)) } -func (c *context) framebufferPixels(f *framebuffer, width, height int) []byte { +func (c *context) framebufferPixels(f *framebuffer, width, height int) ([]byte, error) { gl := c.gl gl.Flush() @@ -156,7 +156,7 @@ func (c *context) framebufferPixels(f *framebuffer, width, height int) []byte { pixels := make([]byte, 4*width*height) gl.ReadPixels(pixels, 0, 0, width, height, mgl.RGBA, mgl.UNSIGNED_BYTE) - return pixels + return pixels, nil } func (c *context) activeTexture(idx int) { diff --git a/internal/graphicsdriver/opengl/graphics.go b/internal/graphicsdriver/opengl/graphics.go index 029cd732e..fb6e15179 100644 --- a/internal/graphicsdriver/opengl/graphics.go +++ b/internal/graphicsdriver/opengl/graphics.go @@ -21,6 +21,7 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/driver" "github.com/hajimehoshi/ebiten/v2/internal/graphics" "github.com/hajimehoshi/ebiten/v2/internal/shaderir" + "github.com/hajimehoshi/ebiten/v2/internal/thread" ) var theGraphics Graphics @@ -43,6 +44,10 @@ type Graphics struct { drawCalled bool } +func (g *Graphics) SetThread(thread *thread.Thread) { + g.context.t = thread +} + func (g *Graphics) Begin() { // Do nothing. } diff --git a/internal/graphicsdriver/opengl/image.go b/internal/graphicsdriver/opengl/image.go index ae73d15b2..c4abea793 100644 --- a/internal/graphicsdriver/opengl/image.go +++ b/internal/graphicsdriver/opengl/image.go @@ -64,7 +64,10 @@ func (i *Image) Pixels() ([]byte, error) { if err := i.ensureFramebuffer(); err != nil { return nil, err } - p := i.graphics.context.framebufferPixels(i.framebuffer, i.width, i.height) + p, err := i.graphics.context.framebufferPixels(i.framebuffer, i.width, i.height) + if err != nil { + return nil, err + } return p, nil } diff --git a/internal/uidriver/glfw/ui.go b/internal/uidriver/glfw/ui.go index 9669b4f75..6a74f3681 100644 --- a/internal/uidriver/glfw/ui.go +++ b/internal/uidriver/glfw/ui.go @@ -29,7 +29,6 @@ import ( "github.com/hajimehoshi/ebiten/v2/internal/devicescale" "github.com/hajimehoshi/ebiten/v2/internal/driver" "github.com/hajimehoshi/ebiten/v2/internal/glfw" - "github.com/hajimehoshi/ebiten/v2/internal/graphicscommand" "github.com/hajimehoshi/ebiten/v2/internal/hooks" "github.com/hajimehoshi/ebiten/v2/internal/thread" ) @@ -589,7 +588,7 @@ func (u *UserInterface) Run(uicontext driver.UIContext) error { // Initialize the main thread first so the thread is available at u.run (#809). u.t = thread.New() - graphicscommand.SetThread(u.t) + u.Graphics().SetThread(u.t) ch := make(chan error, 1) go func() {