diff --git a/graphics.go b/graphics.go index cb0bece64..66dc3f2c2 100644 --- a/graphics.go +++ b/graphics.go @@ -16,6 +16,11 @@ limitations under the License. package ebiten +import ( + "github.com/hajimehoshi/ebiten/internal/opengl" + "github.com/hajimehoshi/ebiten/internal/opengl/internal/shader" +) + // A Rect represents a rectangle. type Rect struct { X int @@ -81,3 +86,28 @@ type RenderTargetID int func (i RenderTargetID) IsNil() bool { return i == 0 } + +func u(x int, width int) float32 { + return float32(x) / float32(opengl.AdjustSizeForTexture(width)) +} + +func v(y int, height int) float32 { + return float32(y) / float32(opengl.AdjustSizeForTexture(height)) +} + +func textureQuads(parts []TexturePart, width, height int) []shader.TextureQuad { + quads := make([]shader.TextureQuad, 0, len(parts)) + for _, part := range parts { + x1 := float32(part.LocationX) + x2 := float32(part.LocationX + part.Source.Width) + y1 := float32(part.LocationY) + y2 := float32(part.LocationY + part.Source.Height) + u1 := u(part.Source.X, width) + u2 := u(part.Source.X+part.Source.Width, width) + v1 := v(part.Source.Y, height) + v2 := v(part.Source.Y+part.Source.Height, height) + quad := shader.TextureQuad{x1, x2, y1, y2, u1, u2, v1, v2} + quads = append(quads, quad) + } + return quads +} diff --git a/internal/opengl/context.go b/graphicscontext.go similarity index 57% rename from internal/opengl/context.go rename to graphicscontext.go index 0f8455055..1f300b7d2 100644 --- a/internal/opengl/context.go +++ b/graphicscontext.go @@ -14,29 +14,29 @@ See the License for the specific language governing permissions and limitations under the License. */ -package opengl +package ebiten import ( "github.com/go-gl/gl" - "github.com/hajimehoshi/ebiten" + "github.com/hajimehoshi/ebiten/internal/opengl" ) -func Initialize(screenWidth, screenHeight, screenScale int) (*GraphicsContext, error) { +func Initialize(screenWidth, screenHeight, screenScale int) (*graphicsContext, error) { gl.Init() gl.Enable(gl.TEXTURE_2D) gl.Enable(gl.BLEND) - c := &GraphicsContext{ + c := &graphicsContext{ screenWidth: screenWidth, screenHeight: screenHeight, screenScale: screenScale, } // The defualt framebuffer should be 0. - c.defaultID = idsInstance.addRenderTarget(&renderTarget{ - width: screenWidth * screenScale, - height: screenHeight * screenScale, - flipY: true, + c.defaultID = idsInstance.addRenderTarget(&opengl.RenderTarget{ + Width: screenWidth * screenScale, + Height: screenHeight * screenScale, + FlipY: true, }) var err error @@ -46,76 +46,76 @@ func Initialize(screenWidth, screenHeight, screenScale int) (*GraphicsContext, e } // TODO: This is a special stack only for clearing. Can we change this? - c.currentIDs = []ebiten.RenderTargetID{c.screenID} + c.currentIDs = []RenderTargetID{c.screenID} c.Clear() return c, nil } -type GraphicsContext struct { - screenID ebiten.RenderTargetID - defaultID ebiten.RenderTargetID - currentIDs []ebiten.RenderTargetID +type graphicsContext struct { + screenID RenderTargetID + defaultID RenderTargetID + currentIDs []RenderTargetID screenWidth int screenHeight int screenScale int } -var _ ebiten.GraphicsContext = new(GraphicsContext) +var _ GraphicsContext = new(graphicsContext) -func (c *GraphicsContext) dispose() { +func (c *graphicsContext) dispose() { // NOTE: Now this method is not used anywhere. idsInstance.deleteRenderTarget(c.screenID) } -func (c *GraphicsContext) Clear() { +func (c *graphicsContext) Clear() { c.Fill(0, 0, 0) } -func (c *GraphicsContext) Fill(r, g, b uint8) { +func (c *graphicsContext) Fill(r, g, b uint8) { idsInstance.fillRenderTarget(c.currentIDs[len(c.currentIDs)-1], r, g, b) } -func (c *GraphicsContext) Texture(id ebiten.TextureID) ebiten.Drawer { +func (c *graphicsContext) Texture(id TextureID) Drawer { return &textureWithContext{id, c} } -func (c *GraphicsContext) RenderTarget(id ebiten.RenderTargetID) ebiten.Drawer { +func (c *graphicsContext) RenderTarget(id RenderTargetID) Drawer { return &textureWithContext{idsInstance.toTexture(id), c} } -func (c *GraphicsContext) PushRenderTarget(renderTargetID ebiten.RenderTargetID) { +func (c *graphicsContext) PushRenderTarget(renderTargetID RenderTargetID) { c.currentIDs = append(c.currentIDs, renderTargetID) } -func (c *GraphicsContext) PopRenderTarget() { +func (c *graphicsContext) PopRenderTarget() { c.currentIDs = c.currentIDs[:len(c.currentIDs)-1] } -func (c *GraphicsContext) PreUpdate() { - c.currentIDs = []ebiten.RenderTargetID{c.defaultID} +func (c *graphicsContext) PreUpdate() { + c.currentIDs = []RenderTargetID{c.defaultID} c.PushRenderTarget(c.screenID) c.Clear() } -func (c *GraphicsContext) PostUpdate() { +func (c *graphicsContext) PostUpdate() { c.PopRenderTarget() c.Clear() scale := float64(c.screenScale) - geo := ebiten.GeometryMatrixI() + geo := GeometryMatrixI() geo.Scale(scale, scale) - ebiten.DrawWhole(c.RenderTarget(c.screenID), c.screenWidth, c.screenHeight, geo, ebiten.ColorMatrixI()) + DrawWhole(c.RenderTarget(c.screenID), c.screenWidth, c.screenHeight, geo, ColorMatrixI()) gl.Flush() } type textureWithContext struct { - id ebiten.TextureID - context *GraphicsContext + id TextureID + context *graphicsContext } -func (t *textureWithContext) Draw(parts []ebiten.TexturePart, geo ebiten.GeometryMatrix, color ebiten.ColorMatrix) { +func (t *textureWithContext) Draw(parts []TexturePart, geo GeometryMatrix, color ColorMatrix) { currentID := t.context.currentIDs[len(t.context.currentIDs)-1] idsInstance.drawTexture(currentID, t.id, parts, geo, color) } diff --git a/internal/opengl/ids.go b/ids.go similarity index 53% rename from internal/opengl/ids.go rename to ids.go index 2cbe728ac..870b39786 100644 --- a/internal/opengl/ids.go +++ b/ids.go @@ -14,11 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -package opengl +package ebiten import ( "github.com/go-gl/gl" - "github.com/hajimehoshi/ebiten" + "github.com/hajimehoshi/ebiten/internal/opengl" "github.com/hajimehoshi/ebiten/internal/opengl/internal/shader" "image" "math" @@ -26,49 +26,49 @@ import ( ) type ids struct { - textures map[ebiten.TextureID]*texture - renderTargets map[ebiten.RenderTargetID]*renderTarget - renderTargetToTexture map[ebiten.RenderTargetID]ebiten.TextureID + textures map[TextureID]*opengl.Texture + renderTargets map[RenderTargetID]*opengl.RenderTarget + renderTargetToTexture map[RenderTargetID]TextureID lastId int - currentRenderTargetId ebiten.RenderTargetID + currentRenderTargetId RenderTargetID sync.RWMutex } var idsInstance = &ids{ - textures: map[ebiten.TextureID]*texture{}, - renderTargets: map[ebiten.RenderTargetID]*renderTarget{}, - renderTargetToTexture: map[ebiten.RenderTargetID]ebiten.TextureID{}, + textures: map[TextureID]*opengl.Texture{}, + renderTargets: map[RenderTargetID]*opengl.RenderTarget{}, + renderTargetToTexture: map[RenderTargetID]TextureID{}, currentRenderTargetId: -1, } -func NewRenderTargetID(width, height int, filter int) (ebiten.RenderTargetID, error) { +func NewRenderTargetID(width, height int, filter int) (RenderTargetID, error) { return idsInstance.createRenderTarget(width, height, filter) } -func NewTextureID(img image.Image, filter int) (ebiten.TextureID, error) { +func NewTextureID(img image.Image, filter int) (TextureID, error) { return idsInstance.createTexture(img, filter) } -func (i *ids) textureAt(id ebiten.TextureID) *texture { +func (i *ids) textureAt(id TextureID) *opengl.Texture { i.RLock() defer i.RUnlock() return i.textures[id] } -func (i *ids) renderTargetAt(id ebiten.RenderTargetID) *renderTarget { +func (i *ids) renderTargetAt(id RenderTargetID) *opengl.RenderTarget { i.RLock() defer i.RUnlock() return i.renderTargets[id] } -func (i *ids) toTexture(id ebiten.RenderTargetID) ebiten.TextureID { +func (i *ids) toTexture(id RenderTargetID) TextureID { i.RLock() defer i.RUnlock() return i.renderTargetToTexture[id] } -func (i *ids) createTexture(img image.Image, filter int) (ebiten.TextureID, error) { - texture, err := createTextureFromImage(img, filter) +func (i *ids) createTexture(img image.Image, filter int) (TextureID, error) { + texture, err := opengl.CreateTextureFromImage(img, filter) if err != nil { return 0, err } @@ -76,31 +76,31 @@ func (i *ids) createTexture(img image.Image, filter int) (ebiten.TextureID, erro i.Lock() defer i.Unlock() i.lastId++ - textureId := ebiten.TextureID(i.lastId) + textureId := TextureID(i.lastId) i.textures[textureId] = texture return textureId, nil } -func (i *ids) createRenderTarget(width, height int, filter int) (ebiten.RenderTargetID, error) { - texture, err := createTexture(width, height, filter) +func (i *ids) createRenderTarget(width, height int, filter int) (RenderTargetID, error) { + texture, err := opengl.CreateTexture(width, height, filter) if err != nil { return 0, err } - framebuffer := createFramebuffer(gl.Texture(texture.native)) + framebuffer := opengl.CreateFramebuffer(texture.Native()) // The current binded framebuffer can be changed. i.currentRenderTargetId = -1 - r := &renderTarget{ - framebuffer: framebuffer, - width: texture.width, - height: texture.height, + r := &opengl.RenderTarget{ + Framebuffer: framebuffer, + Width: texture.Width(), + Height: texture.Height(), } i.Lock() defer i.Unlock() i.lastId++ - textureId := ebiten.TextureID(i.lastId) + textureId := TextureID(i.lastId) i.lastId++ - renderTargetId := ebiten.RenderTargetID(i.lastId) + renderTargetId := RenderTargetID(i.lastId) i.textures[textureId] = texture i.renderTargets[renderTargetId] = r @@ -110,17 +110,17 @@ func (i *ids) createRenderTarget(width, height int, filter int) (ebiten.RenderTa } // NOTE: renderTarget can't be used as a texture. -func (i *ids) addRenderTarget(renderTarget *renderTarget) ebiten.RenderTargetID { +func (i *ids) addRenderTarget(renderTarget *opengl.RenderTarget) RenderTargetID { i.Lock() defer i.Unlock() i.lastId++ - id := ebiten.RenderTargetID(i.lastId) + id := RenderTargetID(i.lastId) i.renderTargets[id] = renderTarget return id } -func (i *ids) deleteRenderTarget(id ebiten.RenderTargetID) { +func (i *ids) deleteRenderTarget(id RenderTargetID) { i.Lock() defer i.Unlock() @@ -128,34 +128,34 @@ func (i *ids) deleteRenderTarget(id ebiten.RenderTargetID) { textureId := i.renderTargetToTexture[id] texture := i.textures[textureId] - renderTarget.dispose() - texture.dispose() + renderTarget.Dispose() + texture.Dispose() delete(i.renderTargets, id) delete(i.renderTargetToTexture, id) delete(i.textures, textureId) } -func (i *ids) fillRenderTarget(id ebiten.RenderTargetID, r, g, b uint8) { +func (i *ids) fillRenderTarget(id RenderTargetID, r, g, b uint8) { i.setViewportIfNeeded(id) const max = float64(math.MaxUint8) gl.ClearColor(gl.GLclampf(float64(r)/max), gl.GLclampf(float64(g)/max), gl.GLclampf(float64(b)/max), 1) gl.Clear(gl.COLOR_BUFFER_BIT) } -func (i *ids) drawTexture(target ebiten.RenderTargetID, id ebiten.TextureID, parts []ebiten.TexturePart, geo ebiten.GeometryMatrix, color ebiten.ColorMatrix) { +func (i *ids) drawTexture(target RenderTargetID, id TextureID, parts []TexturePart, geo GeometryMatrix, color ColorMatrix) { texture := i.textureAt(id) i.setViewportIfNeeded(target) r := i.renderTargetAt(target) - projectionMatrix := r.projectionMatrix() - quads := textureQuads(parts, texture.width, texture.height) - shader.DrawTexture(texture.native, projectionMatrix, quads, &geo, &color) + projectionMatrix := r.ProjectionMatrix() + quads := textureQuads(parts, texture.Width(), texture.Height()) + shader.DrawTexture(texture.Native(), projectionMatrix, quads, &geo, &color) } -func (i *ids) setViewportIfNeeded(id ebiten.RenderTargetID) { +func (i *ids) setViewportIfNeeded(id RenderTargetID) { r := i.renderTargetAt(id) if i.currentRenderTargetId != id { - r.setAsViewport() + r.SetAsViewport() i.currentRenderTargetId = id } } diff --git a/internal/glfw/canvas.go b/internal/glfw/canvas.go index 2523c3ad7..17c7da5f8 100644 --- a/internal/glfw/canvas.go +++ b/internal/glfw/canvas.go @@ -20,15 +20,20 @@ import ( "github.com/go-gl/gl" glfw "github.com/go-gl/glfw3" "github.com/hajimehoshi/ebiten" - "github.com/hajimehoshi/ebiten/internal/opengl" "image" "runtime" ) +type GraphicsContext interface { + ebiten.GraphicsContext + PreUpdate() + PostUpdate() +} + type canvas struct { window *glfw.Window scale int - graphicsContext *opengl.GraphicsContext + graphicsContext GraphicsContext input input funcs chan func() funcsDone chan struct{} @@ -65,7 +70,7 @@ func (c *canvas) NewTextureID(img image.Image, filter ebiten.Filter) (ebiten.Tex default: panic("not reached") } - id, err = opengl.NewTextureID(img, glFilter) + id, err = ebiten.NewTextureID(img, glFilter) }) return id, err } @@ -83,7 +88,7 @@ func (c *canvas) NewRenderTargetID(width, height int, filter ebiten.Filter) (ebi default: panic("not reached") } - id, err = opengl.NewRenderTargetID(width, height, glFilter) + id, err = ebiten.NewRenderTargetID(width, height, glFilter) }) return id, err } diff --git a/internal/glfw/ui.go b/internal/glfw/ui.go index 652b7716f..19d0f355d 100644 --- a/internal/glfw/ui.go +++ b/internal/glfw/ui.go @@ -21,7 +21,6 @@ import ( "fmt" glfw "github.com/go-gl/glfw3" "github.com/hajimehoshi/ebiten" - "github.com/hajimehoshi/ebiten/internal/opengl" ) func init() { @@ -57,7 +56,7 @@ func (u *UI) Start(game ebiten.Game, width, height, scale int, title string) err windowWidth, _ := window.GetFramebufferSize() realScale := windowWidth / width c.use(func() { - c.graphicsContext, err = opengl.Initialize(width, height, realScale) + c.graphicsContext, err = ebiten.Initialize(width, height, realScale) }) if err != nil { return err diff --git a/internal/opengl/rendertarget.go b/internal/opengl/rendertarget.go index 510866af2..f125b2c1d 100644 --- a/internal/opengl/rendertarget.go +++ b/internal/opengl/rendertarget.go @@ -35,14 +35,18 @@ func orthoProjectionMatrix(left, right, bottom, top int) [4][4]float64 { } } -type renderTarget struct { - framebuffer gl.Framebuffer - width int - height int - flipY bool +type RenderTarget struct { + Framebuffer gl.Framebuffer + Width int + Height int + FlipY bool } -func createFramebuffer(nativeTexture gl.Texture) gl.Framebuffer { +func (r *RenderTarget) Dispose() { + r.Framebuffer.Delete() +} + +func CreateFramebuffer(nativeTexture gl.Texture) gl.Framebuffer { framebuffer := gl.GenFramebuffer() framebuffer.Bind() @@ -59,9 +63,9 @@ func createFramebuffer(nativeTexture gl.Texture) gl.Framebuffer { return framebuffer } -func (r *renderTarget) setAsViewport() { +func (r *RenderTarget) SetAsViewport() { gl.Flush() - r.framebuffer.Bind() + r.Framebuffer.Bind() err := gl.CheckFramebufferStatus(gl.FRAMEBUFFER) if err != gl.FRAMEBUFFER_COMPLETE { panic(fmt.Sprintf("glBindFramebuffer failed: %d", err)) @@ -69,22 +73,18 @@ func (r *renderTarget) setAsViewport() { gl.BlendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE) - width := adjustSizeForTexture(r.width) - height := adjustSizeForTexture(r.height) + width := AdjustSizeForTexture(r.Width) + height := AdjustSizeForTexture(r.Height) gl.Viewport(0, 0, width, height) } -func (r *renderTarget) projectionMatrix() [4][4]float64 { - width := adjustSizeForTexture(r.width) - height := adjustSizeForTexture(r.height) - ebiten := orthoProjectionMatrix(0, width, 0, height) - if r.flipY { - ebiten[1][1] *= -1 - ebiten[1][3] += float64(r.height) / float64(adjustSizeForTexture(r.height)) * 2 +func (r *RenderTarget) ProjectionMatrix() [4][4]float64 { + width := AdjustSizeForTexture(r.Width) + height := AdjustSizeForTexture(r.Height) + m := orthoProjectionMatrix(0, width, 0, height) + if r.FlipY { + m[1][1] *= -1 + m[1][3] += float64(r.Height) / float64(AdjustSizeForTexture(r.Height)) * 2 } - return ebiten -} - -func (r *renderTarget) dispose() { - r.framebuffer.Delete() + return m } diff --git a/internal/opengl/texture.go b/internal/opengl/texture.go index ae0af2c1b..b7ae7ebe0 100644 --- a/internal/opengl/texture.go +++ b/internal/opengl/texture.go @@ -22,13 +22,28 @@ import ( "image/draw" ) +func NextPowerOf2(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 +} + +func AdjustSizeForTexture(size int) int { + return int(NextPowerOf2(uint64(size))) +} + func adjustImageForTexture(img image.Image) *image.NRGBA { width, height := img.Bounds().Size().X, img.Bounds().Size().Y adjustedImageBounds := image.Rectangle{ image.ZP, image.Point{ - adjustSizeForTexture(width), - adjustSizeForTexture(height), + AdjustSizeForTexture(width), + AdjustSizeForTexture(height), }, } if nrgba, ok := img.(*image.NRGBA); ok && img.Bounds() == adjustedImageBounds { @@ -44,12 +59,24 @@ func adjustImageForTexture(img image.Image) *image.NRGBA { return adjustedImage } -type texture struct { +type Texture struct { native gl.Texture width int height int } +func (t *Texture) Native() gl.Texture { + return t.native +} + +func (t *Texture) Width() int { + return t.width +} + +func (t *Texture) Height() int { + return t.height +} + func createNativeTexture(textureWidth, textureHeight int, pixels []uint8, filter int) gl.Texture { nativeTexture := gl.GenTexture() if nativeTexture < 0 { @@ -67,20 +94,20 @@ func createNativeTexture(textureWidth, textureHeight int, pixels []uint8, filter return nativeTexture } -func createTexture(width, height int, filter int) (*texture, error) { - w := adjustSizeForTexture(width) - h := adjustSizeForTexture(height) +func CreateTexture(width, height int, filter int) (*Texture, error) { + w := AdjustSizeForTexture(width) + h := AdjustSizeForTexture(height) native := createNativeTexture(w, h, nil, filter) - return &texture{native, width, height}, nil + return &Texture{native, width, height}, nil } -func createTextureFromImage(img image.Image, filter int) (*texture, error) { +func CreateTextureFromImage(img image.Image, filter int) (*Texture, error) { adjustedImage := adjustImageForTexture(img) size := adjustedImage.Bounds().Size() native := createNativeTexture(size.X, size.Y, adjustedImage.Pix, filter) - return &texture{native, size.X, size.Y}, nil + return &Texture{native, size.X, size.Y}, nil } -func (t *texture) dispose() { +func (t *Texture) Dispose() { t.native.Delete() } diff --git a/internal/opengl/texturequad_test.go b/internal/opengl/texture_test.go similarity index 100% rename from internal/opengl/texturequad_test.go rename to internal/opengl/texture_test.go diff --git a/internal/opengl/texturequad.go b/internal/opengl/texturequad.go deleted file mode 100644 index a2a66ed68..000000000 --- a/internal/opengl/texturequad.go +++ /dev/null @@ -1,62 +0,0 @@ -/* -Copyright 2014 Hajime Hoshi - -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 opengl - -import ( - "github.com/hajimehoshi/ebiten" - "github.com/hajimehoshi/ebiten/internal/opengl/internal/shader" -) - -func NextPowerOf2(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 -} - -func adjustSizeForTexture(size int) int { - return int(NextPowerOf2(uint64(size))) -} - -func u(x int, width int) float32 { - return float32(x) / float32(adjustSizeForTexture(width)) -} - -func v(y int, height int) float32 { - return float32(y) / float32(adjustSizeForTexture(height)) -} - -func textureQuads(parts []ebiten.TexturePart, width, height int) []shader.TextureQuad { - quads := make([]shader.TextureQuad, 0, len(parts)) - for _, part := range parts { - x1 := float32(part.LocationX) - x2 := float32(part.LocationX + part.Source.Width) - y1 := float32(part.LocationY) - y2 := float32(part.LocationY + part.Source.Height) - u1 := u(part.Source.X, width) - u2 := u(part.Source.X+part.Source.Width, width) - v1 := v(part.Source.Y, height) - v2 := v(part.Source.Y+part.Source.Height, height) - quad := shader.TextureQuad{x1, x2, y1, y2, u1, u2, v1, v2} - quads = append(quads, quad) - } - return quads -}