twitchapon-anim

Basic Twitchapon Receiver/Visuals
git clone git://bsandro.tech/twitchapon-anim
Log | Files | Refs | README | LICENSE

uicontext.go (6171B)


      1 // Copyright 2014 Hajime Hoshi
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //     http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 package ebiten
     16 
     17 import (
     18 	"fmt"
     19 	"math"
     20 	"sync"
     21 	"sync/atomic"
     22 
     23 	"github.com/hajimehoshi/ebiten/v2/internal/buffered"
     24 	"github.com/hajimehoshi/ebiten/v2/internal/clock"
     25 	"github.com/hajimehoshi/ebiten/v2/internal/driver"
     26 	"github.com/hajimehoshi/ebiten/v2/internal/hooks"
     27 )
     28 
     29 type uiContext struct {
     30 	game      Game
     31 	offscreen *Image
     32 	screen    *Image
     33 
     34 	updateCalled bool
     35 
     36 	// scaleForWindow is the scale of a window. This doesn't represent the scale on fullscreen. This value works
     37 	// only on desktops.
     38 	//
     39 	// scaleForWindow is for backward compatibility and is used to calculate the window size when SetScreenSize
     40 	// is called.
     41 	scaleForWindow float64
     42 
     43 	outsideSizeUpdated bool
     44 	outsideWidth       float64
     45 	outsideHeight      float64
     46 
     47 	err atomic.Value
     48 
     49 	m sync.Mutex
     50 }
     51 
     52 var theUIContext = &uiContext{}
     53 
     54 func (c *uiContext) set(game Game, scaleForWindow float64) {
     55 	c.m.Lock()
     56 	defer c.m.Unlock()
     57 	c.game = game
     58 }
     59 
     60 func (c *uiContext) setError(err error) {
     61 	c.err.Store(err)
     62 }
     63 
     64 func (c *uiContext) Layout(outsideWidth, outsideHeight float64) {
     65 	c.outsideSizeUpdated = true
     66 	c.outsideWidth = outsideWidth
     67 	c.outsideHeight = outsideHeight
     68 }
     69 
     70 func (c *uiContext) updateOffscreen() {
     71 	sw, sh := c.game.Layout(int(c.outsideWidth), int(c.outsideHeight))
     72 	if sw <= 0 || sh <= 0 {
     73 		panic("ebiten: Layout must return positive numbers")
     74 	}
     75 
     76 	if c.offscreen != nil && !c.outsideSizeUpdated {
     77 		if w, h := c.offscreen.Size(); w == sw && h == sh {
     78 			return
     79 		}
     80 	}
     81 	c.outsideSizeUpdated = false
     82 
     83 	if c.screen != nil {
     84 		c.screen.Dispose()
     85 		c.screen = nil
     86 	}
     87 
     88 	if c.offscreen != nil {
     89 		if w, h := c.offscreen.Size(); w != sw || h != sh {
     90 			c.offscreen.Dispose()
     91 			c.offscreen = nil
     92 		}
     93 	}
     94 	if c.offscreen == nil {
     95 		c.offscreen = NewImage(sw, sh)
     96 		c.offscreen.mipmap.SetVolatile(IsScreenClearedEveryFrame())
     97 	}
     98 
     99 	// TODO: This is duplicated with mobile/ebitenmobileview/funcs.go. Refactor this.
    100 	d := uiDriver().DeviceScaleFactor()
    101 	c.screen = newScreenFramebufferImage(int(c.outsideWidth*d), int(c.outsideHeight*d))
    102 
    103 	// Do not have to update scaleForWindow since this is used only for backward compatibility.
    104 	// Then, if a window is resizable, scaleForWindow (= ebiten.ScreenScale) might not match with the actual
    105 	// scale. This is fine since ebiten.ScreenScale will be deprecated.
    106 }
    107 
    108 func (c *uiContext) setScreenClearedEveryFrame(cleared bool) {
    109 	c.m.Lock()
    110 	defer c.m.Unlock()
    111 
    112 	if c.offscreen != nil {
    113 		c.offscreen.mipmap.SetVolatile(cleared)
    114 	}
    115 }
    116 
    117 func (c *uiContext) setWindowResizable(resizable bool) {
    118 	c.m.Lock()
    119 	defer c.m.Unlock()
    120 
    121 	if w := uiDriver().Window(); w != nil {
    122 		w.SetResizable(resizable)
    123 	}
    124 }
    125 
    126 func (c *uiContext) screenScale() float64 {
    127 	if c.offscreen == nil {
    128 		return 0
    129 	}
    130 	sw, sh := c.offscreen.Size()
    131 	d := uiDriver().DeviceScaleFactor()
    132 	scaleX := c.outsideWidth / float64(sw) * d
    133 	scaleY := c.outsideHeight / float64(sh) * d
    134 	return math.Min(scaleX, scaleY)
    135 }
    136 
    137 func (c *uiContext) offsets() (float64, float64) {
    138 	if c.offscreen == nil {
    139 		return 0, 0
    140 	}
    141 	sw, sh := c.offscreen.Size()
    142 	d := uiDriver().DeviceScaleFactor()
    143 	s := c.screenScale()
    144 	width := float64(sw) * s
    145 	height := float64(sh) * s
    146 	return (c.outsideWidth*d - width) / 2, (c.outsideHeight*d - height) / 2
    147 }
    148 
    149 func (c *uiContext) Update() error {
    150 	// TODO: If updateCount is 0 and vsync is disabled, swapping buffers can be skipped.
    151 
    152 	if err, ok := c.err.Load().(error); ok && err != nil {
    153 		return err
    154 	}
    155 	if err := buffered.BeginFrame(); err != nil {
    156 		return err
    157 	}
    158 	if err := c.update(); err != nil {
    159 		return err
    160 	}
    161 	if err := buffered.EndFrame(); err != nil {
    162 		return err
    163 	}
    164 	return nil
    165 }
    166 
    167 func (c *uiContext) Draw() error {
    168 	if err, ok := c.err.Load().(error); ok && err != nil {
    169 		return err
    170 	}
    171 	if err := buffered.BeginFrame(); err != nil {
    172 		return err
    173 	}
    174 	c.draw()
    175 	if err := buffered.EndFrame(); err != nil {
    176 		return err
    177 	}
    178 	return nil
    179 }
    180 
    181 func (c *uiContext) update() error {
    182 	// TODO: Move the clock usage to the UI driver side.
    183 	updateCount := clock.Update(MaxTPS())
    184 
    185 	// Ensure that Update is called once before Draw so that Update can be used for initialization.
    186 	if !c.updateCalled && updateCount == 0 {
    187 		updateCount = 1
    188 		c.updateCalled = true
    189 	}
    190 
    191 	for i := 0; i < updateCount; i++ {
    192 		c.updateOffscreen()
    193 
    194 		if err := hooks.RunBeforeUpdateHooks(); err != nil {
    195 			return err
    196 		}
    197 		if err := c.game.Update(); err != nil {
    198 			return err
    199 		}
    200 		uiDriver().ResetForFrame()
    201 	}
    202 	return nil
    203 }
    204 
    205 func (c *uiContext) draw() {
    206 	// c.screen might be nil when updateCount is 0 in the initial state (#1039).
    207 	if c.screen == nil {
    208 		return
    209 	}
    210 
    211 	if IsScreenClearedEveryFrame() {
    212 		c.offscreen.Clear()
    213 	}
    214 	c.game.Draw(c.offscreen)
    215 
    216 	// This clear is needed for fullscreen mode or some mobile platforms (#622).
    217 	c.screen.Clear()
    218 
    219 	op := &DrawImageOptions{}
    220 
    221 	s := c.screenScale()
    222 	switch vd := uiDriver().Graphics().FramebufferYDirection(); vd {
    223 	case driver.Upward:
    224 		op.GeoM.Scale(s, -s)
    225 		_, h := c.offscreen.Size()
    226 		op.GeoM.Translate(0, float64(h)*s)
    227 	case driver.Downward:
    228 		op.GeoM.Scale(s, s)
    229 	default:
    230 		panic(fmt.Sprintf("ebiten: invalid v-direction: %d", vd))
    231 	}
    232 
    233 	op.GeoM.Translate(c.offsets())
    234 	op.CompositeMode = CompositeModeCopy
    235 
    236 	// filterScreen works with >=1 scale, but does not well with <1 scale.
    237 	// Use regular FilterLinear instead so far (#669).
    238 	if s >= 1 {
    239 		op.Filter = filterScreen
    240 	} else {
    241 		op.Filter = FilterLinear
    242 	}
    243 	c.screen.DrawImage(c.offscreen, op)
    244 }
    245 
    246 func (c *uiContext) AdjustPosition(x, y float64) (float64, float64) {
    247 	d := uiDriver().DeviceScaleFactor()
    248 	ox, oy := c.offsets()
    249 	s := c.screenScale()
    250 	return (x*d - ox) / s, (y*d - oy) / s
    251 }