mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
internal/ui: refactoring: move the logic in gameForUI to context
This commit is contained in:
parent
9c448d207a
commit
5c7917897c
91
gameforui.go
91
gameforui.go
@ -15,16 +15,12 @@
|
|||||||
package ebiten
|
package ebiten
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/hajimehoshi/ebiten/v2/internal/ui"
|
||||||
"math"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type gameForUI struct {
|
type gameForUI struct {
|
||||||
game Game
|
game Game
|
||||||
offscreen *Image
|
offscreen *Image
|
||||||
screen *Image
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newGameForUI(game Game) *gameForUI {
|
func newGameForUI(game Game) *gameForUI {
|
||||||
@ -33,90 +29,23 @@ func newGameForUI(game Game) *gameForUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *gameForUI) Layout(outsideWidth, outsideHeight float64, deviceScaleFactor float64) (int, int) {
|
func (c *gameForUI) NewOffscreenImage(width, height int) *ui.Image {
|
||||||
ow, oh := c.game.Layout(int(outsideWidth), int(outsideHeight))
|
|
||||||
if ow <= 0 || oh <= 0 {
|
|
||||||
panic("ebiten: Layout must return positive numbers")
|
|
||||||
}
|
|
||||||
|
|
||||||
sw, sh := int(outsideWidth*deviceScaleFactor), int(outsideHeight*deviceScaleFactor)
|
|
||||||
if c.screen != nil {
|
|
||||||
if w, h := c.screen.Size(); w != sw || h != sh {
|
|
||||||
c.screen.Dispose()
|
|
||||||
c.screen = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.screen == nil {
|
|
||||||
c.screen = newScreenFramebufferImage(sw, sh)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.offscreen != nil {
|
if c.offscreen != nil {
|
||||||
if w, h := c.offscreen.Size(); w != ow || h != oh {
|
c.offscreen.Dispose()
|
||||||
c.offscreen.Dispose()
|
c.offscreen = nil
|
||||||
c.offscreen = nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if c.offscreen == nil {
|
c.offscreen = NewImage(width, height)
|
||||||
c.offscreen = NewImage(ow, oh)
|
return c.offscreen.image
|
||||||
|
}
|
||||||
|
|
||||||
// Keep the offscreen an independent image from an atlas (#1938).
|
func (c *gameForUI) Layout(outsideWidth, outsideHeight int) (int, int) {
|
||||||
// The shader program for the screen is special and doesn't work well with an image on an atlas.
|
return c.game.Layout(outsideWidth, outsideHeight)
|
||||||
// An image on an atlas is surrounded by a transparent edge,
|
|
||||||
// and the shader program unexpectedly picks the pixel on the edges.
|
|
||||||
c.offscreen.image.SetIndependent(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ow, oh
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *gameForUI) Update() error {
|
func (c *gameForUI) Update() error {
|
||||||
return c.game.Update()
|
return c.game.Update()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *gameForUI) Draw(screenScale float64, offsetX, offsetY float64, needsClearingScreen bool, framebufferYDirection graphicsdriver.YDirection, clearScreenEveryFrame, filterEnabled bool) {
|
func (c *gameForUI) Draw() {
|
||||||
c.offscreen.image.SetVolatile(clearScreenEveryFrame)
|
|
||||||
|
|
||||||
// Even though updateCount == 0, the offscreen is cleared and Draw is called.
|
|
||||||
// Draw should not update the game state and then the screen should not be updated without Update, but
|
|
||||||
// users might want to process something at Draw with the time intervals of FPS.
|
|
||||||
if clearScreenEveryFrame {
|
|
||||||
c.offscreen.Clear()
|
|
||||||
}
|
|
||||||
c.game.Draw(c.offscreen)
|
c.game.Draw(c.offscreen)
|
||||||
|
|
||||||
if needsClearingScreen {
|
|
||||||
// This clear is needed for fullscreen mode or some mobile platforms (#622).
|
|
||||||
c.screen.Clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
op := &DrawImageOptions{}
|
|
||||||
|
|
||||||
s := screenScale
|
|
||||||
switch framebufferYDirection {
|
|
||||||
case graphicsdriver.Upward:
|
|
||||||
op.GeoM.Scale(s, -s)
|
|
||||||
_, h := c.offscreen.Size()
|
|
||||||
op.GeoM.Translate(0, float64(h)*s)
|
|
||||||
case graphicsdriver.Downward:
|
|
||||||
op.GeoM.Scale(s, s)
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("ebiten: invalid v-direction: %d", framebufferYDirection))
|
|
||||||
}
|
|
||||||
|
|
||||||
op.GeoM.Translate(offsetX, offsetY)
|
|
||||||
op.CompositeMode = CompositeModeCopy
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case !filterEnabled:
|
|
||||||
op.Filter = FilterNearest
|
|
||||||
case math.Floor(s) == s:
|
|
||||||
op.Filter = FilterNearest
|
|
||||||
case s > 1:
|
|
||||||
op.Filter = filterScreen
|
|
||||||
default:
|
|
||||||
// filterScreen works with >=1 scale, but does not well with <1 scale.
|
|
||||||
// Use regular FilterLinear instead so far (#669).
|
|
||||||
op.Filter = FilterLinear
|
|
||||||
}
|
|
||||||
c.screen.DrawImage(c.offscreen, op)
|
|
||||||
}
|
}
|
||||||
|
@ -27,11 +27,6 @@ const (
|
|||||||
|
|
||||||
// FilterLinear represents linear filter
|
// FilterLinear represents linear filter
|
||||||
FilterLinear Filter = Filter(graphicsdriver.FilterLinear)
|
FilterLinear Filter = Filter(graphicsdriver.FilterLinear)
|
||||||
|
|
||||||
// filterScreen represents a special filter for screen. Inner usage only.
|
|
||||||
//
|
|
||||||
// Some parameters like a color matrix or color vertex values can be ignored when filterScreen is used.
|
|
||||||
filterScreen Filter = Filter(graphicsdriver.FilterScreen)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CompositeMode represents Porter-Duff composition mode.
|
// CompositeMode represents Porter-Duff composition mode.
|
||||||
|
48
image.go
48
image.go
@ -66,39 +66,22 @@ func (i *Image) Clear() {
|
|||||||
i.Fill(color.Transparent)
|
i.Fill(color.Transparent)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
emptyImage = NewImage(3, 3)
|
|
||||||
emptySubImage = emptyImage.SubImage(image.Rect(1, 1, 2, 2)).(*Image)
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
w, h := emptyImage.Size()
|
|
||||||
pix := make([]byte, 4*w*h)
|
|
||||||
for i := range pix {
|
|
||||||
pix[i] = 0xff
|
|
||||||
}
|
|
||||||
// As emptyImage is used at Fill, use ReplacePixels instead.
|
|
||||||
emptyImage.ReplacePixels(pix)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill fills the image with a solid color.
|
// Fill fills the image with a solid color.
|
||||||
//
|
//
|
||||||
// When the image is disposed, Fill does nothing.
|
// When the image is disposed, Fill does nothing.
|
||||||
func (i *Image) Fill(clr color.Color) {
|
func (i *Image) Fill(clr color.Color) {
|
||||||
// Use the original size to cover the entire region (#1691).
|
i.copyCheck()
|
||||||
// DrawImage automatically clips the rendering region.
|
|
||||||
orig := i
|
var crf, cgf, cbf, caf float32
|
||||||
if i.isSubImage() {
|
cr, cg, cb, ca := clr.RGBA()
|
||||||
orig = i.original
|
if ca != 0 {
|
||||||
|
crf = float32(cr) / float32(ca)
|
||||||
|
cgf = float32(cg) / float32(ca)
|
||||||
|
cbf = float32(cb) / float32(ca)
|
||||||
|
caf = float32(ca) / 0xffff
|
||||||
}
|
}
|
||||||
w, h := orig.Size()
|
b := i.Bounds()
|
||||||
|
i.image.Fill(crf, cgf, cbf, caf, b.Min.X, b.Min.Y, b.Dx(), b.Dy())
|
||||||
op := &DrawImageOptions{}
|
|
||||||
op.GeoM.Scale(float64(w), float64(h))
|
|
||||||
op.ColorM.ScaleWithColor(clr)
|
|
||||||
op.CompositeMode = CompositeModeCopy
|
|
||||||
|
|
||||||
i.DrawImage(emptySubImage, op)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func canSkipMipmap(geom GeoM, filter graphicsdriver.Filter) bool {
|
func canSkipMipmap(geom GeoM, filter graphicsdriver.Filter) bool {
|
||||||
@ -845,12 +828,3 @@ func NewImageFromImage(source image.Image) *Image {
|
|||||||
i.ReplacePixels(imageToBytes(source))
|
i.ReplacePixels(imageToBytes(source))
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
func newScreenFramebufferImage(width, height int) *Image {
|
|
||||||
i := &Image{
|
|
||||||
image: ui.NewScreenFramebufferImage(width, height),
|
|
||||||
bounds: image.Rect(0, 0, width, height),
|
|
||||||
}
|
|
||||||
i.addr = i
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
@ -15,10 +15,12 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/internal/affine"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/buffered"
|
"github.com/hajimehoshi/ebiten/v2/internal/buffered"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/clock"
|
"github.com/hajimehoshi/ebiten/v2/internal/clock"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/debug"
|
"github.com/hajimehoshi/ebiten/v2/internal/debug"
|
||||||
@ -30,9 +32,10 @@ import (
|
|||||||
const DefaultTPS = 60
|
const DefaultTPS = 60
|
||||||
|
|
||||||
type Game interface {
|
type Game interface {
|
||||||
Layout(outsideWidth, outsideHeight float64, deviceScaleFactor float64) (int, int)
|
NewOffscreenImage(width, height int) *Image
|
||||||
|
Layout(outsideWidth, outsideHeight int) (int, int)
|
||||||
Update() error
|
Update() error
|
||||||
Draw(screenScale float64, offsetX, offsetY float64, needsClearingScreen bool, framebufferYDirection graphicsdriver.YDirection, screenClearedEveryFrame, filterEnabled bool)
|
Draw()
|
||||||
}
|
}
|
||||||
|
|
||||||
type context struct {
|
type context struct {
|
||||||
@ -40,11 +43,12 @@ type context struct {
|
|||||||
|
|
||||||
updateCalled bool
|
updateCalled bool
|
||||||
|
|
||||||
|
offscreen *Image
|
||||||
|
screen *Image
|
||||||
|
|
||||||
// The following members must be protected by the mutex m.
|
// The following members must be protected by the mutex m.
|
||||||
outsideWidth float64
|
outsideWidth float64
|
||||||
outsideHeight float64
|
outsideHeight float64
|
||||||
screenWidth int
|
|
||||||
screenHeight int
|
|
||||||
|
|
||||||
m sync.Mutex
|
m sync.Mutex
|
||||||
}
|
}
|
||||||
@ -116,12 +120,11 @@ func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, update
|
|||||||
if err := theGlobalState.error(); err != nil {
|
if err := theGlobalState.error(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
Get().resetForTick()
|
theUI.resetForTick()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the game.
|
// Draw the game.
|
||||||
screenScale, offsetX, offsetY := c.screenScaleAndOffsets(deviceScaleFactor)
|
c.drawGame(graphicsDriver)
|
||||||
c.game.Draw(screenScale, offsetX, offsetY, graphicsDriver.NeedsClearingScreen(), graphicsDriver.FramebufferYDirection(), theGlobalState.isScreenClearedEveryFrame(), theGlobalState.isScreenFilterEnabled())
|
|
||||||
|
|
||||||
// All the vertices data are consumed at the end of the frame, and the data backend can be
|
// All the vertices data are consumed at the end of the frame, and the data backend can be
|
||||||
// available after that. Until then, lock the vertices backend.
|
// available after that. Until then, lock the vertices backend.
|
||||||
@ -133,20 +136,119 @@ func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, update
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics) {
|
||||||
|
c.offscreen.mipmap.SetVolatile(theGlobalState.isScreenClearedEveryFrame())
|
||||||
|
|
||||||
|
// Even though updateCount == 0, the offscreen is cleared and Draw is called.
|
||||||
|
// Draw should not update the game state and then the screen should not be updated without Update, but
|
||||||
|
// users might want to process something at Draw with the time intervals of FPS.
|
||||||
|
if theGlobalState.isScreenClearedEveryFrame() {
|
||||||
|
c.offscreen.clear()
|
||||||
|
}
|
||||||
|
c.game.Draw()
|
||||||
|
|
||||||
|
if graphicsDriver.NeedsClearingScreen() {
|
||||||
|
// This clear is needed for fullscreen mode or some mobile platforms (#622).
|
||||||
|
c.screen.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
ga := 1.0
|
||||||
|
gd := 1.0
|
||||||
|
gtx := 0.0
|
||||||
|
gty := 0.0
|
||||||
|
|
||||||
|
screenScale, offsetX, offsetY := c.screenScaleAndOffsets()
|
||||||
|
s := screenScale
|
||||||
|
switch y := graphicsDriver.FramebufferYDirection(); y {
|
||||||
|
case graphicsdriver.Upward:
|
||||||
|
ga *= s
|
||||||
|
gd *= -s
|
||||||
|
gty += float64(c.offscreen.height) * s
|
||||||
|
case graphicsdriver.Downward:
|
||||||
|
ga *= s
|
||||||
|
gd *= s
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("ui: invalid y-direction: %d", y))
|
||||||
|
}
|
||||||
|
|
||||||
|
gtx += offsetX
|
||||||
|
gty += offsetY
|
||||||
|
|
||||||
|
var filter graphicsdriver.Filter
|
||||||
|
switch {
|
||||||
|
case !theGlobalState.isScreenFilterEnabled():
|
||||||
|
filter = graphicsdriver.FilterNearest
|
||||||
|
case math.Floor(s) == s:
|
||||||
|
filter = graphicsdriver.FilterNearest
|
||||||
|
case s > 1:
|
||||||
|
filter = graphicsdriver.FilterScreen
|
||||||
|
default:
|
||||||
|
// FilterScreen works with >=1 scale, but does not well with <1 scale.
|
||||||
|
// Use regular FilterLinear instead so far (#669).
|
||||||
|
filter = graphicsdriver.FilterLinear
|
||||||
|
}
|
||||||
|
|
||||||
|
dstRegion := graphicsdriver.Region{
|
||||||
|
X: 0,
|
||||||
|
Y: 0,
|
||||||
|
Width: float32(c.screen.width),
|
||||||
|
Height: float32(c.screen.height),
|
||||||
|
}
|
||||||
|
|
||||||
|
vs := graphics.QuadVertices(
|
||||||
|
0, 0, float32(c.offscreen.width), float32(c.offscreen.height),
|
||||||
|
float32(ga), 0, 0, float32(gd), float32(gtx), float32(gty),
|
||||||
|
1, 1, 1, 1)
|
||||||
|
is := graphics.QuadIndices()
|
||||||
|
|
||||||
|
srcs := [graphics.ShaderImageNum]*Image{c.offscreen}
|
||||||
|
c.screen.DrawTriangles(srcs, vs, is, affine.ColorMIdentity{}, graphicsdriver.CompositeModeCopy, filter, graphicsdriver.AddressUnsafe, dstRegion, graphicsdriver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false, true)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *context) layoutGame(outsideWidth, outsideHeight float64, deviceScaleFactor float64) (int, int) {
|
func (c *context) layoutGame(outsideWidth, outsideHeight float64, deviceScaleFactor float64) (int, int) {
|
||||||
c.m.Lock()
|
c.m.Lock()
|
||||||
defer c.m.Unlock()
|
defer c.m.Unlock()
|
||||||
|
|
||||||
c.outsideWidth = outsideWidth
|
c.outsideWidth = outsideWidth
|
||||||
c.outsideHeight = outsideHeight
|
c.outsideHeight = outsideHeight
|
||||||
w, h := c.game.Layout(outsideWidth, outsideHeight, deviceScaleFactor)
|
|
||||||
c.screenWidth = w
|
ow, oh := c.game.Layout(int(outsideWidth), int(outsideHeight))
|
||||||
c.screenHeight = h
|
if ow <= 0 || oh <= 0 {
|
||||||
return w, h
|
panic("ui: Layout must return positive numbers")
|
||||||
|
}
|
||||||
|
|
||||||
|
sw, sh := int(outsideWidth*deviceScaleFactor), int(outsideHeight*deviceScaleFactor)
|
||||||
|
if c.screen != nil {
|
||||||
|
if c.screen.width != sw || c.screen.height != sh {
|
||||||
|
c.screen.MarkDisposed()
|
||||||
|
c.screen = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.screen == nil {
|
||||||
|
c.screen = newScreenFramebufferImage(sw, sh)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.offscreen != nil {
|
||||||
|
if c.offscreen.width != ow || c.offscreen.height != oh {
|
||||||
|
c.offscreen.MarkDisposed()
|
||||||
|
c.offscreen = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.offscreen == nil {
|
||||||
|
c.offscreen = c.game.NewOffscreenImage(ow, oh)
|
||||||
|
|
||||||
|
// Keep the offscreen an independent image from an atlas (#1938).
|
||||||
|
// The shader program for the screen is special and doesn't work well with an image on an atlas.
|
||||||
|
// An image on an atlas is surrounded by a transparent edge,
|
||||||
|
// and the shader program unexpectedly picks the pixel on the edges.
|
||||||
|
c.offscreen.mipmap.SetIndependent(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ow, oh
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) adjustPosition(x, y float64, deviceScaleFactor float64) (float64, float64) {
|
func (c *context) adjustPosition(x, y float64, deviceScaleFactor float64) (float64, float64) {
|
||||||
s, ox, oy := c.screenScaleAndOffsets(deviceScaleFactor)
|
s, ox, oy := c.screenScaleAndOffsets()
|
||||||
// The scale 0 indicates that the screen is not initialized yet.
|
// The scale 0 indicates that the screen is not initialized yet.
|
||||||
// As any cursor values don't make sense, just return NaN.
|
// As any cursor values don't make sense, just return NaN.
|
||||||
if s == 0 {
|
if s == 0 {
|
||||||
@ -155,21 +257,21 @@ func (c *context) adjustPosition(x, y float64, deviceScaleFactor float64) (float
|
|||||||
return (x*deviceScaleFactor - ox) / s, (y*deviceScaleFactor - oy) / s
|
return (x*deviceScaleFactor - ox) / s, (y*deviceScaleFactor - oy) / s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) screenScaleAndOffsets(deviceScaleFactor float64) (float64, float64, float64) {
|
func (c *context) screenScaleAndOffsets() (float64, float64, float64) {
|
||||||
c.m.Lock()
|
c.m.Lock()
|
||||||
defer c.m.Unlock()
|
defer c.m.Unlock()
|
||||||
|
|
||||||
if c.screenWidth == 0 || c.screenHeight == 0 {
|
if c.screen == nil {
|
||||||
return 0, 0, 0
|
return 0, 0, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
scaleX := c.outsideWidth / float64(c.screenWidth) * deviceScaleFactor
|
scaleX := float64(c.screen.width) / float64(c.offscreen.width)
|
||||||
scaleY := c.outsideHeight / float64(c.screenHeight) * deviceScaleFactor
|
scaleY := float64(c.screen.height) / float64(c.offscreen.height)
|
||||||
scale := math.Min(scaleX, scaleY)
|
scale := math.Min(scaleX, scaleY)
|
||||||
width := float64(c.screenWidth) * scale
|
width := float64(c.offscreen.width) * scale
|
||||||
height := float64(c.screenHeight) * scale
|
height := float64(c.offscreen.height) * scale
|
||||||
x := (c.outsideWidth*deviceScaleFactor - width) / 2
|
x := (float64(c.screen.width) - width) / 2
|
||||||
y := (c.outsideHeight*deviceScaleFactor - height) / 2
|
y := (float64(c.screen.height) - height) / 2
|
||||||
return scale, x, y
|
return scale, x, y
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,7 +359,7 @@ func FPSMode() FPSModeType {
|
|||||||
|
|
||||||
func SetFPSMode(fpsMode FPSModeType) {
|
func SetFPSMode(fpsMode FPSModeType) {
|
||||||
theGlobalState.setFPSMode(fpsMode)
|
theGlobalState.setFPSMode(fpsMode)
|
||||||
Get().SetFPSMode(fpsMode)
|
theUI.SetFPSMode(fpsMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MaxTPS() int {
|
func MaxTPS() int {
|
||||||
|
@ -31,17 +31,23 @@ func SetPanicOnErrorOnReadingPixelsForTesting(value bool) {
|
|||||||
|
|
||||||
type Image struct {
|
type Image struct {
|
||||||
mipmap *mipmap.Mipmap
|
mipmap *mipmap.Mipmap
|
||||||
|
width int
|
||||||
|
height int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewImage(width, height int) *Image {
|
func NewImage(width, height int) *Image {
|
||||||
return &Image{
|
return &Image{
|
||||||
mipmap: mipmap.New(width, height),
|
mipmap: mipmap.New(width, height),
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewScreenFramebufferImage(width, height int) *Image {
|
func newScreenFramebufferImage(width, height int) *Image {
|
||||||
return &Image{
|
return &Image{
|
||||||
mipmap: mipmap.NewScreenFramebufferMipmap(width, height),
|
mipmap: mipmap.NewScreenFramebufferMipmap(width, height),
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,14 +98,42 @@ func (i *Image) DumpScreenshot(name string, blackbg bool) error {
|
|||||||
return theUI.dumpScreenshot(i.mipmap, name, blackbg)
|
return theUI.dumpScreenshot(i.mipmap, name, blackbg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) SetIndependent(independent bool) {
|
|
||||||
i.mipmap.SetIndependent(independent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Image) SetVolatile(volatile bool) {
|
|
||||||
i.mipmap.SetVolatile(volatile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DumpImages(dir string) error {
|
func DumpImages(dir string) error {
|
||||||
return theUI.dumpImages(dir)
|
return theUI.dumpImages(dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
emptyImage = NewImage(3, 3)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
pix := make([]byte, 4*emptyImage.width*emptyImage.height)
|
||||||
|
for i := range pix {
|
||||||
|
pix[i] = 0xff
|
||||||
|
}
|
||||||
|
// As emptyImage is used at Fill, use ReplacePixels instead.
|
||||||
|
emptyImage.ReplacePixels(pix, 0, 0, emptyImage.width, emptyImage.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Image) clear() {
|
||||||
|
i.Fill(0, 0, 0, 0, 0, 0, i.width, i.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Image) Fill(r, g, b, a float32, x, y, width, height int) {
|
||||||
|
dstRegion := graphicsdriver.Region{
|
||||||
|
X: float32(x),
|
||||||
|
Y: float32(y),
|
||||||
|
Width: float32(width),
|
||||||
|
Height: float32(height),
|
||||||
|
}
|
||||||
|
|
||||||
|
vs := graphics.QuadVertices(
|
||||||
|
1, 1, float32(emptyImage.width-1), float32(emptyImage.height-1),
|
||||||
|
float32(i.width), 0, 0, float32(i.height), 0, 0,
|
||||||
|
r, g, b, a)
|
||||||
|
is := graphics.QuadIndices()
|
||||||
|
|
||||||
|
srcs := [graphics.ShaderImageNum]*Image{emptyImage}
|
||||||
|
|
||||||
|
i.DrawTriangles(srcs, vs, is, affine.ColorMIdentity{}, graphicsdriver.CompositeModeCopy, graphicsdriver.FilterNearest, graphicsdriver.AddressUnsafe, dstRegion, graphicsdriver.Region{}, [graphics.ShaderImageNum - 1][2]float32{}, nil, nil, false, true)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user